]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
Don't call plugin if nil
[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         "github.com/zyedidia/micro/internal/action"
15         "github.com/zyedidia/micro/internal/buffer"
16         "github.com/zyedidia/micro/internal/config"
17         "github.com/zyedidia/micro/internal/screen"
18         "github.com/zyedidia/micro/internal/shell"
19         "github.com/zyedidia/micro/internal/util"
20         "github.com/zyedidia/tcell"
21 )
22
23 var (
24         // Event channel
25         events   chan tcell.Event
26         autosave chan bool
27
28         // Command line flags
29         flagVersion   = flag.Bool("version", false, "Show the version number and information")
30         flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
31         flagOptions   = flag.Bool("options", false, "Show all option help")
32         flagDebug     = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
33         optionFlags   map[string]*string
34 )
35
36 func InitFlags() {
37         flag.Usage = func() {
38                 fmt.Println("Usage: micro [OPTIONS] [FILE]...")
39                 fmt.Println("-config-dir dir")
40                 fmt.Println("    \tSpecify a custom location for the configuration directory")
41                 fmt.Println("[FILE]:LINE:COL")
42                 fmt.Println("    \tSpecify a line and column to start the cursor at when opening a buffer")
43                 fmt.Println("    \tThis can also be done by opening file:LINE:COL")
44                 fmt.Println("-options")
45                 fmt.Println("    \tShow all option help")
46                 fmt.Println("-debug")
47                 fmt.Println("    \tEnable debug mode (enables logging to ./log.txt)")
48                 fmt.Println("-version")
49                 fmt.Println("    \tShow the version number and information")
50
51                 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")
52                 fmt.Println("-option value")
53                 fmt.Println("    \tSet `option` to `value` for this session")
54                 fmt.Println("    \tFor example: `micro -syntax off file.c`")
55                 fmt.Println("\nUse `micro -options` to see the full list of configuration options")
56         }
57
58         optionFlags = make(map[string]*string)
59
60         for k, v := range config.DefaultAllSettings() {
61                 optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
62         }
63
64         flag.Parse()
65
66         if *flagVersion {
67                 // If -version was passed
68                 fmt.Println("Version:", util.Version)
69                 fmt.Println("Commit hash:", util.CommitHash)
70                 fmt.Println("Compiled on", util.CompileDate)
71                 os.Exit(0)
72         }
73
74         if *flagOptions {
75                 // If -options was passed
76                 var keys []string
77                 m := config.DefaultAllSettings()
78                 for k := range m {
79                         keys = append(keys, k)
80                 }
81                 sort.Strings(keys)
82                 for _, k := range keys {
83                         v := m[k]
84                         fmt.Printf("-%s value\n", k)
85                         fmt.Printf("    \tDefault value: '%v'\n", v)
86                 }
87                 os.Exit(0)
88         }
89
90         if util.Debug == "OFF" && *flagDebug {
91                 util.Debug = "ON"
92         }
93 }
94
95 // LoadInput determines which files should be loaded into buffers
96 // based on the input stored in flag.Args()
97 func LoadInput() []*buffer.Buffer {
98         // There are a number of ways micro should start given its input
99
100         // 1. If it is given a files in flag.Args(), it should open those
101
102         // 2. If there is no input file and the input is not a terminal, that means
103         // something is being piped in and the stdin should be opened in an
104         // empty buffer
105
106         // 3. If there is no input file and the input is a terminal, an empty buffer
107         // should be opened
108
109         var filename string
110         var input []byte
111         var err error
112         args := flag.Args()
113         buffers := make([]*buffer.Buffer, 0, len(args))
114
115         if len(args) > 0 {
116                 // Option 1
117                 // We go through each file and load it
118                 for i := 0; i < len(args); i++ {
119                         buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
120                         if err != nil {
121                                 screen.TermMessage(err)
122                                 continue
123                         }
124                         // If the file didn't exist, input will be empty, and we'll open an empty buffer
125                         buffers = append(buffers, buf)
126                 }
127         } else if !isatty.IsTerminal(os.Stdin.Fd()) {
128                 // Option 2
129                 // The input is not a terminal, so something is being piped in
130                 // and we should read from stdin
131                 input, err = ioutil.ReadAll(os.Stdin)
132                 if err != nil {
133                         screen.TermMessage("Error reading from stdin: ", err)
134                         input = []byte{}
135                 }
136                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
137         } else {
138                 // Option 3, just open an empty buffer
139                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
140         }
141
142         return buffers
143 }
144
145 func main() {
146         defer os.Exit(0)
147
148         // runtime.SetCPUProfileRate(400)
149         // f, _ := os.Create("micro.prof")
150         // pprof.StartCPUProfile(f)
151         // defer pprof.StopCPUProfile()
152
153         var err error
154
155         InitFlags()
156
157         InitLog()
158
159         err = config.InitConfigDir(*flagConfigDir)
160         if err != nil {
161                 screen.TermMessage(err)
162         }
163
164         config.InitRuntimeFiles()
165         err = config.ReadSettings()
166         if err != nil {
167                 screen.TermMessage(err)
168         }
169         config.InitGlobalSettings()
170
171         // flag options
172         for k, v := range optionFlags {
173                 if *v != "" {
174                         nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
175                         if err != nil {
176                                 screen.TermMessage(err)
177                                 continue
178                         }
179                         config.GlobalSettings[k] = nativeValue
180                 }
181         }
182
183         screen.Init()
184
185         // If we have an error, we can exit cleanly and not completely
186         // mess up the terminal being worked in
187         // In other words we need to shut down tcell before the program crashes
188         defer func() {
189                 if err := recover(); err != nil {
190                         screen.Screen.Fini()
191                         fmt.Println("Micro encountered an error:", err)
192                         // backup all open buffers
193                         for _, b := range buffer.OpenBuffers {
194                                 b.Backup(false)
195                         }
196                         // Print the stack trace too
197                         fmt.Print(errors.Wrap(err, 2).ErrorStack())
198                         os.Exit(1)
199                 }
200         }()
201
202         action.InitBindings()
203         action.InitCommands()
204
205         err = config.InitColorscheme()
206         if err != nil {
207                 screen.TermMessage(err)
208         }
209
210         err = config.LoadAllPlugins()
211         if err != nil {
212                 screen.TermMessage(err)
213         }
214         err = config.RunPluginFn("init")
215         if err != nil {
216                 screen.TermMessage(err)
217         }
218
219         b := LoadInput()
220
221         if len(b) == 0 {
222                 // No buffers to open
223                 screen.Screen.Fini()
224                 runtime.Goexit()
225         }
226
227         action.InitTabs(b)
228         action.InitGlobals()
229
230         events = make(chan tcell.Event)
231
232         // Here is the event loop which runs in a separate thread
233         go func() {
234                 for {
235                         screen.Lock()
236                         e := screen.Screen.PollEvent()
237                         screen.Unlock()
238                         if e != nil {
239                                 events <- e
240                         }
241                 }
242         }()
243
244         // clear the drawchan so we don't redraw excessively
245         // if someone requested a redraw before we started displaying
246         for len(screen.DrawChan) > 0 {
247                 <-screen.DrawChan
248         }
249
250         var event tcell.Event
251
252         // wait for initial resize event
253         select {
254         case event = <-events:
255                 action.Tabs.HandleEvent(event)
256         case <-time.After(20 * time.Millisecond):
257                 // time out after 10ms
258         }
259
260         for {
261                 // Display everything
262                 screen.Screen.Fill(' ', config.DefStyle)
263                 screen.Screen.HideCursor()
264                 action.Tabs.Display()
265                 for _, ep := range action.MainTab().Panes {
266                         ep.Display()
267                 }
268                 action.MainTab().Display()
269                 action.InfoBar.Display()
270                 screen.Screen.Show()
271
272                 event = nil
273
274                 // Check for new events
275                 select {
276                 case f := <-shell.Jobs:
277                         // If a new job has finished while running in the background we should execute the callback
278                         f.Function(f.Output, f.Args...)
279                 case <-config.Autosave:
280                         for _, b := range buffer.OpenBuffers {
281                                 b.Save()
282                         }
283                 case <-shell.CloseTerms:
284                 case event = <-events:
285                 case <-screen.DrawChan:
286                 }
287
288                 if action.InfoBar.HasPrompt {
289                         action.InfoBar.HandleEvent(event)
290                 } else {
291                         action.Tabs.HandleEvent(event)
292                 }
293         }
294 }