1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
package buffer
// StartSelection drops the selection anchor where the cursor is. Moving the
// cursor afterwards extends the selection from that anchor, which is what
// holding Shift does.
//
// b := buffer.NewFromString("hello")
// b.StartSelection()
// b.MoveWordRight()
// fmt.Println(b.SelectedText()) // hello
func (b *Buffer) StartSelection() {
anchor := b.cursor
b.anchor = &anchor
}
// ClearSelection forgets the anchor, leaving nothing selected.
func (b *Buffer) ClearSelection() {
b.anchor = nil
}
// SelectAll selects the whole buffer and puts the cursor at its end.
func (b *Buffer) SelectAll() {
b.MoveBufferStart()
b.Extend(b.MoveBufferEnd)
}
// Extend runs a cursor movement while keeping the selection anchor, which is
// exactly what holding Shift does. If nothing was selected yet, the anchor is
// dropped where the cursor stands before the move.
//
// Every movement method works here, so there is no Shift-flavoured twin of
// each of them:
//
// b.Extend(b.MoveWordRight)
// b.Extend(b.MoveDown)
func (b *Buffer) Extend(move func()) {
if b.anchor == nil {
b.StartSelection()
}
anchor := b.anchor
move()
b.anchor = anchor
}
// SetCursorKeepingSelection moves the cursor to p while keeping the selection
// anchor. It is Extend for an absolute position, which is what a mouse drag
// needs; SetCursor is the plain, selection-clearing move.
func (b *Buffer) SetCursorKeepingSelection(p Position) {
b.Extend(func() { b.SetCursor(p) })
}
// Selection returns the selected span and whether there is one. A selection
// that has been reduced to nothing by moving the cursor back onto the anchor
// counts as absent.
func (b *Buffer) Selection() (Range, bool) {
if b.anchor == nil {
return Range{}, false
}
r := NewRange(*b.anchor, b.cursor)
if r.IsEmpty() {
return Range{}, false
}
return r, true
}
// SelectedText returns the selected text, or the empty string when nothing is
// selected. Lines are joined with line feeds regardless of the file's own line
// endings, because this is what goes on the clipboard.
func (b *Buffer) SelectedText() string {
r, ok := b.Selection()
if !ok {
return ""
}
return joinRunes(b.textIn(r))
}
// DeleteSelection removes the selected text and reports whether there was any.
// It is the first thing every editing operation does, so that typing over a
// selection replaces it.
func (b *Buffer) DeleteSelection() bool {
r, ok := b.Selection()
if !ok {
b.ClearSelection()
return false
}
b.ClearSelection()
b.DeleteRange(r)
return true
}
// SelectWord selects the word at a position and puts the cursor at its end.
//
// The word is a run of the runes IsWordRune accepts — the same notion
// Ctrl-Left and Ctrl-Right move by, and the same one the completion popup uses
// to work out what is being typed. One editor, one idea of a word.
//
// b := buffer.NewFromString("func main() {}")
// b.SelectWord(buffer.Position{Line: 0, Col: 6})
// r, _ := b.Selection() // covers "main"
//
// A position that is not on a word rune — a space, a bracket, past the end of
// a line — selects nothing and only moves the cursor. Editors disagree about
// what a run of punctuation means, and answering nothing is at least an answer
// that can be predicted.
func (b *Buffer) SelectWord(p Position) {
p = b.clamp(p)
line := b.lines[p.Line]
if p.Col >= len(line) || !IsWordRune(line[p.Col]) {
b.ClearSelection()
b.SetCursor(p)
return
}
start, end := p.Col, p.Col
for start > 0 && IsWordRune(line[start-1]) {
start--
}
for end < len(line) && IsWordRune(line[end]) {
end++
}
b.SetCursor(Position{Line: p.Line, Col: start})
b.StartSelection()
b.SetCursorKeepingSelection(Position{Line: p.Line, Col: end})
}
|