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