]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
Merge branch 'bug-endless-reload-prompt' of https://github.com/jawahars16/micro into...
[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         if len(args) > 0 {
151                 // Option 1
152                 // We go through each file and load it
153                 for i := 0; i < len(args); i++ {
154                         buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
155                         if err != nil {
156                                 screen.TermMessage(err)
157                                 continue
158                         }
159                         // If the file didn't exist, input will be empty, and we'll open an empty buffer
160                         buffers = append(buffers, buf)
161                 }
162         } else if !isatty.IsTerminal(os.Stdin.Fd()) {
163                 // Option 2
164                 // The input is not a terminal, so something is being piped in
165                 // and we should read from stdin
166                 input, err = ioutil.ReadAll(os.Stdin)
167                 if err != nil {
168                         screen.TermMessage("Error reading from stdin: ", err)
169                         input = []byte{}
170                 }
171                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
172         } else {
173                 // Option 3, just open an empty buffer
174                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
175         }
176
177         return buffers
178 }
179
180 func main() {
181         defer os.Exit(0)
182
183         // runtime.SetCPUProfileRate(400)
184         // f, _ := os.Create("micro.prof")
185         // pprof.StartCPUProfile(f)
186         // defer pprof.StopCPUProfile()
187
188         var err error
189
190         InitFlags()
191
192         InitLog()
193
194         err = config.InitConfigDir(*flagConfigDir)
195         if err != nil {
196                 screen.TermMessage(err)
197         }
198
199         config.InitRuntimeFiles()
200         err = config.ReadSettings()
201         if err != nil {
202                 screen.TermMessage(err)
203         }
204         config.InitGlobalSettings()
205
206         // flag options
207         for k, v := range optionFlags {
208                 if *v != "" {
209                         nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
210                         if err != nil {
211                                 screen.TermMessage(err)
212                                 continue
213                         }
214                         config.GlobalSettings[k] = nativeValue
215                 }
216         }
217
218         DoPluginFlags()
219
220         screen.Init()
221
222         defer func() {
223                 if err := recover(); err != nil {
224                         screen.Screen.Fini()
225                         fmt.Println("Micro encountered an error:", err)
226                         // backup all open buffers
227                         for _, b := range buffer.OpenBuffers {
228                                 b.Backup(false)
229                         }
230                         // Print the stack trace too
231                         fmt.Print(errors.Wrap(err, 2).ErrorStack())
232                         os.Exit(1)
233                 }
234         }()
235
236         err = config.LoadAllPlugins()
237         if err != nil {
238                 screen.TermMessage(err)
239         }
240
241         action.InitBindings()
242         action.InitCommands()
243
244         err = config.InitColorscheme()
245         if err != nil {
246                 screen.TermMessage(err)
247         }
248
249         b := LoadInput()
250
251         if len(b) == 0 {
252                 // No buffers to open
253                 screen.Screen.Fini()
254                 runtime.Goexit()
255         }
256
257         action.InitTabs(b)
258         action.InitGlobals()
259
260         err = config.RunPluginFn("init")
261         if err != nil {
262                 screen.TermMessage(err)
263         }
264
265         events = make(chan tcell.Event)
266
267         // Here is the event loop which runs in a separate thread
268         go func() {
269                 for {
270                         screen.Lock()
271                         e := screen.Screen.PollEvent()
272                         screen.Unlock()
273                         if e != nil {
274                                 events <- e
275                         }
276                 }
277         }()
278
279         // clear the drawchan so we don't redraw excessively
280         // if someone requested a redraw before we started displaying
281         for len(screen.DrawChan()) > 0 {
282                 <-screen.DrawChan()
283         }
284
285         // wait for initial resize event
286         select {
287         case event := <-events:
288                 action.Tabs.HandleEvent(event)
289         case <-time.After(10 * time.Millisecond):
290                 // time out after 10ms
291         }
292
293         // Since this loop is very slow (waits for user input every time) it's
294         // okay to be inefficient and run it via a function every time
295         // We do this so we can recover from panics without crashing the editor
296         for {
297                 DoEvent()
298         }
299 }
300
301 // DoEvent runs the main action loop of the editor
302 func DoEvent() {
303         var event tcell.Event
304
305         // recover from errors without crashing the editor
306         defer func() {
307                 if err := recover(); err != nil {
308                         if e, ok := err.(*lua.ApiError); ok {
309                                 screen.TermMessage("Lua API error:", e)
310                         } else {
311                                 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")
312                         }
313                 }
314         }()
315         // Display everything
316         screen.Screen.Fill(' ', config.DefStyle)
317         screen.Screen.HideCursor()
318         action.Tabs.Display()
319         for _, ep := range action.MainTab().Panes {
320                 ep.Display()
321         }
322         action.MainTab().Display()
323         action.InfoBar.Display()
324         screen.Screen.Show()
325
326         // Check for new events
327         select {
328         case f := <-shell.Jobs:
329                 // If a new job has finished while running in the background we should execute the callback
330                 f.Function(f.Output, f.Args)
331         case <-config.Autosave:
332                 for _, b := range buffer.OpenBuffers {
333                         b.Save()
334                 }
335         case <-shell.CloseTerms:
336         case event = <-events:
337         case <-screen.DrawChan():
338         }
339
340         if action.InfoBar.HasPrompt {
341                 action.InfoBar.HandleEvent(event)
342         } else {
343                 action.Tabs.HandleEvent(event)
344         }
345 }