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