169 lines
4.7 KiB
Go
169 lines
4.7 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.IsVisualMode() && posIsAnchor(m, x, i) {
|
|
view.WriteString(m.visualAnchorStyle().Render(string(runes[x])))
|
|
} else if m.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.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")
|
|
}
|
|
|
|
// 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 m.IsVisualMode() {
|
|
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()
|
|
}
|