]> git.lizzy.rs Git - micro.git/blob - cmd/micro/actions.go
Get undo working properly with multiple cursors
[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         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
578         v.Buf.Insert(v.Cursor.Loc, "\n")
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.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
626                 } else {
627                         loc := v.Cursor.Loc
628                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
629                 }
630         }
631         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
632
633         if usePlugin {
634                 return PostActionCall("Backspace", v)
635         }
636         return true
637 }
638
639 // DeleteWordRight deletes the word to the right of the cursor
640 func (v *View) DeleteWordRight(usePlugin bool) bool {
641         if usePlugin && !PreActionCall("DeleteWordRight", v) {
642                 return false
643         }
644
645         v.SelectWordRight(false)
646         if v.Cursor.HasSelection() {
647                 v.Cursor.DeleteSelection()
648                 v.Cursor.ResetSelection()
649         }
650
651         if usePlugin {
652                 return PostActionCall("DeleteWordRight", v)
653         }
654         return true
655 }
656
657 // DeleteWordLeft deletes the word to the left of the cursor
658 func (v *View) DeleteWordLeft(usePlugin bool) bool {
659         if usePlugin && !PreActionCall("DeleteWordLeft", v) {
660                 return false
661         }
662
663         v.SelectWordLeft(false)
664         if v.Cursor.HasSelection() {
665                 v.Cursor.DeleteSelection()
666                 v.Cursor.ResetSelection()
667         }
668
669         if usePlugin {
670                 return PostActionCall("DeleteWordLeft", v)
671         }
672         return true
673 }
674
675 // Delete deletes the next character
676 func (v *View) Delete(usePlugin bool) bool {
677         if usePlugin && !PreActionCall("Delete", v) {
678                 return false
679         }
680
681         if v.Cursor.HasSelection() {
682                 v.Cursor.DeleteSelection()
683                 v.Cursor.ResetSelection()
684         } else {
685                 loc := v.Cursor.Loc
686                 if loc.LessThan(v.Buf.End()) {
687                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
688                 }
689         }
690
691         if usePlugin {
692                 return PostActionCall("Delete", v)
693         }
694         return true
695 }
696
697 // IndentSelection indents the current selection
698 func (v *View) IndentSelection(usePlugin bool) bool {
699         if usePlugin && !PreActionCall("IndentSelection", v) {
700                 return false
701         }
702
703         if v.Cursor.HasSelection() {
704                 start := v.Cursor.CurSelection[0]
705                 end := v.Cursor.CurSelection[1]
706                 if end.Y < start.Y {
707                         start, end = end, start
708                 }
709
710                 startY := start.Y
711                 endY := end.Move(-1, v.Buf).Y
712                 endX := end.Move(-1, v.Buf).X
713                 for y := startY; y <= endY; y++ {
714                         tabsize := len(v.Buf.IndentString())
715                         v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
716                         if y == startY && start.X > 0 {
717                                 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
718                         }
719                         if y == endY {
720                                 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
721                         }
722                 }
723                 v.Cursor.Relocate()
724
725                 if usePlugin {
726                         return PostActionCall("IndentSelection", v)
727                 }
728                 return true
729         }
730         return false
731 }
732
733 // OutdentLine moves the current line back one indentation
734 func (v *View) OutdentLine(usePlugin bool) bool {
735         if usePlugin && !PreActionCall("OutdentLine", v) {
736                 return false
737         }
738
739         if v.Cursor.HasSelection() {
740                 return false
741         }
742
743         for x := 0; x < len(v.Buf.IndentString()); x++ {
744                 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
745                         break
746                 }
747                 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
748         }
749         v.Cursor.Relocate()
750
751         if usePlugin {
752                 return PostActionCall("OutdentLine", v)
753         }
754         return true
755 }
756
757 // OutdentSelection takes the current selection and moves it back one indent level
758 func (v *View) OutdentSelection(usePlugin bool) bool {
759         if usePlugin && !PreActionCall("OutdentSelection", v) {
760                 return false
761         }
762
763         if v.Cursor.HasSelection() {
764                 start := v.Cursor.CurSelection[0]
765                 end := v.Cursor.CurSelection[1]
766                 if end.Y < start.Y {
767                         start, end = end, start
768                 }
769
770                 startY := start.Y
771                 endY := end.Move(-1, v.Buf).Y
772                 endX := end.Move(-1, v.Buf).X
773                 for y := startY; y <= endY; y++ {
774                         for x := 0; x < len(v.Buf.IndentString()); x++ {
775                                 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
776                                         break
777                                 }
778                                 v.Buf.Remove(Loc{0, y}, Loc{1, y})
779                                 if y == startY && start.X > 0 {
780                                         v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
781                                 }
782                                 if y == endY {
783                                         v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
784                                 }
785                         }
786                 }
787                 v.Cursor.Relocate()
788
789                 if usePlugin {
790                         return PostActionCall("OutdentSelection", v)
791                 }
792                 return true
793         }
794         return false
795 }
796
797 // InsertTab inserts a tab or spaces
798 func (v *View) InsertTab(usePlugin bool) bool {
799         if usePlugin && !PreActionCall("InsertTab", v) {
800                 return false
801         }
802
803         if v.Cursor.HasSelection() {
804                 return false
805         }
806
807         tabBytes := len(v.Buf.IndentString())
808         bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
809         v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
810         // for i := 0; i < bytesUntilIndent; i++ {
811         //      v.Cursor.Right()
812         // }
813
814         if usePlugin {
815                 return PostActionCall("InsertTab", v)
816         }
817         return true
818 }
819
820 // SaveAll saves all open buffers
821 func (v *View) SaveAll(usePlugin bool) bool {
822         if usePlugin && !PreActionCall("SaveAll", v) {
823                 return false
824         }
825
826         for _, t := range tabs {
827                 for _, v := range t.views {
828                         v.Save(false)
829                 }
830         }
831
832         if usePlugin {
833                 return PostActionCall("SaveAll", v)
834         }
835         return false
836 }
837
838 // Save the buffer to disk
839 func (v *View) Save(usePlugin bool) bool {
840         if usePlugin && !PreActionCall("Save", v) {
841                 return false
842         }
843
844         if v.Type.scratch == true {
845                 // We can't save any view type with scratch set. eg help and log text
846                 return false
847         }
848         // If this is an empty buffer, ask for a filename
849         if v.Buf.Path == "" {
850                 v.SaveAs(false)
851         } else {
852                 v.saveToFile(v.Buf.Path)
853         }
854
855         if usePlugin {
856                 return PostActionCall("Save", v)
857         }
858         return false
859 }
860
861 // This function saves the buffer to `filename` and changes the buffer's path and name
862 // to `filename` if the save is successful
863 func (v *View) saveToFile(filename string) {
864         err := v.Buf.SaveAs(filename)
865         if err != nil {
866                 if strings.HasSuffix(err.Error(), "permission denied") {
867                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
868                         if choice {
869                                 err = v.Buf.SaveAsWithSudo(filename)
870                                 if err != nil {
871                                         messenger.Error(err.Error())
872                                 } else {
873                                         v.Buf.Path = filename
874                                         v.Buf.name = filename
875                                         messenger.Message("Saved " + filename)
876                                 }
877                         }
878                         messenger.Reset()
879                         messenger.Clear()
880                 } else {
881                         messenger.Error(err.Error())
882                 }
883         } else {
884                 v.Buf.Path = filename
885                 v.Buf.name = filename
886                 messenger.Message("Saved " + filename)
887         }
888 }
889
890 // SaveAs saves the buffer to disk with the given name
891 func (v *View) SaveAs(usePlugin bool) bool {
892         filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
893         if !canceled {
894                 // the filename might or might not be quoted, so unquote first then join the strings.
895                 filename = strings.Join(SplitCommandArgs(filename), " ")
896                 v.saveToFile(filename)
897         }
898
899         return false
900 }
901
902 // Find opens a prompt and searches forward for the input
903 func (v *View) Find(usePlugin bool) bool {
904         if usePlugin && !PreActionCall("Find", v) {
905                 return false
906         }
907
908         searchStr := ""
909         if v.Cursor.HasSelection() {
910                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
911                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
912                 searchStr = v.Cursor.GetSelection()
913         } else {
914                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
915         }
916         BeginSearch(searchStr)
917
918         if usePlugin {
919                 return PostActionCall("Find", v)
920         }
921         return true
922 }
923
924 // FindNext searches forwards for the last used search term
925 func (v *View) FindNext(usePlugin bool) bool {
926         if usePlugin && !PreActionCall("FindNext", v) {
927                 return false
928         }
929
930         if v.Cursor.HasSelection() {
931                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
932                 // lastSearch = v.Cursor.GetSelection()
933         } else {
934                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
935         }
936         if lastSearch == "" {
937                 return true
938         }
939         messenger.Message("Finding: " + lastSearch)
940         Search(lastSearch, v, true)
941
942         if usePlugin {
943                 return PostActionCall("FindNext", v)
944         }
945         return true
946 }
947
948 // FindPrevious searches backwards for the last used search term
949 func (v *View) FindPrevious(usePlugin bool) bool {
950         if usePlugin && !PreActionCall("FindPrevious", v) {
951                 return false
952         }
953
954         if v.Cursor.HasSelection() {
955                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
956         } else {
957                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
958         }
959         messenger.Message("Finding: " + lastSearch)
960         Search(lastSearch, v, false)
961
962         if usePlugin {
963                 return PostActionCall("FindPrevious", v)
964         }
965         return true
966 }
967
968 // Undo undoes the last action
969 func (v *View) Undo(usePlugin bool) bool {
970         if usePlugin && !PreActionCall("Undo", v) {
971                 return false
972         }
973
974         v.Buf.Undo()
975         messenger.Message("Undid action")
976
977         if usePlugin {
978                 return PostActionCall("Undo", v)
979         }
980         return true
981 }
982
983 // Redo redoes the last action
984 func (v *View) Redo(usePlugin bool) bool {
985         if usePlugin && !PreActionCall("Redo", v) {
986                 return false
987         }
988
989         v.Buf.Redo()
990         messenger.Message("Redid action")
991
992         if usePlugin {
993                 return PostActionCall("Redo", v)
994         }
995         return true
996 }
997
998 // Copy the selection to the system clipboard
999 func (v *View) Copy(usePlugin bool) bool {
1000         if usePlugin && !PreActionCall("Copy", v) {
1001                 return false
1002         }
1003
1004         if v.Cursor.HasSelection() {
1005                 v.Cursor.CopySelection("clipboard")
1006                 v.freshClip = true
1007                 messenger.Message("Copied selection")
1008         }
1009
1010         if usePlugin {
1011                 return PostActionCall("Copy", v)
1012         }
1013         return true
1014 }
1015
1016 // CutLine cuts the current line to the clipboard
1017 func (v *View) CutLine(usePlugin bool) bool {
1018         if usePlugin && !PreActionCall("CutLine", v) {
1019                 return false
1020         }
1021
1022         v.Cursor.SelectLine()
1023         if !v.Cursor.HasSelection() {
1024                 return false
1025         }
1026         if v.freshClip == true {
1027                 if v.Cursor.HasSelection() {
1028                         if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1029                                 messenger.Error(err)
1030                         } else {
1031                                 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1032                         }
1033                 }
1034         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1035                 v.Copy(true)
1036         }
1037         v.freshClip = true
1038         v.lastCutTime = time.Now()
1039         v.Cursor.DeleteSelection()
1040         v.Cursor.ResetSelection()
1041         messenger.Message("Cut line")
1042
1043         if usePlugin {
1044                 return PostActionCall("CutLine", v)
1045         }
1046         return true
1047 }
1048
1049 // Cut the selection to the system clipboard
1050 func (v *View) Cut(usePlugin bool) bool {
1051         if usePlugin && !PreActionCall("Cut", v) {
1052                 return false
1053         }
1054
1055         if v.Cursor.HasSelection() {
1056                 v.Cursor.CopySelection("clipboard")
1057                 v.Cursor.DeleteSelection()
1058                 v.Cursor.ResetSelection()
1059                 v.freshClip = true
1060                 messenger.Message("Cut selection")
1061
1062                 if usePlugin {
1063                         return PostActionCall("Cut", v)
1064                 }
1065                 return true
1066         }
1067
1068         return false
1069 }
1070
1071 // DuplicateLine duplicates the current line or selection
1072 func (v *View) DuplicateLine(usePlugin bool) bool {
1073         if usePlugin && !PreActionCall("DuplicateLine", v) {
1074                 return false
1075         }
1076
1077         if v.Cursor.HasSelection() {
1078                 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1079         } else {
1080                 v.Cursor.End()
1081                 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1082                 // v.Cursor.Right()
1083         }
1084
1085         messenger.Message("Duplicated line")
1086
1087         if usePlugin {
1088                 return PostActionCall("DuplicateLine", v)
1089         }
1090         return true
1091 }
1092
1093 // DeleteLine deletes the current line
1094 func (v *View) DeleteLine(usePlugin bool) bool {
1095         if usePlugin && !PreActionCall("DeleteLine", v) {
1096                 return false
1097         }
1098
1099         v.Cursor.SelectLine()
1100         if !v.Cursor.HasSelection() {
1101                 return false
1102         }
1103         v.Cursor.DeleteSelection()
1104         v.Cursor.ResetSelection()
1105         messenger.Message("Deleted line")
1106
1107         if usePlugin {
1108                 return PostActionCall("DeleteLine", v)
1109         }
1110         return true
1111 }
1112
1113 // MoveLinesUp moves up the current line or selected lines if any
1114 func (v *View) MoveLinesUp(usePlugin bool) bool {
1115         if usePlugin && !PreActionCall("MoveLinesUp", v) {
1116                 return false
1117         }
1118
1119         if v.Cursor.HasSelection() {
1120                 if v.Cursor.CurSelection[0].Y == 0 {
1121                         messenger.Message("Can not move further up")
1122                         return true
1123                 }
1124                 v.Buf.MoveLinesUp(
1125                         v.Cursor.CurSelection[0].Y,
1126                         v.Cursor.CurSelection[1].Y,
1127                 )
1128                 v.Cursor.UpN(1)
1129                 v.Cursor.CurSelection[0].Y -= 1
1130                 v.Cursor.CurSelection[1].Y -= 1
1131                 messenger.Message("Moved up selected line(s)")
1132         } else {
1133                 if v.Cursor.Loc.Y == 0 {
1134                         messenger.Message("Can not move further up")
1135                         return true
1136                 }
1137                 v.Buf.MoveLinesUp(
1138                         v.Cursor.Loc.Y,
1139                         v.Cursor.Loc.Y+1,
1140                 )
1141                 v.Cursor.UpN(1)
1142                 messenger.Message("Moved up current line")
1143         }
1144         v.Buf.IsModified = true
1145
1146         if usePlugin {
1147                 return PostActionCall("MoveLinesUp", v)
1148         }
1149         return true
1150 }
1151
1152 // MoveLinesDown moves down the current line or selected lines if any
1153 func (v *View) MoveLinesDown(usePlugin bool) bool {
1154         if usePlugin && !PreActionCall("MoveLinesDown", v) {
1155                 return false
1156         }
1157
1158         if v.Cursor.HasSelection() {
1159                 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1160                         messenger.Message("Can not move further down")
1161                         return true
1162                 }
1163                 v.Buf.MoveLinesDown(
1164                         v.Cursor.CurSelection[0].Y,
1165                         v.Cursor.CurSelection[1].Y,
1166                 )
1167                 v.Cursor.DownN(1)
1168                 v.Cursor.CurSelection[0].Y += 1
1169                 v.Cursor.CurSelection[1].Y += 1
1170                 messenger.Message("Moved down selected line(s)")
1171         } else {
1172                 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1173                         messenger.Message("Can not move further down")
1174                         return true
1175                 }
1176                 v.Buf.MoveLinesDown(
1177                         v.Cursor.Loc.Y,
1178                         v.Cursor.Loc.Y+1,
1179                 )
1180                 v.Cursor.DownN(1)
1181                 messenger.Message("Moved down current line")
1182         }
1183         v.Buf.IsModified = true
1184
1185         if usePlugin {
1186                 return PostActionCall("MoveLinesDown", v)
1187         }
1188         return true
1189 }
1190
1191 // Paste whatever is in the system clipboard into the buffer
1192 // Delete and paste if the user has a selection
1193 func (v *View) Paste(usePlugin bool) bool {
1194         if usePlugin && !PreActionCall("Paste", v) {
1195                 return false
1196         }
1197
1198         clip, _ := clipboard.ReadAll("clipboard")
1199         v.paste(clip)
1200
1201         if usePlugin {
1202                 return PostActionCall("Paste", v)
1203         }
1204         return true
1205 }
1206
1207 // PastePrimary pastes from the primary clipboard (only use on linux)
1208 func (v *View) PastePrimary(usePlugin bool) bool {
1209         if usePlugin && !PreActionCall("Paste", v) {
1210                 return false
1211         }
1212
1213         clip, _ := clipboard.ReadAll("primary")
1214         v.paste(clip)
1215
1216         if usePlugin {
1217                 return PostActionCall("Paste", v)
1218         }
1219         return true
1220 }
1221
1222 // SelectAll selects the entire buffer
1223 func (v *View) SelectAll(usePlugin bool) bool {
1224         if usePlugin && !PreActionCall("SelectAll", v) {
1225                 return false
1226         }
1227
1228         v.Cursor.SetSelectionStart(v.Buf.Start())
1229         v.Cursor.SetSelectionEnd(v.Buf.End())
1230         // Put the cursor at the beginning
1231         v.Cursor.X = 0
1232         v.Cursor.Y = 0
1233
1234         if usePlugin {
1235                 return PostActionCall("SelectAll", v)
1236         }
1237         return true
1238 }
1239
1240 // OpenFile opens a new file in the buffer
1241 func (v *View) OpenFile(usePlugin bool) bool {
1242         if usePlugin && !PreActionCall("OpenFile", v) {
1243                 return false
1244         }
1245
1246         if v.CanClose() {
1247                 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1248                 if !canceled {
1249                         HandleCommand(input)
1250                         if usePlugin {
1251                                 return PostActionCall("OpenFile", v)
1252                         }
1253                 }
1254         }
1255         return false
1256 }
1257
1258 // Start moves the viewport to the start of the buffer
1259 func (v *View) Start(usePlugin bool) bool {
1260         if usePlugin && !PreActionCall("Start", v) {
1261                 return false
1262         }
1263
1264         v.Topline = 0
1265
1266         if usePlugin {
1267                 return PostActionCall("Start", v)
1268         }
1269         return false
1270 }
1271
1272 // End moves the viewport to the end of the buffer
1273 func (v *View) End(usePlugin bool) bool {
1274         if usePlugin && !PreActionCall("End", v) {
1275                 return false
1276         }
1277
1278         if v.Height > v.Buf.NumLines {
1279                 v.Topline = 0
1280         } else {
1281                 v.Topline = v.Buf.NumLines - v.Height
1282         }
1283
1284         if usePlugin {
1285                 return PostActionCall("End", v)
1286         }
1287         return false
1288 }
1289
1290 // PageUp scrolls the view up a page
1291 func (v *View) PageUp(usePlugin bool) bool {
1292         if usePlugin && !PreActionCall("PageUp", v) {
1293                 return false
1294         }
1295
1296         if v.Topline > v.Height {
1297                 v.ScrollUp(v.Height)
1298         } else {
1299                 v.Topline = 0
1300         }
1301
1302         if usePlugin {
1303                 return PostActionCall("PageUp", v)
1304         }
1305         return false
1306 }
1307
1308 // PageDown scrolls the view down a page
1309 func (v *View) PageDown(usePlugin bool) bool {
1310         if usePlugin && !PreActionCall("PageDown", v) {
1311                 return false
1312         }
1313
1314         if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1315                 v.ScrollDown(v.Height)
1316         } else if v.Buf.NumLines >= v.Height {
1317                 v.Topline = v.Buf.NumLines - v.Height
1318         }
1319
1320         if usePlugin {
1321                 return PostActionCall("PageDown", v)
1322         }
1323         return false
1324 }
1325
1326 // CursorPageUp places the cursor a page up
1327 func (v *View) CursorPageUp(usePlugin bool) bool {
1328         if usePlugin && !PreActionCall("CursorPageUp", v) {
1329                 return false
1330         }
1331
1332         v.deselect(0)
1333
1334         if v.Cursor.HasSelection() {
1335                 v.Cursor.Loc = v.Cursor.CurSelection[0]
1336                 v.Cursor.ResetSelection()
1337         }
1338         v.Cursor.UpN(v.Height)
1339
1340         if usePlugin {
1341                 return PostActionCall("CursorPageUp", v)
1342         }
1343         return true
1344 }
1345
1346 // CursorPageDown places the cursor a page up
1347 func (v *View) CursorPageDown(usePlugin bool) bool {
1348         if usePlugin && !PreActionCall("CursorPageDown", v) {
1349                 return false
1350         }
1351
1352         v.deselect(0)
1353
1354         if v.Cursor.HasSelection() {
1355                 v.Cursor.Loc = v.Cursor.CurSelection[1]
1356                 v.Cursor.ResetSelection()
1357         }
1358         v.Cursor.DownN(v.Height)
1359
1360         if usePlugin {
1361                 return PostActionCall("CursorPageDown", v)
1362         }
1363         return true
1364 }
1365
1366 // HalfPageUp scrolls the view up half a page
1367 func (v *View) HalfPageUp(usePlugin bool) bool {
1368         if usePlugin && !PreActionCall("HalfPageUp", v) {
1369                 return false
1370         }
1371
1372         if v.Topline > v.Height/2 {
1373                 v.ScrollUp(v.Height / 2)
1374         } else {
1375                 v.Topline = 0
1376         }
1377
1378         if usePlugin {
1379                 return PostActionCall("HalfPageUp", v)
1380         }
1381         return false
1382 }
1383
1384 // HalfPageDown scrolls the view down half a page
1385 func (v *View) HalfPageDown(usePlugin bool) bool {
1386         if usePlugin && !PreActionCall("HalfPageDown", v) {
1387                 return false
1388         }
1389
1390         if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1391                 v.ScrollDown(v.Height / 2)
1392         } else {
1393                 if v.Buf.NumLines >= v.Height {
1394                         v.Topline = v.Buf.NumLines - v.Height
1395                 }
1396         }
1397
1398         if usePlugin {
1399                 return PostActionCall("HalfPageDown", v)
1400         }
1401         return false
1402 }
1403
1404 // ToggleRuler turns line numbers off and on
1405 func (v *View) ToggleRuler(usePlugin bool) bool {
1406         if usePlugin && !PreActionCall("ToggleRuler", v) {
1407                 return false
1408         }
1409
1410         if v.Buf.Settings["ruler"] == false {
1411                 v.Buf.Settings["ruler"] = true
1412                 messenger.Message("Enabled ruler")
1413         } else {
1414                 v.Buf.Settings["ruler"] = false
1415                 messenger.Message("Disabled ruler")
1416         }
1417
1418         if usePlugin {
1419                 return PostActionCall("ToggleRuler", v)
1420         }
1421         return false
1422 }
1423
1424 // JumpLine jumps to a line and moves the view accordingly.
1425 func (v *View) JumpLine(usePlugin bool) bool {
1426         if usePlugin && !PreActionCall("JumpLine", v) {
1427                 return false
1428         }
1429
1430         // Prompt for line number
1431         linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1432         if canceled {
1433                 return false
1434         }
1435         lineint, err := strconv.Atoi(linestring)
1436         lineint = lineint - 1 // fix offset
1437         if err != nil {
1438                 messenger.Error(err) // return errors
1439                 return false
1440         }
1441         // Move cursor and view if possible.
1442         if lineint < v.Buf.NumLines && lineint >= 0 {
1443                 v.Cursor.X = 0
1444                 v.Cursor.Y = lineint
1445
1446                 if usePlugin {
1447                         return PostActionCall("JumpLine", v)
1448                 }
1449                 return true
1450         }
1451         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1452         return false
1453 }
1454
1455 // ClearStatus clears the messenger bar
1456 func (v *View) ClearStatus(usePlugin bool) bool {
1457         if usePlugin && !PreActionCall("ClearStatus", v) {
1458                 return false
1459         }
1460
1461         messenger.Message("")
1462
1463         if usePlugin {
1464                 return PostActionCall("ClearStatus", v)
1465         }
1466         return false
1467 }
1468
1469 // ToggleHelp toggles the help screen
1470 func (v *View) ToggleHelp(usePlugin bool) bool {
1471         if usePlugin && !PreActionCall("ToggleHelp", v) {
1472                 return false
1473         }
1474
1475         if v.Type != vtHelp {
1476                 // Open the default help
1477                 v.openHelp("help")
1478         } else {
1479                 v.Quit(true)
1480         }
1481
1482         if usePlugin {
1483                 return PostActionCall("ToggleHelp", v)
1484         }
1485         return true
1486 }
1487
1488 // ShellMode opens a terminal to run a shell command
1489 func (v *View) ShellMode(usePlugin bool) bool {
1490         if usePlugin && !PreActionCall("ShellMode", v) {
1491                 return false
1492         }
1493
1494         input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1495         if !canceled {
1496                 // The true here is for openTerm to make the command interactive
1497                 HandleShellCommand(input, true, true)
1498                 if usePlugin {
1499                         return PostActionCall("ShellMode", v)
1500                 }
1501         }
1502         return false
1503 }
1504
1505 // CommandMode lets the user enter a command
1506 func (v *View) CommandMode(usePlugin bool) bool {
1507         if usePlugin && !PreActionCall("CommandMode", v) {
1508                 return false
1509         }
1510
1511         input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1512         if !canceled {
1513                 HandleCommand(input)
1514                 if usePlugin {
1515                         return PostActionCall("CommandMode", v)
1516                 }
1517         }
1518
1519         return false
1520 }
1521
1522 // Escape leaves current mode
1523 func (v *View) Escape(usePlugin bool) bool {
1524         // check if user is searching, or the last search is still active
1525         if searching || lastSearch != "" {
1526                 ExitSearch(v)
1527                 return true
1528         }
1529         // check if a prompt is shown, hide it and don't quit
1530         if messenger.hasPrompt {
1531                 messenger.Reset() // FIXME
1532                 return true
1533         }
1534
1535         return false
1536 }
1537
1538 // Quit this will close the current tab or view that is open
1539 func (v *View) Quit(usePlugin bool) bool {
1540         if usePlugin && !PreActionCall("Quit", v) {
1541                 return false
1542         }
1543
1544         // Make sure not to quit if there are unsaved changes
1545         if v.CanClose() {
1546                 v.CloseBuffer()
1547                 if len(tabs[curTab].views) > 1 {
1548                         v.splitNode.Delete()
1549                         tabs[v.TabNum].Cleanup()
1550                         tabs[v.TabNum].Resize()
1551                 } else if len(tabs) > 1 {
1552                         if len(tabs[v.TabNum].views) == 1 {
1553                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1554                                 for i, t := range tabs {
1555                                         t.SetNum(i)
1556                                 }
1557                                 if curTab >= len(tabs) {
1558                                         curTab--
1559                                 }
1560                                 if curTab == 0 {
1561                                         CurView().ToggleTabbar()
1562                                 }
1563                         }
1564                 } else {
1565                         if usePlugin {
1566                                 PostActionCall("Quit", v)
1567                         }
1568
1569                         screen.Fini()
1570                         os.Exit(0)
1571                 }
1572         }
1573
1574         if usePlugin {
1575                 return PostActionCall("Quit", v)
1576         }
1577         return false
1578 }
1579
1580 // QuitAll quits the whole editor; all splits and tabs
1581 func (v *View) QuitAll(usePlugin bool) bool {
1582         if usePlugin && !PreActionCall("QuitAll", v) {
1583                 return false
1584         }
1585
1586         closeAll := true
1587         for _, tab := range tabs {
1588                 for _, v := range tab.views {
1589                         if !v.CanClose() {
1590                                 closeAll = false
1591                         }
1592                 }
1593         }
1594
1595         if closeAll {
1596                 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1597                 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1598
1599                 if shouldQuit {
1600                         for _, tab := range tabs {
1601                                 for _, v := range tab.views {
1602                                         v.CloseBuffer()
1603                                 }
1604                         }
1605
1606                         if usePlugin {
1607                                 PostActionCall("QuitAll", v)
1608                         }
1609
1610                         screen.Fini()
1611                         os.Exit(0)
1612                 }
1613         }
1614
1615         return false
1616 }
1617
1618 // AddTab adds a new tab with an empty buffer
1619 func (v *View) AddTab(usePlugin bool) bool {
1620         if usePlugin && !PreActionCall("AddTab", v) {
1621                 return false
1622         }
1623
1624         tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1625         tab.SetNum(len(tabs))
1626         tabs = append(tabs, tab)
1627         curTab = len(tabs) - 1
1628         if len(tabs) == 2 {
1629                 for _, t := range tabs {
1630                         for _, v := range t.views {
1631                                 v.ToggleTabbar()
1632                         }
1633                 }
1634         }
1635
1636         if usePlugin {
1637                 return PostActionCall("AddTab", v)
1638         }
1639         return true
1640 }
1641
1642 // PreviousTab switches to the previous tab in the tab list
1643 func (v *View) PreviousTab(usePlugin bool) bool {
1644         if usePlugin && !PreActionCall("PreviousTab", v) {
1645                 return false
1646         }
1647
1648         if curTab > 0 {
1649                 curTab--
1650         } else if curTab == 0 {
1651                 curTab = len(tabs) - 1
1652         }
1653
1654         if usePlugin {
1655                 return PostActionCall("PreviousTab", v)
1656         }
1657         return false
1658 }
1659
1660 // NextTab switches to the next tab in the tab list
1661 func (v *View) NextTab(usePlugin bool) bool {
1662         if usePlugin && !PreActionCall("NextTab", v) {
1663                 return false
1664         }
1665
1666         if curTab < len(tabs)-1 {
1667                 curTab++
1668         } else if curTab == len(tabs)-1 {
1669                 curTab = 0
1670         }
1671
1672         if usePlugin {
1673                 return PostActionCall("NextTab", v)
1674         }
1675         return false
1676 }
1677
1678 // VSplitBinding opens an empty vertical split
1679 func (v *View) VSplitBinding(usePlugin bool) bool {
1680         if usePlugin && !PreActionCall("VSplit", v) {
1681                 return false
1682         }
1683
1684         v.VSplit(NewBufferFromString("", ""))
1685
1686         if usePlugin {
1687                 return PostActionCall("VSplit", v)
1688         }
1689         return false
1690 }
1691
1692 // HSplitBinding opens an empty horizontal split
1693 func (v *View) HSplitBinding(usePlugin bool) bool {
1694         if usePlugin && !PreActionCall("HSplit", v) {
1695                 return false
1696         }
1697
1698         v.HSplit(NewBufferFromString("", ""))
1699
1700         if usePlugin {
1701                 return PostActionCall("HSplit", v)
1702         }
1703         return false
1704 }
1705
1706 // Unsplit closes all splits in the current tab except the active one
1707 func (v *View) Unsplit(usePlugin bool) bool {
1708         if usePlugin && !PreActionCall("Unsplit", v) {
1709                 return false
1710         }
1711
1712         curView := tabs[curTab].CurView
1713         for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1714                 view := tabs[curTab].views[i]
1715                 if view != nil && view.Num != curView {
1716                         view.Quit(true)
1717                         // messenger.Message("Quit ", view.Buf.Path)
1718                 }
1719         }
1720
1721         if usePlugin {
1722                 return PostActionCall("Unsplit", v)
1723         }
1724         return false
1725 }
1726
1727 // NextSplit changes the view to the next split
1728 func (v *View) NextSplit(usePlugin bool) bool {
1729         if usePlugin && !PreActionCall("NextSplit", v) {
1730                 return false
1731         }
1732
1733         tab := tabs[curTab]
1734         if tab.CurView < len(tab.views)-1 {
1735                 tab.CurView++
1736         } else {
1737                 tab.CurView = 0
1738         }
1739
1740         if usePlugin {
1741                 return PostActionCall("NextSplit", v)
1742         }
1743         return false
1744 }
1745
1746 // PreviousSplit changes the view to the previous split
1747 func (v *View) PreviousSplit(usePlugin bool) bool {
1748         if usePlugin && !PreActionCall("PreviousSplit", v) {
1749                 return false
1750         }
1751
1752         tab := tabs[curTab]
1753         if tab.CurView > 0 {
1754                 tab.CurView--
1755         } else {
1756                 tab.CurView = len(tab.views) - 1
1757         }
1758
1759         if usePlugin {
1760                 return PostActionCall("PreviousSplit", v)
1761         }
1762         return false
1763 }
1764
1765 var curMacro []interface{}
1766 var recordingMacro bool
1767
1768 // ToggleMacro toggles recording of a macro
1769 func (v *View) ToggleMacro(usePlugin bool) bool {
1770         if usePlugin && !PreActionCall("ToggleMacro", v) {
1771                 return false
1772         }
1773
1774         recordingMacro = !recordingMacro
1775
1776         if recordingMacro {
1777                 curMacro = []interface{}{}
1778                 messenger.Message("Recording")
1779         } else {
1780                 messenger.Message("Stopped recording")
1781         }
1782
1783         if usePlugin {
1784                 return PostActionCall("ToggleMacro", v)
1785         }
1786         return true
1787 }
1788
1789 // PlayMacro plays back the most recently recorded macro
1790 func (v *View) PlayMacro(usePlugin bool) bool {
1791         if usePlugin && !PreActionCall("PlayMacro", v) {
1792                 return false
1793         }
1794
1795         for _, action := range curMacro {
1796                 switch t := action.(type) {
1797                 case rune:
1798                         // Insert a character
1799                         if v.Cursor.HasSelection() {
1800                                 v.Cursor.DeleteSelection()
1801                                 v.Cursor.ResetSelection()
1802                         }
1803                         v.Buf.Insert(v.Cursor.Loc, string(t))
1804                         // v.Cursor.Right()
1805
1806                         for pl := range loadedPlugins {
1807                                 _, err := Call(pl+".onRune", string(t), v)
1808                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1809                                         TermMessage(err)
1810                                 }
1811                         }
1812                 case func(*View, bool) bool:
1813                         t(v, true)
1814                 }
1815         }
1816
1817         if usePlugin {
1818                 return PostActionCall("PlayMacro", v)
1819         }
1820         return true
1821 }
1822
1823 // SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
1824 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
1825         spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
1826         // You can only spawn a cursor from the main cursor
1827         if v.Cursor == spawner {
1828                 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
1829                         return false
1830                 }
1831
1832                 if !spawner.HasSelection() {
1833                         spawner.SelectWord()
1834                 } else {
1835                         c := &Cursor{
1836                                 buf: v.Buf,
1837                         }
1838
1839                         sel := spawner.GetSelection()
1840
1841                         searchStart = ToCharPos(spawner.CurSelection[1], v.Buf)
1842                         v.Cursor = c
1843                         Search(sel, v, true)
1844
1845                         for _, cur := range v.Buf.cursors {
1846                                 if c.Loc == cur.Loc {
1847                                         return false
1848                                 }
1849                         }
1850                         v.Buf.cursors = append(v.Buf.cursors, c)
1851                         v.Buf.UpdateCursors()
1852                         v.Relocate()
1853                         v.Cursor = spawner
1854                 }
1855
1856                 if usePlugin {
1857                         PostActionCall("SpawnMultiCursor", v)
1858                 }
1859         }
1860
1861         return false
1862 }
1863
1864 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1865 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
1866         if v.Cursor == &v.Buf.Cursor {
1867                 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
1868                         return false
1869                 }
1870                 x, y := e.Position()
1871                 x -= v.lineNumOffset - v.leftCol + v.x
1872                 y += v.Topline - v.y
1873
1874                 c := &Cursor{
1875                         buf: v.Buf,
1876                 }
1877                 v.Cursor = c
1878                 v.MoveToMouseClick(x, y)
1879                 v.Relocate()
1880                 v.Cursor = &v.Buf.Cursor
1881
1882                 v.Buf.cursors = append(v.Buf.cursors, c)
1883                 v.Buf.UpdateCursors()
1884
1885                 if usePlugin {
1886                         PostActionCall("SpawnMultiCursorAtMouse", v)
1887                 }
1888         }
1889         return false
1890 }
1891
1892 // SkipMultiCursor moves the current multiple cursor to the next available position
1893 func (v *View) SkipMultiCursor(usePlugin bool) bool {
1894         cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
1895
1896         if v.Cursor == cursor {
1897                 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
1898                         return false
1899                 }
1900                 sel := cursor.GetSelection()
1901
1902                 searchStart = ToCharPos(cursor.CurSelection[1], v.Buf)
1903                 v.Cursor = cursor
1904                 Search(sel, v, true)
1905                 v.Relocate()
1906                 v.Cursor = cursor
1907
1908                 if usePlugin {
1909                         PostActionCall("SkipMultiCursor", v)
1910                 }
1911         }
1912         return false
1913 }
1914
1915 // RemoveMultiCursor removes the latest multiple cursor
1916 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
1917         end := len(v.Buf.cursors)
1918         if end > 1 {
1919                 nextOne := v.Buf.cursors[len(v.Buf.cursors)-2]
1920                 if v.Cursor == nextOne {
1921                         if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
1922                                 return false
1923                         }
1924
1925                         if end > 1 {
1926                                 v.Buf.cursors[end-1] = nil
1927                                 v.Buf.cursors = v.Buf.cursors[:end-1]
1928                                 v.Buf.UpdateCursors()
1929                         }
1930                         v.Relocate()
1931
1932                         if usePlugin {
1933                                 return PostActionCall("RemoveMultiCursor", v)
1934                         }
1935                         return true
1936                 }
1937         } else {
1938                 v.RemoveAllMultiCursors(usePlugin)
1939         }
1940         return false
1941 }
1942
1943 // RemoveAllMultiCursors removes all cursors except the base cursor
1944 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
1945         if v.Cursor == &v.Buf.Cursor {
1946                 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
1947                         return false
1948                 }
1949
1950                 for i := 1; i < len(v.Buf.cursors); i++ {
1951                         v.Buf.cursors[i] = nil
1952                 }
1953                 v.Buf.cursors = v.Buf.cursors[:1]
1954                 v.Buf.UpdateCursors()
1955                 v.Cursor.ResetSelection()
1956                 v.Relocate()
1957
1958                 if usePlugin {
1959                         return PostActionCall("RemoveAllMultiCursors", v)
1960                 }
1961                 return true
1962         }
1963         return false
1964 }