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