Selection bug fixes

This commit is contained in:
Luke I. Wilson 2021-03-18 16:44:50 -05:00
parent d5ec12c1c8
commit f7f864d125

View File

@ -11,7 +11,11 @@ import (
// A Selection represents a region of the buffer to be selected for text editing
// purposes. It is asserted that the start position is less than the end position.
type Selection 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.
type Region struct {
StartLine, StartCol int
EndLine, EndCol int
}
@ -36,8 +40,8 @@ type TextEdit struct {
prevCurCol int // Previous maximum column the cursor was at, when the user pressed left or right
scrollx, scrolly int // X and Y offset of view, known as scroll
selection Selection // Selection: nil is no selection
selectMode bool // Whether the user is actively selecting text
selection Region // Selection: selectMode determines if it should be used
selectMode bool // Whether the user is actively selecting text
Theme *Theme
}
@ -82,12 +86,17 @@ loop:
t.buffer = strings.Split(contents, delimiter) // Split contents into lines
}
func (t *TextEdit) String() string {
delimiter := "\n"
// GetLineDelimiter returns "\r\n" for a CRLF buffer, or "\n" for an LF buffer.
func (t *TextEdit) GetLineDelimiter() string {
if t.IsCRLF {
delimiter = "\r\n"
return "\r\n"
} else {
return "\n"
}
return strings.Join(t.buffer, delimiter)
}
func (t *TextEdit) String() string {
return strings.Join(t.buffer, t.GetLineDelimiter())
}
// Changes a file's line delimiters. If `crlf` is true, then line delimiters are replaced
@ -102,6 +111,21 @@ func (t *TextEdit) ChangeLineDelimiters(crlf bool) {
// while Delete with `forwards` true will delete the character after (or on) the cursor.
// In insert mode, forwards is always true.
func (t *TextEdit) Delete(forwards bool) {
if t.selectMode { // If text is selected, delete the whole selection
t.cury, t.curx = t.clampLineCol(t.selection.EndLine, t.selection.EndCol)
t.selectMode = false // Disable selection and prevent infinite loop
t.Delete(true) // Delete last character of selection first
// Delete from end, backwards, until we are at the start of the selection
for { // TODO: inefficient
if t.cury == t.selection.StartLine && t.curx == t.selection.StartCol {
break
}
t.Delete(false) // NOTE: we want to delete start column as well.
}
return
}
// TODO: deleting through lines
if forwards { // Delete the character after the cursor
if t.curx < len(t.buffer[t.cury]) { // If the cursor is not at the end of the line...
@ -146,8 +170,13 @@ func (t *TextEdit) Delete(forwards bool) {
}
// Writes `contents` at the cursor position. Line delimiters and tab character supported.
// Any other control characters will be printed.
// Any other control characters will be printed. Overwrites any active selection.
func (t *TextEdit) Insert(contents string) {
if t.selectMode { // If there is a selection...
// Go to and delete the selection
t.Delete(true) // The parameter doesn't matter with selection
}
runes := []rune(contents)
for i := 0; i < len(runes); i++ {
ch := runes[i]
@ -278,8 +307,10 @@ func (t *TextEdit) SetLineCol(line, col int) {
}
t.cury, t.curx = line, col
if t.focused {
if t.focused && !t.selectMode {
(*t.screen).ShowCursor(t.x+columnWidth+col+tabOffset-t.scrollx, t.y+line-t.scrolly)
} else {
(*t.screen).HideCursor()
}
}
@ -345,6 +376,29 @@ func (t *TextEdit) getColumnWidth() int {
return columnWidth
}
// GetSelectedString returns a string of the region of the buffer that is currently selected.
// If the returned string is empty, then nothing was selected.
func (t *TextEdit) GetSelectedString() string {
if t.selectMode {
lines := make([]string, t.selection.EndLine-t.selection.StartLine+1)
copy(lines, t.buffer[t.selection.StartLine:t.selection.EndLine+1])
// Start last line at end col
lastLine := lines[len(lines)-1]
if t.selection.EndCol >= len(lastLine) { // If the line delimiter of the last line is selected...
// Don't access out-of-bounds and include the line delimiter
lastLine = string([]rune(lastLine)[:t.selection.EndCol]) + t.GetLineDelimiter()
} else { // Normal access
lastLine = string([]rune(lastLine)[:t.selection.EndCol+1])
}
lines[0] = string([]rune(lines[0])[t.selection.StartCol:]) // Start first line at start col
return strings.Join(lines, t.GetLineDelimiter())
}
return ""
}
// Draw renders the TextEdit component.
func (t *TextEdit) Draw(s tcell.Screen) {
columnWidth := t.getColumnWidth()
@ -393,21 +447,27 @@ func (t *TextEdit) Draw(s tcell.Screen) {
tabCount := t.getTabCountInLineAtCol(line, t.selection.StartCol)
selStartIdx = t.selection.StartCol + tabCount*(t.TabSize-1) - t.scrollx
}
selEndIdx := len(lineRunes) - t.scrollx // Not inclusive
selEndIdx := len(lineRunes) - t.scrollx // used inclusively
if line == t.selection.EndLine { // If the selection ends somewhere in the line...
tabCount := t.getTabCountInLineAtCol(line, t.selection.EndCol)
selEndIdx = t.selection.EndCol + 1 + tabCount*(t.TabSize-1) - t.scrollx
selEndIdx = t.selection.EndCol + tabCount*(t.TabSize-1) - t.scrollx
}
// NOTE: a special draw function just for selections. Should combine this with ordinary draw
currentStyle := textEditStyle
for i, ch := range lineRunes {
for i := 0; i < t.width-columnWidth; i++ { // For each column we can draw
if i == selStartIdx {
currentStyle = selectedStyle // begin drawing selected
} else if i == selEndIdx {
} else if i > selEndIdx {
currentStyle = textEditStyle // reset style
}
// Draw the character
s.SetContent(t.x+columnWidth+i, lineY, ch, nil, currentStyle)
r := ' ' // Rune to draw
if i < len(lineRunes) { // While we're drawing the line
r = lineRunes[i]
}
s.SetContent(t.x+columnWidth+i, lineY, r, nil, currentStyle)
}
} else {
DrawStr(s, t.x+columnWidth, lineY, string(lineRunes), textEditStyle) // Draw line
@ -476,14 +536,15 @@ func (t *TextEdit) HandleEvent(event tcell.Event) bool {
t.selection.StartLine, t.selection.StartCol = t.cury, t.curx
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
t.selectMode = true
}
prevCurX, prevCurY := t.curx, t.cury
t.CursorUp()
//
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
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
}
}
} else {
t.selectMode = false
@ -495,13 +556,14 @@ func (t *TextEdit) HandleEvent(event tcell.Event) bool {
t.selection.StartLine, t.selection.StartCol = t.cury, t.curx
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
t.selectMode = true
}
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
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
}
}
} else {
t.selectMode = false
@ -513,13 +575,14 @@ func (t *TextEdit) HandleEvent(event tcell.Event) bool {
t.selection.StartLine, t.selection.StartCol = t.cury, t.curx
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
t.selectMode = true
}
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
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
}
}
} else {
t.selectMode = false
@ -532,13 +595,14 @@ func (t *TextEdit) HandleEvent(event tcell.Event) bool {
t.selection.StartLine, t.selection.StartCol = t.cury, t.curx
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
t.selectMode = true
}
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
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
}
}
} else {
t.selectMode = false