]> git.lizzy.rs Git - micro.git/blob - cmd/micro/cursor.go
Add the text member back.
[micro.git] / cmd / micro / cursor.go
1 package main
2
3 import (
4         "strings"
5 )
6
7 // FromCharPos converts from a character position to an x, y position
8 func FromCharPos(loc int, buf *Buffer) (int, int) {
9         return FromCharPosStart(0, 0, 0, loc, buf)
10 }
11
12 // FromCharPosStart converts from a character position to an x, y position, starting at the specified character location
13 func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int) {
14         charNum := startLoc
15         x, y := startX, startY
16
17         lineLen := Count(buf.Lines[y]) + 1
18         for charNum+lineLen <= loc {
19                 charNum += lineLen
20                 y++
21                 lineLen = Count(buf.Lines[y]) + 1
22         }
23         x = loc - charNum
24
25         return x, y
26 }
27
28 // ToCharPos converts from an x, y position to a character position
29 func ToCharPos(x, y int, buf *Buffer) int {
30         loc := 0
31         for i := 0; i < y; i++ {
32                 // + 1 for the newline
33                 loc += Count(buf.Lines[i]) + 1
34         }
35         loc += x
36         return loc
37 }
38
39 // The Cursor struct stores the location of the cursor in the view
40 // The complicated part about the cursor is storing its location.
41 // The cursor must be displayed at an x, y location, but since the buffer
42 // uses a rope to store text, to insert text we must have an index. It
43 // is also simpler to use character indicies for other tasks such as
44 // selection.
45 type Cursor struct {
46         v *View
47
48         // The cursor display location
49         x int
50         y int
51
52         // Last cursor x position
53         lastVisualX int
54
55         // The current selection as a range of character numbers (inclusive)
56         curSelection [2]int
57         // The original selection as a range of character numbers
58         // This is used for line and word selection where it is necessary
59         // to know what the original selection was
60         origSelection [2]int
61 }
62
63 // SetLoc sets the location of the cursor in terms of character number
64 // and not x, y location
65 // It's just a simple wrapper of FromCharPos
66 func (c *Cursor) SetLoc(loc int) {
67         c.x, c.y = FromCharPos(loc, c.v.Buf)
68         c.lastVisualX = c.GetVisualX()
69 }
70
71 // Loc gets the cursor location in terms of character number instead
72 // of x, y location
73 // It's just a simple wrapper of ToCharPos
74 func (c *Cursor) Loc() int {
75         return ToCharPos(c.x, c.y, c.v.Buf)
76 }
77
78 // ResetSelection resets the user's selection
79 func (c *Cursor) ResetSelection() {
80         c.curSelection[0] = 0
81         c.curSelection[1] = 0
82 }
83
84 // HasSelection returns whether or not the user has selected anything
85 func (c *Cursor) HasSelection() bool {
86         return c.curSelection[0] != c.curSelection[1]
87 }
88
89 // DeleteSelection deletes the currently selected text
90 func (c *Cursor) DeleteSelection() {
91         if c.curSelection[0] > c.curSelection[1] {
92                 c.v.eh.Remove(c.curSelection[1], c.curSelection[0])
93                 c.SetLoc(c.curSelection[1])
94         } else if c.GetSelection() == "" {
95                 return
96         } else {
97                 c.v.eh.Remove(c.curSelection[0], c.curSelection[1])
98                 c.SetLoc(c.curSelection[0])
99         }
100 }
101
102 // GetSelection returns the cursor's selection
103 func (c *Cursor) GetSelection() string {
104         if c.curSelection[0] > c.curSelection[1] {
105                 return string([]rune(c.v.Buf.Text)[c.curSelection[1]:c.curSelection[0]])
106         }
107         return string([]rune(c.v.Buf.Text)[c.curSelection[0]:c.curSelection[1]])
108 }
109
110 // SelectLine selects the current line
111 func (c *Cursor) SelectLine() {
112         c.Start()
113         c.curSelection[0] = c.Loc()
114         c.End()
115         if c.v.Buf.NumLines-1 > c.y {
116                 c.curSelection[1] = c.Loc() + 1
117         } else {
118                 c.curSelection[1] = c.Loc()
119         }
120
121         c.origSelection = c.curSelection
122 }
123
124 // AddLineToSelection adds the current line to the selection
125 func (c *Cursor) AddLineToSelection() {
126         loc := c.Loc()
127
128         if loc < c.origSelection[0] {
129                 c.Start()
130                 c.curSelection[0] = c.Loc()
131                 c.curSelection[1] = c.origSelection[1]
132         }
133         if loc > c.origSelection[1] {
134                 c.End()
135                 c.curSelection[1] = c.Loc()
136                 c.curSelection[0] = c.origSelection[0]
137         }
138
139         if loc < c.origSelection[1] && loc > c.origSelection[0] {
140                 c.curSelection = c.origSelection
141         }
142 }
143
144 // SelectWord selects the word the cursor is currently on
145 func (c *Cursor) SelectWord() {
146         if len(c.v.Buf.Lines[c.y]) == 0 {
147                 return
148         }
149
150         if !IsWordChar(string(c.RuneUnder(c.x))) {
151                 loc := c.Loc()
152                 c.curSelection[0] = loc
153                 c.curSelection[1] = loc + 1
154                 c.origSelection = c.curSelection
155                 return
156         }
157
158         forward, backward := c.x, c.x
159
160         for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
161                 backward--
162         }
163
164         c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf)
165         c.origSelection[0] = c.curSelection[0]
166
167         for forward < Count(c.v.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
168                 forward++
169         }
170
171         c.curSelection[1] = ToCharPos(forward, c.y, c.v.Buf) + 1
172         c.origSelection[1] = c.curSelection[1]
173         c.SetLoc(c.curSelection[1])
174 }
175
176 // AddWordToSelection adds the word the cursor is currently on to the selection
177 func (c *Cursor) AddWordToSelection() {
178         loc := c.Loc()
179
180         if loc > c.origSelection[0] && loc < c.origSelection[1] {
181                 c.curSelection = c.origSelection
182                 return
183         }
184
185         if loc < c.origSelection[0] {
186                 backward := c.x
187
188                 for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
189                         backward--
190                 }
191
192                 c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf)
193                 c.curSelection[1] = c.origSelection[1]
194         }
195
196         if loc > c.origSelection[1] {
197                 forward := c.x
198
199                 for forward < Count(c.v.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
200                         forward++
201                 }
202
203                 c.curSelection[1] = ToCharPos(forward, c.y, c.v.Buf) + 1
204                 c.curSelection[0] = c.origSelection[0]
205         }
206
207         c.SetLoc(c.curSelection[1])
208 }
209
210 // SelectTo selects from the current cursor location to the given location
211 func (c *Cursor) SelectTo(loc int) {
212         if loc > c.origSelection[0] {
213                 c.curSelection[0] = c.origSelection[0]
214                 c.curSelection[1] = loc
215         } else {
216                 c.curSelection[0] = loc
217                 c.curSelection[1] = c.origSelection[0]
218         }
219 }
220
221 // WordRight moves the cursor one word to the right
222 func (c *Cursor) WordRight() {
223         c.Right()
224         for IsWhitespace(c.RuneUnder(c.x)) {
225                 if c.x == Count(c.v.Buf.Lines[c.y]) {
226                         return
227                 }
228                 c.Right()
229         }
230         for !IsWhitespace(c.RuneUnder(c.x)) {
231                 if c.x == Count(c.v.Buf.Lines[c.y]) {
232                         return
233                 }
234                 c.Right()
235         }
236 }
237
238 // WordLeft moves the cursor one word to the left
239 func (c *Cursor) WordLeft() {
240         c.Left()
241         for IsWhitespace(c.RuneUnder(c.x)) {
242                 if c.x == 0 {
243                         return
244                 }
245                 c.Left()
246         }
247         for !IsWhitespace(c.RuneUnder(c.x)) {
248                 if c.x == 0 {
249                         return
250                 }
251                 c.Left()
252         }
253         c.Right()
254 }
255
256 // RuneUnder returns the rune under the given x position
257 func (c *Cursor) RuneUnder(x int) rune {
258         line := []rune(c.v.Buf.Lines[c.y])
259         if len(line) == 0 {
260                 return '\n'
261         }
262         if x >= len(line) {
263                 return '\n'
264         } else if x < 0 {
265                 x = 0
266         }
267         return line[x]
268 }
269
270 // Up moves the cursor up one line (if possible)
271 func (c *Cursor) Up() {
272         if c.y > 0 {
273                 c.y--
274
275                 runes := []rune(c.v.Buf.Lines[c.y])
276                 c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
277                 if c.x > len(runes) {
278                         c.x = len(runes)
279                 }
280         }
281 }
282
283 // Down moves the cursor down one line (if possible)
284 func (c *Cursor) Down() {
285         if c.y < c.v.Buf.NumLines-1 {
286                 c.y++
287
288                 runes := []rune(c.v.Buf.Lines[c.y])
289                 c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
290                 if c.x > len(runes) {
291                         c.x = len(runes)
292                 }
293         }
294 }
295
296 // Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
297 func (c *Cursor) Left() {
298         if c.Loc() == 0 {
299                 return
300         }
301         if c.x > 0 {
302                 c.x--
303         } else {
304                 c.Up()
305                 c.End()
306         }
307         c.lastVisualX = c.GetVisualX()
308 }
309
310 // Right moves the cursor right one cell (if possible) or to the next line if it is at the end
311 func (c *Cursor) Right() {
312         if c.Loc() == c.v.Buf.Len() {
313                 return
314         }
315         if c.x < Count(c.v.Buf.Lines[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.v.Buf.Lines[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 coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
337 func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
338         // Get the tab size
339         tabSize := int(settings["tabsize"].(float64))
340         // This is the visual line -- every \t replaced with the correct number of spaces
341         visualLine := strings.Replace(c.v.Buf.Lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
342         if visualPos > Count(visualLine) {
343                 visualPos = Count(visualLine)
344         }
345         numTabs := NumOccurences(visualLine[:visualPos], '\t')
346         if visualPos >= (tabSize-1)*numTabs {
347                 return visualPos - (tabSize-1)*numTabs
348         }
349         return visualPos / tabSize
350 }
351
352 // GetVisualX returns the x value of the cursor in visual spaces
353 func (c *Cursor) GetVisualX() int {
354         runes := []rune(c.v.Buf.Lines[c.y])
355         tabSize := int(settings["tabsize"].(float64))
356         return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
357 }
358
359 // Relocate makes sure that the cursor is inside the bounds of the buffer
360 // If it isn't, it moves it to be within the buffer's lines
361 func (c *Cursor) Relocate() {
362         if c.y < 0 {
363                 c.y = 0
364         } else if c.y >= c.v.Buf.NumLines {
365                 c.y = c.v.Buf.NumLines - 1
366         }
367
368         if c.x < 0 {
369                 c.x = 0
370         } else if c.x > Count(c.v.Buf.Lines[c.y]) {
371                 c.x = Count(c.v.Buf.Lines[c.y])
372         }
373 }
374
375 // Display draws the cursor to the screen at the correct position
376 func (c *Cursor) Display() {
377         // Don't draw the cursor if it is out of the viewport or if it has a selection
378         if (c.y-c.v.Topline < 0 || c.y-c.v.Topline > c.v.height-1) || c.HasSelection() {
379                 screen.HideCursor()
380         } else {
381                 screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.Topline)
382         }
383 }