]> git.lizzy.rs Git - micro.git/blob - cmd/micro/actions.go
Code optimisation (#1117)
[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 // SelectPageUp selects up one page
1519 func (v *View) SelectPageUp(usePlugin bool) bool {
1520         if usePlugin && !PreActionCall("SelectPageUp", v) {
1521                 return false
1522         }
1523
1524         if !v.Cursor.HasSelection() {
1525                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
1526         }
1527         v.Cursor.UpN(v.Height)
1528         v.Cursor.SelectTo(v.Cursor.Loc)
1529
1530         if usePlugin {
1531                 return PostActionCall("SelectPageUp", v)
1532         }
1533         return true
1534 }
1535
1536 // SelectPageDown selects down one page
1537 func (v *View) SelectPageDown(usePlugin bool) bool {
1538         if usePlugin && !PreActionCall("SelectPageDown", v) {
1539                 return false
1540         }
1541
1542         if !v.Cursor.HasSelection() {
1543                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
1544         }
1545         v.Cursor.DownN(v.Height)
1546         v.Cursor.SelectTo(v.Cursor.Loc)
1547
1548         if usePlugin {
1549                 return PostActionCall("SelectPageDown", v)
1550         }
1551         return true
1552 }
1553
1554 // CursorPageUp places the cursor a page up
1555 func (v *View) CursorPageUp(usePlugin bool) bool {
1556         if usePlugin && !PreActionCall("CursorPageUp", v) {
1557                 return false
1558         }
1559
1560         v.deselect(0)
1561
1562         if v.Cursor.HasSelection() {
1563                 v.Cursor.Loc = v.Cursor.CurSelection[0]
1564                 v.Cursor.ResetSelection()
1565                 v.Cursor.StoreVisualX()
1566         }
1567         v.Cursor.UpN(v.Height)
1568
1569         if usePlugin {
1570                 return PostActionCall("CursorPageUp", v)
1571         }
1572         return true
1573 }
1574
1575 // CursorPageDown places the cursor a page up
1576 func (v *View) CursorPageDown(usePlugin bool) bool {
1577         if usePlugin && !PreActionCall("CursorPageDown", v) {
1578                 return false
1579         }
1580
1581         v.deselect(0)
1582
1583         if v.Cursor.HasSelection() {
1584                 v.Cursor.Loc = v.Cursor.CurSelection[1]
1585                 v.Cursor.ResetSelection()
1586                 v.Cursor.StoreVisualX()
1587         }
1588         v.Cursor.DownN(v.Height)
1589
1590         if usePlugin {
1591                 return PostActionCall("CursorPageDown", v)
1592         }
1593         return true
1594 }
1595
1596 // HalfPageUp scrolls the view up half a page
1597 func (v *View) HalfPageUp(usePlugin bool) bool {
1598         if v.mainCursor() {
1599                 if usePlugin && !PreActionCall("HalfPageUp", v) {
1600                         return false
1601                 }
1602
1603                 if v.Topline > v.Height/2 {
1604                         v.ScrollUp(v.Height / 2)
1605                 } else {
1606                         v.Topline = 0
1607                 }
1608
1609                 if usePlugin {
1610                         return PostActionCall("HalfPageUp", v)
1611                 }
1612         }
1613         return false
1614 }
1615
1616 // HalfPageDown scrolls the view down half a page
1617 func (v *View) HalfPageDown(usePlugin bool) bool {
1618         if v.mainCursor() {
1619                 if usePlugin && !PreActionCall("HalfPageDown", v) {
1620                         return false
1621                 }
1622
1623                 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1624                         v.ScrollDown(v.Height / 2)
1625                 } else {
1626                         if v.Buf.NumLines >= v.Height {
1627                                 v.Topline = v.Buf.NumLines - v.Height
1628                         }
1629                 }
1630
1631                 if usePlugin {
1632                         return PostActionCall("HalfPageDown", v)
1633                 }
1634         }
1635         return false
1636 }
1637
1638 // ToggleRuler turns line numbers off and on
1639 func (v *View) ToggleRuler(usePlugin bool) bool {
1640         if v.mainCursor() {
1641                 if usePlugin && !PreActionCall("ToggleRuler", v) {
1642                         return false
1643                 }
1644
1645                 if v.Buf.Settings["ruler"] == false {
1646                         v.Buf.Settings["ruler"] = true
1647                         messenger.Message("Enabled ruler")
1648                 } else {
1649                         v.Buf.Settings["ruler"] = false
1650                         messenger.Message("Disabled ruler")
1651                 }
1652
1653                 if usePlugin {
1654                         return PostActionCall("ToggleRuler", v)
1655                 }
1656         }
1657         return false
1658 }
1659
1660 // JumpLine jumps to a line and moves the view accordingly.
1661 func (v *View) JumpLine(usePlugin bool) bool {
1662         if usePlugin && !PreActionCall("JumpLine", v) {
1663                 return false
1664         }
1665
1666         // Prompt for line number
1667         message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
1668         input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
1669         if canceled {
1670                 return false
1671         }
1672         var lineInt int
1673         var colInt int
1674         var err error
1675         if strings.Contains(input, ":") {
1676                 split := strings.Split(input, ":")
1677                 lineInt, err = strconv.Atoi(split[0])
1678                 if err != nil {
1679                         messenger.Message("Invalid line number")
1680                         return false
1681                 }
1682                 colInt, err = strconv.Atoi(split[1])
1683                 if err != nil {
1684                         messenger.Message("Invalid column number")
1685                         return false
1686                 }
1687         } else {
1688                 lineInt, err = strconv.Atoi(input)
1689                 if err != nil {
1690                         messenger.Message("Invalid line number")
1691                         return false
1692                 }
1693         }
1694         lineInt--
1695         // Move cursor and view if possible.
1696         if lineInt < v.Buf.NumLines && lineInt >= 0 {
1697                 v.Cursor.X = colInt
1698                 v.Cursor.Y = lineInt
1699
1700                 if usePlugin {
1701                         return PostActionCall("JumpLine", v)
1702                 }
1703                 return true
1704         }
1705         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1706         return false
1707 }
1708
1709 // ClearStatus clears the messenger bar
1710 func (v *View) ClearStatus(usePlugin bool) bool {
1711         if v.mainCursor() {
1712                 if usePlugin && !PreActionCall("ClearStatus", v) {
1713                         return false
1714                 }
1715
1716                 messenger.Message("")
1717
1718                 if usePlugin {
1719                         return PostActionCall("ClearStatus", v)
1720                 }
1721         }
1722         return false
1723 }
1724
1725 // ToggleHelp toggles the help screen
1726 func (v *View) ToggleHelp(usePlugin bool) bool {
1727         if v.mainCursor() {
1728                 if usePlugin && !PreActionCall("ToggleHelp", v) {
1729                         return false
1730                 }
1731
1732                 if v.Type != vtHelp {
1733                         // Open the default help
1734                         v.openHelp("help")
1735                 } else {
1736                         v.Quit(true)
1737                 }
1738
1739                 if usePlugin {
1740                         return PostActionCall("ToggleHelp", v)
1741                 }
1742         }
1743         return true
1744 }
1745
1746 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1747 func (v *View) ToggleKeyMenu(usePlugin bool) bool {
1748         if v.mainCursor() {
1749                 if usePlugin && !PreActionCall("ToggleBindings", v) {
1750                         return false
1751                 }
1752
1753                 globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
1754                 for _, tab := range tabs {
1755                         tab.Resize()
1756                 }
1757
1758                 if usePlugin {
1759                         return PostActionCall("ToggleBindings", v)
1760                 }
1761         }
1762         return true
1763 }
1764
1765 // ShellMode opens a terminal to run a shell command
1766 func (v *View) ShellMode(usePlugin bool) bool {
1767         if v.mainCursor() {
1768                 if usePlugin && !PreActionCall("ShellMode", v) {
1769                         return false
1770                 }
1771
1772                 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1773                 if !canceled {
1774                         // The true here is for openTerm to make the command interactive
1775                         HandleShellCommand(input, true, true)
1776                         if usePlugin {
1777                                 return PostActionCall("ShellMode", v)
1778                         }
1779                 }
1780         }
1781         return false
1782 }
1783
1784 // CommandMode lets the user enter a command
1785 func (v *View) CommandMode(usePlugin bool) bool {
1786         if v.mainCursor() {
1787                 if usePlugin && !PreActionCall("CommandMode", v) {
1788                         return false
1789                 }
1790
1791                 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1792                 if !canceled {
1793                         HandleCommand(input)
1794                         if usePlugin {
1795                                 return PostActionCall("CommandMode", v)
1796                         }
1797                 }
1798         }
1799
1800         return false
1801 }
1802
1803 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1804 func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
1805         if v.mainCursor() {
1806                 if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
1807                         return false
1808                 }
1809
1810                 v.isOverwriteMode = !v.isOverwriteMode
1811
1812                 if usePlugin {
1813                         return PostActionCall("ToggleOverwriteMode", v)
1814                 }
1815         }
1816         return false
1817 }
1818
1819 // Escape leaves current mode
1820 func (v *View) Escape(usePlugin bool) bool {
1821         if v.mainCursor() {
1822                 // check if user is searching, or the last search is still active
1823                 if searching || lastSearch != "" {
1824                         ExitSearch(v)
1825                         return true
1826                 }
1827                 // check if a prompt is shown, hide it and don't quit
1828                 if messenger.hasPrompt {
1829                         messenger.Reset() // FIXME
1830                         return true
1831                 }
1832         }
1833
1834         return false
1835 }
1836
1837 // Quit this will close the current tab or view that is open
1838 func (v *View) Quit(usePlugin bool) bool {
1839         if v.mainCursor() {
1840                 if usePlugin && !PreActionCall("Quit", v) {
1841                         return false
1842                 }
1843
1844                 // Make sure not to quit if there are unsaved changes
1845                 if v.CanClose() {
1846                         v.CloseBuffer()
1847                         if len(tabs[curTab].Views) > 1 {
1848                                 v.splitNode.Delete()
1849                                 tabs[v.TabNum].Cleanup()
1850                                 tabs[v.TabNum].Resize()
1851                         } else if len(tabs) > 1 {
1852                                 if len(tabs[v.TabNum].Views) == 1 {
1853                                         tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1854                                         for i, t := range tabs {
1855                                                 t.SetNum(i)
1856                                         }
1857                                         if curTab >= len(tabs) {
1858                                                 curTab--
1859                                         }
1860                                         if curTab == 0 {
1861                                                 CurView().ToggleTabbar()
1862                                         }
1863                                 }
1864                         } else {
1865                                 if usePlugin {
1866                                         PostActionCall("Quit", v)
1867                                 }
1868
1869                                 screen.Fini()
1870                                 messenger.SaveHistory()
1871                                 os.Exit(0)
1872                         }
1873                 }
1874
1875                 if usePlugin {
1876                         return PostActionCall("Quit", v)
1877                 }
1878         }
1879         return false
1880 }
1881
1882 // QuitAll quits the whole editor; all splits and tabs
1883 func (v *View) QuitAll(usePlugin bool) bool {
1884         if v.mainCursor() {
1885                 if usePlugin && !PreActionCall("QuitAll", v) {
1886                         return false
1887                 }
1888
1889                 closeAll := true
1890                 for _, tab := range tabs {
1891                         for _, v := range tab.Views {
1892                                 if !v.CanClose() {
1893                                         closeAll = false
1894                                 }
1895                         }
1896                 }
1897
1898                 if closeAll {
1899                         // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1900                         shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1901
1902                         if shouldQuit {
1903                                 for _, tab := range tabs {
1904                                         for _, v := range tab.Views {
1905                                                 v.CloseBuffer()
1906                                         }
1907                                 }
1908
1909                                 if usePlugin {
1910                                         PostActionCall("QuitAll", v)
1911                                 }
1912
1913                                 screen.Fini()
1914                                 messenger.SaveHistory()
1915                                 os.Exit(0)
1916                         }
1917                 }
1918         }
1919
1920         return false
1921 }
1922
1923 // AddTab adds a new tab with an empty buffer
1924 func (v *View) AddTab(usePlugin bool) bool {
1925         if v.mainCursor() {
1926                 if usePlugin && !PreActionCall("AddTab", v) {
1927                         return false
1928                 }
1929
1930                 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1931                 tab.SetNum(len(tabs))
1932                 tabs = append(tabs, tab)
1933                 curTab = len(tabs) - 1
1934                 if len(tabs) == 2 {
1935                         for _, t := range tabs {
1936                                 for _, v := range t.Views {
1937                                         v.ToggleTabbar()
1938                                 }
1939                         }
1940                 }
1941
1942                 if usePlugin {
1943                         return PostActionCall("AddTab", v)
1944                 }
1945         }
1946         return true
1947 }
1948
1949 // PreviousTab switches to the previous tab in the tab list
1950 func (v *View) PreviousTab(usePlugin bool) bool {
1951         if v.mainCursor() {
1952                 if usePlugin && !PreActionCall("PreviousTab", v) {
1953                         return false
1954                 }
1955
1956                 if curTab > 0 {
1957                         curTab--
1958                 } else if curTab == 0 {
1959                         curTab = len(tabs) - 1
1960                 }
1961
1962                 if usePlugin {
1963                         return PostActionCall("PreviousTab", v)
1964                 }
1965         }
1966         return false
1967 }
1968
1969 // NextTab switches to the next tab in the tab list
1970 func (v *View) NextTab(usePlugin bool) bool {
1971         if v.mainCursor() {
1972                 if usePlugin && !PreActionCall("NextTab", v) {
1973                         return false
1974                 }
1975
1976                 if curTab < len(tabs)-1 {
1977                         curTab++
1978                 } else if curTab == len(tabs)-1 {
1979                         curTab = 0
1980                 }
1981
1982                 if usePlugin {
1983                         return PostActionCall("NextTab", v)
1984                 }
1985         }
1986         return false
1987 }
1988
1989 // VSplitBinding opens an empty vertical split
1990 func (v *View) VSplitBinding(usePlugin bool) bool {
1991         if v.mainCursor() {
1992                 if usePlugin && !PreActionCall("VSplit", v) {
1993                         return false
1994                 }
1995
1996                 v.VSplit(NewBufferFromString("", ""))
1997
1998                 if usePlugin {
1999                         return PostActionCall("VSplit", v)
2000                 }
2001         }
2002         return false
2003 }
2004
2005 // HSplitBinding opens an empty horizontal split
2006 func (v *View) HSplitBinding(usePlugin bool) bool {
2007         if v.mainCursor() {
2008                 if usePlugin && !PreActionCall("HSplit", v) {
2009                         return false
2010                 }
2011
2012                 v.HSplit(NewBufferFromString("", ""))
2013
2014                 if usePlugin {
2015                         return PostActionCall("HSplit", v)
2016                 }
2017         }
2018         return false
2019 }
2020
2021 // Unsplit closes all splits in the current tab except the active one
2022 func (v *View) Unsplit(usePlugin bool) bool {
2023         if v.mainCursor() {
2024                 if usePlugin && !PreActionCall("Unsplit", v) {
2025                         return false
2026                 }
2027
2028                 curView := tabs[curTab].CurView
2029                 for i := len(tabs[curTab].Views) - 1; i >= 0; i-- {
2030                         view := tabs[curTab].Views[i]
2031                         if view != nil && view.Num != curView {
2032                                 view.Quit(true)
2033                                 // messenger.Message("Quit ", view.Buf.Path)
2034                         }
2035                 }
2036
2037                 if usePlugin {
2038                         return PostActionCall("Unsplit", v)
2039                 }
2040         }
2041         return false
2042 }
2043
2044 // NextSplit changes the view to the next split
2045 func (v *View) NextSplit(usePlugin bool) bool {
2046         if v.mainCursor() {
2047                 if usePlugin && !PreActionCall("NextSplit", v) {
2048                         return false
2049                 }
2050
2051                 tab := tabs[curTab]
2052                 if tab.CurView < len(tab.Views)-1 {
2053                         tab.CurView++
2054                 } else {
2055                         tab.CurView = 0
2056                 }
2057
2058                 if usePlugin {
2059                         return PostActionCall("NextSplit", v)
2060                 }
2061         }
2062         return false
2063 }
2064
2065 // PreviousSplit changes the view to the previous split
2066 func (v *View) PreviousSplit(usePlugin bool) bool {
2067         if v.mainCursor() {
2068                 if usePlugin && !PreActionCall("PreviousSplit", v) {
2069                         return false
2070                 }
2071
2072                 tab := tabs[curTab]
2073                 if tab.CurView > 0 {
2074                         tab.CurView--
2075                 } else {
2076                         tab.CurView = len(tab.Views) - 1
2077                 }
2078
2079                 if usePlugin {
2080                         return PostActionCall("PreviousSplit", v)
2081                 }
2082         }
2083         return false
2084 }
2085
2086 var curMacro []interface{}
2087 var recordingMacro bool
2088
2089 // ToggleMacro toggles recording of a macro
2090 func (v *View) ToggleMacro(usePlugin bool) bool {
2091         if v.mainCursor() {
2092                 if usePlugin && !PreActionCall("ToggleMacro", v) {
2093                         return false
2094                 }
2095
2096                 recordingMacro = !recordingMacro
2097
2098                 if recordingMacro {
2099                         curMacro = []interface{}{}
2100                         messenger.Message("Recording")
2101                 } else {
2102                         messenger.Message("Stopped recording")
2103                 }
2104
2105                 if usePlugin {
2106                         return PostActionCall("ToggleMacro", v)
2107                 }
2108         }
2109         return true
2110 }
2111
2112 // PlayMacro plays back the most recently recorded macro
2113 func (v *View) PlayMacro(usePlugin bool) bool {
2114         if usePlugin && !PreActionCall("PlayMacro", v) {
2115                 return false
2116         }
2117
2118         for _, action := range curMacro {
2119                 switch t := action.(type) {
2120                 case rune:
2121                         // Insert a character
2122                         if v.Cursor.HasSelection() {
2123                                 v.Cursor.DeleteSelection()
2124                                 v.Cursor.ResetSelection()
2125                         }
2126                         v.Buf.Insert(v.Cursor.Loc, string(t))
2127                         // v.Cursor.Right()
2128
2129                         for pl := range loadedPlugins {
2130                                 _, err := Call(pl+".onRune", string(t), v)
2131                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
2132                                         TermMessage(err)
2133                                 }
2134                         }
2135                 case func(*View, bool) bool:
2136                         t(v, true)
2137                 }
2138         }
2139
2140         if usePlugin {
2141                 return PostActionCall("PlayMacro", v)
2142         }
2143         return true
2144 }
2145
2146 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
2147 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
2148         spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
2149         // You can only spawn a cursor from the main cursor
2150         if v.Cursor == spawner {
2151                 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
2152                         return false
2153                 }
2154
2155                 if !spawner.HasSelection() {
2156                         spawner.SelectWord()
2157                 } else {
2158                         c := &Cursor{
2159                                 buf: v.Buf,
2160                         }
2161
2162                         sel := spawner.GetSelection()
2163
2164                         searchStart = spawner.CurSelection[1]
2165                         v.Cursor = c
2166                         Search(regexp.QuoteMeta(sel), v, true)
2167
2168                         for _, cur := range v.Buf.cursors {
2169                                 if c.Loc == cur.Loc {
2170                                         return false
2171                                 }
2172                         }
2173                         v.Buf.cursors = append(v.Buf.cursors, c)
2174                         v.Buf.UpdateCursors()
2175                         v.Relocate()
2176                         v.Cursor = spawner
2177                 }
2178
2179                 if usePlugin {
2180                         PostActionCall("SpawnMultiCursor", v)
2181                 }
2182         }
2183
2184         return false
2185 }
2186
2187 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
2188 func (v *View) SpawnMultiCursorSelect(usePlugin bool) bool {
2189         if v.Cursor == &v.Buf.Cursor {
2190                 if usePlugin && !PreActionCall("SpawnMultiCursorSelect", v) {
2191                         return false
2192                 }
2193
2194                 // Avoid cases where multiple cursors already exist, that would create problems
2195                 if len(v.Buf.cursors) > 1 {
2196                         return false
2197                 }
2198
2199                 var startLine int
2200                 var endLine int
2201
2202                 a, b := v.Cursor.CurSelection[0].Y, v.Cursor.CurSelection[1].Y
2203                 if a > b {
2204                         startLine, endLine = b, a
2205                 } else {
2206                         startLine, endLine = a, b
2207                 }
2208
2209                 if v.Cursor.HasSelection() {
2210                         v.Cursor.ResetSelection()
2211                         v.Cursor.GotoLoc(Loc{0, startLine})
2212
2213                         for i := startLine; i <= endLine; i++ {
2214                                 c := &Cursor{
2215                                         buf: v.Buf,
2216                                 }
2217                                 c.GotoLoc(Loc{0, i})
2218                                 v.Buf.cursors = append(v.Buf.cursors, c)
2219                         }
2220                         v.Buf.MergeCursors()
2221                         v.Buf.UpdateCursors()
2222                 } else {
2223                         return false
2224                 }
2225
2226                 if usePlugin {
2227                         PostActionCall("SpawnMultiCursorSelect", v)
2228                 }
2229
2230                 messenger.Message("Added cursors from selection")
2231         }
2232         return false
2233 }
2234
2235 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
2236 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
2237         if v.Cursor == &v.Buf.Cursor {
2238                 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
2239                         return false
2240                 }
2241                 x, y := e.Position()
2242                 x -= v.lineNumOffset - v.leftCol + v.x
2243                 y += v.Topline - v.y
2244
2245                 c := &Cursor{
2246                         buf: v.Buf,
2247                 }
2248                 v.Cursor = c
2249                 v.MoveToMouseClick(x, y)
2250                 v.Relocate()
2251                 v.Cursor = &v.Buf.Cursor
2252
2253                 v.Buf.cursors = append(v.Buf.cursors, c)
2254                 v.Buf.MergeCursors()
2255                 v.Buf.UpdateCursors()
2256
2257                 if usePlugin {
2258                         PostActionCall("SpawnMultiCursorAtMouse", v)
2259                 }
2260         }
2261         return false
2262 }
2263
2264 // SkipMultiCursor moves the current multiple cursor to the next available position
2265 func (v *View) SkipMultiCursor(usePlugin bool) bool {
2266         cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
2267
2268         if v.mainCursor() {
2269                 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
2270                         return false
2271                 }
2272                 sel := cursor.GetSelection()
2273
2274                 searchStart = cursor.CurSelection[1]
2275                 v.Cursor = cursor
2276                 Search(regexp.QuoteMeta(sel), v, true)
2277                 v.Relocate()
2278                 v.Cursor = cursor
2279
2280                 if usePlugin {
2281                         PostActionCall("SkipMultiCursor", v)
2282                 }
2283         }
2284         return false
2285 }
2286
2287 // RemoveMultiCursor removes the latest multiple cursor
2288 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
2289         end := len(v.Buf.cursors)
2290         if end > 1 {
2291                 if v.mainCursor() {
2292                         if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
2293                                 return false
2294                         }
2295
2296                         v.Buf.cursors[end-1] = nil
2297                         v.Buf.cursors = v.Buf.cursors[:end-1]
2298                         v.Buf.UpdateCursors()
2299                         v.Relocate()
2300
2301                         if usePlugin {
2302                                 return PostActionCall("RemoveMultiCursor", v)
2303                         }
2304                         return true
2305                 }
2306         } else {
2307                 v.RemoveAllMultiCursors(usePlugin)
2308         }
2309         return false
2310 }
2311
2312 // RemoveAllMultiCursors removes all cursors except the base cursor
2313 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
2314         if v.mainCursor() {
2315                 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
2316                         return false
2317                 }
2318
2319                 v.Buf.clearCursors()
2320                 v.Relocate()
2321
2322                 if usePlugin {
2323                         return PostActionCall("RemoveAllMultiCursors", v)
2324                 }
2325                 return true
2326         }
2327         return false
2328 }