From 8f85e0991a475e191328753c9c3e1874c43834a5 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Sun, 8 Jun 2025 20:01:27 +0300 Subject: [PATCH] Add selections --- src/buffer.go | 25 ++++++++ src/main.go | 1 + src/window.go | 173 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/src/buffer.go b/src/buffer.go index 17e1cea..813c3ff 100644 --- a/src/buffer.go +++ b/src/buffer.go @@ -12,11 +12,17 @@ type Buffer struct { Name string Contents string CursorPos int + Selection *Selection canSave bool filename string } +type Selection struct { + selectionStart int + selectionEnd int +} + var Buffers = make(map[int]*Buffer) var LastBufferId int @@ -54,6 +60,25 @@ func (buffer *Buffer) Save() error { return nil } +func (buffer *Buffer) GetSelectedText() string { + if buffer.Selection == nil { + return "" + } + + if len(buffer.Contents) == 0 { + return "" + } + + start := buffer.Selection.selectionStart + end := buffer.Selection.selectionEnd + + if start <= end { + return buffer.Contents[buffer.Selection.selectionStart : buffer.Selection.selectionEnd+1] + } else { + return buffer.Contents[buffer.Selection.selectionEnd : buffer.Selection.selectionStart+1] + } +} + func GetOpenFileBuffer(filename string) *Buffer { // Replace tilde with home directory if filename != "~" && strings.HasPrefix(filename, "~/") { diff --git a/src/main.go b/src/main.go index fe29e9d..331f66f 100644 --- a/src/main.go +++ b/src/main.go @@ -29,4 +29,5 @@ func main() { for window.screen != nil { window.Draw() } + } diff --git a/src/window.go b/src/window.go index e318622..147926d 100644 --- a/src/window.go +++ b/src/window.go @@ -26,6 +26,8 @@ type Window struct { screen tcell.Screen } +var mouseHeld = false + func CreateWindow() (*Window, error) { window := Window{ ShowTopMenu: true, @@ -80,7 +82,10 @@ func (window *Window) drawCurrentBuffer() { width, _ := window.screen.Size() - for _, r := range []rune(buffer.Contents) { + normalStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color234) + selectedStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color243) + + for i, r := range []rune(buffer.Contents) { if x >= width || r == '\n' { x = 0 if window.ShowLineIndex { @@ -89,7 +94,13 @@ func (window *Window) drawCurrentBuffer() { y++ } - window.screen.SetContent(x, y, r, nil, tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.Color234)) + if buffer.Selection != nil && buffer.Selection.selectionEnd >= buffer.Selection.selectionStart && i >= buffer.Selection.selectionStart && i <= buffer.Selection.selectionEnd { + window.screen.SetContent(x, y, r, nil, selectedStyle) + } else if buffer.Selection != nil && i <= buffer.Selection.selectionStart && i >= buffer.Selection.selectionEnd { + window.screen.SetContent(x, y, r, nil, selectedStyle) + } else { + window.screen.SetContent(x, y, r, nil, normalStyle) + } if r != '\n' { x++ @@ -156,17 +167,75 @@ func (window *Window) Draw() { func (window *Window) input(ev *tcell.EventKey) { if ev.Key() == tcell.KeyRight { // Navigation Keys + if window.CursorMode == CursorModeBuffer { + // Add to selection + if ev.Modifiers() == tcell.ModShift { + if window.CurrentBuffer.Selection == nil { + window.CurrentBuffer.Selection = &Selection{ + selectionStart: window.CurrentBuffer.CursorPos, + selectionEnd: window.CurrentBuffer.CursorPos, + } + return + } else { + window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos + 1 + } + // Prevent selecting dummy character at the end of the buffer + if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { + window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 + } + } else if window.CurrentBuffer.Selection != nil { + // Unset selection + window.CurrentBuffer.Selection = nil + return + } + // Move cursor window.SetCursorPos(window.CurrentBuffer.CursorPos + 1) } } else if ev.Key() == tcell.KeyLeft { if window.CursorMode == CursorModeBuffer { + // Add to selection + if ev.Modifiers() == tcell.ModShift { + if window.CurrentBuffer.Selection == nil { + window.CurrentBuffer.Selection = &Selection{ + selectionStart: window.CurrentBuffer.CursorPos, + selectionEnd: window.CurrentBuffer.CursorPos, + } + return + } else { + window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos - 1 + } + } else if window.CurrentBuffer.Selection != nil { + // Unset selection + window.CurrentBuffer.Selection = nil + return + } + // Move cursor window.SetCursorPos(window.CurrentBuffer.CursorPos - 1) } } else if ev.Key() == tcell.KeyUp { + // Move cursor + x, y := window.GetCursorPos2D() + window.SetCursorPos2D(x, y-1) if window.CursorMode == CursorModeBuffer { - x, y := window.GetCursorPos2D() - window.SetCursorPos2D(x, y-1) + // Get original cursor position + pos := window.CurrentBuffer.CursorPos + // Add to selection + if ev.Modifiers() == tcell.ModShift { + // Add to selection + if window.CurrentBuffer.Selection == nil { + window.CurrentBuffer.Selection = &Selection{ + selectionStart: window.CurrentBuffer.CursorPos, + selectionEnd: pos, + } + } else { + window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos + } + } else if window.CurrentBuffer.Selection != nil { + // Unset selection + window.CurrentBuffer.Selection = nil + return + } } else if window.CursorMode == CursorModeDropdown { dropdown := ActiveDropdown dropdown.Selected-- @@ -190,8 +259,31 @@ func (window *Window) input(ev *tcell.EventKey) { } } else if ev.Key() == tcell.KeyDown { if window.CursorMode == CursorModeBuffer { + // Get original cursor position + pos := window.CurrentBuffer.CursorPos + // Move cursor x, y := window.GetCursorPos2D() window.SetCursorPos2D(x, y+1) + // Add to selection + if ev.Modifiers() == tcell.ModShift { + // Add to selection + if window.CurrentBuffer.Selection == nil { + window.CurrentBuffer.Selection = &Selection{ + selectionStart: pos, + selectionEnd: window.CurrentBuffer.CursorPos, + } + } else { + window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos + } + // Prevent selecting dummy character at the end of the buffer + if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { + window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 + } + } else if window.CurrentBuffer.Selection != nil { + // Unset selection + window.CurrentBuffer.Selection = nil + return + } } else if window.CursorMode == CursorModeDropdown { dropdown := ActiveDropdown dropdown.Selected++ @@ -337,8 +429,36 @@ func (window *Window) mouseInput(ev *tcell.EventMouse) { // Ensure click was in buffer area x1, y1, x2, y2 := window.GetTextAreaDimensions() if mouseX >= x1 && mouseY >= y1 && mouseX <= x2 && mouseY <= y2 { + + if mouseHeld { + // Add to selection + if window.CurrentBuffer.Selection == nil { + window.CurrentBuffer.Selection = &Selection{ + selectionStart: window.CurrentBuffer.CursorPos, + selectionEnd: window.CursorPos2DToCursorPos(bufferMouseX, bufferMouseY), + } + return + } else { + window.CurrentBuffer.Selection.selectionEnd = window.CursorPos2DToCursorPos(bufferMouseX, bufferMouseY) + } + // Prevent selecting dummy character at the end of the buffer + if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { + window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 + } + } else { + // Clear selection + if window.CurrentBuffer.Selection != nil { + window.CurrentBuffer.Selection = nil + } + } + // Move cursor window.SetCursorPos2D(bufferMouseX, bufferMouseY) } + mouseHeld = true + } else if ev.Buttons() == tcell.ButtonNone { + if mouseHeld { + mouseHeld = false + } } } @@ -362,6 +482,51 @@ func (window *Window) GetTextAreaDimensions() (int, int, int, int) { return x1, y1, x2, y2 } +func (window *Window) CursorPos2DToCursorPos(x, y int) int { + // Ensure x and y are positive + x = max(x, 0) + y = max(y, 0) + + // Set cursor position to 0 buffer is empty + if len(window.CurrentBuffer.Contents) == 0 { + return 0 + } + + // Create line slice from buffer contents + lines := make([]struct { + charIndex int + str string + }, 0) + + var str string + for i, char := range window.CurrentBuffer.Contents { + str += string(char) + if char == '\n' || i == len(window.CurrentBuffer.Contents)-1 { + lines = append(lines, struct { + charIndex int + str string + }{charIndex: i - len(str) + 1, str: str}) + str = "" + } + } + + // Append extra character or line + if window.CurrentBuffer.Contents[len(window.CurrentBuffer.Contents)-1] == '\n' { + lines = append(lines, struct { + charIndex int + str string + }{charIndex: len(window.CurrentBuffer.Contents), str: " "}) + } else { + lines[len(lines)-1].str += " " + } + + // Limit x and y + y = min(y, len(lines)-1) + x = min(x, len(lines[y].str)-1) + + return lines[y].charIndex + x +} + func (window *Window) AbsolutePosToBufferArea(x, y int) (int, int) { x1, y1, _, _ := window.GetTextAreaDimensions()