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