]> git.lizzy.rs Git - micro.git/blob - cmd/micro/cursor.go
Merge pull request #1242 from dwwmmn/dwwmmn-erl
[micro.git] / cmd / micro / cursor.go
1 package main
2
3 import (
4         "github.com/zyedidia/clipboard"
5 )
6
7 // The Cursor struct stores the location of the cursor in the view
8 // The complicated part about the cursor is storing its location.
9 // The cursor must be displayed at an x, y location, but since the buffer
10 // uses a rope to store text, to insert text we must have an index. It
11 // is also simpler to use character indicies for other tasks such as
12 // selection.
13 type Cursor struct {
14         buf *Buffer
15         Loc
16
17         // Last cursor x position
18         LastVisualX int
19
20         // The current selection as a range of character numbers (inclusive)
21         CurSelection [2]Loc
22         // The original selection as a range of character numbers
23         // This is used for line and word selection where it is necessary
24         // to know what the original selection was
25         OrigSelection [2]Loc
26
27         // Which cursor index is this (for multiple cursors)
28         Num int
29 }
30
31 // Goto puts the cursor at the given cursor's location and gives
32 // the current cursor its selection too
33 func (c *Cursor) Goto(b Cursor) {
34         c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
35         c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
36 }
37
38 // GotoLoc puts the cursor at the given cursor's location and gives
39 // the current cursor its selection too
40 func (c *Cursor) GotoLoc(l Loc) {
41         c.X, c.Y = l.X, l.Y
42         c.LastVisualX = c.GetVisualX()
43 }
44
45 // CopySelection copies the user's selection to either "primary"
46 // or "clipboard"
47 func (c *Cursor) CopySelection(target string) {
48         if c.HasSelection() {
49                 if target != "primary" || c.buf.Settings["useprimary"].(bool) {
50                         clipboard.WriteAll(c.GetSelection(), target)
51                 }
52         }
53 }
54
55 // ResetSelection resets the user's selection
56 func (c *Cursor) ResetSelection() {
57         c.CurSelection[0] = c.buf.Start()
58         c.CurSelection[1] = c.buf.Start()
59 }
60
61 // SetSelectionStart sets the start of the selection
62 func (c *Cursor) SetSelectionStart(pos Loc) {
63         c.CurSelection[0] = pos
64 }
65
66 // SetSelectionEnd sets the end of the selection
67 func (c *Cursor) SetSelectionEnd(pos Loc) {
68         c.CurSelection[1] = pos
69 }
70
71 // HasSelection returns whether or not the user has selected anything
72 func (c *Cursor) HasSelection() bool {
73         return c.CurSelection[0] != c.CurSelection[1]
74 }
75
76 // DeleteSelection deletes the currently selected text
77 func (c *Cursor) DeleteSelection() {
78         if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
79                 c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
80                 c.Loc = c.CurSelection[1]
81         } else if !c.HasSelection() {
82                 return
83         } else {
84                 c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
85                 c.Loc = c.CurSelection[0]
86         }
87 }
88
89 // GetSelection returns the cursor's selection
90 func (c *Cursor) GetSelection() string {
91         if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
92                 if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
93                         return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
94                 }
95                 return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
96         }
97         return ""
98 }
99
100 // SelectLine selects the current line
101 func (c *Cursor) SelectLine() {
102         c.Start()
103         c.SetSelectionStart(c.Loc)
104         c.End()
105         if c.buf.NumLines-1 > c.Y {
106                 c.SetSelectionEnd(c.Loc.Move(1, c.buf))
107         } else {
108                 c.SetSelectionEnd(c.Loc)
109         }
110
111         c.OrigSelection = c.CurSelection
112 }
113
114 // AddLineToSelection adds the current line to the selection
115 func (c *Cursor) AddLineToSelection() {
116         if c.Loc.LessThan(c.OrigSelection[0]) {
117                 c.Start()
118                 c.SetSelectionStart(c.Loc)
119                 c.SetSelectionEnd(c.OrigSelection[1])
120         }
121         if c.Loc.GreaterThan(c.OrigSelection[1]) {
122                 c.End()
123                 c.SetSelectionEnd(c.Loc.Move(1, c.buf))
124                 c.SetSelectionStart(c.OrigSelection[0])
125         }
126
127         if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
128                 c.CurSelection = c.OrigSelection
129         }
130 }
131
132 // SelectWord selects the word the cursor is currently on
133 func (c *Cursor) SelectWord() {
134         if len(c.buf.Line(c.Y)) == 0 {
135                 return
136         }
137
138         if !IsWordChar(string(c.RuneUnder(c.X))) {
139                 c.SetSelectionStart(c.Loc)
140                 c.SetSelectionEnd(c.Loc.Move(1, c.buf))
141                 c.OrigSelection = c.CurSelection
142                 return
143         }
144
145         forward, backward := c.X, c.X
146
147         for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
148                 backward--
149         }
150
151         c.SetSelectionStart(Loc{backward, c.Y})
152         c.OrigSelection[0] = c.CurSelection[0]
153
154         for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
155                 forward++
156         }
157
158         c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
159         c.OrigSelection[1] = c.CurSelection[1]
160         c.Loc = c.CurSelection[1]
161 }
162
163 // AddWordToSelection adds the word the cursor is currently on
164 // to the selection
165 func (c *Cursor) AddWordToSelection() {
166         if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
167                 c.CurSelection = c.OrigSelection
168                 return
169         }
170
171         if c.Loc.LessThan(c.OrigSelection[0]) {
172                 backward := c.X
173
174                 for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
175                         backward--
176                 }
177
178                 c.SetSelectionStart(Loc{backward, c.Y})
179                 c.SetSelectionEnd(c.OrigSelection[1])
180         }
181
182         if c.Loc.GreaterThan(c.OrigSelection[1]) {
183                 forward := c.X
184
185                 for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
186                         forward++
187                 }
188
189                 c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
190                 c.SetSelectionStart(c.OrigSelection[0])
191         }
192
193         c.Loc = c.CurSelection[1]
194 }
195
196 // SelectTo selects from the current cursor location to the given
197 // location
198 func (c *Cursor) SelectTo(loc Loc) {
199         if loc.GreaterThan(c.OrigSelection[0]) {
200                 c.SetSelectionStart(c.OrigSelection[0])
201                 c.SetSelectionEnd(loc)
202         } else {
203                 c.SetSelectionStart(loc)
204                 c.SetSelectionEnd(c.OrigSelection[0])
205         }
206 }
207
208 // WordRight moves the cursor one word to the right
209 func (c *Cursor) WordRight() {
210         for IsWhitespace(c.RuneUnder(c.X)) {
211                 if c.X == Count(c.buf.Line(c.Y)) {
212                         c.Right()
213                         return
214                 }
215                 c.Right()
216         }
217         c.Right()
218         for IsWordChar(string(c.RuneUnder(c.X))) {
219                 if c.X == Count(c.buf.Line(c.Y)) {
220                         return
221                 }
222                 c.Right()
223         }
224 }
225
226 // WordLeft moves the cursor one word to the left
227 func (c *Cursor) WordLeft() {
228         c.Left()
229         for IsWhitespace(c.RuneUnder(c.X)) {
230                 if c.X == 0 {
231                         return
232                 }
233                 c.Left()
234         }
235         c.Left()
236         for IsWordChar(string(c.RuneUnder(c.X))) {
237                 if c.X == 0 {
238                         return
239                 }
240                 c.Left()
241         }
242         c.Right()
243 }
244
245 // RuneUnder returns the rune under the given x position
246 func (c *Cursor) RuneUnder(x int) rune {
247         line := []rune(c.buf.Line(c.Y))
248         if len(line) == 0 {
249                 return '\n'
250         }
251         if x >= len(line) {
252                 return '\n'
253         } else if x < 0 {
254                 x = 0
255         }
256         return line[x]
257 }
258 // UpN moves the cursor up N lines (if possible)
259 func (c *Cursor) UpN(amount int) {
260         proposedY := c.Y - amount
261         if proposedY < 0 {
262                 proposedY = 0
263                 c.LastVisualX = 0
264         } else if proposedY >= c.buf.NumLines {
265                 proposedY = c.buf.NumLines - 1
266         }
267
268         runes := []rune(c.buf.Line(proposedY))
269         c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
270         if c.X > len(runes) || (amount < 0 && proposedY == c.Y) {
271                 c.X = len(runes)
272         }
273
274         c.Y = proposedY
275 }
276
277 // DownN moves the cursor down N lines (if possible)
278 func (c *Cursor) DownN(amount int) {
279         c.UpN(-amount)
280 }
281
282 // Up moves the cursor up one line (if possible)
283 func (c *Cursor) Up() {
284         c.UpN(1)
285 }
286
287 // Down moves the cursor down one line (if possible)
288 func (c *Cursor) Down() {
289         c.DownN(1)
290 }
291
292 // Left moves the cursor left one cell (if possible) or to
293 // the previous line if it is at the beginning
294 func (c *Cursor) Left() {
295         if c.Loc == c.buf.Start() {
296                 return
297         }
298         if c.X > 0 {
299                 c.X--
300         } else {
301                 c.Up()
302                 c.End()
303         }
304         c.LastVisualX = c.GetVisualX()
305 }
306
307 // Right moves the cursor right one cell (if possible) or
308 // to the next line if it is at the end
309 func (c *Cursor) Right() {
310         if c.Loc == c.buf.End() {
311                 return
312         }
313         if c.X < Count(c.buf.Line(c.Y)) {
314                 c.X++
315         } else {
316                 c.Down()
317                 c.Start()
318         }
319         c.LastVisualX = c.GetVisualX()
320 }
321
322 // End moves the cursor to the end of the line it is on
323 func (c *Cursor) End() {
324         c.X = Count(c.buf.Line(c.Y))
325         c.LastVisualX = c.GetVisualX()
326 }
327
328 // Start moves the cursor to the start of the line it is on
329 func (c *Cursor) Start() {
330         c.X = 0
331         c.LastVisualX = c.GetVisualX()
332 }
333
334 // StartOfText moves the cursor to the first non-whitespace rune of
335 // the line it is on
336 func (c *Cursor) StartOfText() {
337         c.Start()
338         for IsWhitespace(c.RuneUnder(c.X)) {
339                 if c.X == Count(c.buf.Line(c.Y)) {
340                         break
341                 }
342                 c.Right()
343         }
344 }
345
346 // GetCharPosInLine gets the char position of a visual x y
347 // coordinate (this is necessary because tabs are 1 char but
348 // 4 visual spaces)
349 func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
350         // Get the tab size
351         tabSize := int(c.buf.Settings["tabsize"].(float64))
352         visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
353         if visualPos > visualLineLen {
354                 visualPos = visualLineLen
355         }
356         width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
357         if visualPos >= width {
358                 return visualPos - width
359         }
360         return visualPos / tabSize
361 }
362
363 // GetVisualX returns the x value of the cursor in visual spaces
364 func (c *Cursor) GetVisualX() int {
365         runes := []rune(c.buf.Line(c.Y))
366         tabSize := int(c.buf.Settings["tabsize"].(float64))
367         if c.X > len(runes) {
368                 c.X = len(runes) - 1
369         }
370
371         if c.X < 0 {
372                 c.X = 0
373         }
374
375         return StringWidth(string(runes[:c.X]), tabSize)
376 }
377
378 // StoreVisualX stores the current visual x value in the cursor
379 func (c *Cursor) StoreVisualX() {
380         c.LastVisualX = c.GetVisualX()
381 }
382
383 // Relocate makes sure that the cursor is inside the bounds
384 // of the buffer If it isn't, it moves it to be within the
385 // buffer's lines
386 func (c *Cursor) Relocate() {
387         if c.Y < 0 {
388                 c.Y = 0
389         } else if c.Y >= c.buf.NumLines {
390                 c.Y = c.buf.NumLines - 1
391         }
392
393         if c.X < 0 {
394                 c.X = 0
395         } else if c.X > Count(c.buf.Line(c.Y)) {
396                 c.X = Count(c.buf.Line(c.Y))
397         }
398 }