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