Selection bug fixes
This commit is contained in:
parent
d5ec12c1c8
commit
f7f864d125
144
ui/textedit.go
144
ui/textedit.go
@ -11,7 +11,11 @@ import (
|
|||||||
|
|
||||||
// A Selection represents a region of the buffer to be selected for text editing
|
// 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.
|
// 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
|
StartLine, StartCol int
|
||||||
EndLine, EndCol 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
|
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
|
scrollx, scrolly int // X and Y offset of view, known as scroll
|
||||||
|
|
||||||
selection Selection // Selection: nil is no selection
|
selection Region // Selection: selectMode determines if it should be used
|
||||||
selectMode bool // Whether the user is actively selecting text
|
selectMode bool // Whether the user is actively selecting text
|
||||||
|
|
||||||
Theme *Theme
|
Theme *Theme
|
||||||
}
|
}
|
||||||
@ -82,12 +86,17 @@ loop:
|
|||||||
t.buffer = strings.Split(contents, delimiter) // Split contents into lines
|
t.buffer = strings.Split(contents, delimiter) // Split contents into lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TextEdit) String() string {
|
// GetLineDelimiter returns "\r\n" for a CRLF buffer, or "\n" for an LF buffer.
|
||||||
delimiter := "\n"
|
func (t *TextEdit) GetLineDelimiter() string {
|
||||||
if t.IsCRLF {
|
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
|
// 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.
|
// while Delete with `forwards` true will delete the character after (or on) the cursor.
|
||||||
// In insert mode, forwards is always true.
|
// In insert mode, forwards is always true.
|
||||||
func (t *TextEdit) Delete(forwards bool) {
|
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
|
// TODO: deleting through lines
|
||||||
if forwards { // Delete the character after the cursor
|
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...
|
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.
|
// 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) {
|
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)
|
runes := []rune(contents)
|
||||||
for i := 0; i < len(runes); i++ {
|
for i := 0; i < len(runes); i++ {
|
||||||
ch := runes[i]
|
ch := runes[i]
|
||||||
@ -278,8 +307,10 @@ func (t *TextEdit) SetLineCol(line, col int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.cury, t.curx = line, col
|
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)
|
(*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
|
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.
|
// Draw renders the TextEdit component.
|
||||||
func (t *TextEdit) Draw(s tcell.Screen) {
|
func (t *TextEdit) Draw(s tcell.Screen) {
|
||||||
columnWidth := t.getColumnWidth()
|
columnWidth := t.getColumnWidth()
|
||||||
@ -393,21 +447,27 @@ func (t *TextEdit) Draw(s tcell.Screen) {
|
|||||||
tabCount := t.getTabCountInLineAtCol(line, t.selection.StartCol)
|
tabCount := t.getTabCountInLineAtCol(line, t.selection.StartCol)
|
||||||
selStartIdx = t.selection.StartCol + tabCount*(t.TabSize-1) - t.scrollx
|
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...
|
if line == t.selection.EndLine { // If the selection ends somewhere in the line...
|
||||||
tabCount := t.getTabCountInLineAtCol(line, t.selection.EndCol)
|
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
|
currentStyle := textEditStyle
|
||||||
for i, ch := range lineRunes {
|
for i := 0; i < t.width-columnWidth; i++ { // For each column we can draw
|
||||||
if i == selStartIdx {
|
if i == selStartIdx {
|
||||||
currentStyle = selectedStyle // begin drawing selected
|
currentStyle = selectedStyle // begin drawing selected
|
||||||
} else if i == selEndIdx {
|
} else if i > selEndIdx {
|
||||||
currentStyle = textEditStyle // reset style
|
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 {
|
} else {
|
||||||
DrawStr(s, t.x+columnWidth, lineY, string(lineRunes), textEditStyle) // Draw line
|
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.StartLine, t.selection.StartCol = t.cury, t.curx
|
||||||
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
||||||
t.selectMode = true
|
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 {
|
} 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 {
|
} else {
|
||||||
t.selectMode = false
|
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.StartLine, t.selection.StartCol = t.cury, t.curx
|
||||||
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
||||||
t.selectMode = true
|
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 {
|
} 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 {
|
} else {
|
||||||
t.selectMode = false
|
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.StartLine, t.selection.StartCol = t.cury, t.curx
|
||||||
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
||||||
t.selectMode = true
|
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 {
|
} 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 {
|
} else {
|
||||||
t.selectMode = false
|
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.StartLine, t.selection.StartCol = t.cury, t.curx
|
||||||
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
t.selection.EndLine, t.selection.EndCol = t.cury, t.curx
|
||||||
t.selectMode = true
|
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 {
|
} 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 {
|
} else {
|
||||||
t.selectMode = false
|
t.selectMode = false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user