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