]> git.lizzy.rs Git - micro.git/blob - cmd/micro/command.go
695d446f6dfa9de3b038ac3079387b74e63aafef
[micro.git] / cmd / micro / command.go
1 package main
2
3 import (
4         "bytes"
5         "os"
6         "os/exec"
7         "os/signal"
8         "regexp"
9         "strings"
10 )
11
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:]
16
17         cmd := exec.Command(inputCmd, args...)
18         outputBytes := &bytes.Buffer{}
19         cmd.Stdout = outputBytes
20         cmd.Stderr = outputBytes
21         cmd.Start()
22         err := cmd.Wait() // wait for command to finish
23         outstring := outputBytes.String()
24         return outstring, err
25 }
26
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]
32         if !openTerm {
33                 // Simply run the command in the background and notify the user when it's done
34                 messenger.Message("Running...")
35                 go func() {
36                         output, err := RunShellCommand(input)
37                         totalLines := strings.Split(output, "\n")
38
39                         if len(totalLines) < 3 {
40                                 if err == nil {
41                                         messenger.Message(inputCmd, " exited without error")
42                                 } else {
43                                         messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
44                                 }
45                         } else {
46                                 messenger.Message(output)
47                         }
48                         // We have to make sure to redraw
49                         RedrawAll()
50                 }()
51         } else {
52                 // Shut down the screen because we're going to interact directly with the shell
53                 screen.Fini()
54                 screen = nil
55
56                 args := strings.Split(input, " ")[1:]
57
58                 // Set up everything for the command
59                 cmd := exec.Command(inputCmd, args...)
60                 cmd.Stdin = os.Stdin
61                 cmd.Stdout = os.Stdout
62                 cmd.Stderr = os.Stderr
63
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)
68                 go func() {
69                         for range c {
70                                 cmd.Process.Kill()
71                         }
72                 }()
73
74                 // Start the command
75                 cmd.Start()
76                 cmd.Wait()
77
78                 // This is just so we don't return right away and let the user press enter to return
79                 TermMessage("")
80
81                 // Start the screen back up
82                 InitScreen()
83         }
84 }
85
86 // HandleCommand handles input from the user
87 func HandleCommand(input string) {
88         inputCmd := strings.Split(input, " ")[0]
89         args := strings.Split(input, " ")[1:]
90
91         switch inputCmd {
92         case "set":
93                 // Set an option and we have to set it for every view
94                 for _, view := range views {
95                         SetOption(view, args)
96                 }
97         case "run":
98                 // Run a shell command in the background (openTerm is false)
99                 HandleShellCommand(strings.Join(args, " "), false)
100         case "quit":
101                 // This is a bit weird because micro only has one view for now so there is no way to close
102                 // a single view
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) ") {
106                         screen.Fini()
107                         os.Exit(0)
108                 }
109         case "save":
110                 // Save the main view
111                 views[mainView].Save()
112         case "replace":
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, " "))
121                         return
122                 }
123
124                 var flags string
125                 if len(replaceCmd) == 3 {
126                         // The user included some flags
127                         flags = replaceCmd[2]
128                 }
129
130                 search := string(replaceCmd[0])
131                 replace := string(replaceCmd[1])
132
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]
136                 }
137                 if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
138                         replace = replace[1 : len(replace)-1]
139                 }
140
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)
147
148                 regex, err := regexp.Compile(search)
149                 if err != nil {
150                         // There was an error with the user's regex
151                         messenger.Error(err.Error())
152                         return
153                 }
154
155                 view := views[mainView]
156
157                 found := false
158                 for {
159                         match := regex.FindStringIndex(view.Buf.String())
160                         if match == nil {
161                                 break
162                         }
163                         found = true
164                         if strings.Contains(flags, "c") {
165                                 // The 'check' flag was used
166                                 Search(search, view, true)
167                                 view.Relocate()
168                                 if settings["syntax"].(bool) {
169                                         view.matches = Match(view)
170                                 }
171                                 RedrawAll()
172                                 choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
173                                 if canceled {
174                                         if view.Cursor.HasSelection() {
175                                                 view.Cursor.SetLoc(view.Cursor.curSelection[0])
176                                                 view.Cursor.ResetSelection()
177                                         }
178                                         messenger.Reset()
179                                         return
180                                 }
181                                 if choice {
182                                         view.Cursor.DeleteSelection()
183                                         view.Buf.Insert(match[0], replace)
184                                         view.Cursor.ResetSelection()
185                                         messenger.Reset()
186                                 } else {
187                                         if view.Cursor.HasSelection() {
188                                                 searchStart = view.Cursor.curSelection[1]
189                                         } else {
190                                                 searchStart = ToCharPos(view.Cursor.x, view.Cursor.y, view.Buf)
191                                         }
192                                         continue
193                                 }
194                         } else {
195                                 view.Buf.Replace(match[0], match[1], replace)
196                         }
197                 }
198                 if !found {
199                         messenger.Message("Nothing matched " + search)
200                 }
201         default:
202                 messenger.Error("Unknown command: " + inputCmd)
203         }
204 }