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