12 "github.com/mitchellh/go-homedir"
17 completions []Completion
20 type StrCommand struct {
22 completions []Completion
25 var commands map[string]Command
27 var commandActions = map[string]func([]string){
40 // InitCommands initializes the default commands
42 commands = make(map[string]Command)
44 defaults := DefaultCommands()
45 parseCommands(defaults)
48 func parseCommands(userCommands map[string]StrCommand) {
49 for k, v := range userCommands {
50 MakeCommand(k, v.action, v.completions...)
54 // MakeCommand is a function to easily create new commands
55 // This can be called by plugins in Lua so that plugins can define their own commands
56 func MakeCommand(name, function string, completions ...Completion) {
57 action := commandActions[function]
58 if _, ok := commandActions[function]; !ok {
59 // If the user seems to be binding a function that doesn't exist
60 // We hope that it's a lua function that exists and bind it to that
61 action = LuaFunctionCommand(function)
64 commands[name] = Command{action, completions}
67 // DefaultCommands returns a map containing micro's default commands
68 func DefaultCommands() map[string]StrCommand {
69 return map[string]StrCommand{
70 "set": StrCommand{"Set", []Completion{OptionCompletion, NoCompletion}},
71 "bind": StrCommand{"Bind", []Completion{NoCompletion}},
72 "run": StrCommand{"Run", []Completion{NoCompletion}},
73 "quit": StrCommand{"Quit", []Completion{NoCompletion}},
74 "save": StrCommand{"Save", []Completion{NoCompletion}},
75 "replace": StrCommand{"Replace", []Completion{NoCompletion}},
76 "vsplit": StrCommand{"VSplit", []Completion{FileCompletion, NoCompletion}},
77 "hsplit": StrCommand{"HSplit", []Completion{FileCompletion, NoCompletion}},
78 "tab": StrCommand{"Tab", []Completion{FileCompletion, NoCompletion}},
79 "help": StrCommand{"Help", []Completion{HelpCompletion, NoCompletion}},
83 // Help tries to open the given help page in a horizontal split
84 func Help(args []string) {
86 // Open the default help if the user just typed "> help"
87 CurView().openHelp("help")
90 if _, ok := helpPages[helpPage]; ok {
91 CurView().openHelp(helpPage)
93 messenger.Error("Sorry, no help for ", helpPage)
98 // VSplit opens a vertical split with file given in the first argument
99 // If no file is given, it opens an empty buffer in a new split
100 func VSplit(args []string) {
102 CurView().VSplit(NewBuffer([]byte{}, ""))
105 home, _ := homedir.Dir()
106 filename = strings.Replace(filename, "~", home, 1)
107 file, err := ioutil.ReadFile(filename)
111 // File does not exist -- create an empty buffer with that name
112 buf = NewBuffer([]byte{}, filename)
114 buf = NewBuffer(file, filename)
116 CurView().VSplit(buf)
120 // HSplit opens a horizontal split with file given in the first argument
121 // If no file is given, it opens an empty buffer in a new split
122 func HSplit(args []string) {
124 CurView().HSplit(NewBuffer([]byte{}, ""))
127 home, _ := homedir.Dir()
128 filename = strings.Replace(filename, "~", home, 1)
129 file, err := ioutil.ReadFile(filename)
133 // File does not exist -- create an empty buffer with that name
134 buf = NewBuffer([]byte{}, filename)
136 buf = NewBuffer(file, filename)
138 CurView().HSplit(buf)
142 // NewTab opens the given file in a new tab
143 func NewTab(args []string) {
145 CurView().AddTab(true)
148 home, _ := homedir.Dir()
149 filename = strings.Replace(filename, "~", home, 1)
150 file, _ := ioutil.ReadFile(filename)
152 tab := NewTabFromView(NewView(NewBuffer(file, filename)))
153 tab.SetNum(len(tabs))
154 tabs = append(tabs, tab)
157 for _, t := range tabs {
158 for _, v := range t.views {
166 // Set sets an option
167 func Set(args []string) {
172 option := strings.TrimSpace(args[0])
173 value := strings.TrimSpace(args[1])
175 SetOptionAndSettings(option, value)
178 // Bind creates a new keybinding
179 func Bind(args []string) {
181 messenger.Error("Incorrect number of arguments")
184 BindKey(args[0], args[1])
187 // Run runs a shell command in the background
188 func Run(args []string) {
189 // Run a shell command in the background (openTerm is false)
190 HandleShellCommand(strings.Join(args, " "), false)
193 // Quit closes the main view
194 func Quit(args []string) {
195 // Close the main view
199 // Save saves the buffer in the main view
200 func Save(args []string) {
201 // Save the main view
205 // Replace runs search and replace
206 func Replace(args []string) {
207 // This is a regex to parse the replace expression
208 // We allow no quotes if there are no spaces, but if you want to search
209 // for or replace an expression with spaces, you can add double quotes
210 r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
211 replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
212 if len(replaceCmd) < 2 {
213 // We need to find both a search and replace expression
214 messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
219 if len(replaceCmd) == 3 {
220 // The user included some flags
221 flags = replaceCmd[2]
224 search := string(replaceCmd[0])
225 replace := string(replaceCmd[1])
227 // If the search and replace expressions have quotes, we need to remove those
228 if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
229 search = search[1 : len(search)-1]
231 if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
232 replace = replace[1 : len(replace)-1]
235 // We replace all escaped double quotes to real double quotes
236 search = strings.Replace(search, `\"`, `"`, -1)
237 replace = strings.Replace(replace, `\"`, `"`, -1)
238 // Replace some things so users can actually insert newlines and tabs in replacements
239 replace = strings.Replace(replace, "\\n", "\n", -1)
240 replace = strings.Replace(replace, "\\t", "\t", -1)
242 regex, err := regexp.Compile(search)
244 // There was an error with the user's regex
245 messenger.Error(err.Error())
253 match := regex.FindStringIndex(view.Buf.String())
258 if strings.Contains(flags, "c") {
259 // The 'check' flag was used
260 Search(search, view, true)
262 if settings["syntax"].(bool) {
263 view.matches = Match(view)
266 choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
268 if view.Cursor.HasSelection() {
269 view.Cursor.Loc = view.Cursor.CurSelection[0]
270 view.Cursor.ResetSelection()
276 view.Cursor.DeleteSelection()
277 view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
278 view.Cursor.ResetSelection()
281 if view.Cursor.HasSelection() {
282 searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
284 searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
289 view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
292 view.Cursor.Relocate()
295 messenger.Message("Replaced ", found, " occurences of ", search)
296 } else if found == 1 {
297 messenger.Message("Replaced ", found, " occurence of ", search)
299 messenger.Message("Nothing matched ", search)
303 // RunShellCommand executes a shell command and returns the output/error
304 func RunShellCommand(input string) (string, error) {
305 inputCmd := strings.Split(input, " ")[0]
306 args := strings.Split(input, " ")[1:]
308 cmd := exec.Command(inputCmd, args...)
309 outputBytes := &bytes.Buffer{}
310 cmd.Stdout = outputBytes
311 cmd.Stderr = outputBytes
313 err := cmd.Wait() // wait for command to finish
314 outstring := outputBytes.String()
315 return outstring, err
318 // HandleShellCommand runs the shell command
319 // The openTerm argument specifies whether a terminal should be opened (for viewing output
320 // or interacting with stdin)
321 func HandleShellCommand(input string, openTerm bool) {
322 inputCmd := strings.Split(input, " ")[0]
324 // Simply run the command in the background and notify the user when it's done
325 messenger.Message("Running...")
327 output, err := RunShellCommand(input)
328 totalLines := strings.Split(output, "\n")
330 if len(totalLines) < 3 {
332 messenger.Message(inputCmd, " exited without error")
334 messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
337 messenger.Message(output)
339 // We have to make sure to redraw
343 // Shut down the screen because we're going to interact directly with the shell
347 args := strings.Split(input, " ")[1:]
349 // Set up everything for the command
350 cmd := exec.Command(inputCmd, args...)
352 cmd.Stdout = os.Stdout
353 cmd.Stderr = os.Stderr
355 // This is a trap for Ctrl-C so that it doesn't kill micro
356 // Instead we trap Ctrl-C to kill the program we're running
357 c := make(chan os.Signal, 1)
358 signal.Notify(c, os.Interrupt)
369 // This is just so we don't return right away and let the user press enter to return
372 // Start the screen back up
377 // HandleCommand handles input from the user
378 func HandleCommand(input string) {
379 inputCmd := strings.Split(input, " ")[0]
380 args := strings.Split(input, " ")[1:]
382 if _, ok := commands[inputCmd]; !ok {
383 messenger.Error("Unkown command ", inputCmd)
385 commands[inputCmd].action(args)