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