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() { var ( gutter string currentLine bool = false lineNumber int ) 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 { lineNumber = i + 1 currentLine = true if lineNumber < 100 { gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-2, lineNumber) } else { 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 { format := fmt.Sprintf("%%-%ds ", m.Settings().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 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() }