6 runewidth "github.com/mattn/go-runewidth"
7 "github.com/zyedidia/clipboard"
8 "github.com/zyedidia/micro/cmd/micro/util"
11 // InBounds returns whether the given location is a valid character position in the given buffer
12 func InBounds(pos Loc, buf *Buffer) bool {
13 if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
20 // The Cursor struct stores the location of the cursor in the buffer
21 // as well as the selection
26 // Last cursor x position
29 // The current selection as a range of character numbers (inclusive)
31 // The original selection as a range of character numbers
32 // This is used for line and word selection where it is necessary
33 // to know what the original selection was
36 // Which cursor index is this (for multiple cursors)
40 func NewCursor(b *Buffer, l Loc) *Cursor {
49 func (c *Cursor) SetBuf(b *Buffer) {
53 func (c *Cursor) Buf() *Buffer {
57 // Goto puts the cursor at the given cursor's location and gives
58 // the current cursor its selection too
59 func (c *Cursor) Goto(b Cursor) {
60 c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
61 c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
64 // GotoLoc puts the cursor at the given cursor's location and gives
65 // the current cursor its selection too
66 func (c *Cursor) GotoLoc(l Loc) {
71 // GetVisualX returns the x value of the cursor in visual spaces
72 func (c *Cursor) GetVisualX() int {
78 bytes := c.buf.LineBytes(c.Y)
79 tabsize := int(c.buf.Settings["tabsize"].(float64))
80 if c.X > utf8.RuneCount(bytes) {
81 c.X = utf8.RuneCount(bytes) - 1
84 return util.StringWidth(bytes, c.X, tabsize)
87 // GetCharPosInLine gets the char position of a visual x y
88 // coordinate (this is necessary because tabs are 1 char but
90 func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
91 tabsize := int(c.buf.Settings["tabsize"].(float64))
93 // Scan rune by rune until we exceed the visual width that we are
94 // looking for. Then we can return the character position we have found
96 width := 0 // string visual width
98 r, size := utf8.DecodeRune(b)
103 ts := tabsize - (width % tabsize)
106 width += runewidth.RuneWidth(r)
109 if width >= visualPos {
110 if width == visualPos {
121 // Start moves the cursor to the start of the line it is on
122 func (c *Cursor) Start() {
124 c.LastVisualX = c.GetVisualX()
127 // StartOfText moves the cursor to the first non-whitespace rune of
129 func (c *Cursor) StartOfText() {
131 for util.IsWhitespace(c.RuneUnder(c.X)) {
132 if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
139 // End moves the cursor to the end of the line it is on
140 func (c *Cursor) End() {
141 c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
142 c.LastVisualX = c.GetVisualX()
145 // CopySelection copies the user's selection to either "primary"
147 func (c *Cursor) CopySelection(target string) {
148 if c.HasSelection() {
149 if target != "primary" || c.buf.Settings["useprimary"].(bool) {
150 clipboard.WriteAll(string(c.GetSelection()), target)
155 // ResetSelection resets the user's selection
156 func (c *Cursor) ResetSelection() {
157 c.CurSelection[0] = c.buf.Start()
158 c.CurSelection[1] = c.buf.Start()
161 // SetSelectionStart sets the start of the selection
162 func (c *Cursor) SetSelectionStart(pos Loc) {
163 c.CurSelection[0] = pos
166 // SetSelectionEnd sets the end of the selection
167 func (c *Cursor) SetSelectionEnd(pos Loc) {
168 c.CurSelection[1] = pos
171 // HasSelection returns whether or not the user has selected anything
172 func (c *Cursor) HasSelection() bool {
173 return c.CurSelection[0] != c.CurSelection[1]
176 // DeleteSelection deletes the currently selected text
177 func (c *Cursor) DeleteSelection() {
178 if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
179 c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
180 c.Loc = c.CurSelection[1]
181 } else if !c.HasSelection() {
184 c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
185 c.Loc = c.CurSelection[0]
189 // Deselect closes the cursor's current selection
190 // Start indicates whether the cursor should be placed
191 // at the start or end of the selection
192 func (c *Cursor) Deselect(start bool) {
193 if c.HasSelection() {
195 c.Loc = c.CurSelection[0]
197 c.Loc = c.CurSelection[1]
204 // GetSelection returns the cursor's selection
205 func (c *Cursor) GetSelection() []byte {
206 if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
207 if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
208 return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
210 return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
215 // SelectLine selects the current line
216 func (c *Cursor) SelectLine() {
218 c.SetSelectionStart(c.Loc)
220 if len(c.buf.lines)-1 > c.Y {
221 c.SetSelectionEnd(c.Loc.Move(1, c.buf))
223 c.SetSelectionEnd(c.Loc)
226 c.OrigSelection = c.CurSelection
229 // AddLineToSelection adds the current line to the selection
230 func (c *Cursor) AddLineToSelection() {
231 if c.Loc.LessThan(c.OrigSelection[0]) {
233 c.SetSelectionStart(c.Loc)
234 c.SetSelectionEnd(c.OrigSelection[1])
236 if c.Loc.GreaterThan(c.OrigSelection[1]) {
238 c.SetSelectionEnd(c.Loc.Move(1, c.buf))
239 c.SetSelectionStart(c.OrigSelection[0])
242 if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
243 c.CurSelection = c.OrigSelection
247 // UpN moves the cursor up N lines (if possible)
248 func (c *Cursor) UpN(amount int) {
249 proposedY := c.Y - amount
252 } else if proposedY >= len(c.buf.lines) {
253 proposedY = len(c.buf.lines) - 1
256 bytes := c.buf.LineBytes(proposedY)
257 c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
259 if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
260 c.X = utf8.RuneCount(bytes)
266 // DownN moves the cursor down N lines (if possible)
267 func (c *Cursor) DownN(amount int) {
271 // Up moves the cursor up one line (if possible)
272 func (c *Cursor) Up() {
276 // Down moves the cursor down one line (if possible)
277 func (c *Cursor) Down() {
281 // Left moves the cursor left one cell (if possible) or to
282 // the previous line if it is at the beginning
283 func (c *Cursor) Left() {
284 if c.Loc == c.buf.Start() {
296 // Right moves the cursor right one cell (if possible) or
297 // to the next line if it is at the end
298 func (c *Cursor) Right() {
299 if c.Loc == c.buf.End() {
302 if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
311 // Relocate makes sure that the cursor is inside the bounds
312 // of the buffer If it isn't, it moves it to be within the
314 func (c *Cursor) Relocate() {
317 } else if c.Y >= len(c.buf.lines) {
318 c.Y = len(c.buf.lines) - 1
323 } else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
324 c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
328 // SelectWord selects the word the cursor is currently on
329 func (c *Cursor) SelectWord() {
330 if len(c.buf.LineBytes(c.Y)) == 0 {
334 if !util.IsWordChar(c.RuneUnder(c.X)) {
335 c.SetSelectionStart(c.Loc)
336 c.SetSelectionEnd(c.Loc.Move(1, c.buf))
337 c.OrigSelection = c.CurSelection
341 forward, backward := c.X, c.X
343 for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) {
347 c.SetSelectionStart(Loc{backward, c.Y})
348 c.OrigSelection[0] = c.CurSelection[0]
350 lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
351 for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
355 c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
356 c.OrigSelection[1] = c.CurSelection[1]
357 c.Loc = c.CurSelection[1]
360 // AddWordToSelection adds the word the cursor is currently on
362 func (c *Cursor) AddWordToSelection() {
363 if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
364 c.CurSelection = c.OrigSelection
368 if c.Loc.LessThan(c.OrigSelection[0]) {
371 for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) {
375 c.SetSelectionStart(Loc{backward, c.Y})
376 c.SetSelectionEnd(c.OrigSelection[1])
379 if c.Loc.GreaterThan(c.OrigSelection[1]) {
382 lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
383 for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
387 c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
388 c.SetSelectionStart(c.OrigSelection[0])
391 c.Loc = c.CurSelection[1]
394 // SelectTo selects from the current cursor location to the given
396 func (c *Cursor) SelectTo(loc Loc) {
397 if loc.GreaterThan(c.OrigSelection[0]) {
398 c.SetSelectionStart(c.OrigSelection[0])
399 c.SetSelectionEnd(loc)
401 c.SetSelectionStart(loc)
402 c.SetSelectionEnd(c.OrigSelection[0])
406 // WordRight moves the cursor one word to the right
407 func (c *Cursor) WordRight() {
408 for util.IsWhitespace(c.RuneUnder(c.X)) {
409 if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
416 for util.IsWordChar(c.RuneUnder(c.X)) {
417 if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
424 // WordLeft moves the cursor one word to the left
425 func (c *Cursor) WordLeft() {
427 for util.IsWhitespace(c.RuneUnder(c.X)) {
434 for util.IsWordChar(c.RuneUnder(c.X)) {
443 // RuneUnder returns the rune under the given x position
444 func (c *Cursor) RuneUnder(x int) rune {
445 line := c.buf.LineBytes(c.Y)
446 if len(line) == 0 || x >= utf8.RuneCount(line) {
453 r, size := utf8.DecodeRune(line)
465 func (c *Cursor) StoreVisualX() {
466 c.LastVisualX = c.GetVisualX()