]> git.lizzy.rs Git - micro.git/blob - cmd/micro/cursor.go
darcula: fix highlighted line and color column
[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
259 // UpN moves the cursor up N lines (if possible)
260 func (c *Cursor) UpN(amount int) {
261         proposedY := c.Y - amount
262         if proposedY < 0 {
263                 proposedY = 0
264                 c.LastVisualX = 0
265         } else if proposedY >= c.buf.NumLines {
266                 proposedY = c.buf.NumLines - 1
267         }
268
269         runes := []rune(c.buf.Line(c.Y))
270         c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
271
272         if c.X > len(runes) || (amount < 0 && proposedY == c.Y) {
273                 c.X = len(runes)
274         }
275
276         c.Y = proposedY
277 }
278
279 // DownN moves the cursor down N lines (if possible)
280 func (c *Cursor) DownN(amount int) {
281         c.UpN(-amount)
282 }
283
284 // Up moves the cursor up one line (if possible)
285 func (c *Cursor) Up() {
286         c.UpN(1)
287 }
288
289 // Down moves the cursor down one line (if possible)
290 func (c *Cursor) Down() {
291         c.DownN(1)
292 }
293
294 // Left moves the cursor left one cell (if possible) or to
295 // the previous line if it is at the beginning
296 func (c *Cursor) Left() {
297         if c.Loc == c.buf.Start() {
298                 return
299         }
300         if c.X > 0 {
301                 c.X--
302         } else {
303                 c.Up()
304                 c.End()
305         }
306         c.LastVisualX = c.GetVisualX()
307 }
308
309 // Right moves the cursor right one cell (if possible) or
310 // to the next line if it is at the end
311 func (c *Cursor) Right() {
312         if c.Loc == c.buf.End() {
313                 return
314         }
315         if c.X < Count(c.buf.Line(c.Y)) {
316                 c.X++
317         } else {
318                 c.Down()
319                 c.Start()
320         }
321         c.LastVisualX = c.GetVisualX()
322 }
323
324 // End moves the cursor to the end of the line it is on
325 func (c *Cursor) End() {
326         c.X = Count(c.buf.Line(c.Y))
327         c.LastVisualX = c.GetVisualX()
328 }
329
330 // Start moves the cursor to the start of the line it is on
331 func (c *Cursor) Start() {
332         c.X = 0
333         c.LastVisualX = c.GetVisualX()
334 }
335
336 // GetCharPosInLine gets the char position of a visual x y
337 // coordinate (this is necessary because tabs are 1 char but
338 // 4 visual spaces)
339 func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
340         // Get the tab size
341         tabSize := int(c.buf.Settings["tabsize"].(float64))
342         visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
343         if visualPos > visualLineLen {
344                 visualPos = visualLineLen
345         }
346         width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
347         if visualPos >= width {
348                 return visualPos - width
349         }
350         return visualPos / tabSize
351 }
352
353 // GetVisualX returns the x value of the cursor in visual spaces
354 func (c *Cursor) GetVisualX() int {
355         runes := []rune(c.buf.Line(c.Y))
356         tabSize := int(c.buf.Settings["tabsize"].(float64))
357         if c.X > len(runes) {
358                 c.X = len(runes) - 1
359         }
360
361         if c.X < 0 {
362                 c.X = 0
363         }
364
365         return StringWidth(string(runes[:c.X]), tabSize)
366 }
367
368 // StoreVisualX stores the current visual x value in the cursor
369 func (c *Cursor) StoreVisualX() {
370         c.LastVisualX = c.GetVisualX()
371 }
372
373 // Relocate makes sure that the cursor is inside the bounds
374 // of the buffer If it isn't, it moves it to be within the
375 // buffer's lines
376 func (c *Cursor) Relocate() {
377         if c.Y < 0 {
378                 c.Y = 0
379         } else if c.Y >= c.buf.NumLines {
380                 c.Y = c.buf.NumLines - 1
381         }
382
383         if c.X < 0 {
384                 c.X = 0
385         } else if c.X > Count(c.buf.Line(c.Y)) {
386                 c.X = Count(c.buf.Line(c.Y))
387         }
388 }