]> git.lizzy.rs Git - micro.git/blob - cmd/micro/actions.go
Add functionality for binding mouse buttons
[micro.git] / cmd / micro / actions.go
1 package main
2
3 import (
4         "os"
5         "strconv"
6         "strings"
7         "time"
8
9         "github.com/yuin/gopher-lua"
10         "github.com/zyedidia/clipboard"
11         "github.com/zyedidia/tcell"
12 )
13
14 // PreActionCall executes the lua pre callback if possible
15 func PreActionCall(funcName string, view *View, args ...interface{}) bool {
16         executeAction := true
17         for pl := range loadedPlugins {
18                 ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
19                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
20                         TermMessage(err)
21                         continue
22                 }
23                 if ret == lua.LFalse {
24                         executeAction = false
25                 }
26         }
27         return executeAction
28 }
29
30 // PostActionCall executes the lua plugin callback if possible
31 func PostActionCall(funcName string, view *View, args ...interface{}) bool {
32         relocate := true
33         for pl := range loadedPlugins {
34                 ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
35                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
36                         TermMessage(err)
37                         continue
38                 }
39                 if ret == lua.LFalse {
40                         relocate = false
41                 }
42         }
43         return relocate
44 }
45
46 func (v *View) deselect(index int) bool {
47         if v.Cursor.HasSelection() {
48                 v.Cursor.Loc = v.Cursor.CurSelection[index]
49                 v.Cursor.ResetSelection()
50                 return true
51         }
52         return false
53 }
54
55 // MousePress is the event that should happen when a normal click happens
56 // This is almost always bound to left click
57 func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
58         if usePlugin && !PreActionCall("MousePress", v, e) {
59                 return false
60         }
61
62         x, y := e.Position()
63         x -= v.lineNumOffset - v.leftCol + v.x
64         y += v.Topline - v.y
65
66         // This is usually bound to left click
67         if v.mouseReleased {
68                 v.MoveToMouseClick(x, y)
69                 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
70                         if v.doubleClick {
71                                 // Triple click
72                                 v.lastClickTime = time.Now()
73
74                                 v.tripleClick = true
75                                 v.doubleClick = false
76
77                                 v.Cursor.SelectLine()
78                                 v.Cursor.CopySelection("primary")
79                         } else {
80                                 // Double click
81                                 v.lastClickTime = time.Now()
82
83                                 v.doubleClick = true
84                                 v.tripleClick = false
85
86                                 v.Cursor.SelectWord()
87                                 v.Cursor.CopySelection("primary")
88                         }
89                 } else {
90                         v.doubleClick = false
91                         v.tripleClick = false
92                         v.lastClickTime = time.Now()
93
94                         v.Cursor.OrigSelection[0] = v.Cursor.Loc
95                         v.Cursor.CurSelection[0] = v.Cursor.Loc
96                         v.Cursor.CurSelection[1] = v.Cursor.Loc
97                 }
98                 v.mouseReleased = false
99         } else if !v.mouseReleased {
100                 v.MoveToMouseClick(x, y)
101                 if v.tripleClick {
102                         v.Cursor.AddLineToSelection()
103                 } else if v.doubleClick {
104                         v.Cursor.AddWordToSelection()
105                 } else {
106                         v.Cursor.SetSelectionEnd(v.Cursor.Loc)
107                         v.Cursor.CopySelection("primary")
108                 }
109         }
110
111         if usePlugin {
112                 PostActionCall("MousePress", v, e)
113         }
114         return false
115 }
116
117 // ScrollUpAction scrolls the view up
118 func (v *View) ScrollUpAction(usePlugin bool) bool {
119         if usePlugin && !PreActionCall("ScrollUp", v) {
120                 return false
121         }
122
123         scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
124         v.ScrollUp(scrollspeed)
125
126         if usePlugin {
127                 PostActionCall("ScrollUp", v)
128         }
129         return false
130 }
131
132 // ScrollDownAction scrolls the view up
133 func (v *View) ScrollDownAction(usePlugin bool) bool {
134         if usePlugin && !PreActionCall("ScrollDown", v) {
135                 return false
136         }
137
138         scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
139         v.ScrollDown(scrollspeed)
140
141         if usePlugin {
142                 PostActionCall("ScrollDown", v)
143         }
144         return false
145 }
146
147 // Center centers the view on the cursor
148 func (v *View) Center(usePlugin bool) bool {
149         if usePlugin && !PreActionCall("Center", v) {
150                 return false
151         }
152
153         v.Topline = v.Cursor.Y - v.Height/2
154         if v.Topline+v.Height > v.Buf.NumLines {
155                 v.Topline = v.Buf.NumLines - v.Height
156         }
157         if v.Topline < 0 {
158                 v.Topline = 0
159         }
160
161         if usePlugin {
162                 return PostActionCall("Center", v)
163         }
164         return true
165 }
166
167 // CursorUp moves the cursor up
168 func (v *View) CursorUp(usePlugin bool) bool {
169         if usePlugin && !PreActionCall("CursorUp", v) {
170                 return false
171         }
172
173         v.deselect(0)
174         v.Cursor.Up()
175
176         if usePlugin {
177                 return PostActionCall("CursorUp", v)
178         }
179         return true
180 }
181
182 // CursorDown moves the cursor down
183 func (v *View) CursorDown(usePlugin bool) bool {
184         if usePlugin && !PreActionCall("CursorDown", v) {
185                 return false
186         }
187
188         v.deselect(1)
189         v.Cursor.Down()
190
191         if usePlugin {
192                 return PostActionCall("CursorDown", v)
193         }
194         return true
195 }
196
197 // CursorLeft moves the cursor left
198 func (v *View) CursorLeft(usePlugin bool) bool {
199         if usePlugin && !PreActionCall("CursorLeft", v) {
200                 return false
201         }
202
203         if v.Cursor.HasSelection() {
204                 v.Cursor.Loc = v.Cursor.CurSelection[0]
205                 v.Cursor.ResetSelection()
206         } else {
207                 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
208                 tabmovement := v.Buf.Settings["tabmovement"].(bool)
209                 if tabstospaces && tabmovement {
210                         tabsize := int(v.Buf.Settings["tabsize"].(float64))
211                         line := v.Buf.Line(v.Cursor.Y)
212                         if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
213                                 for i := 0; i < tabsize; i++ {
214                                         v.Cursor.Left()
215                                 }
216                         } else {
217                                 v.Cursor.Left()
218                         }
219                 } else {
220                         v.Cursor.Left()
221                 }
222         }
223
224         if usePlugin {
225                 return PostActionCall("CursorLeft", v)
226         }
227         return true
228 }
229
230 // CursorRight moves the cursor right
231 func (v *View) CursorRight(usePlugin bool) bool {
232         if usePlugin && !PreActionCall("CursorRight", v) {
233                 return false
234         }
235
236         if v.Cursor.HasSelection() {
237                 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
238                 v.Cursor.ResetSelection()
239         } else {
240                 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
241                 tabmovement := v.Buf.Settings["tabmovement"].(bool)
242                 if tabstospaces && tabmovement {
243                         tabsize := int(v.Buf.Settings["tabsize"].(float64))
244                         line := v.Buf.Line(v.Cursor.Y)
245                         if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
246                                 for i := 0; i < tabsize; i++ {
247                                         v.Cursor.Right()
248                                 }
249                         } else {
250                                 v.Cursor.Right()
251                         }
252                 } else {
253                         v.Cursor.Right()
254                 }
255         }
256
257         if usePlugin {
258                 return PostActionCall("CursorRight", v)
259         }
260         return true
261 }
262
263 // WordRight moves the cursor one word to the right
264 func (v *View) WordRight(usePlugin bool) bool {
265         if usePlugin && !PreActionCall("WordRight", v) {
266                 return false
267         }
268
269         v.Cursor.WordRight()
270
271         if usePlugin {
272                 return PostActionCall("WordRight", v)
273         }
274         return true
275 }
276
277 // WordLeft moves the cursor one word to the left
278 func (v *View) WordLeft(usePlugin bool) bool {
279         if usePlugin && !PreActionCall("WordLeft", v) {
280                 return false
281         }
282
283         v.Cursor.WordLeft()
284
285         if usePlugin {
286                 return PostActionCall("WordLeft", v)
287         }
288         return true
289 }
290
291 // SelectUp selects up one line
292 func (v *View) SelectUp(usePlugin bool) bool {
293         if usePlugin && !PreActionCall("SelectUp", v) {
294                 return false
295         }
296
297         if !v.Cursor.HasSelection() {
298                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
299         }
300         v.Cursor.Up()
301         v.Cursor.SelectTo(v.Cursor.Loc)
302
303         if usePlugin {
304                 return PostActionCall("SelectUp", v)
305         }
306         return true
307 }
308
309 // SelectDown selects down one line
310 func (v *View) SelectDown(usePlugin bool) bool {
311         if usePlugin && !PreActionCall("SelectDown", v) {
312                 return false
313         }
314
315         if !v.Cursor.HasSelection() {
316                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
317         }
318         v.Cursor.Down()
319         v.Cursor.SelectTo(v.Cursor.Loc)
320
321         if usePlugin {
322                 return PostActionCall("SelectDown", v)
323         }
324         return true
325 }
326
327 // SelectLeft selects the character to the left of the cursor
328 func (v *View) SelectLeft(usePlugin bool) bool {
329         if usePlugin && !PreActionCall("SelectLeft", v) {
330                 return false
331         }
332
333         loc := v.Cursor.Loc
334         count := v.Buf.End().Move(-1, v.Buf)
335         if loc.GreaterThan(count) {
336                 loc = count
337         }
338         if !v.Cursor.HasSelection() {
339                 v.Cursor.OrigSelection[0] = loc
340         }
341         v.Cursor.Left()
342         v.Cursor.SelectTo(v.Cursor.Loc)
343
344         if usePlugin {
345                 return PostActionCall("SelectLeft", v)
346         }
347         return true
348 }
349
350 // SelectRight selects the character to the right of the cursor
351 func (v *View) SelectRight(usePlugin bool) bool {
352         if usePlugin && !PreActionCall("SelectRight", v) {
353                 return false
354         }
355
356         loc := v.Cursor.Loc
357         count := v.Buf.End().Move(-1, v.Buf)
358         if loc.GreaterThan(count) {
359                 loc = count
360         }
361         if !v.Cursor.HasSelection() {
362                 v.Cursor.OrigSelection[0] = loc
363         }
364         v.Cursor.Right()
365         v.Cursor.SelectTo(v.Cursor.Loc)
366
367         if usePlugin {
368                 return PostActionCall("SelectRight", v)
369         }
370         return true
371 }
372
373 // SelectWordRight selects the word to the right of the cursor
374 func (v *View) SelectWordRight(usePlugin bool) bool {
375         if usePlugin && !PreActionCall("SelectWordRight", v) {
376                 return false
377         }
378
379         if !v.Cursor.HasSelection() {
380                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
381         }
382         v.Cursor.WordRight()
383         v.Cursor.SelectTo(v.Cursor.Loc)
384
385         if usePlugin {
386                 return PostActionCall("SelectWordRight", v)
387         }
388         return true
389 }
390
391 // SelectWordLeft selects the word to the left of the cursor
392 func (v *View) SelectWordLeft(usePlugin bool) bool {
393         if usePlugin && !PreActionCall("SelectWordLeft", v) {
394                 return false
395         }
396
397         if !v.Cursor.HasSelection() {
398                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
399         }
400         v.Cursor.WordLeft()
401         v.Cursor.SelectTo(v.Cursor.Loc)
402
403         if usePlugin {
404                 return PostActionCall("SelectWordLeft", v)
405         }
406         return true
407 }
408
409 // StartOfLine moves the cursor to the start of the line
410 func (v *View) StartOfLine(usePlugin bool) bool {
411         if usePlugin && !PreActionCall("StartOfLine", v) {
412                 return false
413         }
414
415         v.deselect(0)
416
417         v.Cursor.Start()
418
419         if usePlugin {
420                 return PostActionCall("StartOfLine", v)
421         }
422         return true
423 }
424
425 // EndOfLine moves the cursor to the end of the line
426 func (v *View) EndOfLine(usePlugin bool) bool {
427         if usePlugin && !PreActionCall("EndOfLine", v) {
428                 return false
429         }
430
431         v.deselect(0)
432
433         v.Cursor.End()
434
435         if usePlugin {
436                 return PostActionCall("EndOfLine", v)
437         }
438         return true
439 }
440
441 // SelectToStartOfLine selects to the start of the current line
442 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
443         if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
444                 return false
445         }
446
447         if !v.Cursor.HasSelection() {
448                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
449         }
450         v.Cursor.Start()
451         v.Cursor.SelectTo(v.Cursor.Loc)
452
453         if usePlugin {
454                 return PostActionCall("SelectToStartOfLine", v)
455         }
456         return true
457 }
458
459 // SelectToEndOfLine selects to the end of the current line
460 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
461         if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
462                 return false
463         }
464
465         if !v.Cursor.HasSelection() {
466                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
467         }
468         v.Cursor.End()
469         v.Cursor.SelectTo(v.Cursor.Loc)
470
471         if usePlugin {
472                 return PostActionCall("SelectToEndOfLine", v)
473         }
474         return true
475 }
476
477 // CursorStart moves the cursor to the start of the buffer
478 func (v *View) CursorStart(usePlugin bool) bool {
479         if usePlugin && !PreActionCall("CursorStart", v) {
480                 return false
481         }
482
483         v.deselect(0)
484
485         v.Cursor.X = 0
486         v.Cursor.Y = 0
487
488         if usePlugin {
489                 return PostActionCall("CursorStart", v)
490         }
491         return true
492 }
493
494 // CursorEnd moves the cursor to the end of the buffer
495 func (v *View) CursorEnd(usePlugin bool) bool {
496         if usePlugin && !PreActionCall("CursorEnd", v) {
497                 return false
498         }
499
500         v.deselect(0)
501
502         v.Cursor.Loc = v.Buf.End()
503
504         if usePlugin {
505                 return PostActionCall("CursorEnd", v)
506         }
507         return true
508 }
509
510 // SelectToStart selects the text from the cursor to the start of the buffer
511 func (v *View) SelectToStart(usePlugin bool) bool {
512         if usePlugin && !PreActionCall("SelectToStart", v) {
513                 return false
514         }
515
516         if !v.Cursor.HasSelection() {
517                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
518         }
519         v.CursorStart(false)
520         v.Cursor.SelectTo(v.Buf.Start())
521
522         if usePlugin {
523                 return PostActionCall("SelectToStart", v)
524         }
525         return true
526 }
527
528 // SelectToEnd selects the text from the cursor to the end of the buffer
529 func (v *View) SelectToEnd(usePlugin bool) bool {
530         if usePlugin && !PreActionCall("SelectToEnd", v) {
531                 return false
532         }
533
534         if !v.Cursor.HasSelection() {
535                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
536         }
537         v.CursorEnd(false)
538         v.Cursor.SelectTo(v.Buf.End())
539
540         if usePlugin {
541                 return PostActionCall("SelectToEnd", v)
542         }
543         return true
544 }
545
546 // InsertSpace inserts a space
547 func (v *View) InsertSpace(usePlugin bool) bool {
548         if usePlugin && !PreActionCall("InsertSpace", v) {
549                 return false
550         }
551
552         if v.Cursor.HasSelection() {
553                 v.Cursor.DeleteSelection()
554                 v.Cursor.ResetSelection()
555         }
556         v.Buf.Insert(v.Cursor.Loc, " ")
557         v.Cursor.Right()
558
559         if usePlugin {
560                 return PostActionCall("InsertSpace", v)
561         }
562         return true
563 }
564
565 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
566 func (v *View) InsertNewline(usePlugin bool) bool {
567         if usePlugin && !PreActionCall("InsertNewline", v) {
568                 return false
569         }
570
571         // Insert a newline
572         if v.Cursor.HasSelection() {
573                 v.Cursor.DeleteSelection()
574                 v.Cursor.ResetSelection()
575         }
576
577         v.Buf.Insert(v.Cursor.Loc, "\n")
578         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
579         v.Cursor.Right()
580
581         if v.Buf.Settings["autoindent"].(bool) {
582                 v.Buf.Insert(v.Cursor.Loc, ws)
583                 for i := 0; i < len(ws); i++ {
584                         v.Cursor.Right()
585                 }
586
587                 // Remove the whitespaces if keepautoindent setting is off
588                 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
589                         line := v.Buf.Line(v.Cursor.Y - 1)
590                         v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
591                 }
592         }
593         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
594
595         if usePlugin {
596                 return PostActionCall("InsertNewline", v)
597         }
598         return true
599 }
600
601 // Backspace deletes the previous character
602 func (v *View) Backspace(usePlugin bool) bool {
603         if usePlugin && !PreActionCall("Backspace", v) {
604                 return false
605         }
606
607         // Delete a character
608         if v.Cursor.HasSelection() {
609                 v.Cursor.DeleteSelection()
610                 v.Cursor.ResetSelection()
611         } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
612                 // We have to do something a bit hacky here because we want to
613                 // delete the line by first moving left and then deleting backwards
614                 // but the undo redo would place the cursor in the wrong place
615                 // So instead we move left, save the position, move back, delete
616                 // and restore the position
617
618                 // If the user is using spaces instead of tabs and they are deleting
619                 // whitespace at the start of the line, we should delete as if it's a
620                 // tab (tabSize number of spaces)
621                 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
622                 tabSize := int(v.Buf.Settings["tabsize"].(float64))
623                 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
624                         loc := v.Cursor.Loc
625                         v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
626                         cx, cy := v.Cursor.X, v.Cursor.Y
627                         v.Cursor.Loc = loc
628                         v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
629                         v.Cursor.X, v.Cursor.Y = cx, cy
630                 } else {
631                         v.Cursor.Left()
632                         cx, cy := v.Cursor.X, v.Cursor.Y
633                         v.Cursor.Right()
634                         loc := v.Cursor.Loc
635                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
636                         v.Cursor.X, v.Cursor.Y = cx, cy
637                 }
638         }
639         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
640
641         if usePlugin {
642                 return PostActionCall("Backspace", v)
643         }
644         return true
645 }
646
647 // DeleteWordRight deletes the word to the right of the cursor
648 func (v *View) DeleteWordRight(usePlugin bool) bool {
649         if usePlugin && !PreActionCall("DeleteWordRight", v) {
650                 return false
651         }
652
653         v.SelectWordRight(false)
654         if v.Cursor.HasSelection() {
655                 v.Cursor.DeleteSelection()
656                 v.Cursor.ResetSelection()
657         }
658
659         if usePlugin {
660                 return PostActionCall("DeleteWordRight", v)
661         }
662         return true
663 }
664
665 // DeleteWordLeft deletes the word to the left of the cursor
666 func (v *View) DeleteWordLeft(usePlugin bool) bool {
667         if usePlugin && !PreActionCall("DeleteWordLeft", v) {
668                 return false
669         }
670
671         v.SelectWordLeft(false)
672         if v.Cursor.HasSelection() {
673                 v.Cursor.DeleteSelection()
674                 v.Cursor.ResetSelection()
675         }
676
677         if usePlugin {
678                 return PostActionCall("DeleteWordLeft", v)
679         }
680         return true
681 }
682
683 // Delete deletes the next character
684 func (v *View) Delete(usePlugin bool) bool {
685         if usePlugin && !PreActionCall("Delete", v) {
686                 return false
687         }
688
689         if v.Cursor.HasSelection() {
690                 v.Cursor.DeleteSelection()
691                 v.Cursor.ResetSelection()
692         } else {
693                 loc := v.Cursor.Loc
694                 if loc.LessThan(v.Buf.End()) {
695                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
696                 }
697         }
698
699         if usePlugin {
700                 return PostActionCall("Delete", v)
701         }
702         return true
703 }
704
705 // IndentSelection indents the current selection
706 func (v *View) IndentSelection(usePlugin bool) bool {
707         if usePlugin && !PreActionCall("IndentSelection", v) {
708                 return false
709         }
710
711         if v.Cursor.HasSelection() {
712                 start := v.Cursor.CurSelection[0]
713                 end := v.Cursor.CurSelection[1]
714                 if end.Y < start.Y {
715                         start, end = end, start
716                 }
717
718                 startY := start.Y
719                 endY := end.Move(-1, v.Buf).Y
720                 endX := end.Move(-1, v.Buf).X
721                 for y := startY; y <= endY; y++ {
722                         tabsize := len(v.Buf.IndentString())
723                         v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
724                         if y == startY && start.X > 0 {
725                                 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
726                         }
727                         if y == endY {
728                                 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
729                         }
730                 }
731                 v.Cursor.Relocate()
732
733                 if usePlugin {
734                         return PostActionCall("IndentSelection", v)
735                 }
736                 return true
737         }
738         return false
739 }
740
741 // OutdentLine moves the current line back one indentation
742 func (v *View) OutdentLine(usePlugin bool) bool {
743         if usePlugin && !PreActionCall("OutdentLine", v) {
744                 return false
745         }
746
747         if v.Cursor.HasSelection() {
748                 return false
749         }
750
751         for x := 0; x < len(v.Buf.IndentString()); x++ {
752                 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
753                         break
754                 }
755                 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
756                 v.Cursor.X -= 1
757         }
758         v.Cursor.Relocate()
759
760         if usePlugin {
761                 return PostActionCall("OutdentLine", v)
762         }
763         return true
764 }
765
766 // OutdentSelection takes the current selection and moves it back one indent level
767 func (v *View) OutdentSelection(usePlugin bool) bool {
768         if usePlugin && !PreActionCall("OutdentSelection", v) {
769                 return false
770         }
771
772         if v.Cursor.HasSelection() {
773                 start := v.Cursor.CurSelection[0]
774                 end := v.Cursor.CurSelection[1]
775                 if end.Y < start.Y {
776                         start, end = end, start
777                 }
778
779                 startY := start.Y
780                 endY := end.Move(-1, v.Buf).Y
781                 endX := end.Move(-1, v.Buf).X
782                 for y := startY; y <= endY; y++ {
783                         for x := 0; x < len(v.Buf.IndentString()); x++ {
784                                 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
785                                         break
786                                 }
787                                 v.Buf.Remove(Loc{0, y}, Loc{1, y})
788                                 if y == startY && start.X > 0 {
789                                         v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
790                                 }
791                                 if y == endY {
792                                         v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
793                                 }
794                         }
795                 }
796                 v.Cursor.Relocate()
797
798                 if usePlugin {
799                         return PostActionCall("OutdentSelection", v)
800                 }
801                 return true
802         }
803         return false
804 }
805
806 // InsertTab inserts a tab or spaces
807 func (v *View) InsertTab(usePlugin bool) bool {
808         if usePlugin && !PreActionCall("InsertTab", v) {
809                 return false
810         }
811
812         if v.Cursor.HasSelection() {
813                 return false
814         }
815
816         tabBytes := len(v.Buf.IndentString())
817         bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
818         v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
819         for i := 0; i < bytesUntilIndent; i++ {
820                 v.Cursor.Right()
821         }
822
823         if usePlugin {
824                 return PostActionCall("InsertTab", v)
825         }
826         return true
827 }
828
829 // SaveAll saves all open buffers
830 func (v *View) SaveAll(usePlugin bool) bool {
831         if usePlugin && !PreActionCall("SaveAll", v) {
832                 return false
833         }
834
835         for _, t := range tabs {
836                 for _, v := range t.views {
837                         v.Save(false)
838                 }
839         }
840
841         if usePlugin {
842                 return PostActionCall("SaveAll", v)
843         }
844         return false
845 }
846
847 // Save the buffer to disk
848 func (v *View) Save(usePlugin bool) bool {
849         if usePlugin && !PreActionCall("Save", v) {
850                 return false
851         }
852
853         if v.Type.scratch == true {
854                 // We can't save any view type with scratch set. eg help and log text
855                 return false
856         }
857         // If this is an empty buffer, ask for a filename
858         if v.Buf.Path == "" {
859                 v.SaveAs(false)
860         } else {
861                 v.saveToFile(v.Buf.Path)
862         }
863
864         if usePlugin {
865                 return PostActionCall("Save", v)
866         }
867         return false
868 }
869
870 // This function saves the buffer to `filename` and changes the buffer's path and name
871 // to `filename` if the save is successful
872 func (v *View) saveToFile(filename string) {
873         err := v.Buf.SaveAs(filename)
874         if err != nil {
875                 if strings.HasSuffix(err.Error(), "permission denied") {
876                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
877                         if choice {
878                                 err = v.Buf.SaveAsWithSudo(filename)
879                                 if err != nil {
880                                         messenger.Error(err.Error())
881                                 } else {
882                                         v.Buf.Path = filename
883                                         v.Buf.name = filename
884                                         messenger.Message("Saved " + filename)
885                                 }
886                         }
887                         messenger.Reset()
888                         messenger.Clear()
889                 } else {
890                         messenger.Error(err.Error())
891                 }
892         } else {
893                 v.Buf.Path = filename
894                 v.Buf.name = filename
895                 messenger.Message("Saved " + filename)
896         }
897 }
898
899 // SaveAs saves the buffer to disk with the given name
900 func (v *View) SaveAs(usePlugin bool) bool {
901         filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
902         if !canceled {
903                 // the filename might or might not be quoted, so unquote first then join the strings.
904                 filename = strings.Join(SplitCommandArgs(filename), " ")
905                 v.saveToFile(filename)
906         }
907
908         return false
909 }
910
911 // Find opens a prompt and searches forward for the input
912 func (v *View) Find(usePlugin bool) bool {
913         if usePlugin && !PreActionCall("Find", v) {
914                 return false
915         }
916
917         searchStr := ""
918         if v.Cursor.HasSelection() {
919                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
920                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
921                 searchStr = v.Cursor.GetSelection()
922         } else {
923                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
924         }
925         BeginSearch(searchStr)
926
927         if usePlugin {
928                 return PostActionCall("Find", v)
929         }
930         return true
931 }
932
933 // FindNext searches forwards for the last used search term
934 func (v *View) FindNext(usePlugin bool) bool {
935         if usePlugin && !PreActionCall("FindNext", v) {
936                 return false
937         }
938
939         if v.Cursor.HasSelection() {
940                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
941                 // lastSearch = v.Cursor.GetSelection()
942         } else {
943                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
944         }
945         if lastSearch == "" {
946                 return true
947         }
948         messenger.Message("Finding: " + lastSearch)
949         Search(lastSearch, v, true)
950
951         if usePlugin {
952                 return PostActionCall("FindNext", v)
953         }
954         return true
955 }
956
957 // FindPrevious searches backwards for the last used search term
958 func (v *View) FindPrevious(usePlugin bool) bool {
959         if usePlugin && !PreActionCall("FindPrevious", v) {
960                 return false
961         }
962
963         if v.Cursor.HasSelection() {
964                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
965         } else {
966                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
967         }
968         messenger.Message("Finding: " + lastSearch)
969         Search(lastSearch, v, false)
970
971         if usePlugin {
972                 return PostActionCall("FindPrevious", v)
973         }
974         return true
975 }
976
977 // Undo undoes the last action
978 func (v *View) Undo(usePlugin bool) bool {
979         if usePlugin && !PreActionCall("Undo", v) {
980                 return false
981         }
982
983         v.Buf.Undo()
984         messenger.Message("Undid action")
985
986         if usePlugin {
987                 return PostActionCall("Undo", v)
988         }
989         return true
990 }
991
992 // Redo redoes the last action
993 func (v *View) Redo(usePlugin bool) bool {
994         if usePlugin && !PreActionCall("Redo", v) {
995                 return false
996         }
997
998         v.Buf.Redo()
999         messenger.Message("Redid action")
1000
1001         if usePlugin {
1002                 return PostActionCall("Redo", v)
1003         }
1004         return true
1005 }
1006
1007 // Copy the selection to the system clipboard
1008 func (v *View) Copy(usePlugin bool) bool {
1009         if usePlugin && !PreActionCall("Copy", v) {
1010                 return false
1011         }
1012
1013         if v.Cursor.HasSelection() {
1014                 v.Cursor.CopySelection("clipboard")
1015                 v.freshClip = true
1016                 messenger.Message("Copied selection")
1017         }
1018
1019         if usePlugin {
1020                 return PostActionCall("Copy", v)
1021         }
1022         return true
1023 }
1024
1025 // CutLine cuts the current line to the clipboard
1026 func (v *View) CutLine(usePlugin bool) bool {
1027         if usePlugin && !PreActionCall("CutLine", v) {
1028                 return false
1029         }
1030
1031         v.Cursor.SelectLine()
1032         if !v.Cursor.HasSelection() {
1033                 return false
1034         }
1035         if v.freshClip == true {
1036                 if v.Cursor.HasSelection() {
1037                         if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1038                                 messenger.Error(err)
1039                         } else {
1040                                 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1041                         }
1042                 }
1043         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1044                 v.Copy(true)
1045         }
1046         v.freshClip = true
1047         v.lastCutTime = time.Now()
1048         v.Cursor.DeleteSelection()
1049         v.Cursor.ResetSelection()
1050         messenger.Message("Cut line")
1051
1052         if usePlugin {
1053                 return PostActionCall("CutLine", v)
1054         }
1055         return true
1056 }
1057
1058 // Cut the selection to the system clipboard
1059 func (v *View) Cut(usePlugin bool) bool {
1060         if usePlugin && !PreActionCall("Cut", v) {
1061                 return false
1062         }
1063
1064         if v.Cursor.HasSelection() {
1065                 v.Cursor.CopySelection("clipboard")
1066                 v.Cursor.DeleteSelection()
1067                 v.Cursor.ResetSelection()
1068                 v.freshClip = true
1069                 messenger.Message("Cut selection")
1070
1071                 if usePlugin {
1072                         return PostActionCall("Cut", v)
1073                 }
1074                 return true
1075         }
1076
1077         return false
1078 }
1079
1080 // DuplicateLine duplicates the current line or selection
1081 func (v *View) DuplicateLine(usePlugin bool) bool {
1082         if usePlugin && !PreActionCall("DuplicateLine", v) {
1083                 return false
1084         }
1085
1086         if v.Cursor.HasSelection() {
1087                 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1088         } else {
1089                 v.Cursor.End()
1090                 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1091                 v.Cursor.Right()
1092         }
1093
1094         messenger.Message("Duplicated line")
1095
1096         if usePlugin {
1097                 return PostActionCall("DuplicateLine", v)
1098         }
1099         return true
1100 }
1101
1102 // DeleteLine deletes the current line
1103 func (v *View) DeleteLine(usePlugin bool) bool {
1104         if usePlugin && !PreActionCall("DeleteLine", v) {
1105                 return false
1106         }
1107
1108         v.Cursor.SelectLine()
1109         if !v.Cursor.HasSelection() {
1110                 return false
1111         }
1112         v.Cursor.DeleteSelection()
1113         v.Cursor.ResetSelection()
1114         messenger.Message("Deleted line")
1115
1116         if usePlugin {
1117                 return PostActionCall("DeleteLine", v)
1118         }
1119         return true
1120 }
1121
1122 // MoveLinesUp moves up the current line or selected lines if any
1123 func (v *View) MoveLinesUp(usePlugin bool) bool {
1124         if usePlugin && !PreActionCall("MoveLinesUp", v) {
1125                 return false
1126         }
1127
1128         if v.Cursor.HasSelection() {
1129                 if v.Cursor.CurSelection[0].Y == 0 {
1130                         messenger.Message("Can not move further up")
1131                         return true
1132                 }
1133                 v.Buf.MoveLinesUp(
1134                         v.Cursor.CurSelection[0].Y,
1135                         v.Cursor.CurSelection[1].Y,
1136                 )
1137                 v.Cursor.UpN(1)
1138                 v.Cursor.CurSelection[0].Y -= 1
1139                 v.Cursor.CurSelection[1].Y -= 1
1140                 messenger.Message("Moved up selected line(s)")
1141         } else {
1142                 if v.Cursor.Loc.Y == 0 {
1143                         messenger.Message("Can not move further up")
1144                         return true
1145                 }
1146                 v.Buf.MoveLinesUp(
1147                         v.Cursor.Loc.Y,
1148                         v.Cursor.Loc.Y+1,
1149                 )
1150                 v.Cursor.UpN(1)
1151                 messenger.Message("Moved up current line")
1152         }
1153         v.Buf.IsModified = true
1154
1155         if usePlugin {
1156                 return PostActionCall("MoveLinesUp", v)
1157         }
1158         return true
1159 }
1160
1161 // MoveLinesDown moves down the current line or selected lines if any
1162 func (v *View) MoveLinesDown(usePlugin bool) bool {
1163         if usePlugin && !PreActionCall("MoveLinesDown", v) {
1164                 return false
1165         }
1166
1167         if v.Cursor.HasSelection() {
1168                 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1169                         messenger.Message("Can not move further down")
1170                         return true
1171                 }
1172                 v.Buf.MoveLinesDown(
1173                         v.Cursor.CurSelection[0].Y,
1174                         v.Cursor.CurSelection[1].Y,
1175                 )
1176                 v.Cursor.DownN(1)
1177                 v.Cursor.CurSelection[0].Y += 1
1178                 v.Cursor.CurSelection[1].Y += 1
1179                 messenger.Message("Moved down selected line(s)")
1180         } else {
1181                 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1182                         messenger.Message("Can not move further down")
1183                         return true
1184                 }
1185                 v.Buf.MoveLinesDown(
1186                         v.Cursor.Loc.Y,
1187                         v.Cursor.Loc.Y+1,
1188                 )
1189                 v.Cursor.DownN(1)
1190                 messenger.Message("Moved down current line")
1191         }
1192         v.Buf.IsModified = true
1193
1194         if usePlugin {
1195                 return PostActionCall("MoveLinesDown", v)
1196         }
1197         return true
1198 }
1199
1200 // Paste whatever is in the system clipboard into the buffer
1201 // Delete and paste if the user has a selection
1202 func (v *View) Paste(usePlugin bool) bool {
1203         if usePlugin && !PreActionCall("Paste", v) {
1204                 return false
1205         }
1206
1207         clip, _ := clipboard.ReadAll("clipboard")
1208         v.paste(clip)
1209
1210         if usePlugin {
1211                 return PostActionCall("Paste", v)
1212         }
1213         return true
1214 }
1215
1216 // PastePrimary pastes from the primary clipboard (only use on linux)
1217 func (v *View) PastePrimary(usePlugin bool) bool {
1218         if usePlugin && !PreActionCall("Paste", v) {
1219                 return false
1220         }
1221
1222         clip, _ := clipboard.ReadAll("primary")
1223         v.paste(clip)
1224
1225         if usePlugin {
1226                 return PostActionCall("Paste", v)
1227         }
1228         return true
1229 }
1230
1231 // SelectAll selects the entire buffer
1232 func (v *View) SelectAll(usePlugin bool) bool {
1233         if usePlugin && !PreActionCall("SelectAll", v) {
1234                 return false
1235         }
1236
1237         v.Cursor.SetSelectionStart(v.Buf.Start())
1238         v.Cursor.SetSelectionEnd(v.Buf.End())
1239         // Put the cursor at the beginning
1240         v.Cursor.X = 0
1241         v.Cursor.Y = 0
1242
1243         if usePlugin {
1244                 return PostActionCall("SelectAll", v)
1245         }
1246         return true
1247 }
1248
1249 // OpenFile opens a new file in the buffer
1250 func (v *View) OpenFile(usePlugin bool) bool {
1251         if usePlugin && !PreActionCall("OpenFile", v) {
1252                 return false
1253         }
1254
1255         if v.CanClose() {
1256                 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1257                 if !canceled {
1258                         HandleCommand(input)
1259                         if usePlugin {
1260                                 return PostActionCall("OpenFile", v)
1261                         }
1262                 }
1263         }
1264         return false
1265 }
1266
1267 // Start moves the viewport to the start of the buffer
1268 func (v *View) Start(usePlugin bool) bool {
1269         if usePlugin && !PreActionCall("Start", v) {
1270                 return false
1271         }
1272
1273         v.Topline = 0
1274
1275         if usePlugin {
1276                 return PostActionCall("Start", v)
1277         }
1278         return false
1279 }
1280
1281 // End moves the viewport to the end of the buffer
1282 func (v *View) End(usePlugin bool) bool {
1283         if usePlugin && !PreActionCall("End", v) {
1284                 return false
1285         }
1286
1287         if v.Height > v.Buf.NumLines {
1288                 v.Topline = 0
1289         } else {
1290                 v.Topline = v.Buf.NumLines - v.Height
1291         }
1292
1293         if usePlugin {
1294                 return PostActionCall("End", v)
1295         }
1296         return false
1297 }
1298
1299 // PageUp scrolls the view up a page
1300 func (v *View) PageUp(usePlugin bool) bool {
1301         if usePlugin && !PreActionCall("PageUp", v) {
1302                 return false
1303         }
1304
1305         if v.Topline > v.Height {
1306                 v.ScrollUp(v.Height)
1307         } else {
1308                 v.Topline = 0
1309         }
1310
1311         if usePlugin {
1312                 return PostActionCall("PageUp", v)
1313         }
1314         return false
1315 }
1316
1317 // PageDown scrolls the view down a page
1318 func (v *View) PageDown(usePlugin bool) bool {
1319         if usePlugin && !PreActionCall("PageDown", v) {
1320                 return false
1321         }
1322
1323         if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1324                 v.ScrollDown(v.Height)
1325         } else if v.Buf.NumLines >= v.Height {
1326                 v.Topline = v.Buf.NumLines - v.Height
1327         }
1328
1329         if usePlugin {
1330                 return PostActionCall("PageDown", v)
1331         }
1332         return false
1333 }
1334
1335 // CursorPageUp places the cursor a page up
1336 func (v *View) CursorPageUp(usePlugin bool) bool {
1337         if usePlugin && !PreActionCall("CursorPageUp", v) {
1338                 return false
1339         }
1340
1341         v.deselect(0)
1342
1343         if v.Cursor.HasSelection() {
1344                 v.Cursor.Loc = v.Cursor.CurSelection[0]
1345                 v.Cursor.ResetSelection()
1346         }
1347         v.Cursor.UpN(v.Height)
1348
1349         if usePlugin {
1350                 return PostActionCall("CursorPageUp", v)
1351         }
1352         return true
1353 }
1354
1355 // CursorPageDown places the cursor a page up
1356 func (v *View) CursorPageDown(usePlugin bool) bool {
1357         if usePlugin && !PreActionCall("CursorPageDown", v) {
1358                 return false
1359         }
1360
1361         v.deselect(0)
1362
1363         if v.Cursor.HasSelection() {
1364                 v.Cursor.Loc = v.Cursor.CurSelection[1]
1365                 v.Cursor.ResetSelection()
1366         }
1367         v.Cursor.DownN(v.Height)
1368
1369         if usePlugin {
1370                 return PostActionCall("CursorPageDown", v)
1371         }
1372         return true
1373 }
1374
1375 // HalfPageUp scrolls the view up half a page
1376 func (v *View) HalfPageUp(usePlugin bool) bool {
1377         if usePlugin && !PreActionCall("HalfPageUp", v) {
1378                 return false
1379         }
1380
1381         if v.Topline > v.Height/2 {
1382                 v.ScrollUp(v.Height / 2)
1383         } else {
1384                 v.Topline = 0
1385         }
1386
1387         if usePlugin {
1388                 return PostActionCall("HalfPageUp", v)
1389         }
1390         return false
1391 }
1392
1393 // HalfPageDown scrolls the view down half a page
1394 func (v *View) HalfPageDown(usePlugin bool) bool {
1395         if usePlugin && !PreActionCall("HalfPageDown", v) {
1396                 return false
1397         }
1398
1399         if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1400                 v.ScrollDown(v.Height / 2)
1401         } else {
1402                 if v.Buf.NumLines >= v.Height {
1403                         v.Topline = v.Buf.NumLines - v.Height
1404                 }
1405         }
1406
1407         if usePlugin {
1408                 return PostActionCall("HalfPageDown", v)
1409         }
1410         return false
1411 }
1412
1413 // ToggleRuler turns line numbers off and on
1414 func (v *View) ToggleRuler(usePlugin bool) bool {
1415         if usePlugin && !PreActionCall("ToggleRuler", v) {
1416                 return false
1417         }
1418
1419         if v.Buf.Settings["ruler"] == false {
1420                 v.Buf.Settings["ruler"] = true
1421                 messenger.Message("Enabled ruler")
1422         } else {
1423                 v.Buf.Settings["ruler"] = false
1424                 messenger.Message("Disabled ruler")
1425         }
1426
1427         if usePlugin {
1428                 return PostActionCall("ToggleRuler", v)
1429         }
1430         return false
1431 }
1432
1433 // JumpLine jumps to a line and moves the view accordingly.
1434 func (v *View) JumpLine(usePlugin bool) bool {
1435         if usePlugin && !PreActionCall("JumpLine", v) {
1436                 return false
1437         }
1438
1439         // Prompt for line number
1440         linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1441         if canceled {
1442                 return false
1443         }
1444         lineint, err := strconv.Atoi(linestring)
1445         lineint = lineint - 1 // fix offset
1446         if err != nil {
1447                 messenger.Error(err) // return errors
1448                 return false
1449         }
1450         // Move cursor and view if possible.
1451         if lineint < v.Buf.NumLines && lineint >= 0 {
1452                 v.Cursor.X = 0
1453                 v.Cursor.Y = lineint
1454
1455                 if usePlugin {
1456                         return PostActionCall("JumpLine", v)
1457                 }
1458                 return true
1459         }
1460         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1461         return false
1462 }
1463
1464 // ClearStatus clears the messenger bar
1465 func (v *View) ClearStatus(usePlugin bool) bool {
1466         if usePlugin && !PreActionCall("ClearStatus", v) {
1467                 return false
1468         }
1469
1470         messenger.Message("")
1471
1472         if usePlugin {
1473                 return PostActionCall("ClearStatus", v)
1474         }
1475         return false
1476 }
1477
1478 // ToggleHelp toggles the help screen
1479 func (v *View) ToggleHelp(usePlugin bool) bool {
1480         if usePlugin && !PreActionCall("ToggleHelp", v) {
1481                 return false
1482         }
1483
1484         if v.Type != vtHelp {
1485                 // Open the default help
1486                 v.openHelp("help")
1487         } else {
1488                 v.Quit(true)
1489         }
1490
1491         if usePlugin {
1492                 return PostActionCall("ToggleHelp", v)
1493         }
1494         return true
1495 }
1496
1497 // ShellMode opens a terminal to run a shell command
1498 func (v *View) ShellMode(usePlugin bool) bool {
1499         if usePlugin && !PreActionCall("ShellMode", v) {
1500                 return false
1501         }
1502
1503         input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1504         if !canceled {
1505                 // The true here is for openTerm to make the command interactive
1506                 HandleShellCommand(input, true, true)
1507                 if usePlugin {
1508                         return PostActionCall("ShellMode", v)
1509                 }
1510         }
1511         return false
1512 }
1513
1514 // CommandMode lets the user enter a command
1515 func (v *View) CommandMode(usePlugin bool) bool {
1516         if usePlugin && !PreActionCall("CommandMode", v) {
1517                 return false
1518         }
1519
1520         input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1521         if !canceled {
1522                 HandleCommand(input)
1523                 if usePlugin {
1524                         return PostActionCall("CommandMode", v)
1525                 }
1526         }
1527
1528         return false
1529 }
1530
1531 // Escape leaves current mode
1532 func (v *View) Escape(usePlugin bool) bool {
1533         // check if user is searching, or the last search is still active
1534         if searching || lastSearch != "" {
1535                 ExitSearch(v)
1536                 return true
1537         }
1538         // check if a prompt is shown, hide it and don't quit
1539         if messenger.hasPrompt {
1540                 messenger.Reset() // FIXME
1541                 return true
1542         }
1543
1544         return false
1545 }
1546
1547 // Quit this will close the current tab or view that is open
1548 func (v *View) Quit(usePlugin bool) bool {
1549         if usePlugin && !PreActionCall("Quit", v) {
1550                 return false
1551         }
1552
1553         // Make sure not to quit if there are unsaved changes
1554         if v.CanClose() {
1555                 v.CloseBuffer()
1556                 if len(tabs[curTab].views) > 1 {
1557                         v.splitNode.Delete()
1558                         tabs[v.TabNum].Cleanup()
1559                         tabs[v.TabNum].Resize()
1560                 } else if len(tabs) > 1 {
1561                         if len(tabs[v.TabNum].views) == 1 {
1562                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1563                                 for i, t := range tabs {
1564                                         t.SetNum(i)
1565                                 }
1566                                 if curTab >= len(tabs) {
1567                                         curTab--
1568                                 }
1569                                 if curTab == 0 {
1570                                         CurView().ToggleTabbar()
1571                                 }
1572                         }
1573                 } else {
1574                         if usePlugin {
1575                                 PostActionCall("Quit", v)
1576                         }
1577
1578                         screen.Fini()
1579                         os.Exit(0)
1580                 }
1581         }
1582
1583         if usePlugin {
1584                 return PostActionCall("Quit", v)
1585         }
1586         return false
1587 }
1588
1589 // QuitAll quits the whole editor; all splits and tabs
1590 func (v *View) QuitAll(usePlugin bool) bool {
1591         if usePlugin && !PreActionCall("QuitAll", v) {
1592                 return false
1593         }
1594
1595         closeAll := true
1596         for _, tab := range tabs {
1597                 for _, v := range tab.views {
1598                         if !v.CanClose() {
1599                                 closeAll = false
1600                         }
1601                 }
1602         }
1603
1604         if closeAll {
1605                 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1606                 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1607
1608                 if shouldQuit {
1609                         for _, tab := range tabs {
1610                                 for _, v := range tab.views {
1611                                         v.CloseBuffer()
1612                                 }
1613                         }
1614
1615                         if usePlugin {
1616                                 PostActionCall("QuitAll", v)
1617                         }
1618
1619                         screen.Fini()
1620                         os.Exit(0)
1621                 }
1622         }
1623
1624         return false
1625 }
1626
1627 // AddTab adds a new tab with an empty buffer
1628 func (v *View) AddTab(usePlugin bool) bool {
1629         if usePlugin && !PreActionCall("AddTab", v) {
1630                 return false
1631         }
1632
1633         tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1634         tab.SetNum(len(tabs))
1635         tabs = append(tabs, tab)
1636         curTab = len(tabs) - 1
1637         if len(tabs) == 2 {
1638                 for _, t := range tabs {
1639                         for _, v := range t.views {
1640                                 v.ToggleTabbar()
1641                         }
1642                 }
1643         }
1644
1645         if usePlugin {
1646                 return PostActionCall("AddTab", v)
1647         }
1648         return true
1649 }
1650
1651 // PreviousTab switches to the previous tab in the tab list
1652 func (v *View) PreviousTab(usePlugin bool) bool {
1653         if usePlugin && !PreActionCall("PreviousTab", v) {
1654                 return false
1655         }
1656
1657         if curTab > 0 {
1658                 curTab--
1659         } else if curTab == 0 {
1660                 curTab = len(tabs) - 1
1661         }
1662
1663         if usePlugin {
1664                 return PostActionCall("PreviousTab", v)
1665         }
1666         return false
1667 }
1668
1669 // NextTab switches to the next tab in the tab list
1670 func (v *View) NextTab(usePlugin bool) bool {
1671         if usePlugin && !PreActionCall("NextTab", v) {
1672                 return false
1673         }
1674
1675         if curTab < len(tabs)-1 {
1676                 curTab++
1677         } else if curTab == len(tabs)-1 {
1678                 curTab = 0
1679         }
1680
1681         if usePlugin {
1682                 return PostActionCall("NextTab", v)
1683         }
1684         return false
1685 }
1686
1687 // VSplitBinding opens an empty vertical split
1688 func (v *View) VSplitBinding(usePlugin bool) bool {
1689         if usePlugin && !PreActionCall("VSplit", v) {
1690                 return false
1691         }
1692
1693         v.VSplit(NewBufferFromString("", ""))
1694
1695         if usePlugin {
1696                 return PostActionCall("VSplit", v)
1697         }
1698         return false
1699 }
1700
1701 // HSplitBinding opens an empty horizontal split
1702 func (v *View) HSplitBinding(usePlugin bool) bool {
1703         if usePlugin && !PreActionCall("HSplit", v) {
1704                 return false
1705         }
1706
1707         v.HSplit(NewBufferFromString("", ""))
1708
1709         if usePlugin {
1710                 return PostActionCall("HSplit", v)
1711         }
1712         return false
1713 }
1714
1715 // Unsplit closes all splits in the current tab except the active one
1716 func (v *View) Unsplit(usePlugin bool) bool {
1717         if usePlugin && !PreActionCall("Unsplit", v) {
1718                 return false
1719         }
1720
1721         curView := tabs[curTab].CurView
1722         for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1723                 view := tabs[curTab].views[i]
1724                 if view != nil && view.Num != curView {
1725                         view.Quit(true)
1726                         // messenger.Message("Quit ", view.Buf.Path)
1727                 }
1728         }
1729
1730         if usePlugin {
1731                 return PostActionCall("Unsplit", v)
1732         }
1733         return false
1734 }
1735
1736 // NextSplit changes the view to the next split
1737 func (v *View) NextSplit(usePlugin bool) bool {
1738         if usePlugin && !PreActionCall("NextSplit", v) {
1739                 return false
1740         }
1741
1742         tab := tabs[curTab]
1743         if tab.CurView < len(tab.views)-1 {
1744                 tab.CurView++
1745         } else {
1746                 tab.CurView = 0
1747         }
1748
1749         if usePlugin {
1750                 return PostActionCall("NextSplit", v)
1751         }
1752         return false
1753 }
1754
1755 // PreviousSplit changes the view to the previous split
1756 func (v *View) PreviousSplit(usePlugin bool) bool {
1757         if usePlugin && !PreActionCall("PreviousSplit", v) {
1758                 return false
1759         }
1760
1761         tab := tabs[curTab]
1762         if tab.CurView > 0 {
1763                 tab.CurView--
1764         } else {
1765                 tab.CurView = len(tab.views) - 1
1766         }
1767
1768         if usePlugin {
1769                 return PostActionCall("PreviousSplit", v)
1770         }
1771         return false
1772 }
1773
1774 var curMacro []interface{}
1775 var recordingMacro bool
1776
1777 // ToggleMacro toggles recording of a macro
1778 func (v *View) ToggleMacro(usePlugin bool) bool {
1779         if usePlugin && !PreActionCall("ToggleMacro", v) {
1780                 return false
1781         }
1782
1783         recordingMacro = !recordingMacro
1784
1785         if recordingMacro {
1786                 curMacro = []interface{}{}
1787                 messenger.Message("Recording")
1788         } else {
1789                 messenger.Message("Stopped recording")
1790         }
1791
1792         if usePlugin {
1793                 return PostActionCall("ToggleMacro", v)
1794         }
1795         return true
1796 }
1797
1798 // PlayMacro plays back the most recently recorded macro
1799 func (v *View) PlayMacro(usePlugin bool) bool {
1800         if usePlugin && !PreActionCall("PlayMacro", v) {
1801                 return false
1802         }
1803
1804         for _, action := range curMacro {
1805                 switch t := action.(type) {
1806                 case rune:
1807                         // Insert a character
1808                         if v.Cursor.HasSelection() {
1809                                 v.Cursor.DeleteSelection()
1810                                 v.Cursor.ResetSelection()
1811                         }
1812                         v.Buf.Insert(v.Cursor.Loc, string(t))
1813                         v.Cursor.Right()
1814
1815                         for pl := range loadedPlugins {
1816                                 _, err := Call(pl+".onRune", string(t), v)
1817                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1818                                         TermMessage(err)
1819                                 }
1820                         }
1821                 case func(*View, bool) bool:
1822                         t(v, true)
1823                 }
1824         }
1825
1826         if usePlugin {
1827                 return PostActionCall("PlayMacro", v)
1828         }
1829         return true
1830 }
1831
1832 // None is no action
1833 func None() bool {
1834         return false
1835 }