12 "github.com/mitchellh/go-homedir"
15 var commands map[string]func([]string)
17 var commandActions = map[string]func([]string){
28 // InitCommands initializes the default commands
30 commands = make(map[string]func([]string))
32 defaults := DefaultCommands()
33 parseCommands(defaults)
36 func parseCommands(userCommands map[string]string) {
37 for k, v := range userCommands {
42 // MakeCommand is a function to easily create new commands
43 // This can be called by plugins in Lua so that plugins can define their own commands
44 func MakeCommand(name, function string) {
45 action := commandActions[function]
46 if _, ok := commandActions[function]; !ok {
47 // If the user seems to be binding a function that doesn't exist
48 // We hope that it's a lua function that exists and bind it to that
49 action = LuaFunctionCommand(function)
52 commands[name] = action
55 // DefaultCommands returns a map containing micro's default commands
56 func DefaultCommands() map[string]string {
57 return map[string]string{
69 // VSplit opens a vertical split with file given in the first argument
70 // If no file is given, it opens an empty buffer in a new split
71 func VSplit(args []string) {
73 CurView().VSplit(NewBuffer([]byte{}, ""))
76 home, _ := homedir.Dir()
77 filename = strings.Replace(filename, "~", home, 1)
78 file, err := ioutil.ReadFile(filename)
82 // File does not exist -- create an empty buffer with that name
83 buf = NewBuffer([]byte{}, filename)
85 buf = NewBuffer(file, filename)
91 // HSplit opens a horizontal split with file given in the first argument
92 // If no file is given, it opens an empty buffer in a new split
93 func HSplit(args []string) {
95 CurView().HSplit(NewBuffer([]byte{}, ""))
98 home, _ := homedir.Dir()
99 filename = strings.Replace(filename, "~", home, 1)
100 file, err := ioutil.ReadFile(filename)
104 // File does not exist -- create an empty buffer with that name
105 buf = NewBuffer([]byte{}, filename)
107 buf = NewBuffer(file, filename)
109 CurView().HSplit(buf)
113 // Set sets an option
114 func Set(args []string) {
115 // Set an option and we have to set it for every view
116 for _, tab := range tabs {
117 for _, view := range tab.views {
118 SetOption(view, args)
123 // Bind creates a new keybinding
124 func Bind(args []string) {
126 messenger.Error("Incorrect number of arguments")
129 BindKey(args[0], args[1])
132 // Run runs a shell command in the background
133 func Run(args []string) {
134 // Run a shell command in the background (openTerm is false)
135 HandleShellCommand(strings.Join(args, " "), false)
138 // Quit closes the main view
139 func Quit(args []string) {
140 // Close the main view
144 // Save saves the buffer in the main view
145 func Save(args []string) {
146 // Save the main view
150 // Replace runs search and replace
151 func Replace(args []string) {
152 // This is a regex to parse the replace expression
153 // We allow no quotes if there are no spaces, but if you want to search
154 // for or replace an expression with spaces, you can add double quotes
155 r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
156 replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
157 if len(replaceCmd) < 2 {
158 // We need to find both a search and replace expression
159 messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
164 if len(replaceCmd) == 3 {
165 // The user included some flags
166 flags = replaceCmd[2]
169 search := string(replaceCmd[0])
170 replace := string(replaceCmd[1])
172 // If the search and replace expressions have quotes, we need to remove those
173 if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
174 search = search[1 : len(search)-1]
176 if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
177 replace = replace[1 : len(replace)-1]
180 // We replace all escaped double quotes to real double quotes
181 search = strings.Replace(search, `\"`, `"`, -1)
182 replace = strings.Replace(replace, `\"`, `"`, -1)
183 // Replace some things so users can actually insert newlines and tabs in replacements
184 replace = strings.Replace(replace, "\\n", "\n", -1)
185 replace = strings.Replace(replace, "\\t", "\t", -1)
187 regex, err := regexp.Compile(search)
189 // There was an error with the user's regex
190 messenger.Error(err.Error())
198 match := regex.FindStringIndex(view.Buf.String())
203 if strings.Contains(flags, "c") {
204 // The 'check' flag was used
205 Search(search, view, true)
207 if settings["syntax"].(bool) {
208 view.matches = Match(view)
211 choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
213 if view.Cursor.HasSelection() {
214 view.Cursor.Loc = view.Cursor.CurSelection[0]
215 view.Cursor.ResetSelection()
221 view.Cursor.DeleteSelection()
222 view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
223 view.Cursor.ResetSelection()
226 if view.Cursor.HasSelection() {
227 searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
229 searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
234 view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace)
237 view.Cursor.Relocate()
240 messenger.Message("Replaced ", found, " occurences of ", search)
241 } else if found == 1 {
242 messenger.Message("Replaced ", found, " occurence of ", search)
244 messenger.Message("Nothing matched ", search)
248 // RunShellCommand executes a shell command and returns the output/error
249 func RunShellCommand(input string) (string, error) {
250 inputCmd := strings.Split(input, " ")[0]
251 args := strings.Split(input, " ")[1:]
253 cmd := exec.Command(inputCmd, args...)
254 outputBytes := &bytes.Buffer{}
255 cmd.Stdout = outputBytes
256 cmd.Stderr = outputBytes
258 err := cmd.Wait() // wait for command to finish
259 outstring := outputBytes.String()
260 return outstring, err
263 // HandleShellCommand runs the shell command
264 // The openTerm argument specifies whether a terminal should be opened (for viewing output
265 // or interacting with stdin)
266 func HandleShellCommand(input string, openTerm bool) {
267 inputCmd := strings.Split(input, " ")[0]
269 // Simply run the command in the background and notify the user when it's done
270 messenger.Message("Running...")
272 output, err := RunShellCommand(input)
273 totalLines := strings.Split(output, "\n")
275 if len(totalLines) < 3 {
277 messenger.Message(inputCmd, " exited without error")
279 messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
282 messenger.Message(output)
284 // We have to make sure to redraw
288 // Shut down the screen because we're going to interact directly with the shell
292 args := strings.Split(input, " ")[1:]
294 // Set up everything for the command
295 cmd := exec.Command(inputCmd, args...)
297 cmd.Stdout = os.Stdout
298 cmd.Stderr = os.Stderr
300 // This is a trap for Ctrl-C so that it doesn't kill micro
301 // Instead we trap Ctrl-C to kill the program we're running
302 c := make(chan os.Signal, 1)
303 signal.Notify(c, os.Interrupt)
314 // This is just so we don't return right away and let the user press enter to return
317 // Start the screen back up
322 // HandleCommand handles input from the user
323 func HandleCommand(input string) {
324 inputCmd := strings.Split(input, " ")[0]
325 args := strings.Split(input, " ")[1:]
327 if _, ok := commands[inputCmd]; !ok {
328 messenger.Error("Unkown command ", inputCmd)
330 commands[inputCmd](args)