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