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