diff --git a/ui/buffer/cursor.go b/ui/buffer/cursor.go index 5ed20e3..2a61a95 100755 --- a/ui/buffer/cursor.go +++ b/ui/buffer/cursor.go @@ -17,27 +17,19 @@ type position struct { // The start and end are inclusive. If the EndCol of a Region is one more than the // last column of a line, then it points to the line delimiter at the end of that // line. It is understood that as a Region spans multiple lines, those connecting -// line-delimiters are included, as well. +// line-delimiters are included in the selection, as well. type Region struct { - buffer *Buffer - start position - end position + Start Cursor + End Cursor } func NewRegion(in *Buffer) Region { return Region{ - buffer: in, + NewCursor(in), + NewCursor(in), } } -func (r Region) Start() (line, col int) { - return r.start.line, r.start.col -} - -func (r Region) End() (line, col int) { - return r.end.line, r.end.col -} - // A Cursor's functions emulate common cursor actions. To have a Cursor be // automatically updated when the buffer has text prepended or appended -- one // should register the Cursor with the Buffer's function `RegisterCursor()` @@ -105,3 +97,7 @@ func (c Cursor) SetLineCol(line, col int) Cursor { c.line, c.col = (*c.buffer).ClampLineCol(line, col) return c } + +func (c Cursor) Eq(other Cursor) bool { + return c.buffer == other.buffer && c.line == other.line && c.col == other.col +} diff --git a/ui/textedit.go b/ui/textedit.go index 91dff25..38c7c67 100755 --- a/ui/textedit.go +++ b/ui/textedit.go @@ -77,6 +77,7 @@ loop: t.Buffer = buffer.NewRopeBuffer(contents) t.cursor = buffer.NewCursor(&t.Buffer) + t.selection = buffer.NewRegion(&t.Buffer) // TODO: replace with automatic determination of language via filetype lang := &buffer.Language{ @@ -151,8 +152,8 @@ func (t *TextEdit) Delete(forwards bool) { if t.selectMode { // If text is selected, delete the whole selection t.selectMode = false // Disable selection and prevent infinite loop - startLine, startCol := t.selection.Start() - endLine, endCol := t.selection.End() + startLine, startCol := t.selection.Start.GetLineCol() + endLine, endCol := t.selection.End.GetLineCol() // Delete the region t.Buffer.Remove(startLine, startCol, endLine, endCol) @@ -319,8 +320,8 @@ func (t *TextEdit) getColumnWidth() int { func (t *TextEdit) GetSelectedBytes() []byte { // TODO: there's a bug with copying text if t.selectMode { - startLine, startCol := t.selection.Start() - endLine, endCol := t.selection.End() + startLine, startCol := t.selection.Start.GetLineCol() + endLine, endCol := t.selection.End.GetLineCol() return t.Buffer.Slice(startLine, startCol, endLine, endCol) } return []byte{} @@ -421,8 +422,8 @@ func (t *TextEdit) Draw(s tcell.Screen) { r = ' ' } - startLine, startCol := t.selection.Start() - endLine, endCol := t.selection.End() + startLine, startCol := t.selection.Start.GetLineCol() + endLine, endCol := t.selection.End.GetLineCol() // Determine whether we select the current rune. Also only select runes within // the line bytes range. @@ -508,87 +509,98 @@ func (t *TextEdit) HandleEvent(event tcell.Event) bool { // Cursor movement case tcell.KeyUp: if ev.Modifiers() == tcell.ModShift { - // if !t.selectMode { - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // t.selectMode = true - // } else { - // prevCurX, prevCurY := t.curx, t.cury - // t.CursorUp() - // // Grow the selection in the correct direction - // if prevCurY <= t.selection.StartLine && prevCurX <= t.selection.StartCol { - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // } else { - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // } - // } + if !t.selectMode { + var endCursor buffer.Cursor + if cursLine, _ := t.cursor.GetLineCol(); cursLine != 0 { + endCursor = t.cursor.Left() + } else { + endCursor = t.cursor + } + t.selection.End = endCursor + t.SetCursor(t.cursor.Up()) + t.selection.Start = t.cursor + t.selectMode = true + t.ScrollToCursor() + break // Select only a single character at start + } + + if t.selection.Start.Eq(t.cursor) { + t.SetCursor(t.cursor.Up()) + t.selection.Start = t.cursor + } else { + t.SetCursor(t.cursor.Up()) + t.selection.End = t.cursor + } } else { t.selectMode = false t.SetCursor(t.cursor.Up()) - t.ScrollToCursor() } + t.ScrollToCursor() case tcell.KeyDown: if ev.Modifiers() == tcell.ModShift { - // if !t.selectMode { - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // t.selectMode = true - // } else { - // prevCurX, prevCurY := t.curx, t.cury - // t.CursorDown() - // if prevCurY >= t.selection.EndLine && prevCurX >= t.selection.EndCol { - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // } else { - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // } - // } + if !t.selectMode { + t.selection.Start = t.cursor + t.SetCursor(t.cursor.Down()) + t.selection.End = t.cursor + t.selectMode = true + t.ScrollToCursor() + break + } + + if t.selection.End.Eq(t.cursor) { + t.SetCursor(t.cursor.Down()) + t.selection.End = t.cursor + } else { + t.SetCursor(t.cursor.Down()) + t.selection.Start = t.cursor + } } else { t.selectMode = false t.SetCursor(t.cursor.Down()) - t.ScrollToCursor() } + t.ScrollToCursor() case tcell.KeyLeft: if ev.Modifiers() == tcell.ModShift { - // if !t.selectMode { - // t.CursorLeft() // We want the character to the left to be selected only (think insert) - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // t.selectMode = true - // } else { - // prevCurX, prevCurY := t.curx, t.cury - // t.CursorLeft() - // if prevCurY == t.selection.StartLine && prevCurX == t.selection.StartCol { // We are moving the start... - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // } else { - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // } - // } + if !t.selectMode { + t.SetCursor(t.cursor.Left()) + t.selection.Start, t.selection.End = t.cursor, t.cursor + t.selectMode = true + t.ScrollToCursor() + break // Select only a single character at start + } + + if t.selection.Start.Eq(t.cursor) { + t.SetCursor(t.cursor.Left()) + t.selection.Start = t.cursor + } else { + t.SetCursor(t.cursor.Left()) + t.selection.End = t.cursor + } } else { t.selectMode = false t.SetCursor(t.cursor.Left()) - t.ScrollToCursor() } + t.ScrollToCursor() case tcell.KeyRight: if ev.Modifiers() == tcell.ModShift { - // if !t.selectMode { // If we are not already selecting... - // // Reset the selection to cursor pos - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // t.selectMode = true - // } else { - // prevCurX, prevCurY := t.curx, t.cury - // t.CursorRight() // Advance the cursor - // if prevCurY == t.selection.EndLine && prevCurX == t.selection.EndCol { - // t.selection.EndLine, t.selection.EndCol = t.cury, t.curx - // } else { - // t.selection.StartLine, t.selection.StartCol = t.cury, t.curx - // } - // } + if !t.selectMode { + t.selection.Start, t.selection.End = t.cursor, t.cursor + t.selectMode = true + break + } + + if t.selection.End.Eq(t.cursor) { + t.SetCursor(t.cursor.Right()) + t.selection.End = t.cursor + } else { + t.SetCursor(t.cursor.Right()) + t.selection.Start = t.cursor + } } else { t.selectMode = false t.SetCursor(t.cursor.Right()) - t.ScrollToCursor() } + t.ScrollToCursor() case tcell.KeyHome: cursLine, _ := t.cursor.GetLineCol() // TODO: go to first (non-whitespace) character on current line, if we are not already there