package action
import (
- "log"
+ "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"
"github.com/zyedidia/micro/cmd/micro/util"
)
// 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
}
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
"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}},
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
// 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())
}
}
-// 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")
+ }
+}
+
+// ToggleLogCmd toggles the log view
+func (h *BufHandler) ToggleLogCmd(args []string) {
}
-// ToggleLog toggles the log view
-func ToggleLog(args []string) {
+// ReloadCmd reloads all files (syntax files, colorschemes...)
+func (h *BufHandler) ReloadCmd(args []string) {
}
-// Reload reloads all files (syntax files, colorschemes...)
-func Reload(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
}
-// Help tries to open the given help page in a horizontal split
-func Help(args []string) {
+// 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
}
- log.Println("loaded")
- 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)
+ }
+}
+
+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
}
-// NewTab opens the given file in a new tab
-func NewTab(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)
+ }
}
-// Set sets an option
-func Set(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)
+ }
+
}
-// SetLocal sets an option local to the buffer
-func SetLocal(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)
}
-// Show shows the value of the given option
-func Show(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 := config.Bindings[args[0]]; ok {
+ InfoBar.Message(action)
+ } else {
+ InfoBar.Message(args[0], " has no binding")
+ }
}
-// ShowKey displays the action that a key is bound to
-func ShowKey(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)
+ }
}
-// Bind creates a new keybinding
-func Bind(args []string) {
+// 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)
+ } else {
+ go func() {
+ InfoBar.Message(runf())
+ screen.Redraw()
+ }()
+ }
}
-// 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)
if _, ok := commands[inputCmd]; !ok {
InfoBar.Error("Unknown command ", inputCmd)
} else {
- commands[inputCmd].action(args[1:])
+ commands[inputCmd].action(h, args[1:])
}
}