]> git.lizzy.rs Git - micro.git/blob - internal/action/actions.go
Use upstream updated zyedidia tcell
[micro.git] / internal / action / actions.go
1 package action
2
3 import (
4         "regexp"
5         "runtime"
6         "strings"
7         "time"
8         "unicode/utf8"
9
10         "github.com/zyedidia/clipboard"
11         "github.com/zyedidia/micro/internal/buffer"
12         "github.com/zyedidia/micro/internal/config"
13         "github.com/zyedidia/micro/internal/screen"
14         "github.com/zyedidia/micro/internal/shell"
15         "github.com/zyedidia/micro/internal/util"
16         "github.com/zyedidia/micro/pkg/shellwords"
17         "github.com/zyedidia/tcell"
18 )
19
20 // ScrollUp is not an action
21 func (h *BufPane) ScrollUp(n int) {
22         v := h.GetView()
23         if v.StartLine >= n {
24                 v.StartLine -= n
25                 h.SetView(v)
26         } else {
27                 v.StartLine = 0
28         }
29 }
30
31 // ScrollDown is not an action
32 func (h *BufPane) ScrollDown(n int) {
33         v := h.GetView()
34         if v.StartLine <= h.Buf.LinesNum()-1-n {
35                 v.StartLine += n
36                 h.SetView(v)
37         }
38 }
39
40 // MousePress is the event that should happen when a normal click happens
41 // This is almost always bound to left click
42 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
43         b := h.Buf
44         mx, my := e.Position()
45         mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
46         h.Cursor.Loc = mouseLoc
47         if h.mouseReleased {
48                 if b.NumCursors() > 1 {
49                         b.ClearCursors()
50                         h.Relocate()
51                         h.Cursor = h.Buf.GetActiveCursor()
52                         h.Cursor.Loc = mouseLoc
53                 }
54                 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
55                         if h.doubleClick {
56                                 // Triple click
57                                 h.lastClickTime = time.Now()
58
59                                 h.tripleClick = true
60                                 h.doubleClick = false
61
62                                 h.Cursor.SelectLine()
63                                 h.Cursor.CopySelection("primary")
64                         } else {
65                                 // Double click
66                                 h.lastClickTime = time.Now()
67
68                                 h.doubleClick = true
69                                 h.tripleClick = false
70
71                                 h.Cursor.SelectWord()
72                                 h.Cursor.CopySelection("primary")
73                         }
74                 } else {
75                         h.doubleClick = false
76                         h.tripleClick = false
77                         h.lastClickTime = time.Now()
78
79                         h.Cursor.OrigSelection[0] = h.Cursor.Loc
80                         h.Cursor.CurSelection[0] = h.Cursor.Loc
81                         h.Cursor.CurSelection[1] = h.Cursor.Loc
82                 }
83                 h.mouseReleased = false
84         } else if !h.mouseReleased {
85                 if h.tripleClick {
86                         h.Cursor.AddLineToSelection()
87                 } else if h.doubleClick {
88                         h.Cursor.AddWordToSelection()
89                 } else {
90                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
91                 }
92         }
93
94         h.Cursor.StoreVisualX()
95         h.lastLoc = mouseLoc
96         return true
97 }
98
99 // ScrollUpAction scrolls the view up
100 func (h *BufPane) ScrollUpAction() bool {
101         h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
102         return true
103 }
104
105 // ScrollDownAction scrolls the view up
106 func (h *BufPane) ScrollDownAction() bool {
107         h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
108         return true
109 }
110
111 // Center centers the view on the cursor
112 func (h *BufPane) Center() bool {
113         v := h.GetView()
114         v.StartLine = h.Cursor.Y - v.Height/2
115         if v.StartLine+v.Height > h.Buf.LinesNum() {
116                 v.StartLine = h.Buf.LinesNum() - v.Height
117         }
118         if v.StartLine < 0 {
119                 v.StartLine = 0
120         }
121         h.SetView(v)
122         h.Relocate()
123         return true
124 }
125
126 // CursorUp moves the cursor up
127 func (h *BufPane) CursorUp() bool {
128         h.Cursor.Deselect(true)
129         h.Cursor.Up()
130         h.Relocate()
131         return true
132 }
133
134 // CursorDown moves the cursor down
135 func (h *BufPane) CursorDown() bool {
136         h.Cursor.Deselect(true)
137         h.Cursor.Down()
138         h.Relocate()
139         return true
140 }
141
142 // CursorLeft moves the cursor left
143 func (h *BufPane) CursorLeft() bool {
144         if h.Cursor.HasSelection() {
145                 h.Cursor.Deselect(true)
146         } else {
147                 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
148                 tabmovement := h.Buf.Settings["tabmovement"].(bool)
149                 if tabstospaces && tabmovement {
150                         tabsize := int(h.Buf.Settings["tabsize"].(float64))
151                         line := h.Buf.LineBytes(h.Cursor.Y)
152                         if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
153                                 for i := 0; i < tabsize; i++ {
154                                         h.Cursor.Left()
155                                 }
156                         } else {
157                                 h.Cursor.Left()
158                         }
159                 } else {
160                         h.Cursor.Left()
161                 }
162         }
163         h.Relocate()
164         return true
165 }
166
167 // CursorRight moves the cursor right
168 func (h *BufPane) CursorRight() bool {
169         if h.Cursor.HasSelection() {
170                 h.Cursor.Deselect(false)
171                 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
172         } else {
173                 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
174                 tabmovement := h.Buf.Settings["tabmovement"].(bool)
175                 if tabstospaces && tabmovement {
176                         tabsize := int(h.Buf.Settings["tabsize"].(float64))
177                         line := h.Buf.LineBytes(h.Cursor.Y)
178                         if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
179                                 for i := 0; i < tabsize; i++ {
180                                         h.Cursor.Right()
181                                 }
182                         } else {
183                                 h.Cursor.Right()
184                         }
185                 } else {
186                         h.Cursor.Right()
187                 }
188         }
189
190         h.Relocate()
191         return true
192 }
193
194 // WordRight moves the cursor one word to the right
195 func (h *BufPane) WordRight() bool {
196         h.Cursor.Deselect(false)
197         h.Cursor.WordRight()
198         h.Relocate()
199         return true
200 }
201
202 // WordLeft moves the cursor one word to the left
203 func (h *BufPane) WordLeft() bool {
204         h.Cursor.Deselect(true)
205         h.Cursor.WordLeft()
206         h.Relocate()
207         return true
208 }
209
210 // SelectUp selects up one line
211 func (h *BufPane) SelectUp() bool {
212         if !h.Cursor.HasSelection() {
213                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
214         }
215         h.Cursor.Up()
216         h.Cursor.SelectTo(h.Cursor.Loc)
217         h.Relocate()
218         return true
219 }
220
221 // SelectDown selects down one line
222 func (h *BufPane) SelectDown() bool {
223         if !h.Cursor.HasSelection() {
224                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
225         }
226         h.Cursor.Down()
227         h.Cursor.SelectTo(h.Cursor.Loc)
228         h.Relocate()
229         return true
230 }
231
232 // SelectLeft selects the character to the left of the cursor
233 func (h *BufPane) SelectLeft() bool {
234         loc := h.Cursor.Loc
235         count := h.Buf.End()
236         if loc.GreaterThan(count) {
237                 loc = count
238         }
239         if !h.Cursor.HasSelection() {
240                 h.Cursor.OrigSelection[0] = loc
241         }
242         h.Cursor.Left()
243         h.Cursor.SelectTo(h.Cursor.Loc)
244         h.Relocate()
245         return true
246 }
247
248 // SelectRight selects the character to the right of the cursor
249 func (h *BufPane) SelectRight() bool {
250         loc := h.Cursor.Loc
251         count := h.Buf.End()
252         if loc.GreaterThan(count) {
253                 loc = count
254         }
255         if !h.Cursor.HasSelection() {
256                 h.Cursor.OrigSelection[0] = loc
257         }
258         h.Cursor.Right()
259         h.Cursor.SelectTo(h.Cursor.Loc)
260         h.Relocate()
261         return true
262 }
263
264 // SelectWordRight selects the word to the right of the cursor
265 func (h *BufPane) SelectWordRight() bool {
266         if !h.Cursor.HasSelection() {
267                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
268         }
269         h.Cursor.WordRight()
270         h.Cursor.SelectTo(h.Cursor.Loc)
271         h.Relocate()
272         return true
273 }
274
275 // SelectWordLeft selects the word to the left of the cursor
276 func (h *BufPane) SelectWordLeft() bool {
277         if !h.Cursor.HasSelection() {
278                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
279         }
280         h.Cursor.WordLeft()
281         h.Cursor.SelectTo(h.Cursor.Loc)
282         h.Relocate()
283         return true
284 }
285
286 // StartOfLine moves the cursor to the start of the line
287 func (h *BufPane) StartOfLine() bool {
288         h.Cursor.Deselect(true)
289         h.Cursor.StartOfText()
290         // if h.Cursor.X != 0 {
291         //      h.Cursor.Start()
292         // } else {
293         //      h.Cursor.StartOfText()
294         // }
295         h.Relocate()
296         return true
297 }
298
299 // EndOfLine moves the cursor to the end of the line
300 func (h *BufPane) EndOfLine() bool {
301         h.Cursor.Deselect(true)
302         h.Cursor.End()
303         h.Relocate()
304         return true
305 }
306
307 // SelectLine selects the entire current line
308 func (h *BufPane) SelectLine() bool {
309         h.Cursor.SelectLine()
310         h.Relocate()
311         return true
312 }
313
314 // SelectToStartOfLine selects to the start of the current line
315 func (h *BufPane) SelectToStartOfLine() bool {
316         if !h.Cursor.HasSelection() {
317                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
318         }
319         h.Cursor.Start()
320         h.Cursor.SelectTo(h.Cursor.Loc)
321         h.Relocate()
322         return true
323 }
324
325 // SelectToEndOfLine selects to the end of the current line
326 func (h *BufPane) SelectToEndOfLine() bool {
327         if !h.Cursor.HasSelection() {
328                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
329         }
330         h.Cursor.End()
331         h.Cursor.SelectTo(h.Cursor.Loc)
332         h.Relocate()
333         return true
334 }
335
336 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
337 func (h *BufPane) ParagraphPrevious() bool {
338         var line int
339         for line = h.Cursor.Y; line > 0; line-- {
340                 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
341                         h.Cursor.X = 0
342                         h.Cursor.Y = line
343                         break
344                 }
345         }
346         // If no empty line found. move cursor to end of buffer
347         if line == 0 {
348                 h.Cursor.Loc = h.Buf.Start()
349         }
350         h.Relocate()
351         return true
352 }
353
354 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
355 func (h *BufPane) ParagraphNext() bool {
356         var line int
357         for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
358                 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
359                         h.Cursor.X = 0
360                         h.Cursor.Y = line
361                         break
362                 }
363         }
364         // If no empty line found. move cursor to end of buffer
365         if line == h.Buf.LinesNum() {
366                 h.Cursor.Loc = h.Buf.End()
367         }
368         h.Relocate()
369         return true
370 }
371
372 // Retab changes all tabs to spaces or all spaces to tabs depending
373 // on the user's settings
374 func (h *BufPane) Retab() bool {
375         h.Buf.Retab()
376         h.Relocate()
377         return true
378 }
379
380 // CursorStart moves the cursor to the start of the buffer
381 func (h *BufPane) CursorStart() bool {
382         h.Cursor.Deselect(true)
383         h.Cursor.X = 0
384         h.Cursor.Y = 0
385         h.Relocate()
386         return true
387 }
388
389 // CursorEnd moves the cursor to the end of the buffer
390 func (h *BufPane) CursorEnd() bool {
391         h.Cursor.Deselect(true)
392         h.Cursor.Loc = h.Buf.End()
393         h.Cursor.StoreVisualX()
394         h.Relocate()
395         return true
396 }
397
398 // SelectToStart selects the text from the cursor to the start of the buffer
399 func (h *BufPane) SelectToStart() bool {
400         if !h.Cursor.HasSelection() {
401                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
402         }
403         h.CursorStart()
404         h.Cursor.SelectTo(h.Buf.Start())
405         h.Relocate()
406         return true
407 }
408
409 // SelectToEnd selects the text from the cursor to the end of the buffer
410 func (h *BufPane) SelectToEnd() bool {
411         if !h.Cursor.HasSelection() {
412                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
413         }
414         h.CursorEnd()
415         h.Cursor.SelectTo(h.Buf.End())
416         h.Relocate()
417         return true
418 }
419
420 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
421 func (h *BufPane) InsertNewline() bool {
422         // Insert a newline
423         if h.Cursor.HasSelection() {
424                 h.Cursor.DeleteSelection()
425                 h.Cursor.ResetSelection()
426         }
427
428         ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
429         cx := h.Cursor.X
430         h.Buf.Insert(h.Cursor.Loc, "\n")
431         // h.Cursor.Right()
432
433         if h.Buf.Settings["autoindent"].(bool) {
434                 if cx < len(ws) {
435                         ws = ws[0:cx]
436                 }
437                 h.Buf.Insert(h.Cursor.Loc, string(ws))
438                 // for i := 0; i < len(ws); i++ {
439                 //      h.Cursor.Right()
440                 // }
441
442                 // Remove the whitespaces if keepautoindent setting is off
443                 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
444                         line := h.Buf.LineBytes(h.Cursor.Y - 1)
445                         h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
446                 }
447         }
448         h.Cursor.LastVisualX = h.Cursor.GetVisualX()
449         h.Relocate()
450         return true
451 }
452
453 // Backspace deletes the previous character
454 func (h *BufPane) Backspace() bool {
455         if h.Cursor.HasSelection() {
456                 h.Cursor.DeleteSelection()
457                 h.Cursor.ResetSelection()
458         } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
459                 // We have to do something a bit hacky here because we want to
460                 // delete the line by first moving left and then deleting backwards
461                 // but the undo redo would place the cursor in the wrong place
462                 // So instead we move left, save the position, move back, delete
463                 // and restore the position
464
465                 // If the user is using spaces instead of tabs and they are deleting
466                 // whitespace at the start of the line, we should delete as if it's a
467                 // tab (tabSize number of spaces)
468                 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
469                 tabSize := int(h.Buf.Settings["tabsize"].(float64))
470                 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
471                         loc := h.Cursor.Loc
472                         h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
473                 } else {
474                         loc := h.Cursor.Loc
475                         h.Buf.Remove(loc.Move(-1, h.Buf), loc)
476                 }
477         }
478         h.Cursor.LastVisualX = h.Cursor.GetVisualX()
479         h.Relocate()
480         return true
481 }
482
483 // DeleteWordRight deletes the word to the right of the cursor
484 func (h *BufPane) DeleteWordRight() bool {
485         h.SelectWordRight()
486         if h.Cursor.HasSelection() {
487                 h.Cursor.DeleteSelection()
488                 h.Cursor.ResetSelection()
489         }
490         h.Relocate()
491         return true
492 }
493
494 // DeleteWordLeft deletes the word to the left of the cursor
495 func (h *BufPane) DeleteWordLeft() bool {
496         h.SelectWordLeft()
497         if h.Cursor.HasSelection() {
498                 h.Cursor.DeleteSelection()
499                 h.Cursor.ResetSelection()
500         }
501         h.Relocate()
502         return true
503 }
504
505 // Delete deletes the next character
506 func (h *BufPane) Delete() bool {
507         if h.Cursor.HasSelection() {
508                 h.Cursor.DeleteSelection()
509                 h.Cursor.ResetSelection()
510         } else {
511                 loc := h.Cursor.Loc
512                 if loc.LessThan(h.Buf.End()) {
513                         h.Buf.Remove(loc, loc.Move(1, h.Buf))
514                 }
515         }
516         h.Relocate()
517         return true
518 }
519
520 // IndentSelection indents the current selection
521 func (h *BufPane) IndentSelection() bool {
522         if h.Cursor.HasSelection() {
523                 start := h.Cursor.CurSelection[0]
524                 end := h.Cursor.CurSelection[1]
525                 if end.Y < start.Y {
526                         start, end = end, start
527                         h.Cursor.SetSelectionStart(start)
528                         h.Cursor.SetSelectionEnd(end)
529                 }
530
531                 startY := start.Y
532                 endY := end.Move(-1, h.Buf).Y
533                 endX := end.Move(-1, h.Buf).X
534                 tabsize := int(h.Buf.Settings["tabsize"].(float64))
535                 indentsize := len(h.Buf.IndentString(tabsize))
536                 for y := startY; y <= endY; y++ {
537                         h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
538                         if y == startY && start.X > 0 {
539                                 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
540                         }
541                         if y == endY {
542                                 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
543                         }
544                 }
545                 h.Buf.RelocateCursors()
546
547                 h.Relocate()
548                 return true
549         }
550         return false
551 }
552
553 // OutdentLine moves the current line back one indentation
554 func (h *BufPane) OutdentLine() bool {
555         if h.Cursor.HasSelection() {
556                 return false
557         }
558
559         for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
560                 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
561                         break
562                 }
563                 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
564         }
565         h.Buf.RelocateCursors()
566         h.Relocate()
567         return true
568 }
569
570 // OutdentSelection takes the current selection and moves it back one indent level
571 func (h *BufPane) OutdentSelection() bool {
572         if h.Cursor.HasSelection() {
573                 start := h.Cursor.CurSelection[0]
574                 end := h.Cursor.CurSelection[1]
575                 if end.Y < start.Y {
576                         start, end = end, start
577                         h.Cursor.SetSelectionStart(start)
578                         h.Cursor.SetSelectionEnd(end)
579                 }
580
581                 startY := start.Y
582                 endY := end.Move(-1, h.Buf).Y
583                 for y := startY; y <= endY; y++ {
584                         for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
585                                 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
586                                         break
587                                 }
588                                 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
589                         }
590                 }
591                 h.Buf.RelocateCursors()
592
593                 h.Relocate()
594                 return true
595         }
596         return false
597 }
598
599 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
600 func (h *BufPane) Autocomplete() bool {
601         b := h.Buf
602
603         if h.Cursor.HasSelection() {
604                 return false
605         }
606
607         if b.HasSuggestions {
608                 b.CycleAutocomplete(true)
609                 return true
610         }
611         return b.Autocomplete(buffer.BufferComplete)
612 }
613
614 // InsertTab inserts a tab or spaces
615 func (h *BufPane) InsertTab() bool {
616         b := h.Buf
617         indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
618         tabBytes := len(indent)
619         bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
620         b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
621         h.Relocate()
622         return true
623 }
624
625 // SaveAll saves all open buffers
626 func (h *BufPane) SaveAll() bool {
627         for _, b := range buffer.OpenBuffers {
628                 b.Save()
629         }
630         return true
631 }
632
633 // Save the buffer to disk
634 func (h *BufPane) Save() bool {
635         // If this is an empty buffer, ask for a filename
636         if h.Buf.Path == "" {
637                 h.SaveAs()
638         } else {
639                 h.saveBufToFile(h.Buf.Path, "Save")
640         }
641
642         return false
643 }
644
645 // SaveAs saves the buffer to disk with the given name
646 func (h *BufPane) SaveAs() bool {
647         InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
648                 if !canceled {
649                         // the filename might or might not be quoted, so unquote first then join the strings.
650                         args, err := shellwords.Split(resp)
651                         filename := strings.Join(args, " ")
652                         if err != nil {
653                                 InfoBar.Error("Error parsing arguments: ", err)
654                                 return
655                         }
656                         h.saveBufToFile(filename, "SaveAs")
657                 }
658         })
659         return false
660 }
661
662 // This function saves the buffer to `filename` and changes the buffer's path and name
663 // to `filename` if the save is successful
664 func (h *BufPane) saveBufToFile(filename string, action string) {
665         err := h.Buf.SaveAs(filename)
666         if err != nil {
667                 if strings.HasSuffix(err.Error(), "permission denied") {
668                         InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
669                                 if yes && !canceled {
670                                         err = h.Buf.SaveAsWithSudo(filename)
671                                         if err != nil {
672                                                 InfoBar.Error(err)
673                                         } else {
674                                                 h.Buf.Path = filename
675                                                 h.Buf.SetName(filename)
676                                                 InfoBar.Message("Saved " + filename)
677                                         }
678                                         h.completeAction(action)
679                                 }
680                         })
681                 } else {
682                         InfoBar.Error(err)
683                 }
684         } else {
685                 h.Buf.Path = filename
686                 h.Buf.SetName(filename)
687                 InfoBar.Message("Saved " + filename)
688                 h.completeAction(action)
689         }
690 }
691
692 // Find opens a prompt and searches forward for the input
693 func (h *BufPane) Find() bool {
694         h.searchOrig = h.Cursor.Loc
695         InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
696                 // Event callback
697                 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
698                 if found {
699                         h.Cursor.SetSelectionStart(match[0])
700                         h.Cursor.SetSelectionEnd(match[1])
701                         h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
702                         h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
703                         h.Cursor.GotoLoc(match[1])
704                 } else {
705                         h.Cursor.GotoLoc(h.searchOrig)
706                         h.Cursor.ResetSelection()
707                 }
708                 h.Relocate()
709         }, func(resp string, canceled bool) {
710                 // Finished callback
711                 if !canceled {
712                         match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
713                         if err != nil {
714                                 InfoBar.Error(err)
715                         }
716                         if found {
717                                 h.Cursor.SetSelectionStart(match[0])
718                                 h.Cursor.SetSelectionEnd(match[1])
719                                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
720                                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
721                                 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
722                                 h.lastSearch = resp
723                         } else {
724                                 h.Cursor.ResetSelection()
725                                 InfoBar.Message("No matches found")
726                         }
727                 } else {
728                         h.Cursor.ResetSelection()
729                 }
730                 h.Relocate()
731         })
732
733         return true
734 }
735
736 // FindNext searches forwards for the last used search term
737 func (h *BufPane) FindNext() bool {
738         // If the cursor is at the start of a selection and we search we want
739         // to search from the end of the selection in the case that
740         // the selection is a search result in which case we wouldn't move at
741         // at all which would be bad
742         searchLoc := h.Cursor.Loc
743         if h.Cursor.HasSelection() {
744                 searchLoc = h.Cursor.CurSelection[1]
745         }
746         match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
747         if err != nil {
748                 InfoBar.Error(err)
749         }
750         if found {
751                 h.Cursor.SetSelectionStart(match[0])
752                 h.Cursor.SetSelectionEnd(match[1])
753                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
754                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
755                 h.Cursor.Loc = h.Cursor.CurSelection[1]
756         } else {
757                 h.Cursor.ResetSelection()
758         }
759         h.Relocate()
760         return true
761 }
762
763 // FindPrevious searches backwards for the last used search term
764 func (h *BufPane) FindPrevious() bool {
765         // If the cursor is at the end of a selection and we search we want
766         // to search from the beginning of the selection in the case that
767         // the selection is a search result in which case we wouldn't move at
768         // at all which would be bad
769         searchLoc := h.Cursor.Loc
770         if h.Cursor.HasSelection() {
771                 searchLoc = h.Cursor.CurSelection[0]
772         }
773         match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
774         if err != nil {
775                 InfoBar.Error(err)
776         }
777         if found {
778                 h.Cursor.SetSelectionStart(match[0])
779                 h.Cursor.SetSelectionEnd(match[1])
780                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
781                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
782                 h.Cursor.Loc = h.Cursor.CurSelection[1]
783         } else {
784                 h.Cursor.ResetSelection()
785         }
786         h.Relocate()
787         return true
788 }
789
790 // Undo undoes the last action
791 func (h *BufPane) Undo() bool {
792         h.Buf.Undo()
793         InfoBar.Message("Undid action")
794         h.Relocate()
795         return true
796 }
797
798 // Redo redoes the last action
799 func (h *BufPane) Redo() bool {
800         h.Buf.Redo()
801         InfoBar.Message("Redid action")
802         h.Relocate()
803         return true
804 }
805
806 // Copy the selection to the system clipboard
807 func (h *BufPane) Copy() bool {
808         if h.Cursor.HasSelection() {
809                 h.Cursor.CopySelection("clipboard")
810                 h.freshClip = true
811                 if clipboard.Unsupported {
812                         InfoBar.Message("Copied selection (install xclip for external clipboard)")
813                 } else {
814                         InfoBar.Message("Copied selection")
815                 }
816         }
817         h.Relocate()
818         return true
819 }
820
821 // CutLine cuts the current line to the clipboard
822 func (h *BufPane) CutLine() bool {
823         h.Cursor.SelectLine()
824         if !h.Cursor.HasSelection() {
825                 return false
826         }
827         if h.freshClip == true {
828                 if h.Cursor.HasSelection() {
829                         if clip, err := clipboard.ReadAll("clipboard"); err != nil {
830                                 // messenger.Error(err)
831                         } else {
832                                 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
833                         }
834                 }
835         } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
836                 h.Copy()
837         }
838         h.freshClip = true
839         h.lastCutTime = time.Now()
840         h.Cursor.DeleteSelection()
841         h.Cursor.ResetSelection()
842         InfoBar.Message("Cut line")
843         h.Relocate()
844         return true
845 }
846
847 // Cut the selection to the system clipboard
848 func (h *BufPane) Cut() bool {
849         if h.Cursor.HasSelection() {
850                 h.Cursor.CopySelection("clipboard")
851                 h.Cursor.DeleteSelection()
852                 h.Cursor.ResetSelection()
853                 h.freshClip = true
854                 InfoBar.Message("Cut selection")
855
856                 h.Relocate()
857                 return true
858         } else {
859                 return h.CutLine()
860         }
861 }
862
863 // DuplicateLine duplicates the current line or selection
864 func (h *BufPane) DuplicateLine() bool {
865         if h.Cursor.HasSelection() {
866                 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
867         } else {
868                 h.Cursor.End()
869                 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
870                 // h.Cursor.Right()
871         }
872
873         InfoBar.Message("Duplicated line")
874         h.Relocate()
875         return true
876 }
877
878 // DeleteLine deletes the current line
879 func (h *BufPane) DeleteLine() bool {
880         h.Cursor.SelectLine()
881         if !h.Cursor.HasSelection() {
882                 return false
883         }
884         h.Cursor.DeleteSelection()
885         h.Cursor.ResetSelection()
886         InfoBar.Message("Deleted line")
887         h.Relocate()
888         return true
889 }
890
891 // MoveLinesUp moves up the current line or selected lines if any
892 func (h *BufPane) MoveLinesUp() bool {
893         if h.Cursor.HasSelection() {
894                 if h.Cursor.CurSelection[0].Y == 0 {
895                         InfoBar.Message("Cannot move further up")
896                         return false
897                 }
898                 start := h.Cursor.CurSelection[0].Y
899                 end := h.Cursor.CurSelection[1].Y
900                 if start > end {
901                         end, start = start, end
902                 }
903
904                 h.Buf.MoveLinesUp(
905                         start,
906                         end,
907                 )
908                 h.Cursor.CurSelection[1].Y -= 1
909         } else {
910                 if h.Cursor.Loc.Y == 0 {
911                         InfoBar.Message("Cannot move further up")
912                         return false
913                 }
914                 h.Buf.MoveLinesUp(
915                         h.Cursor.Loc.Y,
916                         h.Cursor.Loc.Y+1,
917                 )
918         }
919
920         h.Relocate()
921         return true
922 }
923
924 // MoveLinesDown moves down the current line or selected lines if any
925 func (h *BufPane) MoveLinesDown() bool {
926         if h.Cursor.HasSelection() {
927                 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
928                         InfoBar.Message("Cannot move further down")
929                         return false
930                 }
931                 start := h.Cursor.CurSelection[0].Y
932                 end := h.Cursor.CurSelection[1].Y
933                 if start > end {
934                         end, start = start, end
935                 }
936
937                 h.Buf.MoveLinesDown(
938                         start,
939                         end,
940                 )
941         } else {
942                 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
943                         InfoBar.Message("Cannot move further down")
944                         return false
945                 }
946                 h.Buf.MoveLinesDown(
947                         h.Cursor.Loc.Y,
948                         h.Cursor.Loc.Y+1,
949                 )
950         }
951
952         h.Relocate()
953         return true
954 }
955
956 // Paste whatever is in the system clipboard into the buffer
957 // Delete and paste if the user has a selection
958 func (h *BufPane) Paste() bool {
959         clip, _ := clipboard.ReadAll("clipboard")
960         h.paste(clip)
961         h.Relocate()
962         return true
963 }
964
965 // PastePrimary pastes from the primary clipboard (only use on linux)
966 func (h *BufPane) PastePrimary() bool {
967         clip, _ := clipboard.ReadAll("primary")
968         h.paste(clip)
969         h.Relocate()
970         return true
971 }
972
973 func (h *BufPane) paste(clip string) {
974         if h.Buf.Settings["smartpaste"].(bool) {
975                 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
976                         leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
977                         clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
978                 }
979         }
980
981         if h.Cursor.HasSelection() {
982                 h.Cursor.DeleteSelection()
983                 h.Cursor.ResetSelection()
984         }
985
986         h.Buf.Insert(h.Cursor.Loc, clip)
987         // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
988         h.freshClip = false
989         if clipboard.Unsupported {
990                 InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
991         } else {
992                 InfoBar.Message("Pasted clipboard")
993         }
994 }
995
996 // JumpToMatchingBrace moves the cursor to the matching brace if it is
997 // currently on a brace
998 func (h *BufPane) JumpToMatchingBrace() bool {
999         for _, bp := range buffer.BracePairs {
1000                 r := h.Cursor.RuneUnder(h.Cursor.X)
1001                 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1002                 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1003                         matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1004                         if left {
1005                                 h.Cursor.GotoLoc(matchingBrace)
1006                         } else {
1007                                 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1008                         }
1009                 }
1010         }
1011
1012         h.Relocate()
1013         return true
1014 }
1015
1016 // SelectAll selects the entire buffer
1017 func (h *BufPane) SelectAll() bool {
1018         h.Cursor.SetSelectionStart(h.Buf.Start())
1019         h.Cursor.SetSelectionEnd(h.Buf.End())
1020         // Put the cursor at the beginning
1021         h.Cursor.X = 0
1022         h.Cursor.Y = 0
1023         h.Relocate()
1024         return true
1025 }
1026
1027 // OpenFile opens a new file in the buffer
1028 func (h *BufPane) OpenFile() bool {
1029         InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1030                 if !canceled {
1031                         h.HandleCommand(resp)
1032                 }
1033         })
1034         return true
1035 }
1036
1037 // Start moves the viewport to the start of the buffer
1038 func (h *BufPane) Start() bool {
1039         v := h.GetView()
1040         v.StartLine = 0
1041         h.SetView(v)
1042         return true
1043 }
1044
1045 // End moves the viewport to the end of the buffer
1046 func (h *BufPane) End() bool {
1047         // TODO: softwrap problems?
1048         v := h.GetView()
1049         if v.Height > h.Buf.LinesNum() {
1050                 v.StartLine = 0
1051                 h.SetView(v)
1052         } else {
1053                 v.StartLine = h.Buf.LinesNum() - v.Height
1054                 h.SetView(v)
1055         }
1056         return true
1057 }
1058
1059 // PageUp scrolls the view up a page
1060 func (h *BufPane) PageUp() bool {
1061         v := h.GetView()
1062         if v.StartLine > v.Height {
1063                 h.ScrollUp(v.Height)
1064         } else {
1065                 v.StartLine = 0
1066         }
1067         h.SetView(v)
1068         return true
1069 }
1070
1071 // PageDown scrolls the view down a page
1072 func (h *BufPane) PageDown() bool {
1073         v := h.GetView()
1074         if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1075                 h.ScrollDown(v.Height)
1076         } else if h.Buf.LinesNum() >= v.Height {
1077                 v.StartLine = h.Buf.LinesNum() - v.Height
1078         }
1079         return true
1080 }
1081
1082 // SelectPageUp selects up one page
1083 func (h *BufPane) SelectPageUp() bool {
1084         if !h.Cursor.HasSelection() {
1085                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1086         }
1087         h.Cursor.UpN(h.GetView().Height)
1088         h.Cursor.SelectTo(h.Cursor.Loc)
1089         h.Relocate()
1090         return true
1091 }
1092
1093 // SelectPageDown selects down one page
1094 func (h *BufPane) SelectPageDown() bool {
1095         if !h.Cursor.HasSelection() {
1096                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1097         }
1098         h.Cursor.DownN(h.GetView().Height)
1099         h.Cursor.SelectTo(h.Cursor.Loc)
1100         h.Relocate()
1101         return true
1102 }
1103
1104 // CursorPageUp places the cursor a page up
1105 func (h *BufPane) CursorPageUp() bool {
1106         h.Cursor.Deselect(true)
1107
1108         if h.Cursor.HasSelection() {
1109                 h.Cursor.Loc = h.Cursor.CurSelection[0]
1110                 h.Cursor.ResetSelection()
1111                 h.Cursor.StoreVisualX()
1112         }
1113         h.Cursor.UpN(h.GetView().Height)
1114         h.Relocate()
1115         return true
1116 }
1117
1118 // CursorPageDown places the cursor a page up
1119 func (h *BufPane) CursorPageDown() bool {
1120         h.Cursor.Deselect(false)
1121
1122         if h.Cursor.HasSelection() {
1123                 h.Cursor.Loc = h.Cursor.CurSelection[1]
1124                 h.Cursor.ResetSelection()
1125                 h.Cursor.StoreVisualX()
1126         }
1127         h.Cursor.DownN(h.GetView().Height)
1128         h.Relocate()
1129         return true
1130 }
1131
1132 // HalfPageUp scrolls the view up half a page
1133 func (h *BufPane) HalfPageUp() bool {
1134         v := h.GetView()
1135         if v.StartLine > v.Height/2 {
1136                 h.ScrollUp(v.Height / 2)
1137         } else {
1138                 v.StartLine = 0
1139         }
1140         h.SetView(v)
1141         return true
1142 }
1143
1144 // HalfPageDown scrolls the view down half a page
1145 func (h *BufPane) HalfPageDown() bool {
1146         v := h.GetView()
1147         if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1148                 h.ScrollDown(v.Height / 2)
1149         } else {
1150                 if h.Buf.LinesNum() >= v.Height {
1151                         v.StartLine = h.Buf.LinesNum() - v.Height
1152                 }
1153         }
1154         h.SetView(v)
1155         return true
1156 }
1157
1158 // ToggleRuler turns line numbers off and on
1159 func (h *BufPane) ToggleRuler() bool {
1160         if !h.Buf.Settings["ruler"].(bool) {
1161                 h.Buf.Settings["ruler"] = true
1162                 InfoBar.Message("Enabled ruler")
1163         } else {
1164                 h.Buf.Settings["ruler"] = false
1165                 InfoBar.Message("Disabled ruler")
1166         }
1167         return true
1168 }
1169
1170 // ClearStatus clears the messenger bar
1171 func (h *BufPane) ClearStatus() bool {
1172         InfoBar.Message("")
1173         return true
1174 }
1175
1176 // ToggleHelp toggles the help screen
1177 func (h *BufPane) ToggleHelp() bool {
1178         if h.Buf.Type == buffer.BTHelp {
1179                 h.Quit()
1180         } else {
1181                 h.openHelp("help")
1182         }
1183         return true
1184 }
1185
1186 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1187 func (h *BufPane) ToggleKeyMenu() bool {
1188         config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1189         Tabs.Resize()
1190         return true
1191 }
1192
1193 // ShellMode opens a terminal to run a shell command
1194 func (h *BufPane) ShellMode() bool {
1195         InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1196                 if !canceled {
1197                         // The true here is for openTerm to make the command interactive
1198                         shell.RunInteractiveShell(resp, true, false)
1199                 }
1200         })
1201
1202         return true
1203 }
1204
1205 // CommandMode lets the user enter a command
1206 func (h *BufPane) CommandMode() bool {
1207         InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1208                 if !canceled {
1209                         h.HandleCommand(resp)
1210                 }
1211         })
1212         return true
1213 }
1214
1215 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1216 func (h *BufPane) ToggleOverwriteMode() bool {
1217         h.isOverwriteMode = !h.isOverwriteMode
1218         return true
1219 }
1220
1221 // Escape leaves current mode
1222 func (h *BufPane) Escape() bool {
1223         return true
1224 }
1225
1226 // Quit this will close the current tab or view that is open
1227 func (h *BufPane) Quit() bool {
1228         quit := func() {
1229                 h.Buf.Close()
1230                 if len(MainTab().Panes) > 1 {
1231                         h.Unsplit()
1232                 } else if len(Tabs.List) > 1 {
1233                         Tabs.RemoveTab(h.splitID)
1234                 } else {
1235                         screen.Screen.Fini()
1236                         InfoBar.Close()
1237                         runtime.Goexit()
1238                 }
1239         }
1240         if h.Buf.Modified() {
1241                 // if config.GlobalSettings["autosave"].(float64) > 0 {
1242                 // autosave on means we automatically save when quitting
1243                 // h.Save()
1244                 // quit()
1245                 // } else {
1246                 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1247                         if !canceled && !yes {
1248                                 quit()
1249                         } else if !canceled && yes {
1250                                 h.Save()
1251                                 quit()
1252                         }
1253                 })
1254                 // }
1255         } else {
1256                 quit()
1257         }
1258         return true
1259 }
1260
1261 // QuitAll quits the whole editor; all splits and tabs
1262 func (h *BufPane) QuitAll() bool {
1263         anyModified := false
1264         for _, b := range buffer.OpenBuffers {
1265                 if b.Modified() {
1266                         anyModified = true
1267                         break
1268                 }
1269         }
1270
1271         quit := func() {
1272                 for _, b := range buffer.OpenBuffers {
1273                         b.Close()
1274                 }
1275                 screen.Screen.Fini()
1276                 InfoBar.Close()
1277                 runtime.Goexit()
1278         }
1279
1280         if anyModified {
1281                 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1282                         if !canceled && yes {
1283                                 quit()
1284                         }
1285                 })
1286         } else {
1287                 quit()
1288         }
1289
1290         return true
1291 }
1292
1293 // AddTab adds a new tab with an empty buffer
1294 func (h *BufPane) AddTab() bool {
1295         width, height := screen.Screen.Size()
1296         iOffset := config.GetInfoBarOffset()
1297         b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1298         tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1299         Tabs.AddTab(tp)
1300         Tabs.SetActive(len(Tabs.List) - 1)
1301
1302         return true
1303 }
1304
1305 // PreviousTab switches to the previous tab in the tab list
1306 func (h *BufPane) PreviousTab() bool {
1307         a := Tabs.Active()
1308         Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
1309
1310         return true
1311 }
1312
1313 // NextTab switches to the next tab in the tab list
1314 func (h *BufPane) NextTab() bool {
1315         a := Tabs.Active()
1316         Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
1317         return true
1318 }
1319
1320 // VSplitAction opens an empty vertical split
1321 func (h *BufPane) VSplitAction() bool {
1322         h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1323
1324         return true
1325 }
1326
1327 // HSplitAction opens an empty horizontal split
1328 func (h *BufPane) HSplitAction() bool {
1329         h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1330
1331         return true
1332 }
1333
1334 // Unsplit closes all splits in the current tab except the active one
1335 func (h *BufPane) Unsplit() bool {
1336         n := MainTab().GetNode(h.splitID)
1337         n.Unsplit()
1338
1339         MainTab().RemovePane(MainTab().GetPane(h.splitID))
1340         MainTab().Resize()
1341         MainTab().SetActive(len(MainTab().Panes) - 1)
1342         return true
1343 }
1344
1345 // NextSplit changes the view to the next split
1346 func (h *BufPane) NextSplit() bool {
1347         a := MainTab().active
1348         if a < len(MainTab().Panes)-1 {
1349                 a++
1350         } else {
1351                 a = 0
1352         }
1353
1354         MainTab().SetActive(a)
1355
1356         return true
1357 }
1358
1359 // PreviousSplit changes the view to the previous split
1360 func (h *BufPane) PreviousSplit() bool {
1361         a := MainTab().active
1362         if a > 0 {
1363                 a--
1364         } else {
1365                 a = len(MainTab().Panes) - 1
1366         }
1367         MainTab().SetActive(a)
1368
1369         return true
1370 }
1371
1372 var curmacro []interface{}
1373 var recording_macro bool
1374
1375 // ToggleMacro toggles recording of a macro
1376 func (h *BufPane) ToggleMacro() bool {
1377         recording_macro = !recording_macro
1378         if recording_macro {
1379                 curmacro = []interface{}{}
1380                 InfoBar.Message("Recording")
1381         } else {
1382                 InfoBar.Message("Stopped recording")
1383         }
1384         h.Relocate()
1385         return true
1386 }
1387
1388 // PlayMacro plays back the most recently recorded macro
1389 func (h *BufPane) PlayMacro() bool {
1390         if recording_macro {
1391                 return false
1392         }
1393         for _, action := range curmacro {
1394                 switch t := action.(type) {
1395                 case rune:
1396                         h.DoRuneInsert(t)
1397                 case func(*BufPane) bool:
1398                         t(h)
1399                 }
1400         }
1401         h.Relocate()
1402         return true
1403 }
1404
1405 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1406 func (h *BufPane) SpawnMultiCursor() bool {
1407         spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1408         if !spawner.HasSelection() {
1409                 spawner.SelectWord()
1410                 h.multiWord = true
1411                 h.Relocate()
1412                 return true
1413         }
1414
1415         sel := spawner.GetSelection()
1416         searchStart := spawner.CurSelection[1]
1417
1418         search := string(sel)
1419         search = regexp.QuoteMeta(search)
1420         if h.multiWord {
1421                 search = "\\b" + search + "\\b"
1422         }
1423         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1424         if err != nil {
1425                 InfoBar.Error(err)
1426         }
1427         if found {
1428                 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1429                 c.SetSelectionStart(match[0])
1430                 c.SetSelectionEnd(match[1])
1431                 c.OrigSelection[0] = c.CurSelection[0]
1432                 c.OrigSelection[1] = c.CurSelection[1]
1433                 c.Loc = c.CurSelection[1]
1434
1435                 h.Buf.AddCursor(c)
1436                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1437                 h.Buf.MergeCursors()
1438         } else {
1439                 InfoBar.Message("No matches found")
1440         }
1441
1442         h.Relocate()
1443         return true
1444 }
1445
1446 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1447 func (h *BufPane) SpawnMultiCursorSelect() bool {
1448         // Avoid cases where multiple cursors already exist, that would create problems
1449         if h.Buf.NumCursors() > 1 {
1450                 return false
1451         }
1452
1453         var startLine int
1454         var endLine int
1455
1456         a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1457         if a > b {
1458                 startLine, endLine = b, a
1459         } else {
1460                 startLine, endLine = a, b
1461         }
1462
1463         if h.Cursor.HasSelection() {
1464                 h.Cursor.ResetSelection()
1465                 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1466
1467                 for i := startLine; i <= endLine; i++ {
1468                         c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1469                         c.StoreVisualX()
1470                         h.Buf.AddCursor(c)
1471                 }
1472                 h.Buf.MergeCursors()
1473         } else {
1474                 return false
1475         }
1476         InfoBar.Message("Added cursors from selection")
1477         return true
1478 }
1479
1480 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1481 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1482         b := h.Buf
1483         mx, my := e.Position()
1484         mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1485         c := buffer.NewCursor(b, mouseLoc)
1486         b.AddCursor(c)
1487         b.MergeCursors()
1488
1489         return true
1490 }
1491
1492 // SkipMultiCursor moves the current multiple cursor to the next available position
1493 func (h *BufPane) SkipMultiCursor() bool {
1494         lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1495         sel := lastC.GetSelection()
1496         searchStart := lastC.CurSelection[1]
1497
1498         search := string(sel)
1499         search = regexp.QuoteMeta(search)
1500         if h.multiWord {
1501                 search = "\\b" + search + "\\b"
1502         }
1503
1504         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1505         if err != nil {
1506                 InfoBar.Error(err)
1507         }
1508         if found {
1509                 lastC.SetSelectionStart(match[0])
1510                 lastC.SetSelectionEnd(match[1])
1511                 lastC.OrigSelection[0] = lastC.CurSelection[0]
1512                 lastC.OrigSelection[1] = lastC.CurSelection[1]
1513                 lastC.Loc = lastC.CurSelection[1]
1514
1515                 h.Buf.MergeCursors()
1516                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1517         } else {
1518                 InfoBar.Message("No matches found")
1519         }
1520         h.Relocate()
1521         return true
1522 }
1523
1524 // RemoveMultiCursor removes the latest multiple cursor
1525 func (h *BufPane) RemoveMultiCursor() bool {
1526         if h.Buf.NumCursors() > 1 {
1527                 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1528                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1529                 h.Buf.UpdateCursors()
1530         } else {
1531                 h.multiWord = false
1532         }
1533         h.Relocate()
1534         return true
1535 }
1536
1537 // RemoveAllMultiCursors removes all cursors except the base cursor
1538 func (h *BufPane) RemoveAllMultiCursors() bool {
1539         h.Buf.ClearCursors()
1540         h.multiWord = false
1541         h.Relocate()
1542         return true
1543 }
1544
1545 // None is an action that does nothing
1546 func (h *BufPane) None() bool {
1547         return true
1548 }