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