14 "github.com/go-errors/errors"
15 isatty "github.com/mattn/go-isatty"
16 lua "github.com/yuin/gopher-lua"
17 "github.com/zyedidia/micro/v2/internal/action"
18 "github.com/zyedidia/micro/v2/internal/buffer"
19 "github.com/zyedidia/micro/v2/internal/config"
20 "github.com/zyedidia/micro/v2/internal/screen"
21 "github.com/zyedidia/micro/v2/internal/shell"
22 "github.com/zyedidia/micro/v2/internal/util"
23 "github.com/zyedidia/tcell"
28 events chan tcell.Event
32 flagVersion = flag.Bool("version", false, "Show the version number and information")
33 flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
34 flagOptions = flag.Bool("options", false, "Show all option help")
35 flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
36 flagPlugin = flag.String("plugin", "", "Plugin command")
37 flagClean = flag.Bool("clean", false, "Clean configuration directory")
38 optionFlags map[string]*string
43 fmt.Println("Usage: micro [OPTIONS] [FILE]...")
45 fmt.Println(" \tCleans the configuration directory")
46 fmt.Println("-config-dir dir")
47 fmt.Println(" \tSpecify a custom location for the configuration directory")
48 fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
49 fmt.Println("+LINE:COL")
50 fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
51 fmt.Println("-options")
52 fmt.Println(" \tShow all option help")
54 fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
55 fmt.Println("-version")
56 fmt.Println(" \tShow the version number and information")
58 fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
59 fmt.Println("-plugin install [PLUGIN]...")
60 fmt.Println(" \tInstall plugin(s)")
61 fmt.Println("-plugin remove [PLUGIN]...")
62 fmt.Println(" \tRemove plugin(s)")
63 fmt.Println("-plugin update [PLUGIN]...")
64 fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
65 fmt.Println("-plugin search [PLUGIN]...")
66 fmt.Println(" \tSearch for a plugin")
67 fmt.Println("-plugin list")
68 fmt.Println(" \tList installed plugins")
69 fmt.Println("-plugin available")
70 fmt.Println(" \tList available plugins")
72 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")
73 fmt.Println("-option value")
74 fmt.Println(" \tSet `option` to `value` for this session")
75 fmt.Println(" \tFor example: `micro -syntax off file.c`")
76 fmt.Println("\nUse `micro -options` to see the full list of configuration options")
79 optionFlags = make(map[string]*string)
81 for k, v := range config.DefaultAllSettings() {
82 optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
88 // If -version was passed
89 fmt.Println("Version:", util.Version)
90 fmt.Println("Commit hash:", util.CommitHash)
91 fmt.Println("Compiled on", util.CompileDate)
96 // If -options was passed
98 m := config.DefaultAllSettings()
100 keys = append(keys, k)
103 for _, k := range keys {
105 fmt.Printf("-%s value\n", k)
106 fmt.Printf(" \tDefault value: '%v'\n", v)
111 if util.Debug == "OFF" && *flagDebug {
116 // DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
117 func DoPluginFlags() {
118 if *flagClean || *flagPlugin != "" {
119 config.LoadAllPlugins()
121 if *flagPlugin != "" {
124 config.PluginCommand(os.Stdout, *flagPlugin, args)
125 } else if *flagClean {
133 // LoadInput determines which files should be loaded into buffers
134 // based on the input stored in flag.Args()
135 func LoadInput() []*buffer.Buffer {
136 // There are a number of ways micro should start given its input
138 // 1. If it is given a files in flag.Args(), it should open those
140 // 2. If there is no input file and the input is not a terminal, that means
141 // something is being piped in and the stdin should be opened in an
144 // 3. If there is no input file and the input is a terminal, an empty buffer
151 buffers := make([]*buffer.Buffer, 0, len(args))
153 btype := buffer.BTDefault
154 if !isatty.IsTerminal(os.Stdout.Fd()) {
155 btype = buffer.BTStdout
158 files := make([]string, 0, len(args))
159 flagStartPos := buffer.Loc{-1, -1}
160 flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
161 for _, a := range args {
162 match := flagr.FindStringSubmatch(a)
163 if len(match) == 3 && match[2] != "" {
164 line, err := strconv.Atoi(match[1])
166 screen.TermMessage(err)
169 col, err := strconv.Atoi(match[2])
171 screen.TermMessage(err)
174 flagStartPos = buffer.Loc{col - 1, line - 1}
175 } else if len(match) == 3 && match[2] == "" {
176 line, err := strconv.Atoi(match[1])
178 screen.TermMessage(err)
181 flagStartPos = buffer.Loc{0, line - 1}
183 files = append(files, a)
189 // We go through each file and load it
190 for i := 0; i < len(files); i++ {
191 buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
193 screen.TermMessage(err)
196 // If the file didn't exist, input will be empty, and we'll open an empty buffer
197 buffers = append(buffers, buf)
199 } else if !isatty.IsTerminal(os.Stdin.Fd()) {
201 // The input is not a terminal, so something is being piped in
202 // and we should read from stdin
203 input, err = ioutil.ReadAll(os.Stdin)
205 screen.TermMessage("Error reading from stdin: ", err)
208 buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
210 // Option 3, just open an empty buffer
211 buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
219 if util.Stdout.Len() > 0 {
220 fmt.Fprint(os.Stdout, util.Stdout.String())
225 // runtime.SetCPUProfileRate(400)
226 // f, _ := os.Create("micro.prof")
227 // pprof.StartCPUProfile(f)
228 // defer pprof.StopCPUProfile()
236 err = config.InitConfigDir(*flagConfigDir)
238 screen.TermMessage(err)
241 config.InitRuntimeFiles()
242 err = config.ReadSettings()
244 screen.TermMessage(err)
246 err = config.InitGlobalSettings()
248 screen.TermMessage(err)
252 for k, v := range optionFlags {
254 nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
256 screen.TermMessage(err)
259 config.GlobalSettings[k] = nativeValue
268 if err := recover(); err != nil {
270 fmt.Println("Micro encountered an error:", err)
271 // backup all open buffers
272 for _, b := range buffer.OpenBuffers {
275 // Print the stack trace too
276 fmt.Print(errors.Wrap(err, 2).ErrorStack())
281 err = config.LoadAllPlugins()
283 screen.TermMessage(err)
286 action.InitBindings()
287 action.InitCommands()
289 err = config.InitColorscheme()
291 screen.TermMessage(err)
297 // No buffers to open
305 err = config.RunPluginFn("init")
307 screen.TermMessage(err)
310 events = make(chan tcell.Event)
312 // Here is the event loop which runs in a separate thread
316 e := screen.Screen.PollEvent()
324 // clear the drawchan so we don't redraw excessively
325 // if someone requested a redraw before we started displaying
326 for len(screen.DrawChan()) > 0 {
330 // wait for initial resize event
332 case event := <-events:
333 action.Tabs.HandleEvent(event)
334 case <-time.After(10 * time.Millisecond):
335 // time out after 10ms
338 // Since this loop is very slow (waits for user input every time) it's
339 // okay to be inefficient and run it via a function every time
340 // We do this so we can recover from panics without crashing the editor
346 // DoEvent runs the main action loop of the editor
348 var event tcell.Event
350 // recover from errors without crashing the editor
352 if err := recover(); err != nil {
353 if e, ok := err.(*lua.ApiError); ok {
354 screen.TermMessage("Lua API error:", e)
356 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")
360 // Display everything
361 screen.Screen.Fill(' ', config.DefStyle)
362 screen.Screen.HideCursor()
363 action.Tabs.Display()
364 for _, ep := range action.MainTab().Panes {
367 action.MainTab().Display()
368 action.InfoBar.Display()
371 // Check for new events
373 case f := <-shell.Jobs:
374 // If a new job has finished while running in the background we should execute the callback
375 f.Function(f.Output, f.Args)
376 case <-config.Autosave:
377 for _, b := range buffer.OpenBuffers {
380 case <-shell.CloseTerms:
381 case event = <-events:
382 case <-screen.DrawChan():
383 for len(screen.DrawChan()) > 0 {
388 if action.InfoBar.HasPrompt {
389 action.InfoBar.HandleEvent(event)
391 action.Tabs.HandleEvent(event)