]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/action/command.go
Fix yn callback bug
[micro.git] / cmd / micro / action / command.go
index 27b16729aed09ff6ffedf0dec36da27ac43103f1..ef4762df95b4d9be9d2d01d90db27c40f3493d28 100644 (file)
@@ -1,9 +1,15 @@
 package action
 
 import (
+       "errors"
+       "fmt"
        "os"
+       "path/filepath"
+       "strconv"
+       "strings"
 
        "github.com/zyedidia/micro/cmd/micro/buffer"
+       "github.com/zyedidia/micro/cmd/micro/config"
        "github.com/zyedidia/micro/cmd/micro/screen"
        "github.com/zyedidia/micro/cmd/micro/shell"
        "github.com/zyedidia/micro/cmd/micro/shellwords"
@@ -12,7 +18,7 @@ import (
 
 // A Command contains an action (a function to call) as well as information about how to autocomplete the command
 type Command struct {
-       action      func([]string)
+       action      func(*BufHandler, []string)
        completions []Completion
 }
 
@@ -24,37 +30,34 @@ type StrCommand struct {
 
 var commands map[string]Command
 
-var commandActions map[string]func([]string)
-
-func init() {
-       commandActions = map[string]func([]string){
-               "Set":        Set,
-               "SetLocal":   SetLocal,
-               "Show":       Show,
-               "ShowKey":    ShowKey,
-               "Run":        Run,
-               "Bind":       Bind,
-               "Quit":       Quit,
-               "Save":       Save,
-               "Replace":    Replace,
-               "ReplaceAll": ReplaceAll,
-               "VSplit":     VSplit,
-               "HSplit":     HSplit,
-               "Tab":        NewTab,
-               "Help":       Help,
-               "Eval":       Eval,
-               "ToggleLog":  ToggleLog,
-               "Plugin":     PluginCmd,
-               "Reload":     Reload,
-               "Cd":         Cd,
-               "Pwd":        Pwd,
-               "Open":       Open,
-               "TabSwitch":  TabSwitch,
-               "Term":       Term,
-               "MemUsage":   MemUsage,
-               "Retab":      Retab,
-               "Raw":        Raw,
-       }
+var commandActions = map[string]func(*BufHandler, []string){
+       "Set":        (*BufHandler).SetCmd,
+       "SetLocal":   (*BufHandler).SetLocalCmd,
+       "Show":       (*BufHandler).ShowCmd,
+       "ShowKey":    (*BufHandler).ShowKeyCmd,
+       "Run":        (*BufHandler).RunCmd,
+       "Bind":       (*BufHandler).BindCmd,
+       "Unbind":     (*BufHandler).UnbindCmd,
+       "Quit":       (*BufHandler).QuitCmd,
+       "Save":       (*BufHandler).SaveCmd,
+       "Replace":    (*BufHandler).ReplaceCmd,
+       "ReplaceAll": (*BufHandler).ReplaceAllCmd,
+       "VSplit":     (*BufHandler).VSplitCmd,
+       "HSplit":     (*BufHandler).HSplitCmd,
+       "Tab":        (*BufHandler).NewTabCmd,
+       "Help":       (*BufHandler).HelpCmd,
+       "Eval":       (*BufHandler).EvalCmd,
+       "ToggleLog":  (*BufHandler).ToggleLogCmd,
+       "Plugin":     (*BufHandler).PluginCmd,
+       "Reload":     (*BufHandler).ReloadCmd,
+       "Cd":         (*BufHandler).CdCmd,
+       "Pwd":        (*BufHandler).PwdCmd,
+       "Open":       (*BufHandler).OpenCmd,
+       "TabSwitch":  (*BufHandler).TabSwitchCmd,
+       "Term":       (*BufHandler).TermCmd,
+       "MemUsage":   (*BufHandler).MemUsageCmd,
+       "Retab":      (*BufHandler).RetabCmd,
+       "Raw":        (*BufHandler).RawCmd,
 }
 
 // InitCommands initializes the default commands
@@ -92,6 +95,7 @@ func DefaultCommands() map[string]StrCommand {
                "show":       {"Show", []Completion{OptionCompletion, NoCompletion}},
                "showkey":    {"ShowKey", []Completion{NoCompletion}},
                "bind":       {"Bind", []Completion{NoCompletion}},
+               "unbind":     {"Unbind", []Completion{NoCompletion}},
                "run":        {"Run", []Completion{NoCompletion}},
                "quit":       {"Quit", []Completion{NoCompletion}},
                "save":       {"Save", []Completion{NoCompletion}},
@@ -123,7 +127,7 @@ func CommandEditAction(prompt string) BufKeyAction {
        return func(h *BufHandler) bool {
                InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) {
                        if !canceled {
-                               HandleCommand(resp)
+                               MainTab().CurPane().HandleCommand(resp)
                        }
                })
                return false
@@ -134,46 +138,92 @@ func CommandEditAction(prompt string) BufKeyAction {
 // given command
 func CommandAction(cmd string) BufKeyAction {
        return func(h *BufHandler) bool {
-               HandleCommand(cmd)
+               MainTab().CurPane().HandleCommand(cmd)
                return false
        }
 }
 
 // PluginCmd installs, removes, updates, lists, or searches for given plugins
-func PluginCmd(args []string) {
+func (h *BufHandler) PluginCmd(args []string) {
 }
 
-// Retab changes all spaces to tabs or all tabs to spaces
+// RetabCmd changes all spaces to tabs or all tabs to spaces
 // depending on the user's settings
-func Retab(args []string) {
+func (h *BufHandler) RetabCmd(args []string) {
+       h.Buf.Retab()
 }
 
-// Raw opens a new raw view which displays the escape sequences micro
+// RawCmd opens a new raw view which displays the escape sequences micro
 // is receiving in real-time
-func Raw(args []string) {
-}
-
-// TabSwitch switches to a given tab either by name or by number
-func TabSwitch(args []string) {
+func (h *BufHandler) RawCmd(args []string) {
+}
+
+// TabSwitchCmd switches to a given tab either by name or by number
+func (h *BufHandler) TabSwitchCmd(args []string) {
+       if len(args) > 0 {
+               num, err := strconv.Atoi(args[0])
+               if err != nil {
+                       // Check for tab with this name
+
+                       found := false
+                       for i, t := range Tabs.List {
+                               if t.Panes[t.active].Name() == args[0] {
+                                       Tabs.SetActive(i)
+                                       found = true
+                               }
+                       }
+                       if !found {
+                               InfoBar.Error("Could not find tab: ", err)
+                       }
+               } else {
+                       num--
+                       if num >= 0 && num < len(Tabs.List) {
+                               Tabs.SetActive(num)
+                       } else {
+                               InfoBar.Error("Invalid tab index")
+                       }
+               }
+       }
 }
 
-// Cd changes the current working directory
-func Cd(args []string) {
+// CdCmd changes the current working directory
+func (h *BufHandler) CdCmd(args []string) {
+       if len(args) > 0 {
+               path, err := util.ReplaceHome(args[0])
+               if err != nil {
+                       InfoBar.Error(err)
+                       return
+               }
+               err = os.Chdir(path)
+               if err != nil {
+                       InfoBar.Error(err)
+                       return
+               }
+               wd, _ := os.Getwd()
+               for _, b := range buffer.OpenBuffers {
+                       if len(b.Path) > 0 {
+                               b.Path, _ = util.MakeRelative(b.AbsPath, wd)
+                               if p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {
+                                       b.Path = b.AbsPath
+                               }
+                       }
+               }
+       }
 }
 
-// MemUsage prints micro's memory usage
+// MemUsageCmd prints micro's memory usage
 // Alloc shows how many bytes are currently in use
 // Sys shows how many bytes have been requested from the operating system
 // NumGC shows how many times the GC has been run
 // Note that Go commonly reserves more memory from the OS than is currently in-use/required
 // Additionally, even if Go returns memory to the OS, the OS does not always claim it because
 // there may be plenty of memory to spare
-func MemUsage(args []string) {
+func (h *BufHandler) MemUsageCmd(args []string) {
        InfoBar.Message(util.GetMemStats())
 }
 
-// Pwd prints the current working directory
-func Pwd(args []string) {
+// PwdCmd prints the current working directory
+func (h *BufHandler) PwdCmd(args []string) {
        wd, err := os.Getwd()
        if err != nil {
                InfoBar.Message(err.Error())
@@ -182,86 +232,291 @@ func Pwd(args []string) {
        }
 }
 
-// Open opens a new buffer with a given filename
-func Open(args []string) {
+// OpenCmd opens a new buffer with a given filename
+func (h *BufHandler) OpenCmd(args []string) {
+       if len(args) > 0 {
+               filename := args[0]
+               // the filename might or might not be quoted, so unquote first then join the strings.
+               args, err := shellwords.Split(filename)
+               if err != nil {
+                       InfoBar.Error("Error parsing args ", err)
+                       return
+               }
+               filename = strings.Join(args, " ")
+
+               open := func() {
+                       b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
+                       if err != nil {
+                               InfoBar.Error(err)
+                               return
+                       }
+                       h.OpenBuffer(b)
+               }
+               if h.Buf.Modified() {
+                       InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
+                               if !canceled && !yes {
+                                       open()
+                               } else if !canceled && yes {
+                                       h.Save()
+                                       open()
+                               }
+                       })
+               } else {
+                       open()
+               }
+       } else {
+               InfoBar.Error("No filename")
+       }
 }
 
-// ToggleLog toggles the log view
-func ToggleLog(args []string) {
+// ToggleLogCmd toggles the log view
+func (h *BufHandler) ToggleLogCmd(args []string) {
 }
 
-// Reload reloads all files (syntax files, colorschemes...)
-func Reload(args []string) {
+// ReloadCmd reloads all files (syntax files, colorschemes...)
+func (h *BufHandler) ReloadCmd(args []string) {
 }
 
-// Help tries to open the given help page in a horizontal split
-func Help(args []string) {
+func (h *BufHandler) openHelp(page string) error {
+       if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
+               return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err))
+       } else {
+               helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp)
+               helpBuffer.SetName("Help " + page)
+
+               if h.Buf.Type == buffer.BTHelp {
+                       h.OpenBuffer(helpBuffer)
+               } else {
+                       h.HSplitBuf(helpBuffer)
+               }
+       }
+       return nil
+}
+
+// HelpCmd tries to open the given help page in a horizontal split
+func (h *BufHandler) HelpCmd(args []string) {
+       if len(args) < 1 {
+               // Open the default help if the user just typed "> help"
+               h.openHelp("help")
+       } else {
+               if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
+                       err := h.openHelp(args[0])
+                       if err != nil {
+                               InfoBar.Error(err)
+                       }
+               } else {
+                       InfoBar.Error("Sorry, no help for ", args[0])
+               }
+       }
 }
 
-// VSplit opens a vertical split with file given in the first argument
+// VSplitCmd opens a vertical split with file given in the first argument
 // If no file is given, it opens an empty buffer in a new split
-func VSplit(args []string) {
+func (h *BufHandler) VSplitCmd(args []string) {
+       if len(args) == 0 {
+               // Open an empty vertical split
+               h.VSplitAction()
+               return
+       }
+
        buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
        if err != nil {
                InfoBar.Error(err)
                return
        }
 
-       MainTab().CurPane().vsplit(buf)
+       h.VSplitBuf(buf)
 }
 
-// HSplit opens a horizontal split with file given in the first argument
+// HSplitCmd opens a horizontal split with file given in the first argument
 // If no file is given, it opens an empty buffer in a new split
-func HSplit(args []string) {
+func (h *BufHandler) HSplitCmd(args []string) {
+       if len(args) == 0 {
+               // Open an empty horizontal split
+               h.HSplitAction()
+               return
+       }
+
        buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
        if err != nil {
                InfoBar.Error(err)
                return
        }
 
-       MainTab().CurPane().hsplit(buf)
+       h.HSplitBuf(buf)
+}
+
+// EvalCmd evaluates a lua expression
+func (h *BufHandler) EvalCmd(args []string) {
 }
 
-// Eval evaluates a lua expression
-func Eval(args []string) {
+// NewTabCmd opens the given file in a new tab
+func (h *BufHandler) NewTabCmd(args []string) {
+       width, height := screen.Screen.Size()
+       iOffset := config.GetInfoBarOffset()
+       if len(args) > 0 {
+               for _, a := range args {
+                       b, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
+                       if err != nil {
+                               InfoBar.Error(err)
+                               return
+                       }
+                       tp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)
+                       Tabs.AddTab(tp)
+                       Tabs.SetActive(len(Tabs.List) - 1)
+               }
+       } else {
+               b := buffer.NewBufferFromString("", "", buffer.BTDefault)
+               tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
+               Tabs.AddTab(tp)
+               Tabs.SetActive(len(Tabs.List) - 1)
+       }
 }
 
-// NewTab opens the given file in a new tab
-func NewTab(args []string) {
+func SetGlobalOption(option, value string) error {
+       if _, ok := config.GlobalSettings[option]; !ok {
+               return config.ErrInvalidOption
+       }
+
+       nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
+       if err != nil {
+               return err
+       }
+
+       config.GlobalSettings[option] = nativeValue
+
+       if option == "colorscheme" {
+               // LoadSyntaxFiles()
+               config.InitColorscheme()
+               for _, b := range buffer.OpenBuffers {
+                       b.UpdateRules()
+               }
+       }
+
+       // TODO: info and keymenu option change
+       if option == "infobar" || option == "keymenu" {
+               Tabs.Resize()
+       }
+
+       if option == "mouse" {
+               if !nativeValue.(bool) {
+                       screen.Screen.DisableMouse()
+               } else {
+                       screen.Screen.EnableMouse()
+               }
+       }
+
+       for _, b := range buffer.OpenBuffers {
+               b.SetOption(option, value)
+       }
+
+       config.WriteSettings(config.ConfigDir + "/settings.json")
+
+       return nil
 }
 
-// Set sets an option
-func Set(args []string) {
+// SetCmd sets an option
+func (h *BufHandler) SetCmd(args []string) {
+       if len(args) < 2 {
+               InfoBar.Error("Not enough arguments")
+               return
+       }
+
+       option := args[0]
+       value := args[1]
+
+       err := SetGlobalOption(option, value)
+       if err == config.ErrInvalidOption {
+               err := h.Buf.SetOption(option, value)
+               if err != nil {
+                       InfoBar.Error(err)
+               }
+       } else if err != nil {
+               InfoBar.Error(err)
+       }
 }
 
-// SetLocal sets an option local to the buffer
-func SetLocal(args []string) {
+// SetLocalCmd sets an option local to the buffer
+func (h *BufHandler) SetLocalCmd(args []string) {
+       if len(args) < 2 {
+               InfoBar.Error("Not enough arguments")
+               return
+       }
+
+       option := args[0]
+       value := args[1]
+
+       err := h.Buf.SetOption(option, value)
+       if err != nil {
+               InfoBar.Error(err)
+       }
+
 }
 
-// Show shows the value of the given option
-func Show(args []string) {
+// ShowCmd shows the value of the given option
+func (h *BufHandler) ShowCmd(args []string) {
+       if len(args) < 1 {
+               InfoBar.Error("Please provide an option to show")
+               return
+       }
+
+       var option interface{}
+       if opt, ok := h.Buf.Settings[args[0]]; ok {
+               option = opt
+       } else if opt, ok := config.GlobalSettings[args[0]]; ok {
+               option = opt
+       }
+
+       if option == nil {
+               InfoBar.Error(args[0], " is not a valid option")
+               return
+       }
+
+       InfoBar.Message(option)
 }
 
-// ShowKey displays the action that a key is bound to
-func ShowKey(args []string) {
+// ShowKeyCmd displays the action that a key is bound to
+func (h *BufHandler) ShowKeyCmd(args []string) {
        if len(args) < 1 {
                InfoBar.Error("Please provide a key to show")
                return
        }
 
-       if action, ok := Bindings[args[0]]; ok {
+       if action, ok := config.Bindings[args[0]]; ok {
                InfoBar.Message(action)
        } else {
                InfoBar.Message(args[0], " has no binding")
        }
 }
 
-// Bind creates a new keybinding
-func Bind(args []string) {
+// BindCmd creates a new keybinding
+func (h *BufHandler) BindCmd(args []string) {
+       if len(args) < 2 {
+               InfoBar.Error("Not enough arguments")
+               return
+       }
+
+       _, err := TryBindKey(args[0], args[1], true)
+       if err != nil {
+               InfoBar.Error(err)
+       }
+}
+
+// UnbindCmd binds a key to its default action
+func (h *BufHandler) UnbindCmd(args []string) {
+       if len(args) < 1 {
+               InfoBar.Error("Not enough arguements")
+               return
+       }
+
+       err := UnbindKey(args[0])
+       if err != nil {
+               InfoBar.Error(err)
+       }
 }
 
-// Run runs a shell command in the background
-func Run(args []string) {
+// RunCmd runs a shell command in the background
+func (h *BufHandler) RunCmd(args []string) {
        runf, err := shell.RunBackgroundShell(shellwords.Join(args...))
        if err != nil {
                InfoBar.Error(err)
@@ -273,28 +528,84 @@ func Run(args []string) {
        }
 }
 
-// Quit closes the main view
-func Quit(args []string) {
+// QuitCmd closes the main view
+func (h *BufHandler) QuitCmd(args []string) {
+       h.Quit()
 }
 
-// Save saves the buffer in the main view
-func Save(args []string) {
+// SaveCmd saves the buffer in the main view
+func (h *BufHandler) SaveCmd(args []string) {
+       h.Save()
 }
 
-// Replace runs search and replace
-func Replace(args []string) {
+// ReplaceCmd runs search and replace
+func (h *BufHandler) ReplaceCmd(args []string) {
 }
 
-// ReplaceAll replaces search term all at once
-func ReplaceAll(args []string) {
+// ReplaceAllCmd replaces search term all at once
+func (h *BufHandler) ReplaceAllCmd(args []string) {
 }
 
-// Term opens a terminal in the current view
-func Term(args []string) {
+// TermCmd opens a terminal in the current view
+func (h *BufHandler) TermCmd(args []string) {
+       ps := MainTab().Panes
+
+       if len(args) == 0 {
+               sh := os.Getenv("SHELL")
+               if sh == "" {
+                       InfoBar.Error("Shell environment not found")
+                       return
+               }
+               args = []string{sh}
+       }
+
+       term := func(i int, newtab bool) {
+
+               t := new(shell.Terminal)
+               t.Start(args, false, true)
+
+               id := h.ID()
+               if newtab {
+                       h.AddTab()
+                       i = 0
+                       id = MainTab().Panes[0].ID()
+               } else {
+                       MainTab().Panes[i].Close()
+               }
+
+               v := h.GetView()
+               MainTab().Panes[i] = NewTermHandler(v.X, v.Y, v.Width, v.Height, t, id)
+               MainTab().SetActive(i)
+       }
+
+       // If there is only one open file we make a new tab instead of overwriting it
+       newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
+
+       if newtab {
+               term(0, true)
+               return
+       }
+
+       for i, p := range ps {
+               if p.ID() == h.ID() {
+                       if h.Buf.Modified() {
+                               InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
+                                       if !canceled && !yes {
+                                               term(i, false)
+                                       } else if !canceled && yes {
+                                               h.Save()
+                                               term(i, false)
+                                       }
+                               })
+                       } else {
+                               term(i, false)
+                       }
+               }
+       }
 }
 
 // HandleCommand handles input from the user
-func HandleCommand(input string) {
+func (h *BufHandler) HandleCommand(input string) {
        args, err := shellwords.Split(input)
        if err != nil {
                InfoBar.Error("Error parsing args ", err)
@@ -306,6 +617,6 @@ func HandleCommand(input string) {
        if _, ok := commands[inputCmd]; !ok {
                InfoBar.Error("Unknown command ", inputCmd)
        } else {
-               commands[inputCmd].action(args[1:])
+               commands[inputCmd].action(h, args[1:])
        }
 }