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