]> git.lizzy.rs Git - micro.git/blob - internal/action/command.go
667de11b921b2f04400b7b1902bfada08f70b17e
[micro.git] / internal / action / command.go
1 package action
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "os"
8         "os/exec"
9         "path/filepath"
10         "regexp"
11         "strconv"
12         "strings"
13         "unicode/utf8"
14
15         shellquote "github.com/kballard/go-shellquote"
16         "github.com/zyedidia/micro/internal/buffer"
17         "github.com/zyedidia/micro/internal/config"
18         "github.com/zyedidia/micro/internal/screen"
19         "github.com/zyedidia/micro/internal/shell"
20         "github.com/zyedidia/micro/internal/util"
21 )
22
23 // A Command contains information about how to execute a command
24 // It has the action for that command as well as a completer function
25 type Command struct {
26         action    func(*BufPane, []string)
27         completer buffer.Completer
28 }
29
30 var commands map[string]Command
31
32 func InitCommands() {
33         commands = map[string]Command{
34                 "set":        {(*BufPane).SetCmd, OptionValueComplete},
35                 "reset":      {(*BufPane).ResetCmd, OptionValueComplete},
36                 "setlocal":   {(*BufPane).SetLocalCmd, OptionValueComplete},
37                 "show":       {(*BufPane).ShowCmd, OptionComplete},
38                 "showkey":    {(*BufPane).ShowKeyCmd, nil},
39                 "run":        {(*BufPane).RunCmd, nil},
40                 "bind":       {(*BufPane).BindCmd, nil},
41                 "unbind":     {(*BufPane).UnbindCmd, nil},
42                 "quit":       {(*BufPane).QuitCmd, nil},
43                 "goto":       {(*BufPane).GotoCmd, nil},
44                 "save":       {(*BufPane).SaveCmd, nil},
45                 "replace":    {(*BufPane).ReplaceCmd, nil},
46                 "replaceall": {(*BufPane).ReplaceAllCmd, nil},
47                 "vsplit":     {(*BufPane).VSplitCmd, buffer.FileComplete},
48                 "hsplit":     {(*BufPane).HSplitCmd, buffer.FileComplete},
49                 "tab":        {(*BufPane).NewTabCmd, buffer.FileComplete},
50                 "help":       {(*BufPane).HelpCmd, HelpComplete},
51                 "eval":       {(*BufPane).EvalCmd, nil},
52                 "log":        {(*BufPane).ToggleLogCmd, nil},
53                 "plugin":     {(*BufPane).PluginCmd, PluginComplete},
54                 "reload":     {(*BufPane).ReloadCmd, nil},
55                 "reopen":     {(*BufPane).ReopenCmd, nil},
56                 "cd":         {(*BufPane).CdCmd, buffer.FileComplete},
57                 "pwd":        {(*BufPane).PwdCmd, nil},
58                 "open":       {(*BufPane).OpenCmd, buffer.FileComplete},
59                 "tabswitch":  {(*BufPane).TabSwitchCmd, nil},
60                 "term":       {(*BufPane).TermCmd, nil},
61                 "memusage":   {(*BufPane).MemUsageCmd, nil},
62                 "retab":      {(*BufPane).RetabCmd, nil},
63                 "raw":        {(*BufPane).RawCmd, nil},
64                 "textfilter": {(*BufPane).TextFilterCmd, nil},
65         }
66 }
67
68 // MakeCommand is a function to easily create new commands
69 // This can be called by plugins in Lua so that plugins can define their own commands
70 func MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
71         if action != nil {
72                 commands[name] = Command{action, completer}
73         }
74 }
75
76 // CommandEditAction returns a bindable function that opens a prompt with
77 // the given string and executes the command when the user presses
78 // enter
79 func CommandEditAction(prompt string) BufKeyAction {
80         return func(h *BufPane) bool {
81                 InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) {
82                         if !canceled {
83                                 MainTab().CurPane().HandleCommand(resp)
84                         }
85                 })
86                 return false
87         }
88 }
89
90 // CommandAction returns a bindable function which executes the
91 // given command
92 func CommandAction(cmd string) BufKeyAction {
93         return func(h *BufPane) bool {
94                 MainTab().CurPane().HandleCommand(cmd)
95                 return false
96         }
97 }
98
99 var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
100
101 // PluginCmd installs, removes, updates, lists, or searches for given plugins
102 func (h *BufPane) PluginCmd(args []string) {
103         if len(args) < 1 {
104                 InfoBar.Error("Not enough arguments")
105                 return
106         }
107
108         if h.Buf.Type != buffer.BTLog {
109                 h.OpenLogBuf()
110         }
111
112         config.PluginCommand(buffer.LogBuf, args[0], args[1:])
113 }
114
115 // RetabCmd changes all spaces to tabs or all tabs to spaces
116 // depending on the user's settings
117 func (h *BufPane) RetabCmd(args []string) {
118         h.Buf.Retab()
119 }
120
121 // RawCmd opens a new raw view which displays the escape sequences micro
122 // is receiving in real-time
123 func (h *BufPane) RawCmd(args []string) {
124         width, height := screen.Screen.Size()
125         iOffset := config.GetInfoBarOffset()
126         tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
127         Tabs.AddTab(tp)
128         Tabs.SetActive(len(Tabs.List) - 1)
129 }
130
131 // TextFilterCmd filters the selection through the command.
132 // Selection goes to the command input.
133 // On successful run command output replaces the current selection.
134 func (h *BufPane) TextFilterCmd(args []string) {
135         if len(args) == 0 {
136                 InfoBar.Error("usage: textfilter arguments")
137                 return
138         }
139         sel := h.Cursor.GetSelection()
140         if len(sel) == 0 {
141                 h.Cursor.SelectWord()
142                 sel = h.Cursor.GetSelection()
143         }
144         var bout, berr bytes.Buffer
145         cmd := exec.Command(args[0], args[1:]...)
146         cmd.Stdin = strings.NewReader(string(sel))
147         cmd.Stderr = &berr
148         cmd.Stdout = &bout
149         err := cmd.Run()
150         if err != nil {
151                 InfoBar.Error(err.Error() + " " + berr.String())
152                 return
153         }
154         h.Cursor.DeleteSelection()
155         h.Buf.Insert(h.Cursor.Loc, bout.String())
156 }
157
158 // TabSwitchCmd switches to a given tab either by name or by number
159 func (h *BufPane) TabSwitchCmd(args []string) {
160         if len(args) > 0 {
161                 num, err := strconv.Atoi(args[0])
162                 if err != nil {
163                         // Check for tab with this name
164
165                         found := false
166                         for i, t := range Tabs.List {
167                                 if t.Panes[t.active].Name() == args[0] {
168                                         Tabs.SetActive(i)
169                                         found = true
170                                 }
171                         }
172                         if !found {
173                                 InfoBar.Error("Could not find tab: ", err)
174                         }
175                 } else {
176                         num--
177                         if num >= 0 && num < len(Tabs.List) {
178                                 Tabs.SetActive(num)
179                         } else {
180                                 InfoBar.Error("Invalid tab index")
181                         }
182                 }
183         }
184 }
185
186 // CdCmd changes the current working directory
187 func (h *BufPane) CdCmd(args []string) {
188         if len(args) > 0 {
189                 path, err := util.ReplaceHome(args[0])
190                 if err != nil {
191                         InfoBar.Error(err)
192                         return
193                 }
194                 err = os.Chdir(path)
195                 if err != nil {
196                         InfoBar.Error(err)
197                         return
198                 }
199                 wd, _ := os.Getwd()
200                 for _, b := range buffer.OpenBuffers {
201                         if len(b.Path) > 0 {
202                                 b.Path, _ = util.MakeRelative(b.AbsPath, wd)
203                                 if p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {
204                                         b.Path = b.AbsPath
205                                 }
206                         }
207                 }
208         }
209 }
210
211 // MemUsageCmd prints micro's memory usage
212 // Alloc shows how many bytes are currently in use
213 // Sys shows how many bytes have been requested from the operating system
214 // NumGC shows how many times the GC has been run
215 // Note that Go commonly reserves more memory from the OS than is currently in-use/required
216 // Additionally, even if Go returns memory to the OS, the OS does not always claim it because
217 // there may be plenty of memory to spare
218 func (h *BufPane) MemUsageCmd(args []string) {
219         InfoBar.Message(util.GetMemStats())
220 }
221
222 // PwdCmd prints the current working directory
223 func (h *BufPane) PwdCmd(args []string) {
224         wd, err := os.Getwd()
225         if err != nil {
226                 InfoBar.Message(err.Error())
227         } else {
228                 InfoBar.Message(wd)
229         }
230 }
231
232 // OpenCmd opens a new buffer with a given filename
233 func (h *BufPane) OpenCmd(args []string) {
234         if len(args) > 0 {
235                 filename := args[0]
236                 // the filename might or might not be quoted, so unquote first then join the strings.
237                 args, err := shellquote.Split(filename)
238                 if err != nil {
239                         InfoBar.Error("Error parsing args ", err)
240                         return
241                 }
242                 if len(args) == 0 {
243                         return
244                 }
245                 filename = strings.Join(args, " ")
246
247                 open := func() {
248                         b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
249                         if err != nil {
250                                 InfoBar.Error(err)
251                                 return
252                         }
253                         h.OpenBuffer(b)
254                 }
255                 if h.Buf.Modified() {
256                         InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
257                                 if !canceled && !yes {
258                                         open()
259                                 } else if !canceled && yes {
260                                         h.Save()
261                                         open()
262                                 }
263                         })
264                 } else {
265                         open()
266                 }
267         } else {
268                 InfoBar.Error("No filename")
269         }
270 }
271
272 // ToggleLogCmd toggles the log view
273 func (h *BufPane) ToggleLogCmd(args []string) {
274         if h.Buf.Type != buffer.BTLog {
275                 h.OpenLogBuf()
276         } else {
277                 h.Quit()
278         }
279 }
280
281 // ReloadCmd reloads all files (syntax files, colorschemes...)
282 func (h *BufPane) ReloadCmd(args []string) {
283         ReloadConfig()
284 }
285
286 func ReloadConfig() {
287         config.InitRuntimeFiles()
288         err := config.ReadSettings()
289         if err != nil {
290                 screen.TermMessage(err)
291         }
292         config.InitGlobalSettings()
293         InitBindings()
294         InitCommands()
295
296         err = config.InitColorscheme()
297         if err != nil {
298                 screen.TermMessage(err)
299         }
300
301         for _, b := range buffer.OpenBuffers {
302                 b.UpdateRules()
303         }
304 }
305
306 // ReopenCmd reopens the buffer (reload from disk)
307 func (h *BufPane) ReopenCmd(args []string) {
308         if h.Buf.Modified() {
309                 InfoBar.YNPrompt("Save file before reopen?", func(yes, canceled bool) {
310                         if !canceled && yes {
311                                 h.Save()
312                                 h.Buf.ReOpen()
313                         } else if !canceled {
314                                 h.Buf.ReOpen()
315                         }
316                 })
317         } else {
318                 h.Buf.ReOpen()
319         }
320 }
321
322 func (h *BufPane) openHelp(page string) error {
323         if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
324                 return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err))
325         } else {
326                 helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
327                 helpBuffer.SetName("Help " + page)
328
329                 if h.Buf.Type == buffer.BTHelp {
330                         h.OpenBuffer(helpBuffer)
331                 } else {
332                         h.HSplitBuf(helpBuffer)
333                 }
334         }
335         return nil
336 }
337
338 // HelpCmd tries to open the given help page in a horizontal split
339 func (h *BufPane) HelpCmd(args []string) {
340         if len(args) < 1 {
341                 // Open the default help if the user just typed "> help"
342                 h.openHelp("help")
343         } else {
344                 if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
345                         err := h.openHelp(args[0])
346                         if err != nil {
347                                 InfoBar.Error(err)
348                         }
349                 } else {
350                         InfoBar.Error("Sorry, no help for ", args[0])
351                 }
352         }
353 }
354
355 // VSplitCmd opens a vertical split with file given in the first argument
356 // If no file is given, it opens an empty buffer in a new split
357 func (h *BufPane) VSplitCmd(args []string) {
358         if len(args) == 0 {
359                 // Open an empty vertical split
360                 h.VSplitAction()
361                 return
362         }
363
364         buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
365         if err != nil {
366                 InfoBar.Error(err)
367                 return
368         }
369
370         h.VSplitBuf(buf)
371 }
372
373 // HSplitCmd opens a horizontal split with file given in the first argument
374 // If no file is given, it opens an empty buffer in a new split
375 func (h *BufPane) HSplitCmd(args []string) {
376         if len(args) == 0 {
377                 // Open an empty horizontal split
378                 h.HSplitAction()
379                 return
380         }
381
382         buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
383         if err != nil {
384                 InfoBar.Error(err)
385                 return
386         }
387
388         h.HSplitBuf(buf)
389 }
390
391 // EvalCmd evaluates a lua expression
392 func (h *BufPane) EvalCmd(args []string) {
393         InfoBar.Error("Eval unsupported")
394 }
395
396 // NewTabCmd opens the given file in a new tab
397 func (h *BufPane) NewTabCmd(args []string) {
398         width, height := screen.Screen.Size()
399         iOffset := config.GetInfoBarOffset()
400         if len(args) > 0 {
401                 for _, a := range args {
402                         b, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
403                         if err != nil {
404                                 InfoBar.Error(err)
405                                 return
406                         }
407                         tp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)
408                         Tabs.AddTab(tp)
409                         Tabs.SetActive(len(Tabs.List) - 1)
410                 }
411         } else {
412                 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
413                 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
414                 Tabs.AddTab(tp)
415                 Tabs.SetActive(len(Tabs.List) - 1)
416         }
417 }
418
419 func SetGlobalOptionNative(option string, nativeValue interface{}) error {
420         local := false
421         for _, s := range config.LocalSettings {
422                 if s == option {
423                         local = true
424                         break
425                 }
426         }
427
428         if !local {
429                 config.GlobalSettings[option] = nativeValue
430
431                 if option == "colorscheme" {
432                         // LoadSyntaxFiles()
433                         config.InitColorscheme()
434                         for _, b := range buffer.OpenBuffers {
435                                 b.UpdateRules()
436                         }
437                 } else if option == "infobar" || option == "keymenu" {
438                         Tabs.Resize()
439                 } else if option == "mouse" {
440                         if !nativeValue.(bool) {
441                                 screen.Screen.DisableMouse()
442                         } else {
443                                 screen.Screen.EnableMouse()
444                         }
445                 } else if option == "autosave" {
446                         if nativeValue.(float64) > 0 {
447                                 config.SetAutoTime(int(nativeValue.(float64)))
448                                 config.StartAutoSave()
449                         } else {
450                                 config.SetAutoTime(0)
451                         }
452                 } else if option == "paste" {
453                         screen.Screen.SetPaste(nativeValue.(bool))
454                 } else {
455                         for _, pl := range config.Plugins {
456                                 if option == pl.Name {
457                                         if nativeValue.(bool) && !pl.Loaded {
458                                                 pl.Load()
459                                                 _, err := pl.Call("init")
460                                                 if err != nil && err != config.ErrNoSuchFunction {
461                                                         screen.TermMessage(err)
462                                                 }
463                                         } else if !nativeValue.(bool) && pl.Loaded {
464                                                 _, err := pl.Call("deinit")
465                                                 if err != nil && err != config.ErrNoSuchFunction {
466                                                         screen.TermMessage(err)
467                                                 }
468                                         }
469                                 }
470                         }
471                 }
472         }
473
474         for _, b := range buffer.OpenBuffers {
475                 b.SetOptionNative(option, nativeValue)
476         }
477
478         return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
479 }
480
481 func SetGlobalOption(option, value string) error {
482         if _, ok := config.GlobalSettings[option]; !ok {
483                 return config.ErrInvalidOption
484         }
485
486         nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
487         if err != nil {
488                 return err
489         }
490
491         return SetGlobalOptionNative(option, nativeValue)
492 }
493
494 // ResetCmd resets a setting to its default value
495 func (h *BufPane) ResetCmd(args []string) {
496         if len(args) < 1 {
497                 InfoBar.Error("Not enough arguments")
498                 return
499         }
500
501         option := args[0]
502
503         defaultGlobals := config.DefaultGlobalSettings()
504         defaultLocals := config.DefaultCommonSettings()
505
506         if _, ok := defaultGlobals[option]; ok {
507                 SetGlobalOptionNative(option, defaultGlobals[option])
508                 return
509         }
510         if _, ok := defaultLocals[option]; ok {
511                 h.Buf.SetOptionNative(option, defaultLocals[option])
512                 return
513         }
514         InfoBar.Error(config.ErrInvalidOption)
515 }
516
517 // SetCmd sets an option
518 func (h *BufPane) SetCmd(args []string) {
519         if len(args) < 2 {
520                 InfoBar.Error("Not enough arguments")
521                 return
522         }
523
524         option := args[0]
525         value := args[1]
526
527         err := SetGlobalOption(option, value)
528         if err == config.ErrInvalidOption {
529                 err := h.Buf.SetOption(option, value)
530                 if err != nil {
531                         InfoBar.Error(err)
532                 }
533         } else if err != nil {
534                 InfoBar.Error(err)
535         }
536 }
537
538 // SetLocalCmd sets an option local to the buffer
539 func (h *BufPane) SetLocalCmd(args []string) {
540         if len(args) < 2 {
541                 InfoBar.Error("Not enough arguments")
542                 return
543         }
544
545         option := args[0]
546         value := args[1]
547
548         err := h.Buf.SetOption(option, value)
549         if err != nil {
550                 InfoBar.Error(err)
551         }
552 }
553
554 // ShowCmd shows the value of the given option
555 func (h *BufPane) ShowCmd(args []string) {
556         if len(args) < 1 {
557                 InfoBar.Error("Please provide an option to show")
558                 return
559         }
560
561         var option interface{}
562         if opt, ok := h.Buf.Settings[args[0]]; ok {
563                 option = opt
564         } else if opt, ok := config.GlobalSettings[args[0]]; ok {
565                 option = opt
566         }
567
568         if option == nil {
569                 InfoBar.Error(args[0], " is not a valid option")
570                 return
571         }
572
573         InfoBar.Message(option)
574 }
575
576 // ShowKeyCmd displays the action that a key is bound to
577 func (h *BufPane) ShowKeyCmd(args []string) {
578         if len(args) < 1 {
579                 InfoBar.Error("Please provide a key to show")
580                 return
581         }
582
583         if action, ok := config.Bindings[args[0]]; ok {
584                 InfoBar.Message(action)
585         } else {
586                 InfoBar.Message(args[0], " has no binding")
587         }
588 }
589
590 // BindCmd creates a new keybinding
591 func (h *BufPane) BindCmd(args []string) {
592         if len(args) < 2 {
593                 InfoBar.Error("Not enough arguments")
594                 return
595         }
596
597         _, err := TryBindKey(args[0], args[1], true)
598         if err != nil {
599                 InfoBar.Error(err)
600         }
601 }
602
603 // UnbindCmd binds a key to its default action
604 func (h *BufPane) UnbindCmd(args []string) {
605         if len(args) < 1 {
606                 InfoBar.Error("Not enough arguments")
607                 return
608         }
609
610         err := UnbindKey(args[0])
611         if err != nil {
612                 InfoBar.Error(err)
613         }
614 }
615
616 // RunCmd runs a shell command in the background
617 func (h *BufPane) RunCmd(args []string) {
618         runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
619         if err != nil {
620                 InfoBar.Error(err)
621         } else {
622                 go func() {
623                         InfoBar.Message(runf())
624                         screen.Redraw()
625                 }()
626         }
627 }
628
629 // QuitCmd closes the main view
630 func (h *BufPane) QuitCmd(args []string) {
631         h.Quit()
632 }
633
634 // GotoCmd is a command that will send the cursor to a certain
635 // position in the buffer
636 // For example: `goto line`, or `goto line:col`
637 func (h *BufPane) GotoCmd(args []string) {
638         if len(args) <= 0 {
639                 InfoBar.Error("Not enough arguments")
640         } else {
641                 h.RemoveAllMultiCursors()
642                 if strings.Contains(args[0], ":") {
643                         parts := strings.SplitN(args[0], ":", 2)
644                         line, err := strconv.Atoi(parts[0])
645                         if err != nil {
646                                 InfoBar.Error(err)
647                                 return
648                         }
649                         col, err := strconv.Atoi(parts[1])
650                         if err != nil {
651                                 InfoBar.Error(err)
652                                 return
653                         }
654                         line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
655                         col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
656                         h.Cursor.GotoLoc(buffer.Loc{col, line})
657                 } else {
658                         line, err := strconv.Atoi(args[0])
659                         if err != nil {
660                                 InfoBar.Error(err)
661                                 return
662                         }
663                         line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
664                         h.Cursor.GotoLoc(buffer.Loc{0, line})
665                 }
666                 h.Relocate()
667         }
668 }
669
670 // SaveCmd saves the buffer optionally with an argument file name
671 func (h *BufPane) SaveCmd(args []string) {
672         if len(args) == 0 {
673                 h.Save()
674         } else {
675                 h.Buf.SaveAs(args[0])
676         }
677 }
678
679 // ReplaceCmd runs search and replace
680 func (h *BufPane) ReplaceCmd(args []string) {
681         if len(args) < 2 || len(args) > 4 {
682                 // We need to find both a search and replace expression
683                 InfoBar.Error("Invalid replace statement: " + strings.Join(args, " "))
684                 return
685         }
686
687         all := false
688         noRegex := false
689
690         foundSearch := false
691         foundReplace := false
692         var search string
693         var replaceStr string
694         for _, arg := range args {
695                 switch arg {
696                 case "-a":
697                         all = true
698                 case "-l":
699                         noRegex = true
700                 default:
701                         if !foundSearch {
702                                 foundSearch = true
703                                 search = arg
704                         } else if !foundReplace {
705                                 foundReplace = true
706                                 replaceStr = arg
707                         } else {
708                                 InfoBar.Error("Invalid flag: " + arg)
709                                 return
710                         }
711                 }
712         }
713
714         if noRegex {
715                 search = regexp.QuoteMeta(search)
716         }
717
718         replace := []byte(replaceStr)
719
720         var regex *regexp.Regexp
721         var err error
722         if h.Buf.Settings["ignorecase"].(bool) {
723                 regex, err = regexp.Compile("(?im)" + search)
724         } else {
725                 regex, err = regexp.Compile("(?m)" + search)
726         }
727         if err != nil {
728                 // There was an error with the user's regex
729                 InfoBar.Error(err)
730                 return
731         }
732
733         nreplaced := 0
734         start := h.Buf.Start()
735         // end := h.Buf.End()
736         // if h.Cursor.HasSelection() {
737         //      start = h.Cursor.CurSelection[0]
738         //      end = h.Cursor.CurSelection[1]
739         // }
740         if all {
741                 nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
742         } else {
743                 inRange := func(l buffer.Loc) bool {
744                         return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
745                 }
746
747                 searchLoc := start
748                 searching := true
749                 var doReplacement func()
750                 doReplacement = func() {
751                         locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
752                         if err != nil {
753                                 InfoBar.Error(err)
754                                 return
755                         }
756                         if !found || !inRange(locs[0]) || !inRange(locs[1]) {
757                                 h.Cursor.ResetSelection()
758                                 h.Buf.RelocateCursors()
759                                 return
760                         }
761
762                         h.Cursor.SetSelectionStart(locs[0])
763                         h.Cursor.SetSelectionEnd(locs[1])
764
765                         InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
766                                 if !canceled && yes {
767                                         h.Buf.Replace(locs[0], locs[1], replaceStr)
768
769                                         searchLoc = locs[0]
770                                         searchLoc.X += utf8.RuneCount(replace)
771                                         h.Cursor.Loc = searchLoc
772                                         nreplaced++
773                                 } else if !canceled && !yes {
774                                         searchLoc = locs[0]
775                                         searchLoc.X += utf8.RuneCount(replace)
776                                 } else if canceled {
777                                         h.Cursor.ResetSelection()
778                                         h.Buf.RelocateCursors()
779                                         return
780                                 }
781                                 if searching {
782                                         doReplacement()
783                                 }
784                         })
785                 }
786                 doReplacement()
787         }
788
789         h.Buf.RelocateCursors()
790
791         if nreplaced > 1 {
792                 InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
793         } else if nreplaced == 1 {
794                 InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
795         } else {
796                 InfoBar.Message("Nothing matched ", search)
797         }
798 }
799
800 // ReplaceAllCmd replaces search term all at once
801 func (h *BufPane) ReplaceAllCmd(args []string) {
802         // aliased to Replace command
803         h.ReplaceCmd(append(args, "-a"))
804 }
805
806 // TermCmd opens a terminal in the current view
807 func (h *BufPane) TermCmd(args []string) {
808         ps := h.tab.Panes
809
810         if !TermEmuSupported {
811                 InfoBar.Error("Terminal emulator not supported on this system")
812                 return
813         }
814
815         if len(args) == 0 {
816                 sh := os.Getenv("SHELL")
817                 if sh == "" {
818                         InfoBar.Error("Shell environment not found")
819                         return
820                 }
821                 args = []string{sh}
822         }
823
824         term := func(i int, newtab bool) {
825                 t := new(shell.Terminal)
826                 t.Start(args, false, true, nil, nil)
827
828                 id := h.ID()
829                 if newtab {
830                         h.AddTab()
831                         i = 0
832                         id = MainTab().Panes[0].ID()
833                 } else {
834                         MainTab().Panes[i].Close()
835                 }
836
837                 v := h.GetView()
838                 tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
839                 if err != nil {
840                         InfoBar.Error(err)
841                         return
842                 }
843                 MainTab().Panes[i] = tp
844                 MainTab().SetActive(i)
845         }
846
847         // If there is only one open file we make a new tab instead of overwriting it
848         newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
849
850         if newtab {
851                 term(0, true)
852                 return
853         }
854
855         for i, p := range ps {
856                 if p.ID() == h.ID() {
857                         if h.Buf.Modified() {
858                                 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
859                                         if !canceled && !yes {
860                                                 term(i, false)
861                                         } else if !canceled && yes {
862                                                 h.Save()
863                                                 term(i, false)
864                                         }
865                                 })
866                         } else {
867                                 term(i, false)
868                         }
869                 }
870         }
871 }
872
873 // HandleCommand handles input from the user
874 func (h *BufPane) HandleCommand(input string) {
875         args, err := shellquote.Split(input)
876         if err != nil {
877                 InfoBar.Error("Error parsing args ", err)
878                 return
879         }
880
881         if len(args) == 0 {
882                 return
883         }
884
885         inputCmd := args[0]
886
887         if _, ok := commands[inputCmd]; !ok {
888                 InfoBar.Error("Unknown command ", inputCmd)
889         } else {
890                 WriteLog("> " + input + "\n")
891                 commands[inputCmd].action(h, args[1:])
892                 WriteLog("\n")
893         }
894 }