Gim/internal/editor/view.go

191 lines
4.9 KiB
Go

package editor
import (
"fmt"
"strings"
"git.gophernest.net/azpect/TextEditor/internal/action"
)
func posInsideSelection(m Model, col, line int) bool {
switch m.Mode() {
case action.VisualLineMode:
startY := min(m.AnchorY(), m.CursorY())
endY := max(m.AnchorY(), m.CursorY())
return line >= startY && line <= endY
case action.VisualMode:
ax := m.AnchorX()
ay := m.AnchorY()
cx := m.CursorX()
cy := m.CursorY()
// Normalize so start is always before end in document order
var startX, startY, endX, endY int
if ay < cy || (ay == cy && ax <= cx) {
startX, startY = ax, ay
endX, endY = cx, cy
} else {
startX, startY = cx, cy
endX, endY = ax, ay
}
// Position is inside if it falls within [start, end] inclusive
afterStart := line > startY || (line == startY && col >= startX)
beforeEnd := line < endY || (line == endY && col <= endX)
return afterStart && beforeEnd
case action.VisualBlockMode:
startX := min(m.AnchorX(), m.CursorX())
startY := min(m.AnchorY(), m.CursorY())
endX := max(m.AnchorX(), m.CursorX())
endY := max(m.AnchorY(), m.CursorY())
return col >= startX && col <= endX &&
line >= startY && line <= endY
default:
return false
}
}
func posIsAnchor(m Model, col, line int) bool {
ax := m.AnchorX()
ay := m.AnchorY()
return col == ax && line == ay
}
func (m Model) View() string {
var view strings.Builder
viewportHeight := m.win_h - 1 // -1 for status bar
start := m.ScrollY()
end := m.ScrollY() + viewportHeight
for i := start; i < end; i++ {
if i < m.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 > m.CursorY() {
lineNumber = i - m.CursorY()
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
} else if i < m.CursorY() {
lineNumber = m.CursorY() - 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 == m.CursorY())
gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
}
view.WriteString(m.gutterStyle(currentLine).Render(gutter))
}
runes := []rune(m.Line(i))
for x := 0; x <= len(runes); x++ {
if m.CursorY() == i && m.CursorX() == x {
if x < len(runes) {
view.WriteString(m.cursorStyle().Render(string(runes[x])))
} else {
view.WriteString(m.cursorStyle().Render(" "))
}
} else if x < len(runes) {
if m.Mode().IsVisualMode() && posIsAnchor(m, x, i) {
view.WriteString(m.visualAnchorStyle().Render(string(runes[x])))
} else if m.Mode().IsVisualMode() && posInsideSelection(m, x, i) {
view.WriteString(m.visualHighlightStyle().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.visualHighlightStyle().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")
}
bar := drawStatusBar(m)
view.WriteString(bar)
return view.String()
}
func drawStatusBar(m Model) string {
left := leftBar(m)
right := rightBar(m)
diff := m.win_w - (len(left) + len(right))
// This happens when the terminal spawns
if diff <= 0 {
return ""
}
middle := strings.Repeat(" ", diff)
return left + middle + right
}
func leftBar(m Model) (bar string) {
if m.Mode() == action.CommandMode {
bar = ":"
cmd := m.Command()
cur := m.CommandCursor()
for i := 0; i < len(cmd); i++ {
if i == cur {
bar += m.cursorStyle().Render(string(cmd[i]))
} else {
bar += string(cmd[i])
}
}
// Cursor at end of command
if cur >= len(cmd) {
bar += m.cursorStyle().Render(" ")
}
// bar = fmt.Sprintf("%s %d", bar, cur)
} else {
bar = fmt.Sprintf(" %s", m.Mode().ToString())
}
return
}
func rightBar(m Model) (bar string) {
if m.Mode().IsVisualMode() {
lineCount := max(m.AnchorY(), m.CursorY()) - min(m.AnchorY(), m.CursorY()) + 1
bar = fmt.Sprintf("%d:%d <%d>", m.CursorY(), m.CursorX(), lineCount)
} else {
bar = fmt.Sprintf("%d:%d ", m.CursorY(), m.CursorX())
}
return
}