12 // RunShellCommand executes a shell command and returns the output/error
13 func RunShellCommand(input string) (string, error) {
14 inputCmd := strings.Split(input, " ")[0]
15 args := strings.Split(input, " ")[1:]
17 cmd := exec.Command(inputCmd, args...)
18 outputBytes := &bytes.Buffer{}
19 cmd.Stdout = outputBytes
20 cmd.Stderr = outputBytes
22 err := cmd.Wait() // wait for command to finish
23 outstring := outputBytes.String()
27 // HandleShellCommand runs the shell command
28 // The openTerm argument specifies whether a terminal should be opened (for viewing output
29 // or interacting with stdin)
30 func HandleShellCommand(input string, openTerm bool) {
31 inputCmd := strings.Split(input, " ")[0]
33 // Simply run the command in the background and notify the user when it's done
34 messenger.Message("Running...")
36 output, err := RunShellCommand(input)
37 totalLines := strings.Split(output, "\n")
39 if len(totalLines) < 3 {
41 messenger.Message(inputCmd, " exited without error")
43 messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
46 messenger.Message(output)
48 // We have to make sure to redraw
52 // Shut down the screen because we're going to interact directly with the shell
56 args := strings.Split(input, " ")[1:]
58 // Set up everything for the command
59 cmd := exec.Command(inputCmd, args...)
61 cmd.Stdout = os.Stdout
62 cmd.Stderr = os.Stderr
64 // This is a trap for Ctrl-C so that it doesn't kill micro
65 // Instead we trap Ctrl-C to kill the program we're running
66 c := make(chan os.Signal, 1)
67 signal.Notify(c, os.Interrupt)
78 // This is just so we don't return right away and let the user press enter to return
81 // Start the screen back up
86 // HandleCommand handles input from the user
87 func HandleCommand(input string) {
88 inputCmd := strings.Split(input, " ")[0]
89 args := strings.Split(input, " ")[1:]
93 // Set an option and we have to set it for every view
94 for _, view := range views {
98 // Run a shell command in the background (openTerm is false)
99 HandleShellCommand(strings.Join(args, " "), false)
101 // This is a bit weird because micro only has one view for now so there is no way to close
103 // Currently if multiple views were open, it would close all of them, and not check the non-mainviews
104 // for unsaved changes. This, and the behavior of Ctrl-Q need to be changed when splits are implemented
105 if views[mainView].CanClose("Quit anyway? (yes, no, save) ") {
110 // Save the main view
111 views[mainView].Save()
113 // This is a regex to parse the replace expression
114 // We allow no quotes if there are no spaces, but if you want to search
115 // for or replace an expression with spaces, you can add double quotes
116 r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
117 replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
118 if len(replaceCmd) < 2 {
119 // We need to find both a search and replace expression
120 messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
125 if len(replaceCmd) == 3 {
126 // The user included some flags
127 flags = replaceCmd[2]
130 search := string(replaceCmd[0])
131 replace := string(replaceCmd[1])
133 // If the search and replace expressions have quotes, we need to remove those
134 if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
135 search = search[1 : len(search)-1]
137 if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
138 replace = replace[1 : len(replace)-1]
141 // We replace all escaped double quotes to real double quotes
142 search = strings.Replace(search, `\"`, `"`, -1)
143 replace = strings.Replace(replace, `\"`, `"`, -1)
144 // Replace some things so users can actually insert newlines and tabs in replacements
145 replace = strings.Replace(replace, "\\n", "\n", -1)
146 replace = strings.Replace(replace, "\\t", "\t", -1)
148 regex, err := regexp.Compile(search)
150 // There was an error with the user's regex
151 messenger.Error(err.Error())
155 view := views[mainView]
159 match := regex.FindStringIndex(view.Buf.String())
164 if strings.Contains(flags, "c") {
165 // The 'check' flag was used
166 Search(search, view, true)
168 if settings["syntax"].(bool) {
169 view.matches = Match(view)
172 choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
174 if view.Cursor.HasSelection() {
175 view.Cursor.SetLoc(view.Cursor.curSelection[0])
176 view.Cursor.ResetSelection()
182 view.Cursor.DeleteSelection()
183 view.Buf.Insert(match[0], replace)
184 view.Cursor.ResetSelection()
187 if view.Cursor.HasSelection() {
188 searchStart = view.Cursor.curSelection[1]
190 searchStart = ToCharPos(view.Cursor.x, view.Cursor.y, view.Buf)
195 view.Buf.Replace(match[0], match[1], replace)
199 messenger.Message("Nothing matched " + search)
202 messenger.Error("Unknown command: " + inputCmd)