]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
If stdout is a pipe, output to the pipe
[micro.git] / cmd / micro / micro.go
1 package main
2
3 import (
4         "flag"
5         "fmt"
6         "io/ioutil"
7         "os"
8         "runtime"
9         "sort"
10         "time"
11
12         "github.com/go-errors/errors"
13         isatty "github.com/mattn/go-isatty"
14         lua "github.com/yuin/gopher-lua"
15         "github.com/zyedidia/micro/internal/action"
16         "github.com/zyedidia/micro/internal/buffer"
17         "github.com/zyedidia/micro/internal/config"
18         "github.com/zyedidia/micro/internal/screen"
19         "github.com/zyedidia/micro/internal/shell"
20         "github.com/zyedidia/micro/internal/util"
21         "github.com/zyedidia/tcell"
22 )
23
24 var (
25         // Event channel
26         events   chan tcell.Event
27         autosave chan bool
28
29         // Command line flags
30         flagVersion   = flag.Bool("version", false, "Show the version number and information")
31         flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
32         flagOptions   = flag.Bool("options", false, "Show all option help")
33         flagDebug     = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
34         flagPlugin    = flag.String("plugin", "", "Plugin command")
35         flagClean     = flag.Bool("clean", false, "Clean configuration directory")
36         optionFlags   map[string]*string
37 )
38
39 func InitFlags() {
40         flag.Usage = func() {
41                 fmt.Println("Usage: micro [OPTIONS] [FILE]...")
42                 fmt.Println("-clean")
43                 fmt.Println("    \tCleans the configuration directory")
44                 fmt.Println("-config-dir dir")
45                 fmt.Println("    \tSpecify a custom location for the configuration directory")
46                 fmt.Println("[FILE]:LINE:COL")
47                 fmt.Println("    \tSpecify a line and column to start the cursor at when opening a buffer")
48                 fmt.Println("-options")
49                 fmt.Println("    \tShow all option help")
50                 fmt.Println("-debug")
51                 fmt.Println("    \tEnable debug mode (enables logging to ./log.txt)")
52                 fmt.Println("-version")
53                 fmt.Println("    \tShow the version number and information")
54
55                 fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
56                 fmt.Println("-plugin install [PLUGIN]...")
57                 fmt.Println("    \tInstall plugin(s)")
58                 fmt.Println("-plugin remove [PLUGIN]...")
59                 fmt.Println("    \tRemove plugin(s)")
60                 fmt.Println("-plugin update [PLUGIN]...")
61                 fmt.Println("    \tUpdate plugin(s) (if no argument is given, updates all plugins)")
62                 fmt.Println("-plugin search [PLUGIN]...")
63                 fmt.Println("    \tSearch for a plugin")
64                 fmt.Println("-plugin list")
65                 fmt.Println("    \tList installed plugins")
66                 fmt.Println("-plugin available")
67                 fmt.Println("    \tList available plugins")
68
69                 fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
70                 fmt.Println("-option value")
71                 fmt.Println("    \tSet `option` to `value` for this session")
72                 fmt.Println("    \tFor example: `micro -syntax off file.c`")
73                 fmt.Println("\nUse `micro -options` to see the full list of configuration options")
74         }
75
76         optionFlags = make(map[string]*string)
77
78         for k, v := range config.DefaultAllSettings() {
79                 optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
80         }
81
82         flag.Parse()
83
84         if *flagVersion {
85                 // If -version was passed
86                 fmt.Println("Version:", util.Version)
87                 fmt.Println("Commit hash:", util.CommitHash)
88                 fmt.Println("Compiled on", util.CompileDate)
89                 os.Exit(0)
90         }
91
92         if *flagOptions {
93                 // If -options was passed
94                 var keys []string
95                 m := config.DefaultAllSettings()
96                 for k := range m {
97                         keys = append(keys, k)
98                 }
99                 sort.Strings(keys)
100                 for _, k := range keys {
101                         v := m[k]
102                         fmt.Printf("-%s value\n", k)
103                         fmt.Printf("    \tDefault value: '%v'\n", v)
104                 }
105                 os.Exit(0)
106         }
107
108         if util.Debug == "OFF" && *flagDebug {
109                 util.Debug = "ON"
110         }
111 }
112
113 // DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
114 func DoPluginFlags() {
115         if *flagClean || *flagPlugin != "" {
116                 config.LoadAllPlugins()
117
118                 if *flagPlugin != "" {
119                         args := flag.Args()
120
121                         config.PluginCommand(os.Stdout, *flagPlugin, args)
122                 } else if *flagClean {
123                         CleanConfig()
124                 }
125
126                 os.Exit(0)
127         }
128 }
129
130 // LoadInput determines which files should be loaded into buffers
131 // based on the input stored in flag.Args()
132 func LoadInput() []*buffer.Buffer {
133         // There are a number of ways micro should start given its input
134
135         // 1. If it is given a files in flag.Args(), it should open those
136
137         // 2. If there is no input file and the input is not a terminal, that means
138         // something is being piped in and the stdin should be opened in an
139         // empty buffer
140
141         // 3. If there is no input file and the input is a terminal, an empty buffer
142         // should be opened
143
144         var filename string
145         var input []byte
146         var err error
147         args := flag.Args()
148         buffers := make([]*buffer.Buffer, 0, len(args))
149
150         btype := buffer.BTDefault
151         if !isatty.IsTerminal(os.Stdout.Fd()) {
152                 btype = buffer.BTStdout
153         }
154
155         if len(args) > 0 {
156                 // Option 1
157                 // We go through each file and load it
158                 for i := 0; i < len(args); i++ {
159                         buf, err := buffer.NewBufferFromFile(args[i], btype)
160                         if err != nil {
161                                 screen.TermMessage(err)
162                                 continue
163                         }
164                         // If the file didn't exist, input will be empty, and we'll open an empty buffer
165                         buffers = append(buffers, buf)
166                 }
167         } else if !isatty.IsTerminal(os.Stdin.Fd()) {
168                 // Option 2
169                 // The input is not a terminal, so something is being piped in
170                 // and we should read from stdin
171                 input, err = ioutil.ReadAll(os.Stdin)
172                 if err != nil {
173                         screen.TermMessage("Error reading from stdin: ", err)
174                         input = []byte{}
175                 }
176                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
177         } else {
178                 // Option 3, just open an empty buffer
179                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
180         }
181
182         return buffers
183 }
184
185 func main() {
186         defer func() {
187                 if util.Stdout.Len() > 0 {
188                         fmt.Fprint(os.Stdout, util.Stdout.String())
189                 }
190                 os.Exit(0)
191         }()
192
193         // runtime.SetCPUProfileRate(400)
194         // f, _ := os.Create("micro.prof")
195         // pprof.StartCPUProfile(f)
196         // defer pprof.StopCPUProfile()
197
198         var err error
199
200         InitFlags()
201
202         InitLog()
203
204         err = config.InitConfigDir(*flagConfigDir)
205         if err != nil {
206                 screen.TermMessage(err)
207         }
208
209         config.InitRuntimeFiles()
210         err = config.ReadSettings()
211         if err != nil {
212                 screen.TermMessage(err)
213         }
214         config.InitGlobalSettings()
215
216         // flag options
217         for k, v := range optionFlags {
218                 if *v != "" {
219                         nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
220                         if err != nil {
221                                 screen.TermMessage(err)
222                                 continue
223                         }
224                         config.GlobalSettings[k] = nativeValue
225                 }
226         }
227
228         DoPluginFlags()
229
230         screen.Init()
231
232         defer func() {
233                 if err := recover(); err != nil {
234                         screen.Screen.Fini()
235                         fmt.Println("Micro encountered an error:", err)
236                         // backup all open buffers
237                         for _, b := range buffer.OpenBuffers {
238                                 b.Backup(false)
239                         }
240                         // Print the stack trace too
241                         fmt.Print(errors.Wrap(err, 2).ErrorStack())
242                         os.Exit(1)
243                 }
244         }()
245
246         err = config.LoadAllPlugins()
247         if err != nil {
248                 screen.TermMessage(err)
249         }
250
251         action.InitBindings()
252         action.InitCommands()
253
254         err = config.InitColorscheme()
255         if err != nil {
256                 screen.TermMessage(err)
257         }
258
259         b := LoadInput()
260
261         if len(b) == 0 {
262                 // No buffers to open
263                 screen.Screen.Fini()
264                 runtime.Goexit()
265         }
266
267         action.InitTabs(b)
268         action.InitGlobals()
269
270         err = config.RunPluginFn("init")
271         if err != nil {
272                 screen.TermMessage(err)
273         }
274
275         events = make(chan tcell.Event)
276
277         // Here is the event loop which runs in a separate thread
278         go func() {
279                 for {
280                         screen.Lock()
281                         e := screen.Screen.PollEvent()
282                         screen.Unlock()
283                         if e != nil {
284                                 events <- e
285                         }
286                 }
287         }()
288
289         // clear the drawchan so we don't redraw excessively
290         // if someone requested a redraw before we started displaying
291         for len(screen.DrawChan()) > 0 {
292                 <-screen.DrawChan()
293         }
294
295         // wait for initial resize event
296         select {
297         case event := <-events:
298                 action.Tabs.HandleEvent(event)
299         case <-time.After(10 * time.Millisecond):
300                 // time out after 10ms
301         }
302
303         // Since this loop is very slow (waits for user input every time) it's
304         // okay to be inefficient and run it via a function every time
305         // We do this so we can recover from panics without crashing the editor
306         for {
307                 DoEvent()
308         }
309 }
310
311 // DoEvent runs the main action loop of the editor
312 func DoEvent() {
313         var event tcell.Event
314
315         // recover from errors without crashing the editor
316         defer func() {
317                 if err := recover(); err != nil {
318                         if e, ok := err.(*lua.ApiError); ok {
319                                 screen.TermMessage("Lua API error:", e)
320                         } else {
321                                 screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
322                         }
323                 }
324         }()
325         // Display everything
326         screen.Screen.Fill(' ', config.DefStyle)
327         screen.Screen.HideCursor()
328         action.Tabs.Display()
329         for _, ep := range action.MainTab().Panes {
330                 ep.Display()
331         }
332         action.MainTab().Display()
333         action.InfoBar.Display()
334         screen.Screen.Show()
335
336         // Check for new events
337         select {
338         case f := <-shell.Jobs:
339                 // If a new job has finished while running in the background we should execute the callback
340                 f.Function(f.Output, f.Args)
341         case <-config.Autosave:
342                 for _, b := range buffer.OpenBuffers {
343                         b.Save()
344                 }
345         case <-shell.CloseTerms:
346         case event = <-events:
347         case <-screen.DrawChan():
348         }
349
350         if action.InfoBar.HasPrompt {
351                 action.InfoBar.HandleEvent(event)
352         } else {
353                 action.Tabs.HandleEvent(event)
354         }
355 }