]> git.lizzy.rs Git - micro.git/blob - cmd/micro/cursor.go
Create ~/.micro if it does not exist
[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 }
69
70 // Loc gets the cursor location in terms of character number instead
71 // of x, y location
72 // It's just a simple wrapper of ToCharPos
73 func (c *Cursor) Loc() int {
74         return ToCharPos(c.x, c.y, c.v.buf)
75 }
76
77 // ResetSelection resets the user's selection
78 func (c *Cursor) ResetSelection() {
79         c.curSelection[0] = 0
80         c.curSelection[1] = 0
81 }
82
83 // HasSelection returns whether or not the user has selected anything
84 func (c *Cursor) HasSelection() bool {
85         return c.curSelection[0] != c.curSelection[1]
86 }
87
88 // DeleteSelection deletes the currently selected text
89 func (c *Cursor) DeleteSelection() {
90         if c.curSelection[0] > c.curSelection[1] {
91                 c.v.eh.Remove(c.curSelection[1], c.curSelection[0])
92                 c.SetLoc(c.curSelection[1])
93         } else {
94                 c.v.eh.Remove(c.curSelection[0], c.curSelection[1])
95                 c.SetLoc(c.curSelection[0])
96         }
97 }
98
99 // GetSelection returns the cursor's selection
100 func (c *Cursor) GetSelection() string {
101         if c.curSelection[0] > c.curSelection[1] {
102                 return string([]rune(c.v.buf.text)[c.curSelection[1]:c.curSelection[0]])
103         }
104         return string([]rune(c.v.buf.text)[c.curSelection[0]:c.curSelection[1]])
105 }
106
107 // SelectLine selects the current line
108 func (c *Cursor) SelectLine() {
109         c.Start()
110         c.curSelection[0] = c.Loc()
111         c.End()
112         c.curSelection[1] = c.Loc()
113
114         c.origSelection = c.curSelection
115 }
116
117 // AddLineToSelection adds the current line to the selection
118 func (c *Cursor) AddLineToSelection() {
119         loc := c.Loc()
120
121         if loc < c.origSelection[0] {
122                 c.Start()
123                 c.curSelection[0] = c.Loc()
124                 c.curSelection[1] = c.origSelection[1]
125         }
126         if loc > c.origSelection[1] {
127                 c.End()
128                 c.curSelection[1] = c.Loc()
129                 c.curSelection[0] = c.origSelection[0]
130         }
131
132         if loc < c.origSelection[1] && loc > c.origSelection[0] {
133                 c.curSelection = c.origSelection
134         }
135 }
136
137 // SelectWord selects the word the cursor is currently on
138 func (c *Cursor) SelectWord() {
139         if len(c.v.buf.lines[c.y]) == 0 {
140                 return
141         }
142
143         if !IsWordChar(string(c.RuneUnder(c.x))) {
144                 loc := c.Loc()
145                 c.curSelection[0] = loc
146                 c.curSelection[1] = loc + 1
147                 c.origSelection = c.curSelection
148                 return
149         }
150
151         forward, backward := c.x, c.x
152
153         for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
154                 backward--
155         }
156
157         c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf)
158         c.origSelection[0] = c.curSelection[0]
159
160         for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
161                 forward++
162         }
163
164         c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
165         c.origSelection[1] = c.curSelection[1]
166 }
167
168 // AddWordToSelection adds the word the cursor is currently on to the selection
169 func (c *Cursor) AddWordToSelection() {
170         loc := c.Loc()
171
172         if loc > c.origSelection[0] && loc < c.origSelection[1] {
173                 c.curSelection = c.origSelection
174                 return
175         }
176
177         if loc < c.origSelection[0] {
178                 backward := c.x
179
180                 for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
181                         backward--
182                 }
183
184                 c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf)
185                 c.curSelection[1] = c.origSelection[1]
186         }
187
188         if loc > c.origSelection[1] {
189                 forward := c.x
190
191                 for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
192                         forward++
193                 }
194
195                 c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
196                 c.curSelection[0] = c.origSelection[0]
197         }
198 }
199
200 // RuneUnder returns the rune under the given x position
201 func (c *Cursor) RuneUnder(x int) rune {
202         line := []rune(c.v.buf.lines[c.y])
203         if x >= len(line) {
204                 x = len(line) - 1
205         } else if x < 0 {
206                 x = 0
207         }
208         return line[x]
209 }
210
211 // Up moves the cursor up one line (if possible)
212 func (c *Cursor) Up() {
213         if c.y > 0 {
214                 c.y--
215
216                 runes := []rune(c.v.buf.lines[c.y])
217                 c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
218                 if c.x > len(runes) {
219                         c.x = len(runes)
220                 }
221         }
222 }
223
224 // Down moves the cursor down one line (if possible)
225 func (c *Cursor) Down() {
226         if c.y < len(c.v.buf.lines)-1 {
227                 c.y++
228
229                 runes := []rune(c.v.buf.lines[c.y])
230                 c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
231                 if c.x > len(runes) {
232                         c.x = len(runes)
233                 }
234         }
235 }
236
237 // Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
238 func (c *Cursor) Left() {
239         if c.Loc() == 0 {
240                 return
241         }
242         if c.x > 0 {
243                 c.x--
244         } else {
245                 c.Up()
246                 c.End()
247         }
248         c.lastVisualX = c.GetVisualX()
249 }
250
251 // Right moves the cursor right one cell (if possible) or to the next line if it is at the end
252 func (c *Cursor) Right() {
253         if c.Loc() == c.v.buf.Len() {
254                 return
255         }
256         if c.x < Count(c.v.buf.lines[c.y]) {
257                 c.x++
258         } else {
259                 c.Down()
260                 c.Start()
261         }
262         c.lastVisualX = c.GetVisualX()
263 }
264
265 // End moves the cursor to the end of the line it is on
266 func (c *Cursor) End() {
267         c.x = Count(c.v.buf.lines[c.y])
268         c.lastVisualX = c.GetVisualX()
269 }
270
271 // Start moves the cursor to the start of the line it is on
272 func (c *Cursor) Start() {
273         c.x = 0
274         c.lastVisualX = c.GetVisualX()
275 }
276
277 // GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
278 func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
279         // Get the tab size
280         tabSize := settings.TabSize
281         // This is the visual line -- every \t replaced with the correct number of spaces
282         visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
283         if visualPos > Count(visualLine) {
284                 visualPos = Count(visualLine)
285         }
286         numTabs := NumOccurences(visualLine[:visualPos], '\t')
287         if visualPos >= (tabSize-1)*numTabs {
288                 return visualPos - (tabSize-1)*numTabs
289         }
290         return visualPos / tabSize
291 }
292
293 // GetVisualX returns the x value of the cursor in visual spaces
294 func (c *Cursor) GetVisualX() int {
295         runes := []rune(c.v.buf.lines[c.y])
296         tabSize := settings.TabSize
297         return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
298 }
299
300 // Display draws the cursor to the screen at the correct position
301 func (c *Cursor) Display() {
302         // Don't draw the cursor if it is out of the viewport or if it has a selection
303         if (c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1) || c.HasSelection() {
304                 screen.HideCursor()
305         } else {
306                 screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.topline)
307         }
308 }