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"
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
26 action func(*BufPane, []string)
27 completer buffer.Completer
30 var commands map[string]Command
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},
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) {
72 commands[name] = Command{action, completer}
76 // CommandEditAction returns a bindable function that opens a prompt with
77 // the given string and executes the command when the user presses
79 func CommandEditAction(prompt string) BufKeyAction {
80 return func(h *BufPane) bool {
81 InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) {
83 MainTab().CurPane().HandleCommand(resp)
90 // CommandAction returns a bindable function which executes the
92 func CommandAction(cmd string) BufKeyAction {
93 return func(h *BufPane) bool {
94 MainTab().CurPane().HandleCommand(cmd)
99 var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
101 // PluginCmd installs, removes, updates, lists, or searches for given plugins
102 func (h *BufPane) PluginCmd(args []string) {
104 InfoBar.Error("Not enough arguments")
108 if h.Buf.Type != buffer.BTLog {
112 config.PluginCommand(buffer.LogBuf, args[0], args[1:])
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) {
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))
128 Tabs.SetActive(len(Tabs.List) - 1)
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) {
136 InfoBar.Error("usage: textfilter arguments")
139 sel := h.Cursor.GetSelection()
141 h.Cursor.SelectWord()
142 sel = h.Cursor.GetSelection()
144 var bout, berr bytes.Buffer
145 cmd := exec.Command(args[0], args[1:]...)
146 cmd.Stdin = strings.NewReader(string(sel))
151 InfoBar.Error(err.Error() + " " + berr.String())
154 h.Cursor.DeleteSelection()
155 h.Buf.Insert(h.Cursor.Loc, bout.String())
158 // TabSwitchCmd switches to a given tab either by name or by number
159 func (h *BufPane) TabSwitchCmd(args []string) {
161 num, err := strconv.Atoi(args[0])
163 // Check for tab with this name
166 for i, t := range Tabs.List {
167 if t.Panes[t.active].Name() == args[0] {
173 InfoBar.Error("Could not find tab: ", err)
177 if num >= 0 && num < len(Tabs.List) {
180 InfoBar.Error("Invalid tab index")
186 // CdCmd changes the current working directory
187 func (h *BufPane) CdCmd(args []string) {
189 path, err := util.ReplaceHome(args[0])
200 for _, b := range buffer.OpenBuffers {
202 b.Path, _ = util.MakeRelative(b.AbsPath, wd)
203 if p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {
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())
222 // PwdCmd prints the current working directory
223 func (h *BufPane) PwdCmd(args []string) {
224 wd, err := os.Getwd()
226 InfoBar.Message(err.Error())
232 // OpenCmd opens a new buffer with a given filename
233 func (h *BufPane) OpenCmd(args []string) {
236 // the filename might or might not be quoted, so unquote first then join the strings.
237 args, err := shellquote.Split(filename)
239 InfoBar.Error("Error parsing args ", err)
245 filename = strings.Join(args, " ")
248 b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
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 {
259 } else if !canceled && yes {
268 InfoBar.Error("No filename")
272 // ToggleLogCmd toggles the log view
273 func (h *BufPane) ToggleLogCmd(args []string) {
274 if h.Buf.Type != buffer.BTLog {
281 // ReloadCmd reloads all files (syntax files, colorschemes...)
282 func (h *BufPane) ReloadCmd(args []string) {
286 func ReloadConfig() {
287 config.InitRuntimeFiles()
288 err := config.ReadSettings()
290 screen.TermMessage(err)
292 config.InitGlobalSettings()
296 err = config.InitColorscheme()
298 screen.TermMessage(err)
301 for _, b := range buffer.OpenBuffers {
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 {
313 } else if !canceled {
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))
326 helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
327 helpBuffer.SetName("Help " + page)
329 if h.Buf.Type == buffer.BTHelp {
330 h.OpenBuffer(helpBuffer)
332 h.HSplitBuf(helpBuffer)
338 // HelpCmd tries to open the given help page in a horizontal split
339 func (h *BufPane) HelpCmd(args []string) {
341 // Open the default help if the user just typed "> help"
344 if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
345 err := h.openHelp(args[0])
350 InfoBar.Error("Sorry, no help for ", args[0])
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) {
359 // Open an empty vertical split
364 buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
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) {
377 // Open an empty horizontal split
382 buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
391 // EvalCmd evaluates a lua expression
392 func (h *BufPane) EvalCmd(args []string) {
393 InfoBar.Error("Eval unsupported")
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()
401 for _, a := range args {
402 b, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
407 tp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)
409 Tabs.SetActive(len(Tabs.List) - 1)
412 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
413 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
415 Tabs.SetActive(len(Tabs.List) - 1)
419 func SetGlobalOptionNative(option string, nativeValue interface{}) error {
421 for _, s := range config.LocalSettings {
429 config.GlobalSettings[option] = nativeValue
431 if option == "colorscheme" {
433 config.InitColorscheme()
434 for _, b := range buffer.OpenBuffers {
437 } else if option == "infobar" || option == "keymenu" {
439 } else if option == "mouse" {
440 if !nativeValue.(bool) {
441 screen.Screen.DisableMouse()
443 screen.Screen.EnableMouse()
445 } else if option == "autosave" {
446 if nativeValue.(float64) > 0 {
447 config.SetAutoTime(int(nativeValue.(float64)))
448 config.StartAutoSave()
450 config.SetAutoTime(0)
452 } else if option == "paste" {
453 screen.Screen.SetPaste(nativeValue.(bool))
455 for _, pl := range config.Plugins {
456 if option == pl.Name {
457 if nativeValue.(bool) && !pl.Loaded {
459 _, err := pl.Call("init")
460 if err != nil && err != config.ErrNoSuchFunction {
461 screen.TermMessage(err)
463 } else if !nativeValue.(bool) && pl.Loaded {
464 _, err := pl.Call("deinit")
465 if err != nil && err != config.ErrNoSuchFunction {
466 screen.TermMessage(err)
474 for _, b := range buffer.OpenBuffers {
475 b.SetOptionNative(option, nativeValue)
478 return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
481 func SetGlobalOption(option, value string) error {
482 if _, ok := config.GlobalSettings[option]; !ok {
483 return config.ErrInvalidOption
486 nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
491 return SetGlobalOptionNative(option, nativeValue)
494 // ResetCmd resets a setting to its default value
495 func (h *BufPane) ResetCmd(args []string) {
497 InfoBar.Error("Not enough arguments")
503 defaultGlobals := config.DefaultGlobalSettings()
504 defaultLocals := config.DefaultCommonSettings()
506 if _, ok := defaultGlobals[option]; ok {
507 SetGlobalOptionNative(option, defaultGlobals[option])
510 if _, ok := defaultLocals[option]; ok {
511 h.Buf.SetOptionNative(option, defaultLocals[option])
514 InfoBar.Error(config.ErrInvalidOption)
517 // SetCmd sets an option
518 func (h *BufPane) SetCmd(args []string) {
520 InfoBar.Error("Not enough arguments")
527 err := SetGlobalOption(option, value)
528 if err == config.ErrInvalidOption {
529 err := h.Buf.SetOption(option, value)
533 } else if err != nil {
538 // SetLocalCmd sets an option local to the buffer
539 func (h *BufPane) SetLocalCmd(args []string) {
541 InfoBar.Error("Not enough arguments")
548 err := h.Buf.SetOption(option, value)
554 // ShowCmd shows the value of the given option
555 func (h *BufPane) ShowCmd(args []string) {
557 InfoBar.Error("Please provide an option to show")
561 var option interface{}
562 if opt, ok := h.Buf.Settings[args[0]]; ok {
564 } else if opt, ok := config.GlobalSettings[args[0]]; ok {
569 InfoBar.Error(args[0], " is not a valid option")
573 InfoBar.Message(option)
576 // ShowKeyCmd displays the action that a key is bound to
577 func (h *BufPane) ShowKeyCmd(args []string) {
579 InfoBar.Error("Please provide a key to show")
583 if action, ok := config.Bindings[args[0]]; ok {
584 InfoBar.Message(action)
586 InfoBar.Message(args[0], " has no binding")
590 // BindCmd creates a new keybinding
591 func (h *BufPane) BindCmd(args []string) {
593 InfoBar.Error("Not enough arguments")
597 _, err := TryBindKey(args[0], args[1], true)
603 // UnbindCmd binds a key to its default action
604 func (h *BufPane) UnbindCmd(args []string) {
606 InfoBar.Error("Not enough arguments")
610 err := UnbindKey(args[0])
616 // RunCmd runs a shell command in the background
617 func (h *BufPane) RunCmd(args []string) {
618 runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
623 InfoBar.Message(runf())
629 // QuitCmd closes the main view
630 func (h *BufPane) QuitCmd(args []string) {
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) {
639 InfoBar.Error("Not enough arguments")
641 h.RemoveAllMultiCursors()
642 if strings.Contains(args[0], ":") {
643 parts := strings.SplitN(args[0], ":", 2)
644 line, err := strconv.Atoi(parts[0])
649 col, err := strconv.Atoi(parts[1])
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})
658 line, err := strconv.Atoi(args[0])
663 line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
664 h.Cursor.GotoLoc(buffer.Loc{0, line})
670 // SaveCmd saves the buffer optionally with an argument file name
671 func (h *BufPane) SaveCmd(args []string) {
675 h.Buf.SaveAs(args[0])
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, " "))
691 foundReplace := false
693 var replaceStr string
694 for _, arg := range args {
704 } else if !foundReplace {
708 InfoBar.Error("Invalid flag: " + arg)
715 search = regexp.QuoteMeta(search)
718 replace := []byte(replaceStr)
720 var regex *regexp.Regexp
722 if h.Buf.Settings["ignorecase"].(bool) {
723 regex, err = regexp.Compile("(?im)" + search)
725 regex, err = regexp.Compile("(?m)" + search)
728 // There was an error with the user's regex
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]
741 nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
743 inRange := func(l buffer.Loc) bool {
744 return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
749 var doReplacement func()
750 doReplacement = func() {
751 locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
756 if !found || !inRange(locs[0]) || !inRange(locs[1]) {
757 h.Cursor.ResetSelection()
758 h.Buf.RelocateCursors()
762 h.Cursor.SetSelectionStart(locs[0])
763 h.Cursor.SetSelectionEnd(locs[1])
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)
770 searchLoc.X += utf8.RuneCount(replace)
771 h.Cursor.Loc = searchLoc
773 } else if !canceled && !yes {
775 searchLoc.X += utf8.RuneCount(replace)
777 h.Cursor.ResetSelection()
778 h.Buf.RelocateCursors()
789 h.Buf.RelocateCursors()
792 InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
793 } else if nreplaced == 1 {
794 InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
796 InfoBar.Message("Nothing matched ", search)
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"))
806 // TermCmd opens a terminal in the current view
807 func (h *BufPane) TermCmd(args []string) {
810 if !TermEmuSupported {
811 InfoBar.Error("Terminal emulator not supported on this system")
816 sh := os.Getenv("SHELL")
818 InfoBar.Error("Shell environment not found")
824 term := func(i int, newtab bool) {
825 t := new(shell.Terminal)
826 t.Start(args, false, true, nil, nil)
832 id = MainTab().Panes[0].ID()
834 MainTab().Panes[i].Close()
838 tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
843 MainTab().Panes[i] = tp
844 MainTab().SetActive(i)
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
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 {
861 } else if !canceled && yes {
873 // HandleCommand handles input from the user
874 func (h *BufPane) HandleCommand(input string) {
875 args, err := shellquote.Split(input)
877 InfoBar.Error("Error parsing args ", err)
887 if _, ok := commands[inputCmd]; !ok {
888 InfoBar.Error("Unknown command ", inputCmd)
890 WriteLog("> " + input + "\n")
891 commands[inputCmd].action(h, args[1:])