]> git.lizzy.rs Git - micro.git/blob - cmd/micro/command.go
Store string keys for bindings
[micro.git] / cmd / micro / command.go
1 package main
2
3 import (
4         "bytes"
5         "fmt"
6         "os"
7         "os/exec"
8         "os/signal"
9         "path/filepath"
10         "regexp"
11         "runtime"
12         "strconv"
13         "strings"
14
15         humanize "github.com/dustin/go-humanize"
16         "github.com/zyedidia/micro/cmd/micro/shellwords"
17 )
18
19 // A Command contains a action (a function to call) as well as information about how to autocomplete the command
20 type Command struct {
21         action      func([]string)
22         completions []Completion
23 }
24
25 // A StrCommand is similar to a command but keeps the name of the action
26 type StrCommand struct {
27         action      string
28         completions []Completion
29 }
30
31 var commands map[string]Command
32
33 var commandActions map[string]func([]string)
34
35 func init() {
36         commandActions = map[string]func([]string){
37                 "Set":        Set,
38                 "SetLocal":   SetLocal,
39                 "Show":       Show,
40                 "ShowKey":    ShowKey,
41                 "Run":        Run,
42                 "Bind":       Bind,
43                 "Quit":       Quit,
44                 "Save":       Save,
45                 "Replace":    Replace,
46                 "ReplaceAll": ReplaceAll,
47                 "VSplit":     VSplit,
48                 "HSplit":     HSplit,
49                 "Tab":        NewTab,
50                 "Help":       Help,
51                 "Eval":       Eval,
52                 "ToggleLog":  ToggleLog,
53                 "Plugin":     PluginCmd,
54                 "Reload":     Reload,
55                 "Cd":         Cd,
56                 "Pwd":        Pwd,
57                 "Open":       Open,
58                 "TabSwitch":  TabSwitch,
59                 "MemUsage":   MemUsage,
60                 "Retab":      Retab,
61                 "Raw":        Raw,
62         }
63 }
64
65 // InitCommands initializes the default commands
66 func InitCommands() {
67         commands = make(map[string]Command)
68
69         defaults := DefaultCommands()
70         parseCommands(defaults)
71 }
72
73 func parseCommands(userCommands map[string]StrCommand) {
74         for k, v := range userCommands {
75                 MakeCommand(k, v.action, v.completions...)
76         }
77 }
78
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)
87         }
88
89         commands[name] = Command{action, completions}
90 }
91
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                 "run":        {"Run", []Completion{NoCompletion}},
101                 "quit":       {"Quit", []Completion{NoCompletion}},
102                 "save":       {"Save", []Completion{NoCompletion}},
103                 "replace":    {"Replace", []Completion{NoCompletion}},
104                 "replaceall": {"ReplaceAll", []Completion{NoCompletion}},
105                 "vsplit":     {"VSplit", []Completion{FileCompletion, NoCompletion}},
106                 "hsplit":     {"HSplit", []Completion{FileCompletion, NoCompletion}},
107                 "tab":        {"Tab", []Completion{FileCompletion, NoCompletion}},
108                 "help":       {"Help", []Completion{HelpCompletion, NoCompletion}},
109                 "eval":       {"Eval", []Completion{NoCompletion}},
110                 "log":        {"ToggleLog", []Completion{NoCompletion}},
111                 "plugin":     {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
112                 "reload":     {"Reload", []Completion{NoCompletion}},
113                 "cd":         {"Cd", []Completion{FileCompletion}},
114                 "pwd":        {"Pwd", []Completion{NoCompletion}},
115                 "open":       {"Open", []Completion{FileCompletion}},
116                 "tabswitch":  {"TabSwitch", []Completion{NoCompletion}},
117                 "memusage":   {"MemUsage", []Completion{NoCompletion}},
118                 "retab":      {"Retab", []Completion{NoCompletion}},
119                 "raw":        {"Raw", []Completion{NoCompletion}},
120         }
121 }
122
123 // PluginCmd installs, removes, updates, lists, or searches for given plugins
124 func PluginCmd(args []string) {
125         if len(args) >= 1 {
126                 switch args[0] {
127                 case "install":
128                         installedVersions := GetInstalledVersions(false)
129                         for _, plugin := range args[1:] {
130                                 pp := GetAllPluginPackages().Get(plugin)
131                                 if pp == nil {
132                                         messenger.Error("Unknown plugin \"" + plugin + "\"")
133                                 } else if err := pp.IsInstallable(); err != nil {
134                                         messenger.Error("Error installing ", plugin, ": ", err)
135                                 } else {
136                                         for _, installed := range installedVersions {
137                                                 if pp.Name == installed.pack.Name {
138                                                         if pp.Versions[0].Version.Compare(installed.Version) == 1 {
139                                                                 messenger.Error(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
140                                                         } else {
141                                                                 messenger.Error(pp.Name, " is already installed")
142                                                         }
143                                                 }
144                                         }
145                                         pp.Install()
146                                 }
147                         }
148                 case "remove":
149                         removed := ""
150                         for _, plugin := range args[1:] {
151                                 // check if the plugin exists.
152                                 if _, ok := loadedPlugins[plugin]; ok {
153                                         UninstallPlugin(plugin)
154                                         removed += plugin + " "
155                                         continue
156                                 }
157                         }
158                         if !IsSpaces(removed) {
159                                 messenger.Message("Removed ", removed)
160                         } else {
161                                 messenger.Error("The requested plugins do not exist")
162                         }
163                 case "update":
164                         UpdatePlugins(args[1:])
165                 case "list":
166                         plugins := GetInstalledVersions(false)
167                         messenger.AddLog("----------------")
168                         messenger.AddLog("The following plugins are currently installed:\n")
169                         for _, p := range plugins {
170                                 messenger.AddLog(fmt.Sprintf("%s (%s)", p.pack.Name, p.Version))
171                         }
172                         messenger.AddLog("----------------")
173                         if len(plugins) > 0 {
174                                 if CurView().Type != vtLog {
175                                         ToggleLog([]string{})
176                                 }
177                         }
178                 case "search":
179                         plugins := SearchPlugin(args[1:])
180                         messenger.Message(len(plugins), " plugins found")
181                         for _, p := range plugins {
182                                 messenger.AddLog("----------------")
183                                 messenger.AddLog(p.String())
184                         }
185                         messenger.AddLog("----------------")
186                         if len(plugins) > 0 {
187                                 if CurView().Type != vtLog {
188                                         ToggleLog([]string{})
189                                 }
190                         }
191                 case "available":
192                         packages := GetAllPluginPackages()
193                         messenger.AddLog("Available Plugins:")
194                         for _, pkg := range packages {
195                                 messenger.AddLog(pkg.Name)
196                         }
197                         if CurView().Type != vtLog {
198                                 ToggleLog([]string{})
199                         }
200                 }
201         } else {
202                 messenger.Error("Not enough arguments")
203         }
204 }
205
206 func Retab(args []string) {
207         CurView().Retab(true)
208 }
209
210 func Raw(args []string) {
211         buf := NewBufferFromString("", "Raw events")
212
213         view := NewView(buf)
214         view.Buf.Insert(view.Cursor.Loc, "Warning: Showing raw event escape codes\n")
215         view.Buf.Insert(view.Cursor.Loc, "Use CtrlQ to exit\n")
216         view.Type = vtRaw
217         tab := NewTabFromView(view)
218         tab.SetNum(len(tabs))
219         tabs = append(tabs, tab)
220         curTab = len(tabs) - 1
221         if len(tabs) == 2 {
222                 for _, t := range tabs {
223                         for _, v := range t.views {
224                                 v.ToggleTabbar()
225                         }
226                 }
227         }
228 }
229
230 // TabSwitch switches to a given tab either by name or by number
231 func TabSwitch(args []string) {
232         if len(args) > 0 {
233                 num, err := strconv.Atoi(args[0])
234                 if err != nil {
235                         // Check for tab with this name
236
237                         found := false
238                         for _, t := range tabs {
239                                 v := t.views[t.CurView]
240                                 if v.Buf.GetName() == args[0] {
241                                         curTab = v.TabNum
242                                         found = true
243                                 }
244                         }
245                         if !found {
246                                 messenger.Error("Could not find tab: ", err)
247                         }
248                 } else {
249                         num--
250                         if num >= 0 && num < len(tabs) {
251                                 curTab = num
252                         } else {
253                                 messenger.Error("Invalid tab index")
254                         }
255                 }
256         }
257 }
258
259 // Cd changes the current working directory
260 func Cd(args []string) {
261         if len(args) > 0 {
262                 path := ReplaceHome(args[0])
263                 err := os.Chdir(path)
264                 if err != nil {
265                         messenger.Error("Error with cd: ", err)
266                         return
267                 }
268                 wd, _ := os.Getwd()
269                 for _, tab := range tabs {
270                         for _, view := range tab.views {
271                                 if len(view.Buf.name) == 0 {
272                                         continue
273                                 }
274
275                                 view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
276                                 if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
277                                         view.Buf.Path = view.Buf.AbsPath
278                                 }
279                         }
280                 }
281         }
282 }
283
284 // MemUsage prints micro's memory usage
285 // Alloc shows how many bytes are currently in use
286 // Sys shows how many bytes have been requested from the operating system
287 // NumGC shows how many times the GC has been run
288 // Note that Go commonly reserves more memory from the OS than is currently in-use/required
289 // Additionally, even if Go returns memory to the OS, the OS does not always claim it because
290 // there may be plenty of memory to spare
291 func MemUsage(args []string) {
292         var mem runtime.MemStats
293         runtime.ReadMemStats(&mem)
294
295         messenger.Message(fmt.Sprintf("Alloc: %v, Sys: %v, NumGC: %v", humanize.Bytes(mem.Alloc), humanize.Bytes(mem.Sys), mem.NumGC))
296 }
297
298 // Pwd prints the current working directory
299 func Pwd(args []string) {
300         wd, err := os.Getwd()
301         if err != nil {
302                 messenger.Message(err.Error())
303         } else {
304                 messenger.Message(wd)
305         }
306 }
307
308 // Open opens a new buffer with a given filename
309 func Open(args []string) {
310         if len(args) > 0 {
311                 filename := args[0]
312                 // the filename might or might not be quoted, so unquote first then join the strings.
313                 args, err := shellwords.Split(filename)
314                 if err != nil {
315                         messenger.Error("Error parsing args ", err)
316                         return
317                 }
318                 filename = strings.Join(args, " ")
319
320                 CurView().Open(filename)
321         } else {
322                 messenger.Error("No filename")
323         }
324 }
325
326 // ToggleLog toggles the log view
327 func ToggleLog(args []string) {
328         buffer := messenger.getBuffer()
329         if CurView().Type != vtLog {
330                 CurView().HSplit(buffer)
331                 CurView().Type = vtLog
332                 RedrawAll()
333                 buffer.Cursor.Loc = buffer.Start()
334                 CurView().Relocate()
335                 buffer.Cursor.Loc = buffer.End()
336                 CurView().Relocate()
337         } else {
338                 CurView().Quit(true)
339         }
340 }
341
342 // Reload reloads all files (syntax files, colorschemes...)
343 func Reload(args []string) {
344         LoadAll()
345 }
346
347 // Help tries to open the given help page in a horizontal split
348 func Help(args []string) {
349         if len(args) < 1 {
350                 // Open the default help if the user just typed "> help"
351                 CurView().openHelp("help")
352         } else {
353                 helpPage := args[0]
354                 if FindRuntimeFile(RTHelp, helpPage) != nil {
355                         CurView().openHelp(helpPage)
356                 } else {
357                         messenger.Error("Sorry, no help for ", helpPage)
358                 }
359         }
360 }
361
362 // VSplit opens a vertical split with file given in the first argument
363 // If no file is given, it opens an empty buffer in a new split
364 func VSplit(args []string) {
365         if len(args) == 0 {
366                 CurView().VSplit(NewBufferFromString("", ""))
367         } else {
368                 filename := args[0]
369                 filename = ReplaceHome(filename)
370                 file, err := os.Open(filename)
371                 fileInfo, _ := os.Stat(filename)
372
373                 if err == nil && fileInfo.IsDir() {
374                         messenger.Error(filename, " is a directory")
375                         return
376                 }
377
378                 defer file.Close()
379
380                 var buf *Buffer
381                 if err != nil {
382                         // File does not exist -- create an empty buffer with that name
383                         buf = NewBufferFromString("", filename)
384                 } else {
385                         buf = NewBuffer(file, FSize(file), filename)
386                 }
387                 CurView().VSplit(buf)
388         }
389 }
390
391 // HSplit opens a horizontal split with file given in the first argument
392 // If no file is given, it opens an empty buffer in a new split
393 func HSplit(args []string) {
394         if len(args) == 0 {
395                 CurView().HSplit(NewBufferFromString("", ""))
396         } else {
397                 filename := args[0]
398                 filename = ReplaceHome(filename)
399                 file, err := os.Open(filename)
400                 fileInfo, _ := os.Stat(filename)
401
402                 if err == nil && fileInfo.IsDir() {
403                         messenger.Error(filename, " is a directory")
404                         return
405                 }
406
407                 defer file.Close()
408
409                 var buf *Buffer
410                 if err != nil {
411                         // File does not exist -- create an empty buffer with that name
412                         buf = NewBufferFromString("", filename)
413                 } else {
414                         buf = NewBuffer(file, FSize(file), filename)
415                 }
416                 CurView().HSplit(buf)
417         }
418 }
419
420 // Eval evaluates a lua expression
421 func Eval(args []string) {
422         if len(args) >= 1 {
423                 err := L.DoString(args[0])
424                 if err != nil {
425                         messenger.Error(err)
426                 }
427         } else {
428                 messenger.Error("Not enough arguments")
429         }
430 }
431
432 // NewTab opens the given file in a new tab
433 func NewTab(args []string) {
434         if len(args) == 0 {
435                 CurView().AddTab(true)
436         } else {
437                 filename := args[0]
438                 filename = ReplaceHome(filename)
439                 file, err := os.Open(filename)
440                 fileInfo, _ := os.Stat(filename)
441
442                 if err == nil && fileInfo.IsDir() {
443                         messenger.Error(filename, " is a directory")
444                         return
445                 }
446
447                 defer file.Close()
448
449                 var buf *Buffer
450                 if err != nil {
451                         buf = NewBufferFromString("", filename)
452                 } else {
453                         buf = NewBuffer(file, FSize(file), filename)
454                 }
455
456                 tab := NewTabFromView(NewView(buf))
457                 tab.SetNum(len(tabs))
458                 tabs = append(tabs, tab)
459                 curTab = len(tabs) - 1
460                 if len(tabs) == 2 {
461                         for _, t := range tabs {
462                                 for _, v := range t.views {
463                                         v.ToggleTabbar()
464                                 }
465                         }
466                 }
467         }
468 }
469
470 // Set sets an option
471 func Set(args []string) {
472         if len(args) < 2 {
473                 messenger.Error("Not enough arguments")
474                 return
475         }
476
477         option := args[0]
478         value := args[1]
479
480         SetOptionAndSettings(option, value)
481 }
482
483 // SetLocal sets an option local to the buffer
484 func SetLocal(args []string) {
485         if len(args) < 2 {
486                 messenger.Error("Not enough arguments")
487                 return
488         }
489
490         option := args[0]
491         value := args[1]
492
493         err := SetLocalOption(option, value, CurView())
494         if err != nil {
495                 messenger.Error(err.Error())
496         }
497 }
498
499 // Show shows the value of the given option
500 func Show(args []string) {
501         if len(args) < 1 {
502                 messenger.Error("Please provide an option to show")
503                 return
504         }
505
506         option := GetOption(args[0])
507
508         if option == nil {
509                 messenger.Error(args[0], " is not a valid option")
510                 return
511         }
512
513         messenger.Message(option)
514 }
515
516 // ShowKey displays the action that a key is bound to
517 func ShowKey(args []string) {
518         if len(args) < 1 {
519                 messenger.Error("Please provide a key to show")
520                 return
521         }
522
523         if action, ok := bindingsStr[args[0]]; ok {
524                 messenger.Message(action)
525         } else {
526                 messenger.Message(args[0], " has no binding")
527         }
528 }
529
530 // Bind creates a new keybinding
531 func Bind(args []string) {
532         if len(args) < 2 {
533                 messenger.Error("Not enough arguments")
534                 return
535         }
536         BindKey(args[0], args[1])
537 }
538
539 // Run runs a shell command in the background
540 func Run(args []string) {
541         // Run a shell command in the background (openTerm is false)
542         HandleShellCommand(shellwords.Join(args...), false, true)
543 }
544
545 // Quit closes the main view
546 func Quit(args []string) {
547         // Close the main view
548         CurView().Quit(true)
549 }
550
551 // Save saves the buffer in the main view
552 func Save(args []string) {
553         if len(args) == 0 {
554                 // Save the main view
555                 CurView().Save(true)
556         } else {
557                 CurView().Buf.SaveAs(args[0])
558         }
559 }
560
561 // Replace runs search and replace
562 func Replace(args []string) {
563         if len(args) < 2 || len(args) > 4 {
564                 // We need to find both a search and replace expression
565                 messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
566                 return
567         }
568
569         all := false
570         noRegex := false
571
572         if len(args) > 2 {
573                 for _, arg := range args[2:] {
574                         switch arg {
575                         case "-a":
576                                 all = true
577                         case "-l":
578                                 noRegex = true
579                         default:
580                                 messenger.Error("Invalid flag: " + arg)
581                                 return
582                         }
583                 }
584         }
585
586         search := string(args[0])
587
588         if noRegex {
589                 search = regexp.QuoteMeta(search)
590         }
591
592         replace := string(args[1])
593
594         regex, err := regexp.Compile("(?m)" + search)
595         if err != nil {
596                 // There was an error with the user's regex
597                 messenger.Error(err.Error())
598                 return
599         }
600
601         view := CurView()
602
603         found := 0
604         replaceAll := func() {
605                 var deltas []Delta
606                 deltaXOffset := Count(replace) - Count(search)
607                 for i := 0; i < view.Buf.LinesNum(); i++ {
608                         matches := regex.FindAllIndex(view.Buf.lines[i].data, -1)
609                         str := string(view.Buf.lines[i].data)
610
611                         if matches != nil {
612                                 xOffset := 0
613                                 for _, m := range matches {
614                                         from := Loc{runePos(m[0], str) + xOffset, i}
615                                         to := Loc{runePos(m[1], str) + xOffset, i}
616
617                                         xOffset += deltaXOffset
618
619                                         deltas = append(deltas, Delta{replace, from, to})
620                                         found++
621                                 }
622                         }
623                 }
624                 view.Buf.MultipleReplace(deltas)
625         }
626
627         if all {
628                 replaceAll()
629         } else {
630                 for {
631                         // The 'check' flag was used
632                         Search(search, view, true)
633                         if !view.Cursor.HasSelection() {
634                                 break
635                         }
636                         view.Relocate()
637                         RedrawAll()
638                         choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
639                         if canceled {
640                                 if view.Cursor.HasSelection() {
641                                         view.Cursor.Loc = view.Cursor.CurSelection[0]
642                                         view.Cursor.ResetSelection()
643                                 }
644                                 messenger.Reset()
645                                 break
646                         } else if choice == 'a' {
647                                 if view.Cursor.HasSelection() {
648                                         view.Cursor.Loc = view.Cursor.CurSelection[0]
649                                         view.Cursor.ResetSelection()
650                                 }
651                                 messenger.Reset()
652                                 replaceAll()
653                                 break
654                         } else if choice == 'y' {
655                                 view.Cursor.DeleteSelection()
656                                 view.Buf.Insert(view.Cursor.Loc, replace)
657                                 view.Cursor.ResetSelection()
658                                 messenger.Reset()
659                                 found++
660                         }
661                         if view.Cursor.HasSelection() {
662                                 searchStart = view.Cursor.CurSelection[1]
663                         } else {
664                                 searchStart = view.Cursor.Loc
665                         }
666                 }
667         }
668         view.Cursor.Relocate()
669
670         if found > 1 {
671                 messenger.Message("Replaced ", found, " occurrences of ", search)
672         } else if found == 1 {
673                 messenger.Message("Replaced ", found, " occurrence of ", search)
674         } else {
675                 messenger.Message("Nothing matched ", search)
676         }
677 }
678
679 // ReplaceAll replaces search term all at once
680 func ReplaceAll(args []string) {
681         // aliased to Replace command
682         Replace(append(args, "-a"))
683 }
684
685 // RunShellCommand executes a shell command and returns the output/error
686 func RunShellCommand(input string) (string, error) {
687         args, err := shellwords.Split(input)
688         if err != nil {
689                 return "", err
690         }
691         inputCmd := args[0]
692
693         cmd := exec.Command(inputCmd, args[1:]...)
694         outputBytes := &bytes.Buffer{}
695         cmd.Stdout = outputBytes
696         cmd.Stderr = outputBytes
697         cmd.Start()
698         err = cmd.Wait() // wait for command to finish
699         outstring := outputBytes.String()
700         return outstring, err
701 }
702
703 // HandleShellCommand runs the shell command
704 // The openTerm argument specifies whether a terminal should be opened (for viewing output
705 // or interacting with stdin)
706 func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
707         args, err := shellwords.Split(input)
708         if err != nil {
709                 return ""
710         }
711         inputCmd := args[0]
712         if !openTerm {
713                 // Simply run the command in the background and notify the user when it's done
714                 messenger.Message("Running...")
715                 go func() {
716                         output, err := RunShellCommand(input)
717                         totalLines := strings.Split(output, "\n")
718
719                         if len(totalLines) < 3 {
720                                 if err == nil {
721                                         messenger.Message(inputCmd, " exited without error")
722                                 } else {
723                                         messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
724                                 }
725                         } else {
726                                 messenger.Message(output)
727                         }
728                         // We have to make sure to redraw
729                         RedrawAll()
730                 }()
731         } else {
732                 // Shut down the screen because we're going to interact directly with the shell
733                 screen.Fini()
734                 screen = nil
735
736                 args := args[1:]
737
738                 // Set up everything for the command
739                 var output string
740                 cmd := exec.Command(inputCmd, args...)
741                 cmd.Stdin = os.Stdin
742                 cmd.Stdout = os.Stdout
743                 cmd.Stderr = os.Stderr
744
745                 // This is a trap for Ctrl-C so that it doesn't kill micro
746                 // Instead we trap Ctrl-C to kill the program we're running
747                 c := make(chan os.Signal, 1)
748                 signal.Notify(c, os.Interrupt)
749                 go func() {
750                         for range c {
751                                 cmd.Process.Kill()
752                         }
753                 }()
754
755                 cmd.Start()
756                 err := cmd.Wait()
757
758                 if err != nil {
759                         output = err.Error()
760                 }
761
762                 if waitToFinish {
763                         // This is just so we don't return right away and let the user press enter to return
764                         TermMessage("")
765                 }
766
767                 // Start the screen back up
768                 InitScreen()
769
770                 return output
771         }
772         return ""
773 }
774
775 // HandleCommand handles input from the user
776 func HandleCommand(input string) {
777         args, err := shellwords.Split(input)
778         if err != nil {
779                 messenger.Error("Error parsing args ", err)
780                 return
781         }
782
783         inputCmd := args[0]
784
785         if _, ok := commands[inputCmd]; !ok {
786                 messenger.Error("Unknown command ", inputCmd)
787         } else {
788                 commands[inputCmd].action(args[1:])
789         }
790 }