fix: viewing is much better, dynamic as well :)
This commit is contained in:
parent
ccb061989a
commit
03c3a41162
@ -62,8 +62,10 @@ func (w *Window) AdjustScroll() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewPort := w.ViewportHeight()
|
||||||
|
|
||||||
// Effective scrollOff (can't be more than half the viewport)
|
// Effective scrollOff (can't be more than half the viewport)
|
||||||
off := min(w.Options.ScrollOff, w.Height/2)
|
off := min(w.Options.ScrollOff, viewPort/2)
|
||||||
|
|
||||||
// Cursor too close to top — scroll up
|
// Cursor too close to top — scroll up
|
||||||
if w.Cursor.Line < w.ScrollY+off {
|
if w.Cursor.Line < w.ScrollY+off {
|
||||||
@ -71,15 +73,27 @@ func (w *Window) AdjustScroll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cursor too close to bottom — scroll down
|
// Cursor too close to bottom — scroll down
|
||||||
if w.Cursor.Line > w.ScrollY+w.Height-1-off {
|
if w.Cursor.Line > w.ScrollY+viewPort-1-off {
|
||||||
w.ScrollY = w.Cursor.Line - w.Height + 1 + off
|
w.ScrollY = w.Cursor.Line - viewPort + 1 + off
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp scrollY to valid range
|
// Clamp scrollY to valid range
|
||||||
maxScroll := max(0, w.Buffer.LineCount()-w.Height)
|
maxScroll := max(0, w.Buffer.LineCount()-viewPort)
|
||||||
w.ScrollY = max(0, min(w.ScrollY, maxScroll))
|
w.ScrollY = max(0, min(w.ScrollY, maxScroll))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Getters (for computed values)
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
func (w *Window) ViewportHeight() int {
|
||||||
|
// TODO: This will need more magic when splits come into play
|
||||||
|
|
||||||
|
// -1 for command bar
|
||||||
|
// -1 for status line
|
||||||
|
return w.Height - 2
|
||||||
|
}
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Setters
|
// Setters
|
||||||
// ==================================================
|
// ==================================================
|
||||||
|
|||||||
@ -5,25 +5,282 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Model.View: Renders the complete editor view including buffer content, line
|
||||||
|
// numbers, status bar, and command line.
|
||||||
|
func (m Model) View() string {
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
|
||||||
|
// NOTES:
|
||||||
|
// One single command line across entire viewport
|
||||||
|
// Each window has its own line numbers and gutter
|
||||||
|
// Each window has its own status bar and mode
|
||||||
|
|
||||||
|
styles := m.Styles()
|
||||||
|
settings := m.Settings()
|
||||||
|
|
||||||
|
// Draw window
|
||||||
|
view := viewWindow(win, styles, settings, m.Mode())
|
||||||
|
|
||||||
|
// Command bar is seperate
|
||||||
|
cmdBar := drawCommandBar(m)
|
||||||
|
|
||||||
|
return view + cmdBar
|
||||||
|
|
||||||
|
// view.WriteString(drawStatusBar(m))
|
||||||
|
// view.WriteString("\n")
|
||||||
|
// view.WriteString(drawCommandBar(m))
|
||||||
|
//
|
||||||
|
// return view.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// viewWindow: Renders a single window's content including line numbers and buffer text.
|
||||||
|
// Each window has its own line numbers, gutter, and viewport dimensions.
|
||||||
|
func viewWindow(w *core.Window, styles style.Styles, settings core.Settings, mode core.Mode) string {
|
||||||
|
buf := w.Buffer
|
||||||
|
var view strings.Builder
|
||||||
|
|
||||||
|
// Compute window size (y)
|
||||||
|
start := w.ScrollY
|
||||||
|
end := w.ScrollY + w.ViewportHeight()
|
||||||
|
|
||||||
|
// Draw buffer lines
|
||||||
|
for lineNum := start; lineNum < end; lineNum++ {
|
||||||
|
if lineNum < buf.LineCount() {
|
||||||
|
line := drawLine(w, styles, settings, mode, buf.Line(lineNum), lineNum)
|
||||||
|
view.WriteString(line)
|
||||||
|
}
|
||||||
|
view.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw status line
|
||||||
|
statusBar := drawStatusBar(w, mode)
|
||||||
|
view.WriteString(statusBar + "\n")
|
||||||
|
|
||||||
|
return view.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawLine: Renders a single line with syntax highlighting, cursor, and visual selection.
|
||||||
|
// Handles gutter, cursor rendering, and visual mode highlighting.
|
||||||
|
func drawLine(w *core.Window, styles style.Styles, settings core.Settings, mode core.Mode, line string, lineNumber int) string {
|
||||||
|
runes := []rune(line)
|
||||||
|
|
||||||
|
curStyle := styles.CursorStyle(mode)
|
||||||
|
visStyle := styles.VisualHighlight
|
||||||
|
|
||||||
|
var view strings.Builder
|
||||||
|
|
||||||
|
// Draw gutter first
|
||||||
|
gutter := drawGutter(w, styles, settings, lineNumber)
|
||||||
|
view.WriteString(gutter)
|
||||||
|
|
||||||
|
// Now draw the line content
|
||||||
|
for col := 0; col <= len(runes); col++ {
|
||||||
|
// Current char is cursor
|
||||||
|
if col == w.Cursor.Col && lineNumber == w.Cursor.Line {
|
||||||
|
if col < len(runes) {
|
||||||
|
view.WriteString(curStyle.Render(string(runes[col])))
|
||||||
|
} else {
|
||||||
|
view.WriteString(curStyle.Render(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not cursor, but not end
|
||||||
|
} else if col < len(runes) {
|
||||||
|
if mode.IsVisualMode() && posInsideSelection(w, mode, col, lineNumber) {
|
||||||
|
view.WriteString(visStyle.Render(string(runes[col])))
|
||||||
|
} else {
|
||||||
|
view.WriteRune(runes[col])
|
||||||
|
}
|
||||||
|
// Allow highlight on blank lines or chars
|
||||||
|
} else if mode.IsVisualMode() && posInsideSelection(w, mode, col, lineNumber) {
|
||||||
|
view.WriteString(visStyle.Render(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawGutter: Renders the line number gutter with support for both absolute and
|
||||||
|
// relative line numbers, highlighting the current line differently.
|
||||||
|
func drawGutter(w *core.Window, styles style.Styles, settings core.Settings, curLine int) string {
|
||||||
|
if !(settings.Number || settings.RelativeNumber) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required vars
|
||||||
|
var (
|
||||||
|
view strings.Builder
|
||||||
|
gutSize int = settings.GutterSize - 1 // -1 is for padding
|
||||||
|
currentLine bool = curLine == w.Cursor.Line
|
||||||
|
lineNumber int
|
||||||
|
|
||||||
|
gutter string
|
||||||
|
gutterStyle = styles.Gutter
|
||||||
|
gutterStyleCur = styles.GutterCurrentLine
|
||||||
|
)
|
||||||
|
|
||||||
|
// If we have relative setting, set the numbers relatively
|
||||||
|
if settings.RelativeNumber {
|
||||||
|
if curLine > w.Cursor.Line {
|
||||||
|
lineNumber = curLine - w.Cursor.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
if curLine < w.Cursor.Line {
|
||||||
|
lineNumber = w.Cursor.Line - curLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have number setting AND not relative setting OR we are on current line, use current line number
|
||||||
|
if (settings.Number && !settings.RelativeNumber) || (settings.Number && currentLine) {
|
||||||
|
lineNumber = curLine + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the gutter
|
||||||
|
gutter = fmt.Sprintf("%*d ", gutSize, lineNumber)
|
||||||
|
if currentLine {
|
||||||
|
view.WriteString(gutterStyleCur.Render(gutter))
|
||||||
|
} else {
|
||||||
|
view.WriteString(gutterStyle.Render(gutter))
|
||||||
|
}
|
||||||
|
|
||||||
|
return view.String()
|
||||||
|
|
||||||
|
// if m.Settings().Number || m.Settings().RelativeNumber {
|
||||||
|
// var (
|
||||||
|
// gutter string
|
||||||
|
// currentLine bool = false
|
||||||
|
// lineNumber int
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// if m.Settings().RelativeNumber {
|
||||||
|
// // Relative line numbers: show distance from cursor, current line shows absolute
|
||||||
|
// if i > win.Cursor.Line {
|
||||||
|
// lineNumber = i - win.Cursor.Line
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// } else if i < win.Cursor.Line {
|
||||||
|
// lineNumber = win.Cursor.Line - i
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// } else {
|
||||||
|
// // Current line: show absolute number if Number is also set, otherwise show 0
|
||||||
|
// currentLine = true
|
||||||
|
// if m.Settings().Number {
|
||||||
|
// lineNumber = i + 1
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// } else {
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else if m.Settings().Number {
|
||||||
|
// // Absolute line numbers only
|
||||||
|
// lineNumber = i + 1
|
||||||
|
// currentLine = (i == win.Cursor.Line)
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// }
|
||||||
|
// if currentLine {
|
||||||
|
// view.WriteString(m.Styles().GutterCurrentLine.Render(gutter))
|
||||||
|
// } else {
|
||||||
|
// view.WriteString(m.Styles().Gutter.Render(gutter))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawStatusBar: Renders the status bar with mode and cursor position,
|
||||||
|
// padding the middle with spaces to fill the terminal width.
|
||||||
|
func drawStatusBar(w *core.Window, mode core.Mode) string {
|
||||||
|
left := leftBar(w, mode)
|
||||||
|
right := rightBar(w, mode)
|
||||||
|
|
||||||
|
diff := w.Width - (len(left) + len(right))
|
||||||
|
|
||||||
|
// This happens when the terminal spawns
|
||||||
|
if diff <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
middle := strings.Repeat(" ", diff)
|
||||||
|
return left + middle + right
|
||||||
|
}
|
||||||
|
|
||||||
|
// leftBar: Returns the left side of the status bar showing the current mode.
|
||||||
|
func leftBar(w *core.Window, mode core.Mode) string {
|
||||||
|
buf := w.Buffer
|
||||||
|
return fmt.Sprintf(" %s %s", mode.ToString(), buf.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rightBar: Returns the right side of the status bar showing cursor position
|
||||||
|
// and selection count in visual mode.
|
||||||
|
func rightBar(w *core.Window, mode core.Mode) (bar string) {
|
||||||
|
if mode.IsVisualMode() {
|
||||||
|
lineCount := max(w.Anchor.Line, w.Cursor.Line) - min(w.Anchor.Line, w.Cursor.Line) + 1
|
||||||
|
bar = fmt.Sprintf("%d:%d <%d>", w.Cursor.Line+1, w.Cursor.Col+1, lineCount)
|
||||||
|
} else {
|
||||||
|
bar = fmt.Sprintf("%d:%d ", w.Cursor.Line+1, w.Cursor.Col+1)
|
||||||
|
}
|
||||||
|
buf := w.Buffer
|
||||||
|
bar = fmt.Sprintf("%s %s", buf.Filetype, bar)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawCommandBar: Renders the command line showing command input, errors, or
|
||||||
|
// output depending on the current mode and state.
|
||||||
|
func drawCommandBar(m Model) string {
|
||||||
|
// Compute left bar (command side)
|
||||||
|
var leftBar string
|
||||||
|
if m.Mode() == core.CommandMode {
|
||||||
|
leftBar = ":"
|
||||||
|
cmd := m.Command()
|
||||||
|
cur := m.CommandCursor()
|
||||||
|
for i := 0; i < len(cmd); i++ {
|
||||||
|
if i == cur {
|
||||||
|
leftBar += m.Styles().CursorStyle(m.Mode()).Render(string(cmd[i]))
|
||||||
|
} else {
|
||||||
|
leftBar += string(cmd[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cursor at end of command
|
||||||
|
if cur >= len(cmd) {
|
||||||
|
leftBar += m.Styles().CursorStyle(m.Mode()).Render(" ")
|
||||||
|
}
|
||||||
|
// bar = fmt.Sprintf("%s %d", bar, cur)
|
||||||
|
} else if m.CommandError() != nil {
|
||||||
|
leftBar = m.Styles().CommandError.Render(m.CommandError().Error())
|
||||||
|
} else if strings.TrimSpace(m.CommandOutput()) != "" {
|
||||||
|
leftBar = m.CommandOutput()
|
||||||
|
} else if strings.TrimSpace(m.Command()) != "" {
|
||||||
|
leftBar = fmt.Sprintf(":%s", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute right bar
|
||||||
|
|
||||||
|
var rightBar string
|
||||||
|
if len(m.input.Pending()) > 0 {
|
||||||
|
width := 10 // Size of the block to display
|
||||||
|
rightBar = fmt.Sprintf("%-*s", width, m.input.Pending())
|
||||||
|
}
|
||||||
|
|
||||||
|
dif := m.termWidth - (len(leftBar) + len(rightBar))
|
||||||
|
|
||||||
|
bar := leftBar + strings.Repeat(" ", dif) + rightBar
|
||||||
|
return bar
|
||||||
|
}
|
||||||
|
|
||||||
// posInsideSelection: Returns true if the given position is inside the current
|
// posInsideSelection: Returns true if the given position is inside the current
|
||||||
// visual selection, handling all three visual modes differently.
|
// visual selection, handling all three visual modes differently.
|
||||||
func posInsideSelection(m Model, col, line int) bool {
|
func posInsideSelection(w *core.Window, mode core.Mode, col, line int) bool {
|
||||||
win := m.ActiveWindow()
|
switch mode {
|
||||||
|
|
||||||
switch m.Mode() {
|
|
||||||
case core.VisualLineMode:
|
case core.VisualLineMode:
|
||||||
startY := min(win.Anchor.Line, win.Cursor.Line)
|
startY := min(w.Anchor.Line, w.Cursor.Line)
|
||||||
endY := max(win.Anchor.Line, win.Cursor.Line)
|
endY := max(w.Anchor.Line, w.Cursor.Line)
|
||||||
return line >= startY && line <= endY
|
return line >= startY && line <= endY
|
||||||
|
|
||||||
case core.VisualMode:
|
case core.VisualMode:
|
||||||
ax := win.Anchor.Col
|
ax := w.Anchor.Col
|
||||||
ay := win.Anchor.Line
|
ay := w.Anchor.Line
|
||||||
|
|
||||||
cx := win.Cursor.Col
|
cx := w.Cursor.Col
|
||||||
cy := win.Cursor.Line
|
cy := w.Cursor.Line
|
||||||
|
|
||||||
// Normalize so start is always before end in document order
|
// Normalize so start is always before end in document order
|
||||||
var startX, startY, endX, endY int
|
var startX, startY, endX, endY int
|
||||||
@ -41,10 +298,10 @@ func posInsideSelection(m Model, col, line int) bool {
|
|||||||
return afterStart && beforeEnd
|
return afterStart && beforeEnd
|
||||||
|
|
||||||
case core.VisualBlockMode:
|
case core.VisualBlockMode:
|
||||||
startX := min(win.Anchor.Col, win.Cursor.Col)
|
startX := min(w.Anchor.Col, w.Cursor.Col)
|
||||||
startY := min(win.Anchor.Line, win.Cursor.Line)
|
startY := min(w.Anchor.Line, w.Cursor.Line)
|
||||||
endX := max(win.Anchor.Col, win.Cursor.Col)
|
endX := max(w.Anchor.Col, w.Cursor.Col)
|
||||||
endY := max(win.Anchor.Line, win.Cursor.Line)
|
endY := max(w.Anchor.Line, w.Cursor.Line)
|
||||||
|
|
||||||
return col >= startX && col <= endX &&
|
return col >= startX && col <= endX &&
|
||||||
line >= startY && line <= endY
|
line >= startY && line <= endY
|
||||||
@ -53,183 +310,3 @@ func posInsideSelection(m Model, col, line int) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// posIsAnchor: Returns true if the given position matches the anchor position
|
|
||||||
// used for visual mode debugging/rendering.
|
|
||||||
func posIsAnchor(m Model, col, line int) bool {
|
|
||||||
win := m.ActiveWindow()
|
|
||||||
ax := win.Anchor.Col
|
|
||||||
ay := win.Anchor.Line
|
|
||||||
return col == ax && line == ay
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model.View: Renders the complete editor view including buffer content, line
|
|
||||||
// numbers, status bar, and command line.
|
|
||||||
func (m Model) View() string {
|
|
||||||
win := m.ActiveWindow()
|
|
||||||
buf := m.ActiveBuffer()
|
|
||||||
|
|
||||||
var view strings.Builder
|
|
||||||
|
|
||||||
viewportHeight := win.Height - 2
|
|
||||||
start := win.ScrollY
|
|
||||||
end := win.ScrollY + viewportHeight
|
|
||||||
|
|
||||||
for i := start; i < end; i++ {
|
|
||||||
|
|
||||||
if i < buf.LineCount() {
|
|
||||||
|
|
||||||
if m.Settings().Number || m.Settings().RelativeNumber {
|
|
||||||
var (
|
|
||||||
gutter string
|
|
||||||
currentLine bool = false
|
|
||||||
lineNumber int
|
|
||||||
)
|
|
||||||
|
|
||||||
if m.Settings().RelativeNumber {
|
|
||||||
// Relative line numbers: show distance from cursor, current line shows absolute
|
|
||||||
if i > win.Cursor.Line {
|
|
||||||
lineNumber = i - win.Cursor.Line
|
|
||||||
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
|
||||||
} else if i < win.Cursor.Line {
|
|
||||||
lineNumber = win.Cursor.Line - i
|
|
||||||
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
|
||||||
} else {
|
|
||||||
// Current line: show absolute number if Number is also set, otherwise show 0
|
|
||||||
currentLine = true
|
|
||||||
if m.Settings().Number {
|
|
||||||
lineNumber = i + 1
|
|
||||||
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
|
||||||
} else {
|
|
||||||
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if m.Settings().Number {
|
|
||||||
// Absolute line numbers only
|
|
||||||
lineNumber = i + 1
|
|
||||||
currentLine = (i == win.Cursor.Line)
|
|
||||||
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
|
||||||
}
|
|
||||||
if currentLine {
|
|
||||||
view.WriteString(m.Styles().GutterCurrentLine.Render(gutter))
|
|
||||||
} else {
|
|
||||||
view.WriteString(m.Styles().Gutter.Render(gutter))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runes := []rune(buf.Lines[i])
|
|
||||||
for x := 0; x <= len(runes); x++ {
|
|
||||||
if win.Cursor.Line == i && win.Cursor.Col == x {
|
|
||||||
if x < len(runes) {
|
|
||||||
view.WriteString(m.Styles().CursorStyle(m.Mode()).Render(string(runes[x])))
|
|
||||||
} else {
|
|
||||||
view.WriteString(m.Styles().CursorStyle(m.Mode()).Render(" "))
|
|
||||||
}
|
|
||||||
} else if x < len(runes) {
|
|
||||||
if m.Mode().IsVisualMode() && posIsAnchor(m, x, i) {
|
|
||||||
view.WriteString(m.Styles().VisualAnchor.Render(string(runes[x])))
|
|
||||||
} else if m.Mode().IsVisualMode() && posInsideSelection(m, x, i) {
|
|
||||||
view.WriteString(m.Styles().VisualHighlight.Render(string(runes[x])))
|
|
||||||
} else {
|
|
||||||
view.WriteRune(runes[x])
|
|
||||||
}
|
|
||||||
// To highlight blank lines when in visual mode
|
|
||||||
} else if m.Mode().IsVisualMode() && posInsideSelection(m, x, i) {
|
|
||||||
view.WriteString(m.Styles().VisualHighlight.Render(" "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Empty lines beyond file content
|
|
||||||
if m.Settings().Number || m.Settings().RelativeNumber {
|
|
||||||
format := fmt.Sprintf("%%-%ds ", m.Settings().GutterSize-1)
|
|
||||||
fmt.Fprintf(&view, format, "~")
|
|
||||||
} else {
|
|
||||||
view.WriteString("~")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
view.WriteString(drawStatusBar(m))
|
|
||||||
view.WriteString("\n")
|
|
||||||
view.WriteString(drawCommandBar(m))
|
|
||||||
|
|
||||||
return view.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func viewWindow(w core.Window) string {
|
|
||||||
var view string
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
// drawStatusBar: Renders the status bar with mode and cursor position,
|
|
||||||
// padding the middle with spaces to fill the terminal width.
|
|
||||||
func drawStatusBar(m Model) string {
|
|
||||||
left := leftBar(m)
|
|
||||||
right := rightBar(m)
|
|
||||||
|
|
||||||
diff := m.termWidth - (len(left) + len(right))
|
|
||||||
|
|
||||||
// This happens when the terminal spawns
|
|
||||||
if diff <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
middle := strings.Repeat(" ", diff)
|
|
||||||
return left + middle + right
|
|
||||||
}
|
|
||||||
|
|
||||||
// leftBar: Returns the left side of the status bar showing the current mode.
|
|
||||||
func leftBar(m Model) string {
|
|
||||||
return fmt.Sprintf(" %s", m.Mode().ToString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// rightBar: Returns the right side of the status bar showing cursor position
|
|
||||||
// and selection count in visual mode.
|
|
||||||
func rightBar(m Model) (bar string) {
|
|
||||||
win := m.ActiveWindow()
|
|
||||||
|
|
||||||
if m.Mode().IsVisualMode() {
|
|
||||||
lineCount := max(win.Anchor.Line, win.Cursor.Line) - min(win.Anchor.Line, win.Cursor.Line) + 1
|
|
||||||
bar = fmt.Sprintf("%d:%d <%d>", win.Cursor.Line, win.Cursor.Col, lineCount)
|
|
||||||
} else {
|
|
||||||
bar = fmt.Sprintf("%d:%d ", win.Cursor.Line, win.Cursor.Col)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// drawCommandBar: Renders the command line showing command input, errors, or
|
|
||||||
// output depending on the current mode and state.
|
|
||||||
func drawCommandBar(m Model) (bar string) {
|
|
||||||
if m.Mode() == core.CommandMode {
|
|
||||||
bar = ":"
|
|
||||||
cmd := m.Command()
|
|
||||||
cur := m.CommandCursor()
|
|
||||||
for i := 0; i < len(cmd); i++ {
|
|
||||||
if i == cur {
|
|
||||||
bar += m.Styles().CursorStyle(m.Mode()).Render(string(cmd[i]))
|
|
||||||
} else {
|
|
||||||
bar += string(cmd[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Cursor at end of command
|
|
||||||
if cur >= len(cmd) {
|
|
||||||
bar += m.Styles().CursorStyle(m.Mode()).Render(" ")
|
|
||||||
}
|
|
||||||
// bar = fmt.Sprintf("%s %d", bar, cur)
|
|
||||||
} else if m.CommandError() != nil {
|
|
||||||
bar = m.Styles().CommandError.Render(m.CommandError().Error())
|
|
||||||
} else if strings.TrimSpace(m.CommandOutput()) != "" {
|
|
||||||
bar = m.CommandOutput()
|
|
||||||
} else if strings.TrimSpace(m.Command()) != "" {
|
|
||||||
bar = fmt.Sprintf(":%s", m.Command())
|
|
||||||
} else if len(m.input.Pending()) > 0 {
|
|
||||||
// Get width of window and padding
|
|
||||||
rep := m.ActiveWindow().Width - 10 // 10 is padding
|
|
||||||
bar = fmt.Sprintf("%s%s", strings.Repeat(" ", rep), m.input.Pending())
|
|
||||||
}
|
|
||||||
|
|
||||||
return bar
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user