13 "github.com/zyedidia/micro/cmd/micro/buffer"
14 "github.com/zyedidia/micro/cmd/micro/config"
15 "github.com/zyedidia/micro/cmd/micro/screen"
16 "github.com/zyedidia/micro/cmd/micro/shell"
17 "github.com/zyedidia/micro/cmd/micro/shellwords"
18 "github.com/zyedidia/micro/cmd/micro/util"
21 // A Command contains an action (a function to call) as well as information about how to autocomplete the command
23 action func(*BufHandler, []string)
24 completions []Completion
27 // A StrCommand is similar to a command but keeps the name of the action
28 type StrCommand struct {
30 completions []Completion
33 var commands map[string]Command
35 var commandActions = map[string]func(*BufHandler, []string){
36 "Set": (*BufHandler).SetCmd,
37 "SetLocal": (*BufHandler).SetLocalCmd,
38 "Show": (*BufHandler).ShowCmd,
39 "ShowKey": (*BufHandler).ShowKeyCmd,
40 "Run": (*BufHandler).RunCmd,
41 "Bind": (*BufHandler).BindCmd,
42 "Unbind": (*BufHandler).UnbindCmd,
43 "Quit": (*BufHandler).QuitCmd,
44 "Save": (*BufHandler).SaveCmd,
45 "Replace": (*BufHandler).ReplaceCmd,
46 "ReplaceAll": (*BufHandler).ReplaceAllCmd,
47 "VSplit": (*BufHandler).VSplitCmd,
48 "HSplit": (*BufHandler).HSplitCmd,
49 "Tab": (*BufHandler).NewTabCmd,
50 "Help": (*BufHandler).HelpCmd,
51 "Eval": (*BufHandler).EvalCmd,
52 "ToggleLog": (*BufHandler).ToggleLogCmd,
53 "Plugin": (*BufHandler).PluginCmd,
54 "Reload": (*BufHandler).ReloadCmd,
55 "Cd": (*BufHandler).CdCmd,
56 "Pwd": (*BufHandler).PwdCmd,
57 "Open": (*BufHandler).OpenCmd,
58 "TabSwitch": (*BufHandler).TabSwitchCmd,
59 "Term": (*BufHandler).TermCmd,
60 "MemUsage": (*BufHandler).MemUsageCmd,
61 "Retab": (*BufHandler).RetabCmd,
62 "Raw": (*BufHandler).RawCmd,
65 // InitCommands initializes the default commands
67 commands = make(map[string]Command)
69 defaults := DefaultCommands()
70 parseCommands(defaults)
73 func parseCommands(userCommands map[string]StrCommand) {
74 for k, v := range userCommands {
75 MakeCommand(k, v.action, v.completions...)
79 // MakeCommand is a function to easily create new commands
80 // This can be called by plugins in Lua so that plugins can define their own commands
81 func MakeCommand(name, function string, completions ...Completion) {
82 action := commandActions[function]
83 // if _, ok := commandActions[function]; !ok {
84 // If the user seems to be binding a function that doesn't exist
85 // We hope that it's a lua function that exists and bind it to that
86 // action = LuaFunctionCommand(function)
89 commands[name] = Command{action, completions}
92 // DefaultCommands returns a map containing micro's default commands
93 func DefaultCommands() map[string]StrCommand {
94 return map[string]StrCommand{
95 "set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
96 "setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
97 "show": {"Show", []Completion{OptionCompletion, NoCompletion}},
98 "showkey": {"ShowKey", []Completion{NoCompletion}},
99 "bind": {"Bind", []Completion{NoCompletion}},
100 "unbind": {"Unbind", []Completion{NoCompletion}},
101 "run": {"Run", []Completion{NoCompletion}},
102 "quit": {"Quit", []Completion{NoCompletion}},
103 "save": {"Save", []Completion{NoCompletion}},
104 "replace": {"Replace", []Completion{NoCompletion}},
105 "replaceall": {"ReplaceAll", []Completion{NoCompletion}},
106 "vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
107 "hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
108 "tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
109 "help": {"Help", []Completion{HelpCompletion, NoCompletion}},
110 "eval": {"Eval", []Completion{NoCompletion}},
111 "log": {"ToggleLog", []Completion{NoCompletion}},
112 "plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
113 "reload": {"Reload", []Completion{NoCompletion}},
114 "cd": {"Cd", []Completion{FileCompletion}},
115 "pwd": {"Pwd", []Completion{NoCompletion}},
116 "open": {"Open", []Completion{FileCompletion}},
117 "tabswitch": {"TabSwitch", []Completion{NoCompletion}},
118 "term": {"Term", []Completion{NoCompletion}},
119 "memusage": {"MemUsage", []Completion{NoCompletion}},
120 "retab": {"Retab", []Completion{NoCompletion}},
121 "raw": {"Raw", []Completion{NoCompletion}},
125 // CommandEditAction returns a bindable function that opens a prompt with
126 // the given string and executes the command when the user presses
128 func CommandEditAction(prompt string) BufKeyAction {
129 return func(h *BufHandler) bool {
130 InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) {
132 MainTab().CurPane().HandleCommand(resp)
139 // CommandAction returns a bindable function which executes the
141 func CommandAction(cmd string) BufKeyAction {
142 return func(h *BufHandler) bool {
143 MainTab().CurPane().HandleCommand(cmd)
148 // PluginCmd installs, removes, updates, lists, or searches for given plugins
149 func (h *BufHandler) PluginCmd(args []string) {
152 // RetabCmd changes all spaces to tabs or all tabs to spaces
153 // depending on the user's settings
154 func (h *BufHandler) RetabCmd(args []string) {
158 // RawCmd opens a new raw view which displays the escape sequences micro
159 // is receiving in real-time
160 func (h *BufHandler) RawCmd(args []string) {
163 // TabSwitchCmd switches to a given tab either by name or by number
164 func (h *BufHandler) TabSwitchCmd(args []string) {
166 num, err := strconv.Atoi(args[0])
168 // Check for tab with this name
171 for i, t := range Tabs.List {
172 if t.Panes[t.active].Name() == args[0] {
178 InfoBar.Error("Could not find tab: ", err)
182 if num >= 0 && num < len(Tabs.List) {
185 InfoBar.Error("Invalid tab index")
191 // CdCmd changes the current working directory
192 func (h *BufHandler) CdCmd(args []string) {
194 path, err := util.ReplaceHome(args[0])
205 for _, b := range buffer.OpenBuffers {
207 b.Path, _ = util.MakeRelative(b.AbsPath, wd)
208 if p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {
216 // MemUsageCmd prints micro's memory usage
217 // Alloc shows how many bytes are currently in use
218 // Sys shows how many bytes have been requested from the operating system
219 // NumGC shows how many times the GC has been run
220 // Note that Go commonly reserves more memory from the OS than is currently in-use/required
221 // Additionally, even if Go returns memory to the OS, the OS does not always claim it because
222 // there may be plenty of memory to spare
223 func (h *BufHandler) MemUsageCmd(args []string) {
224 InfoBar.Message(util.GetMemStats())
227 // PwdCmd prints the current working directory
228 func (h *BufHandler) PwdCmd(args []string) {
229 wd, err := os.Getwd()
231 InfoBar.Message(err.Error())
237 // OpenCmd opens a new buffer with a given filename
238 func (h *BufHandler) OpenCmd(args []string) {
241 // the filename might or might not be quoted, so unquote first then join the strings.
242 args, err := shellwords.Split(filename)
244 InfoBar.Error("Error parsing args ", err)
247 filename = strings.Join(args, " ")
250 b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
257 if h.Buf.Modified() {
258 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
259 if !canceled && !yes {
261 } else if !canceled && yes {
270 InfoBar.Error("No filename")
274 // ToggleLogCmd toggles the log view
275 func (h *BufHandler) ToggleLogCmd(args []string) {
278 // ReloadCmd reloads all files (syntax files, colorschemes...)
279 func (h *BufHandler) ReloadCmd(args []string) {
282 func (h *BufHandler) openHelp(page string) error {
283 if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
284 return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err))
286 helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
287 helpBuffer.SetName("Help " + page)
289 if h.Buf.Type == buffer.BTHelp {
290 h.OpenBuffer(helpBuffer)
292 h.HSplitBuf(helpBuffer)
298 // HelpCmd tries to open the given help page in a horizontal split
299 func (h *BufHandler) HelpCmd(args []string) {
301 // Open the default help if the user just typed "> help"
304 if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
305 err := h.openHelp(args[0])
310 InfoBar.Error("Sorry, no help for ", args[0])
315 // VSplitCmd opens a vertical split with file given in the first argument
316 // If no file is given, it opens an empty buffer in a new split
317 func (h *BufHandler) VSplitCmd(args []string) {
319 // Open an empty vertical split
324 buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
333 // HSplitCmd opens a horizontal split with file given in the first argument
334 // If no file is given, it opens an empty buffer in a new split
335 func (h *BufHandler) HSplitCmd(args []string) {
337 // Open an empty horizontal split
342 buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
351 // EvalCmd evaluates a lua expression
352 func (h *BufHandler) EvalCmd(args []string) {
355 // NewTabCmd opens the given file in a new tab
356 func (h *BufHandler) NewTabCmd(args []string) {
357 width, height := screen.Screen.Size()
358 iOffset := config.GetInfoBarOffset()
360 for _, a := range args {
361 b, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
366 tp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)
368 Tabs.SetActive(len(Tabs.List) - 1)
371 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
372 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
374 Tabs.SetActive(len(Tabs.List) - 1)
378 func SetGlobalOption(option, value string) error {
379 if _, ok := config.GlobalSettings[option]; !ok {
380 return config.ErrInvalidOption
383 nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
388 config.GlobalSettings[option] = nativeValue
390 if option == "colorscheme" {
392 config.InitColorscheme()
393 for _, b := range buffer.OpenBuffers {
398 if option == "infobar" || option == "keymenu" {
402 if option == "mouse" {
403 if !nativeValue.(bool) {
404 screen.Screen.DisableMouse()
406 screen.Screen.EnableMouse()
410 for _, b := range buffer.OpenBuffers {
411 b.SetOption(option, value)
414 config.WriteSettings(config.ConfigDir + "/settings.json")
419 // SetCmd sets an option
420 func (h *BufHandler) SetCmd(args []string) {
422 InfoBar.Error("Not enough arguments")
429 err := SetGlobalOption(option, value)
430 if err == config.ErrInvalidOption {
431 err := h.Buf.SetOption(option, value)
435 } else if err != nil {
440 // SetLocalCmd sets an option local to the buffer
441 func (h *BufHandler) SetLocalCmd(args []string) {
443 InfoBar.Error("Not enough arguments")
450 err := h.Buf.SetOption(option, value)
457 // ShowCmd shows the value of the given option
458 func (h *BufHandler) ShowCmd(args []string) {
460 InfoBar.Error("Please provide an option to show")
464 var option interface{}
465 if opt, ok := h.Buf.Settings[args[0]]; ok {
467 } else if opt, ok := config.GlobalSettings[args[0]]; ok {
472 InfoBar.Error(args[0], " is not a valid option")
476 InfoBar.Message(option)
479 // ShowKeyCmd displays the action that a key is bound to
480 func (h *BufHandler) ShowKeyCmd(args []string) {
482 InfoBar.Error("Please provide a key to show")
486 if action, ok := config.Bindings[args[0]]; ok {
487 InfoBar.Message(action)
489 InfoBar.Message(args[0], " has no binding")
493 // BindCmd creates a new keybinding
494 func (h *BufHandler) BindCmd(args []string) {
496 InfoBar.Error("Not enough arguments")
500 _, err := TryBindKey(args[0], args[1], true)
506 // UnbindCmd binds a key to its default action
507 func (h *BufHandler) UnbindCmd(args []string) {
509 InfoBar.Error("Not enough arguements")
513 err := UnbindKey(args[0])
519 // RunCmd runs a shell command in the background
520 func (h *BufHandler) RunCmd(args []string) {
521 runf, err := shell.RunBackgroundShell(shellwords.Join(args...))
526 InfoBar.Message(runf())
532 // QuitCmd closes the main view
533 func (h *BufHandler) QuitCmd(args []string) {
537 // SaveCmd saves the buffer in the main view
538 func (h *BufHandler) SaveCmd(args []string) {
542 // ReplaceCmd runs search and replace
543 func (h *BufHandler) ReplaceCmd(args []string) {
544 if len(args) < 2 || len(args) > 4 {
545 // We need to find both a search and replace expression
546 InfoBar.Error("Invalid replace statement: " + strings.Join(args, " "))
554 for _, arg := range args[2:] {
561 InfoBar.Error("Invalid flag: " + arg)
570 search = regexp.QuoteMeta(search)
573 replace := []byte(args[1])
575 var regex *regexp.Regexp
577 if h.Buf.Settings["ignorecase"].(bool) {
578 regex, err = regexp.Compile("(?im)" + search)
580 regex, err = regexp.Compile("(?m)" + search)
583 // There was an error with the user's regex
589 start := h.Buf.Start()
591 if h.Cursor.HasSelection() {
592 start = h.Cursor.CurSelection[0]
593 end = h.Cursor.CurSelection[1]
596 nreplaced = h.Buf.ReplaceRegex(start, end, regex, replace)
598 inRange := func(l buffer.Loc) bool {
599 return l.GreaterEqual(start) && l.LessThan(end)
604 var doReplacement func()
605 doReplacement = func() {
606 locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
611 if !found || !inRange(locs[0]) || !inRange(locs[1]) {
612 h.Cursor.ResetSelection()
617 h.Cursor.SetSelectionStart(locs[0])
618 h.Cursor.SetSelectionEnd(locs[1])
620 InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
621 if !canceled && yes {
622 h.Buf.Replace(locs[0], locs[1], replace)
624 searchLoc.X += utf8.RuneCount(replace)
625 h.Cursor.Loc = searchLoc
627 } else if !canceled && !yes {
629 searchLoc.X += utf8.RuneCount(replace)
631 h.Cursor.ResetSelection()
643 // TODO: relocate all cursors?
647 InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
648 } else if nreplaced == 1 {
649 InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
651 InfoBar.Message("Nothing matched ", search)
655 // ReplaceAllCmd replaces search term all at once
656 func (h *BufHandler) ReplaceAllCmd(args []string) {
659 // TermCmd opens a terminal in the current view
660 func (h *BufHandler) TermCmd(args []string) {
661 ps := MainTab().Panes
664 sh := os.Getenv("SHELL")
666 InfoBar.Error("Shell environment not found")
672 term := func(i int, newtab bool) {
674 t := new(shell.Terminal)
675 t.Start(args, false, true)
681 id = MainTab().Panes[0].ID()
683 MainTab().Panes[i].Close()
687 MainTab().Panes[i] = NewTermHandler(v.X, v.Y, v.Width, v.Height, t, id)
688 MainTab().SetActive(i)
691 // If there is only one open file we make a new tab instead of overwriting it
692 newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
699 for i, p := range ps {
700 if p.ID() == h.ID() {
701 if h.Buf.Modified() {
702 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
703 if !canceled && !yes {
705 } else if !canceled && yes {
717 // HandleCommand handles input from the user
718 func (h *BufHandler) HandleCommand(input string) {
719 args, err := shellwords.Split(input)
721 InfoBar.Error("Error parsing args ", err)
727 if _, ok := commands[inputCmd]; !ok {
728 InfoBar.Error("Unknown command ", inputCmd)
730 commands[inputCmd].action(h, args[1:])