7 // FromCharPos converts from a character position to an x, y position
8 func FromCharPos(loc int, buf *Buffer) (int, int) {
12 for charNum+Count(buf.lines[y])+1 <= loc {
13 charNum += Count(buf.lines[y]) + 1
21 // ToCharPos converts from an x, y position to a character position
22 func ToCharPos(x, y int, buf *Buffer) int {
24 for i := 0; i < y; i++ {
25 // + 1 for the newline
26 loc += Count(buf.lines[i]) + 1
32 // The Cursor struct stores the location of the cursor in the view
33 // The complicated part about the cursor is storing its location.
34 // The cursor must be displayed at an x, y location, but since the buffer
35 // uses a rope to store text, to insert text we must have an index. It
36 // is also simpler to use character indicies for other tasks such as
41 // The cursor display location
45 // Last cursor x position
48 // The current selection as a range of character numbers (inclusive)
50 // The original selection as a range of character numbers
51 // This is used for line and word selection where it is necessary
52 // to know what the original selection was
56 // SetLoc sets the location of the cursor in terms of character number
57 // and not x, y location
58 // It's just a simple wrapper of FromCharPos
59 func (c *Cursor) SetLoc(loc int) {
60 c.x, c.y = FromCharPos(loc, c.v.buf)
63 // Loc gets the cursor location in terms of character number instead
65 // It's just a simple wrapper of ToCharPos
66 func (c *Cursor) Loc() int {
67 return ToCharPos(c.x, c.y, c.v.buf)
70 // ResetSelection resets the user's selection
71 func (c *Cursor) ResetSelection() {
76 // HasSelection returns whether or not the user has selected anything
77 func (c *Cursor) HasSelection() bool {
78 return c.curSelection[1] != c.curSelection[0]
81 // DeleteSelection deletes the currently selected text
82 func (c *Cursor) DeleteSelection() {
83 if c.curSelection[0] > c.curSelection[1] {
84 c.v.eh.Remove(c.curSelection[1], c.curSelection[0]+1)
85 c.SetLoc(c.curSelection[1])
87 c.v.eh.Remove(c.curSelection[0], c.curSelection[1]+1)
88 c.SetLoc(c.curSelection[0])
92 // GetSelection returns the cursor's selection
93 func (c *Cursor) GetSelection() string {
94 if c.curSelection[0] > c.curSelection[1] {
95 return string([]rune(c.v.buf.text)[c.curSelection[1] : c.curSelection[0]+1])
97 return string([]rune(c.v.buf.text)[c.curSelection[0] : c.curSelection[1]+1])
100 // SelectLine selects the current line
101 func (c *Cursor) SelectLine() {
103 c.curSelection[0] = c.Loc()
105 c.curSelection[1] = c.Loc()
107 c.origSelection = c.curSelection
110 // AddLineToSelection adds the current line to the selection
111 func (c *Cursor) AddLineToSelection() {
114 if loc < c.origSelection[0] {
116 c.curSelection[0] = c.Loc()
117 c.curSelection[1] = c.origSelection[1]
119 if loc > c.origSelection[1] {
121 c.curSelection[1] = c.Loc()
122 c.curSelection[0] = c.origSelection[0]
125 if loc < c.origSelection[1] && loc > c.origSelection[0] {
126 c.curSelection = c.origSelection
130 // SelectWord selects the word the cursor is currently on
131 func (c *Cursor) SelectWord() {
132 if len(c.v.buf.lines[c.y]) == 0 {
136 if !IsWordChar(string(c.RuneUnder(c.x))) {
138 c.curSelection[0] = loc
139 c.curSelection[1] = loc
140 c.origSelection = c.curSelection
144 forward, backward := c.x, c.x
146 for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
150 c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf)
151 c.origSelection[0] = c.curSelection[0]
153 for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
157 c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf)
158 c.origSelection[1] = c.curSelection[1]
161 // AddWordToSelection adds the word the cursor is currently on to the selection
162 func (c *Cursor) AddWordToSelection() {
165 if loc > c.origSelection[0] && loc < c.origSelection[1] {
166 c.curSelection = c.origSelection
170 if loc < c.origSelection[0] {
173 for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
177 c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf)
178 c.curSelection[1] = c.origSelection[1]
181 if loc > c.origSelection[1] {
184 for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
188 c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf)
189 c.curSelection[0] = c.origSelection[0]
193 // RuneUnder returns the rune under the given x position
194 func (c *Cursor) RuneUnder(x int) rune {
195 line := []rune(c.v.buf.lines[c.y])
204 // Up moves the cursor up one line (if possible)
205 func (c *Cursor) Up() {
209 runes := []rune(c.v.buf.lines[c.y])
210 c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
211 if c.x > len(runes) {
217 // Down moves the cursor down one line (if possible)
218 func (c *Cursor) Down() {
219 if c.y < len(c.v.buf.lines)-1 {
222 runes := []rune(c.v.buf.lines[c.y])
223 c.x = c.GetCharPosInLine(c.y, c.lastVisualX)
224 if c.x > len(runes) {
230 // Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
231 func (c *Cursor) Left() {
241 c.lastVisualX = c.GetVisualX()
244 // Right moves the cursor right one cell (if possible) or to the next line if it is at the end
245 func (c *Cursor) Right() {
246 if c.Loc() == c.v.buf.Len() {
249 if c.x < Count(c.v.buf.lines[c.y]) {
255 c.lastVisualX = c.GetVisualX()
258 // End moves the cursor to the end of the line it is on
259 func (c *Cursor) End() {
260 c.x = Count(c.v.buf.lines[c.y])
261 c.lastVisualX = c.GetVisualX()
264 // Start moves the cursor to the start of the line it is on
265 func (c *Cursor) Start() {
267 c.lastVisualX = c.GetVisualX()
270 // GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
271 func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
273 tabSize := settings.TabSize
274 // This is the visual line -- every \t replaced with the correct number of spaces
275 visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
276 if visualPos > Count(visualLine) {
277 visualPos = Count(visualLine)
279 numTabs := NumOccurences(visualLine[:visualPos], '\t')
280 if visualPos >= (tabSize-1)*numTabs {
281 return visualPos - (tabSize-1)*numTabs
283 return visualPos / tabSize
286 // GetVisualX returns the x value of the cursor in visual spaces
287 func (c *Cursor) GetVisualX() int {
288 runes := []rune(c.v.buf.lines[c.y])
289 tabSize := settings.TabSize
290 return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
293 // Display draws the cursor to the screen at the correct position
294 func (c *Cursor) Display() {
295 // Don't draw the cursor if it is out of the viewport or if it has a selection
296 if (c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1) || c.HasSelection() {
299 screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.topline)