12 var commands map[string]func([]string)
14 var commandActions = map[string]func([]string){
23 // InitCommands initializes the default commands
25 commands = make(map[string]func([]string))
27 defaults := DefaultCommands()
28 parseCommands(defaults)
31 func parseCommands(userCommands map[string]string) {
32 for k, v := range userCommands {
37 // MakeCommand is a function to easily create new commands
38 // This can be called by plugins in Lua so that plugins can define their own commands
39 func MakeCommand(name, function string) {
40 action := commandActions[function]
41 if _, ok := commandActions[function]; !ok {
42 // If the user seems to be binding a function that doesn't exist
43 // We hope that it's a lua function that exists and bind it to that
44 action = LuaFunctionCommand(function)
47 commands[name] = action
50 // DefaultCommands returns a map containing micro's default commands
51 func DefaultCommands() map[string]string {
52 return map[string]string{
63 func Set(args []string) {
64 // Set an option and we have to set it for every view
65 for _, view := range views {
70 // Bind creates a new keybinding
71 func Bind(args []string) {
73 messenger.Error("Incorrect number of arguments")
76 BindKey(args[0], args[1])
79 // Run runs a shell command in the background
80 func Run(args []string) {
81 // Run a shell command in the background (openTerm is false)
82 HandleShellCommand(strings.Join(args, " "), false)
85 // Quit closes the main view
86 func Quit(args []string) {
87 // Close the main view
88 views[mainView].Quit()
91 // Save saves the buffer in the main view
92 func Save(args []string) {
94 views[mainView].Save()
97 // Replace runs search and replace
98 func Replace(args []string) {
99 // This is a regex to parse the replace expression
100 // We allow no quotes if there are no spaces, but if you want to search
101 // for or replace an expression with spaces, you can add double quotes
102 r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
103 replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
104 if len(replaceCmd) < 2 {
105 // We need to find both a search and replace expression
106 messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
111 if len(replaceCmd) == 3 {
112 // The user included some flags
113 flags = replaceCmd[2]
116 search := string(replaceCmd[0])
117 replace := string(replaceCmd[1])
119 // If the search and replace expressions have quotes, we need to remove those
120 if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
121 search = search[1 : len(search)-1]
123 if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
124 replace = replace[1 : len(replace)-1]
127 // We replace all escaped double quotes to real double quotes
128 search = strings.Replace(search, `\"`, `"`, -1)
129 replace = strings.Replace(replace, `\"`, `"`, -1)
130 // Replace some things so users can actually insert newlines and tabs in replacements
131 replace = strings.Replace(replace, "\\n", "\n", -1)
132 replace = strings.Replace(replace, "\\t", "\t", -1)
134 regex, err := regexp.Compile(search)
136 // There was an error with the user's regex
137 messenger.Error(err.Error())
141 view := views[mainView]
145 match := regex.FindStringIndex(view.Buf.String())
150 if strings.Contains(flags, "c") {
151 // The 'check' flag was used
152 Search(search, view, true)
154 if settings["syntax"].(bool) {
155 view.matches = Match(view)
158 choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
160 if view.Cursor.HasSelection() {
161 view.Cursor.Loc = view.Cursor.CurSelection[0]
162 view.Cursor.ResetSelection()
168 view.Cursor.DeleteSelection()
169 view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
170 view.Cursor.ResetSelection()
173 if view.Cursor.HasSelection() {
174 searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
176 searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
181 view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
185 messenger.Message("Nothing matched " + search)
189 // RunShellCommand executes a shell command and returns the output/error
190 func RunShellCommand(input string) (string, error) {
191 inputCmd := strings.Split(input, " ")[0]
192 args := strings.Split(input, " ")[1:]
194 cmd := exec.Command(inputCmd, args...)
195 outputBytes := &bytes.Buffer{}
196 cmd.Stdout = outputBytes
197 cmd.Stderr = outputBytes
199 err := cmd.Wait() // wait for command to finish
200 outstring := outputBytes.String()
201 return outstring, err
204 // HandleShellCommand runs the shell command
205 // The openTerm argument specifies whether a terminal should be opened (for viewing output
206 // or interacting with stdin)
207 func HandleShellCommand(input string, openTerm bool) {
208 inputCmd := strings.Split(input, " ")[0]
210 // Simply run the command in the background and notify the user when it's done
211 messenger.Message("Running...")
213 output, err := RunShellCommand(input)
214 totalLines := strings.Split(output, "\n")
216 if len(totalLines) < 3 {
218 messenger.Message(inputCmd, " exited without error")
220 messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
223 messenger.Message(output)
225 // We have to make sure to redraw
229 // Shut down the screen because we're going to interact directly with the shell
233 args := strings.Split(input, " ")[1:]
235 // Set up everything for the command
236 cmd := exec.Command(inputCmd, args...)
238 cmd.Stdout = os.Stdout
239 cmd.Stderr = os.Stderr
241 // This is a trap for Ctrl-C so that it doesn't kill micro
242 // Instead we trap Ctrl-C to kill the program we're running
243 c := make(chan os.Signal, 1)
244 signal.Notify(c, os.Interrupt)
255 // This is just so we don't return right away and let the user press enter to return
258 // Start the screen back up
263 // HandleCommand handles input from the user
264 func HandleCommand(input string) {
265 inputCmd := strings.Split(input, " ")[0]
266 args := strings.Split(input, " ")[1:]
268 if _, ok := commands[inputCmd]; !ok {
269 messenger.Error("Unkown command ", inputCmd)
271 commands[inputCmd](args)