]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
Ensure screen cannot draw during a term prompt
[micro.git] / cmd / micro / micro.go
1 package main
2
3 import (
4         "flag"
5         "fmt"
6         "io/ioutil"
7         "log"
8         "os"
9         "os/signal"
10         "regexp"
11         "runtime"
12         "sort"
13         "strconv"
14         "syscall"
15         "time"
16
17         "github.com/go-errors/errors"
18         isatty "github.com/mattn/go-isatty"
19         lua "github.com/yuin/gopher-lua"
20         "github.com/zyedidia/micro/v2/internal/action"
21         "github.com/zyedidia/micro/v2/internal/buffer"
22         "github.com/zyedidia/micro/v2/internal/clipboard"
23         "github.com/zyedidia/micro/v2/internal/config"
24         ulua "github.com/zyedidia/micro/v2/internal/lua"
25         "github.com/zyedidia/micro/v2/internal/screen"
26         "github.com/zyedidia/micro/v2/internal/shell"
27         "github.com/zyedidia/micro/v2/internal/util"
28         "github.com/zyedidia/tcell/v2"
29 )
30
31 var (
32         // Event channel
33         autosave chan bool
34
35         // Command line flags
36         flagVersion   = flag.Bool("version", false, "Show the version number and information")
37         flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
38         flagOptions   = flag.Bool("options", false, "Show all option help")
39         flagDebug     = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
40         flagPlugin    = flag.String("plugin", "", "Plugin command")
41         flagClean     = flag.Bool("clean", false, "Clean configuration directory")
42         optionFlags   map[string]*string
43
44         sigterm chan os.Signal
45         sighup  chan os.Signal
46 )
47
48 func InitFlags() {
49         flag.Usage = func() {
50                 fmt.Println("Usage: micro [OPTIONS] [FILE]...")
51                 fmt.Println("-clean")
52                 fmt.Println("    \tCleans the configuration directory")
53                 fmt.Println("-config-dir dir")
54                 fmt.Println("    \tSpecify a custom location for the configuration directory")
55                 fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
56                 fmt.Println("+LINE:COL")
57                 fmt.Println("    \tSpecify a line and column to start the cursor at when opening a buffer")
58                 fmt.Println("-options")
59                 fmt.Println("    \tShow all option help")
60                 fmt.Println("-debug")
61                 fmt.Println("    \tEnable debug mode (enables logging to ./log.txt)")
62                 fmt.Println("-version")
63                 fmt.Println("    \tShow the version number and information")
64
65                 fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
66                 fmt.Println("-plugin install [PLUGIN]...")
67                 fmt.Println("    \tInstall plugin(s)")
68                 fmt.Println("-plugin remove [PLUGIN]...")
69                 fmt.Println("    \tRemove plugin(s)")
70                 fmt.Println("-plugin update [PLUGIN]...")
71                 fmt.Println("    \tUpdate plugin(s) (if no argument is given, updates all plugins)")
72                 fmt.Println("-plugin search [PLUGIN]...")
73                 fmt.Println("    \tSearch for a plugin")
74                 fmt.Println("-plugin list")
75                 fmt.Println("    \tList installed plugins")
76                 fmt.Println("-plugin available")
77                 fmt.Println("    \tList available plugins")
78
79                 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")
80                 fmt.Println("-option value")
81                 fmt.Println("    \tSet `option` to `value` for this session")
82                 fmt.Println("    \tFor example: `micro -syntax off file.c`")
83                 fmt.Println("\nUse `micro -options` to see the full list of configuration options")
84         }
85
86         optionFlags = make(map[string]*string)
87
88         for k, v := range config.DefaultAllSettings() {
89                 optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
90         }
91
92         flag.Parse()
93
94         if *flagVersion {
95                 // If -version was passed
96                 fmt.Println("Version:", util.Version)
97                 fmt.Println("Commit hash:", util.CommitHash)
98                 fmt.Println("Compiled on", util.CompileDate)
99                 os.Exit(0)
100         }
101
102         if *flagOptions {
103                 // If -options was passed
104                 var keys []string
105                 m := config.DefaultAllSettings()
106                 for k := range m {
107                         keys = append(keys, k)
108                 }
109                 sort.Strings(keys)
110                 for _, k := range keys {
111                         v := m[k]
112                         fmt.Printf("-%s value\n", k)
113                         fmt.Printf("    \tDefault value: '%v'\n", v)
114                 }
115                 os.Exit(0)
116         }
117
118         if util.Debug == "OFF" && *flagDebug {
119                 util.Debug = "ON"
120         }
121 }
122
123 // DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
124 func DoPluginFlags() {
125         if *flagClean || *flagPlugin != "" {
126                 config.LoadAllPlugins()
127
128                 if *flagPlugin != "" {
129                         args := flag.Args()
130
131                         config.PluginCommand(os.Stdout, *flagPlugin, args)
132                 } else if *flagClean {
133                         CleanConfig()
134                 }
135
136                 os.Exit(0)
137         }
138 }
139
140 // LoadInput determines which files should be loaded into buffers
141 // based on the input stored in flag.Args()
142 func LoadInput(args []string) []*buffer.Buffer {
143         // There are a number of ways micro should start given its input
144
145         // 1. If it is given a files in flag.Args(), it should open those
146
147         // 2. If there is no input file and the input is not a terminal, that means
148         // something is being piped in and the stdin should be opened in an
149         // empty buffer
150
151         // 3. If there is no input file and the input is a terminal, an empty buffer
152         // should be opened
153
154         var filename string
155         var input []byte
156         var err error
157         buffers := make([]*buffer.Buffer, 0, len(args))
158
159         btype := buffer.BTDefault
160         if !isatty.IsTerminal(os.Stdout.Fd()) {
161                 btype = buffer.BTStdout
162         }
163
164         files := make([]string, 0, len(args))
165         flagStartPos := buffer.Loc{-1, -1}
166         flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
167         for _, a := range args {
168                 match := flagr.FindStringSubmatch(a)
169                 if len(match) == 3 && match[2] != "" {
170                         line, err := strconv.Atoi(match[1])
171                         if err != nil {
172                                 screen.TermMessage(err)
173                                 continue
174                         }
175                         col, err := strconv.Atoi(match[2])
176                         if err != nil {
177                                 screen.TermMessage(err)
178                                 continue
179                         }
180                         flagStartPos = buffer.Loc{col - 1, line - 1}
181                 } else if len(match) == 3 && match[2] == "" {
182                         line, err := strconv.Atoi(match[1])
183                         if err != nil {
184                                 screen.TermMessage(err)
185                                 continue
186                         }
187                         flagStartPos = buffer.Loc{0, line - 1}
188                 } else {
189                         files = append(files, a)
190                 }
191         }
192
193         if len(files) > 0 {
194                 // Option 1
195                 // We go through each file and load it
196                 for i := 0; i < len(files); i++ {
197                         buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
198                         if err != nil {
199                                 screen.TermMessage(err)
200                                 continue
201                         }
202                         // If the file didn't exist, input will be empty, and we'll open an empty buffer
203                         buffers = append(buffers, buf)
204                 }
205         } else if !isatty.IsTerminal(os.Stdin.Fd()) {
206                 // Option 2
207                 // The input is not a terminal, so something is being piped in
208                 // and we should read from stdin
209                 input, err = ioutil.ReadAll(os.Stdin)
210                 if err != nil {
211                         screen.TermMessage("Error reading from stdin: ", err)
212                         input = []byte{}
213                 }
214                 buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
215         } else {
216                 // Option 3, just open an empty buffer
217                 buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
218         }
219
220         return buffers
221 }
222
223 func main() {
224         defer func() {
225                 if util.Stdout.Len() > 0 {
226                         fmt.Fprint(os.Stdout, util.Stdout.String())
227                 }
228                 os.Exit(0)
229         }()
230
231         // runtime.SetCPUProfileRate(400)
232         // f, _ := os.Create("micro.prof")
233         // pprof.StartCPUProfile(f)
234         // defer pprof.StopCPUProfile()
235
236         var err error
237
238         InitFlags()
239
240         InitLog()
241
242         err = config.InitConfigDir(*flagConfigDir)
243         if err != nil {
244                 screen.TermMessage(err)
245         }
246
247         config.InitRuntimeFiles()
248         err = config.ReadSettings()
249         if err != nil {
250                 screen.TermMessage(err)
251         }
252         err = config.InitGlobalSettings()
253         if err != nil {
254                 screen.TermMessage(err)
255         }
256
257         // flag options
258         for k, v := range optionFlags {
259                 if *v != "" {
260                         nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
261                         if err != nil {
262                                 screen.TermMessage(err)
263                                 continue
264                         }
265                         config.GlobalSettings[k] = nativeValue
266                 }
267         }
268
269         DoPluginFlags()
270
271         err = screen.Init()
272         if err != nil {
273                 fmt.Println(err)
274                 fmt.Println("Fatal: Micro could not initialize a Screen.")
275                 os.Exit(1)
276         }
277         m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
278         clipErr := clipboard.Initialize(m)
279
280         defer func() {
281                 if err := recover(); err != nil {
282                         if screen.Screen != nil {
283                                 screen.Screen.Fini()
284                         }
285                         if e, ok := err.(*lua.ApiError); ok {
286                                 fmt.Println("Lua API error:", e)
287                         } else {
288                                 fmt.Println("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")
289                         }
290                         // backup all open buffers
291                         for _, b := range buffer.OpenBuffers {
292                                 b.Backup()
293                         }
294                         os.Exit(1)
295                 }
296         }()
297
298         err = config.LoadAllPlugins()
299         if err != nil {
300                 screen.TermMessage(err)
301         }
302
303         action.InitBindings()
304         action.InitCommands()
305
306         err = config.InitColorscheme()
307         if err != nil {
308                 screen.TermMessage(err)
309         }
310
311         err = config.RunPluginFn("preinit")
312         if err != nil {
313                 screen.TermMessage(err)
314         }
315
316         action.InitGlobals()
317         buffer.SetMessager(action.InfoBar)
318         args := flag.Args()
319         b := LoadInput(args)
320
321         if len(b) == 0 {
322                 // No buffers to open
323                 screen.Screen.Fini()
324                 runtime.Goexit()
325         }
326
327         action.InitTabs(b)
328
329         err = config.RunPluginFn("init")
330         if err != nil {
331                 screen.TermMessage(err)
332         }
333
334         err = config.RunPluginFn("postinit")
335         if err != nil {
336                 screen.TermMessage(err)
337         }
338
339         if clipErr != nil {
340                 log.Println(clipErr, " or change 'clipboard' option")
341         }
342
343         if a := config.GetGlobalOption("autosave").(float64); a > 0 {
344                 config.SetAutoTime(int(a))
345                 config.StartAutoSave()
346         }
347
348         screen.Events = make(chan tcell.Event)
349
350         sigterm = make(chan os.Signal, 1)
351         sighup = make(chan os.Signal, 1)
352         signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
353         signal.Notify(sighup, syscall.SIGHUP)
354
355         // Here is the event loop which runs in a separate thread
356         go func() {
357                 for {
358                         screen.Lock()
359                         e := screen.Screen.PollEvent()
360                         screen.Unlock()
361                         if e != nil {
362                                 screen.Events <- e
363                         }
364                 }
365         }()
366
367         // clear the drawchan so we don't redraw excessively
368         // if someone requested a redraw before we started displaying
369         for len(screen.DrawChan()) > 0 {
370                 <-screen.DrawChan()
371         }
372
373         // wait for initial resize event
374         select {
375         case event := <-screen.Events:
376                 action.Tabs.HandleEvent(event)
377         case <-time.After(10 * time.Millisecond):
378                 // time out after 10ms
379         }
380
381         for {
382                 DoEvent()
383         }
384 }
385
386 // DoEvent runs the main action loop of the editor
387 func DoEvent() {
388         var event tcell.Event
389
390         // Display everything
391         screen.DrawLock.Lock()
392         screen.Screen.Fill(' ', config.DefStyle)
393         screen.Screen.HideCursor()
394         action.Tabs.Display()
395         for _, ep := range action.MainTab().Panes {
396                 ep.Display()
397         }
398         action.MainTab().Display()
399         action.InfoBar.Display()
400         screen.Screen.Show()
401         screen.DrawLock.Unlock()
402
403         // Check for new events
404         select {
405         case f := <-shell.Jobs:
406                 // If a new job has finished while running in the background we should execute the callback
407                 ulua.Lock.Lock()
408                 f.Function(f.Output, f.Args)
409                 ulua.Lock.Unlock()
410         case <-config.Autosave:
411                 ulua.Lock.Lock()
412                 for _, b := range buffer.OpenBuffers {
413                         b.Save()
414                 }
415                 ulua.Lock.Unlock()
416         case <-shell.CloseTerms:
417         case event = <-screen.Events:
418         case <-screen.DrawChan():
419                 for len(screen.DrawChan()) > 0 {
420                         <-screen.DrawChan()
421                 }
422         case <-sighup:
423                 for _, b := range buffer.OpenBuffers {
424                         if !b.Modified() {
425                                 b.Fini()
426                         }
427                 }
428                 os.Exit(0)
429         case <-sigterm:
430                 for _, b := range buffer.OpenBuffers {
431                         if !b.Modified() {
432                                 b.Fini()
433                         }
434                 }
435
436                 if screen.Screen != nil {
437                         screen.Screen.Fini()
438                 }
439                 os.Exit(0)
440         }
441
442         ulua.Lock.Lock()
443         // if event != nil {
444         if action.InfoBar.HasPrompt {
445                 action.InfoBar.HandleEvent(event)
446         } else {
447                 action.Tabs.HandleEvent(event)
448         }
449         // }
450         ulua.Lock.Unlock()
451 }