]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/command.go
Use messenger error instead of termerror
[micro.git] / cmd / micro / command.go
index 85565bdc75c7470073d66402ba41b4de51cc526f..7ef885133cc92d5eb2edb85b07efe96d74c86bb5 100644 (file)
@@ -9,16 +9,21 @@ import (
        "os/signal"
        "path/filepath"
        "regexp"
+       "runtime"
+       "strconv"
        "strings"
 
+       humanize "github.com/dustin/go-humanize"
        "github.com/mitchellh/go-homedir"
 )
 
+// A Command contains a action (a function to call) as well as information about how to autocomplete the command
 type Command struct {
        action      func([]string)
        completions []Completion
 }
 
+// A StrCommand is similar to a command but keeps the name of the action
 type StrCommand struct {
        action      string
        completions []Completion
@@ -49,6 +54,8 @@ func init() {
                "Cd":        Cd,
                "Pwd":       Pwd,
                "Open":      Open,
+               "TabSwitch": TabSwitch,
+               "MemUsage":  MemUsage,
        }
 }
 
@@ -82,25 +89,27 @@ func MakeCommand(name, function string, completions ...Completion) {
 // DefaultCommands returns a map containing micro's default commands
 func DefaultCommands() map[string]StrCommand {
        return map[string]StrCommand{
-               "set":      {"Set", []Completion{OptionCompletion, NoCompletion}},
-               "setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
-               "show":     {"Show", []Completion{OptionCompletion, NoCompletion}},
-               "bind":     {"Bind", []Completion{NoCompletion}},
-               "run":      {"Run", []Completion{NoCompletion}},
-               "quit":     {"Quit", []Completion{NoCompletion}},
-               "save":     {"Save", []Completion{NoCompletion}},
-               "replace":  {"Replace", []Completion{NoCompletion}},
-               "vsplit":   {"VSplit", []Completion{FileCompletion, NoCompletion}},
-               "hsplit":   {"HSplit", []Completion{FileCompletion, NoCompletion}},
-               "tab":      {"Tab", []Completion{FileCompletion, NoCompletion}},
-               "help":     {"Help", []Completion{HelpCompletion, NoCompletion}},
-               "eval":     {"Eval", []Completion{NoCompletion}},
-               "log":      {"ToggleLog", []Completion{NoCompletion}},
-               "plugin":   {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
-               "reload":   {"Reload", []Completion{NoCompletion}},
-               "cd":       {"Cd", []Completion{FileCompletion}},
-               "pwd":      {"Pwd", []Completion{NoCompletion}},
-               "open":     {"Open", []Completion{FileCompletion}},
+               "set":       {"Set", []Completion{OptionCompletion, NoCompletion}},
+               "setlocal":  {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
+               "show":      {"Show", []Completion{OptionCompletion, NoCompletion}},
+               "bind":      {"Bind", []Completion{NoCompletion}},
+               "run":       {"Run", []Completion{NoCompletion}},
+               "quit":      {"Quit", []Completion{NoCompletion}},
+               "save":      {"Save", []Completion{NoCompletion}},
+               "replace":   {"Replace", []Completion{NoCompletion}},
+               "vsplit":    {"VSplit", []Completion{FileCompletion, NoCompletion}},
+               "hsplit":    {"HSplit", []Completion{FileCompletion, NoCompletion}},
+               "tab":       {"Tab", []Completion{FileCompletion, NoCompletion}},
+               "help":      {"Help", []Completion{HelpCompletion, NoCompletion}},
+               "eval":      {"Eval", []Completion{NoCompletion}},
+               "log":       {"ToggleLog", []Completion{NoCompletion}},
+               "plugin":    {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
+               "reload":    {"Reload", []Completion{NoCompletion}},
+               "cd":        {"Cd", []Completion{FileCompletion}},
+               "pwd":       {"Pwd", []Completion{NoCompletion}},
+               "open":      {"Open", []Completion{FileCompletion}},
+               "tabswitch": {"TabSwitch", []Completion{NoCompletion}},
+               "memusage":  {"MemUsage", []Completion{NoCompletion}},
        }
 }
 
@@ -187,6 +196,36 @@ func PluginCmd(args []string) {
        }
 }
 
+// TabSwitch switches to a given tab either by name or by number
+func TabSwitch(args []string) {
+       if len(args) > 0 {
+               num, err := strconv.Atoi(args[0])
+               if err != nil {
+                       // Check for tab with this name
+
+                       found := false
+                       for _, t := range tabs {
+                               v := t.views[t.CurView]
+                               if v.Buf.GetName() == args[0] {
+                                       curTab = v.TabNum
+                                       found = true
+                               }
+                       }
+                       if !found {
+                               messenger.Error("Could not find tab: ", err)
+                       }
+               } else {
+                       num--
+                       if num >= 0 && num < len(tabs) {
+                               curTab = num
+                       } else {
+                               messenger.Error("Invalid tab index")
+                       }
+               }
+       }
+}
+
+// Cd changes the current working directory
 func Cd(args []string) {
        if len(args) > 0 {
                home, _ := homedir.Dir()
@@ -204,6 +243,21 @@ func Cd(args []string) {
        }
 }
 
+// MemUsage 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) {
+       var mem runtime.MemStats
+       runtime.ReadMemStats(&mem)
+
+       messenger.Message(fmt.Sprintf("Alloc: %v, Sys: %v, NumGC: %v", humanize.Bytes(mem.Alloc), humanize.Bytes(mem.Sys), mem.NumGC))
+}
+
+// Pwd prints the current working directory
 func Pwd(args []string) {
        wd, err := os.Getwd()
        if err != nil {
@@ -213,6 +267,7 @@ func Pwd(args []string) {
        }
 }
 
+// Open opens a new buffer with a given filename
 func Open(args []string) {
        if len(args) > 0 {
                filename := args[0]
@@ -225,6 +280,7 @@ func Open(args []string) {
        }
 }
 
+// ToggleLog toggles the log view
 func ToggleLog(args []string) {
        buffer := messenger.getBuffer()
        if CurView().Type != vtLog {
@@ -240,6 +296,7 @@ func ToggleLog(args []string) {
        }
 }
 
+// Reload reloads all files (syntax files, colorschemes...)
 func Reload(args []string) {
        LoadAll()
 }
@@ -263,20 +320,27 @@ func Help(args []string) {
 // If no file is given, it opens an empty buffer in a new split
 func VSplit(args []string) {
        if len(args) == 0 {
-               CurView().VSplit(NewBuffer(strings.NewReader(""), ""))
+               CurView().VSplit(NewBufferFromString("", ""))
        } else {
                filename := args[0]
                home, _ := homedir.Dir()
                filename = strings.Replace(filename, "~", home, 1)
                file, err := os.Open(filename)
+               fileInfo, _ := os.Stat(filename)
+
+               if err == nil && fileInfo.IsDir() {
+                       messenger.Error(filename, " is a directory")
+                       return
+               }
+
                defer file.Close()
 
                var buf *Buffer
                if err != nil {
                        // File does not exist -- create an empty buffer with that name
-                       buf = NewBuffer(strings.NewReader(""), filename)
+                       buf = NewBufferFromString("", filename)
                } else {
-                       buf = NewBuffer(file, filename)
+                       buf = NewBuffer(file, FSize(file), filename)
                }
                CurView().VSplit(buf)
        }
@@ -286,20 +350,27 @@ func VSplit(args []string) {
 // If no file is given, it opens an empty buffer in a new split
 func HSplit(args []string) {
        if len(args) == 0 {
-               CurView().HSplit(NewBuffer(strings.NewReader(""), ""))
+               CurView().HSplit(NewBufferFromString("", ""))
        } else {
                filename := args[0]
                home, _ := homedir.Dir()
                filename = strings.Replace(filename, "~", home, 1)
                file, err := os.Open(filename)
+               fileInfo, _ := os.Stat(filename)
+
+               if err == nil && fileInfo.IsDir() {
+                       messenger.Error(filename, " is a directory")
+                       return
+               }
+
                defer file.Close()
 
                var buf *Buffer
                if err != nil {
                        // File does not exist -- create an empty buffer with that name
-                       buf = NewBuffer(strings.NewReader(""), filename)
+                       buf = NewBufferFromString("", filename)
                } else {
-                       buf = NewBuffer(file, filename)
+                       buf = NewBuffer(file, FSize(file), filename)
                }
                CurView().HSplit(buf)
        }
@@ -325,10 +396,24 @@ func NewTab(args []string) {
                filename := args[0]
                home, _ := homedir.Dir()
                filename = strings.Replace(filename, "~", home, 1)
-               file, _ := os.Open(filename)
+               file, err := os.Open(filename)
+               fileInfo, _ := os.Stat(filename)
+
+               if err == nil && fileInfo.IsDir() {
+                       messenger.Error(filename, " is a directory")
+                       return
+               }
+
                defer file.Close()
 
-               tab := NewTabFromView(NewView(NewBuffer(file, filename)))
+               var buf *Buffer
+               if err != nil {
+                       buf = NewBufferFromString("", filename)
+               } else {
+                       buf = NewBuffer(file, FSize(file), filename)
+               }
+
+               tab := NewTabFromView(NewView(buf))
                tab.SetNum(len(tabs))
                tabs = append(tabs, tab)
                curTab = len(tabs) - 1
@@ -349,8 +434,8 @@ func Set(args []string) {
                return
        }
 
-       option := strings.TrimSpace(args[0])
-       value := strings.TrimSpace(args[1])
+       option := args[0]
+       value := args[1]
 
        SetOptionAndSettings(option, value)
 }
@@ -362,8 +447,8 @@ func SetLocal(args []string) {
                return
        }
 
-       option := strings.TrimSpace(args[0])
-       value := strings.TrimSpace(args[1])
+       option := args[0]
+       value := args[1]
 
        err := SetLocalOption(option, value, CurView())
        if err != nil {
@@ -454,9 +539,6 @@ func Replace(args []string) {
                                break
                        }
                        view.Relocate()
-                       if view.Buf.Settings["syntax"].(bool) {
-                               view.matches = Match(view)
-                       }
                        RedrawAll()
                        choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
                        if canceled {
@@ -483,28 +565,25 @@ func Replace(args []string) {
                        }
                }
        } else {
-               bufStr := view.Buf.String()
-               matches := regex.FindAllStringIndex(bufStr, -1)
-               if matches != nil && len(matches) > 0 {
-                       prevMatchCount := runePos(matches[0][0], bufStr)
-                       searchCount := runePos(matches[0][1], bufStr) - prevMatchCount
-                       from := FromCharPos(matches[0][0], view.Buf)
-                       to := from.Move(searchCount, view.Buf)
-                       adjust := Count(replace) - searchCount
-                       view.Buf.Replace(from, to, replace)
-                       if len(matches) > 1 {
-                               for _, match := range matches[1:] {
-                                       found++
-                                       matchCount := runePos(match[0], bufStr)
-                                       searchCount = runePos(match[1], bufStr) - matchCount
-                                       from = from.Move(matchCount-prevMatchCount+adjust, view.Buf)
-                                       to = from.Move(searchCount, view.Buf)
+               // var deltas []Delta
+               for i := 0; i < view.Buf.LinesNum(); i++ {
+                       // view.Buf.lines[i].data = regex.ReplaceAll(view.Buf.lines[i].data, []byte(replace))
+                       for {
+                               m := regex.FindIndex(view.Buf.lines[i].data)
+
+                               if m != nil {
+                                       from := Loc{m[0], i}
+                                       to := Loc{m[1], i}
+
+                                       // deltas = append(deltas, Delta{replace, from, to})
                                        view.Buf.Replace(from, to, replace)
-                                       prevMatchCount = matchCount
-                                       adjust = Count(replace) - searchCount
+                                       found++
+                               } else {
+                                       break
                                }
                        }
                }
+               // view.Buf.MultipleReplace(deltas)
        }
        view.Cursor.Relocate()