Gim/internal/editor/view.go
2026-02-11 15:00:02 -07:00

154 lines
4.1 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 isVisualMode(m action.Mode) bool {
return m == action.VisualMode ||
m == action.VisualLineMode ||
m == action.VisualBlockMode
}
func (m Model) View() string {
var view strings.Builder
for y := 0; y < m.win_h-1; y++ {
if y < len(m.lines) {
var (
gutter string
currentLine bool = false
lineNumber int
)
if y > m.cursor.y {
lineNumber = y - m.cursor.y
gutter = fmt.Sprintf("%*d ", m.gutterSize-1, lineNumber)
} else if y < m.cursor.y {
lineNumber = m.cursor.y - y
gutter = fmt.Sprintf("%*d ", m.gutterSize-1, lineNumber)
} else {
lineNumber = y + 1
currentLine = true
if lineNumber < 100 {
gutter = fmt.Sprintf("%*d ", m.gutterSize-2, lineNumber)
} else {
gutter = fmt.Sprintf("%*d ", m.gutterSize-1, lineNumber)
}
}
view.WriteString(m.gutterStyle(currentLine).Render(gutter))
runes := []rune(m.lines[y])
for x := 0; x <= len(runes); x++ {
if m.cursor.y == y && m.cursor.x == 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 isVisualMode(m.Mode()) && posIsAnchor(m, x, y) {
view.WriteString(m.visualAnchorStyle().Render(string(runes[x])))
} else if isVisualMode(m.Mode()) && posInsideSelection(m, x, y) {
view.WriteString(m.visualHighlightStyle().Render(string(runes[x])))
} else {
view.WriteRune(runes[x])
}
// To highlight blank lines when in visual mode
} else if isVisualMode(m.Mode()) && posInsideSelection(m, x, y) {
view.WriteString(m.visualHighlightStyle().Render(" "))
}
}
} else {
format := fmt.Sprintf("%%-%ds ", m.gutterSize-1)
fmt.Fprintf(&view, format, "~")
}
view.WriteString("\n")
}
// Draw status bar
var modeString string
switch m.mode {
case action.NormalMode:
modeString = "NORMAL"
case action.InsertMode:
modeString = "INSERT"
case action.CommandMode:
modeString = "COMMAND"
case action.VisualMode:
modeString = "VISUAL"
case action.VisualLineMode:
modeString = "V-LINE"
case action.VisualBlockMode:
modeString = "V-BLOCK"
}
// DEBUG BAR! Def not the final bar
var bar string
if m.Mode() == action.CommandMode {
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) :%s ", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.command)
} else if isVisualMode(m.Mode()) {
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) <%d, %d> ", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.AnchorX(), m.AnchorY())
} else {
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) | %s | %+v | %d", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.input.Pending(), m.insertKeys, m.insertCount)
}
view.WriteString(bar)
return view.String()
}