]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
Small optimization
[micro.git] / cmd / micro / micro.go
1 package main
2
3 import (
4         "flag"
5         "fmt"
6         "io/ioutil"
7         "os"
8
9         "github.com/go-errors/errors"
10         "github.com/mattn/go-isatty"
11         "github.com/mitchellh/go-homedir"
12         "github.com/zyedidia/tcell"
13         "github.com/zyedidia/tcell/encoding"
14 )
15
16 const (
17         synLinesUp           = 75  // How many lines up to look to do syntax highlighting
18         synLinesDown         = 75  // How many lines down to look to do syntax highlighting
19         doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
20         undoThreshold        = 500 // If two events are less than n milliseconds apart, undo both of them
21 )
22
23 var (
24         // The main screen
25         screen tcell.Screen
26
27         // Object to send messages and prompts to the user
28         messenger *Messenger
29
30         // The default style
31         defStyle tcell.Style
32
33         // Where the user's configuration is
34         // This should be $XDG_CONFIG_HOME/micro
35         // If $XDG_CONFIG_HOME is not set, it is ~/.config/micro
36         configDir string
37
38         // Version is the version number.
39         // This should be set by the linker
40         Version = "Unknown"
41
42         // Is the help screen open
43         helpOpen = false
44 )
45
46 // LoadInput loads the file input for the editor
47 func LoadInput() (string, []byte, error) {
48         // There are a number of ways micro should start given its input
49         // 1. If it is given a file in os.Args, it should open that
50
51         // 2. If there is no input file and the input is not a terminal, that means
52         // something is being piped in and the stdin should be opened in an
53         // empty buffer
54
55         // 3. If there is no input file and the input is a terminal, an empty buffer
56         // should be opened
57
58         // These are empty by default so if we get to option 3, we can just returns the
59         // default values
60         var filename string
61         var input []byte
62         var err error
63
64         if len(os.Args) > 1 {
65                 // Option 1
66                 filename = os.Args[1]
67                 // Check that the file exists
68                 if _, e := os.Stat(filename); e == nil {
69                         input, err = ioutil.ReadFile(filename)
70                 }
71         } else if !isatty.IsTerminal(os.Stdin.Fd()) {
72                 // Option 2
73                 // The input is not a terminal, so something is being piped in
74                 // and we should read from stdin
75                 input, err = ioutil.ReadAll(os.Stdin)
76         }
77
78         // Option 3, or just return whatever we got
79         return filename, input, err
80 }
81
82 // InitConfigDir finds the configuration directory for micro according to the
83 // XDG spec.
84 // If no directory is found, it creates one.
85 func InitConfigDir() {
86         xdgHome := os.Getenv("XDG_CONFIG_HOME")
87         if xdgHome == "" {
88                 home, err := homedir.Dir()
89                 if err != nil {
90                         TermMessage("Error finding your home directory\nCan't load syntax files")
91                         return
92                 }
93                 xdgHome = home + "/.config"
94         }
95         configDir = xdgHome + "/micro"
96
97         if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
98                 err = os.Mkdir(xdgHome, os.ModePerm)
99                 if err != nil {
100                         TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
101                 }
102         }
103
104         if _, err := os.Stat(configDir); os.IsNotExist(err) {
105                 err = os.Mkdir(configDir, os.ModePerm)
106                 if err != nil {
107                         TermMessage("Error creating configuration directory: " + err.Error())
108                 }
109         }
110 }
111
112 // InitScreen creates and initializes the tcell screen
113 func InitScreen() {
114         // Should we enable true color?
115         truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
116
117         // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
118         // initializing tcell, but after that, we can set the TERM back to whatever it was
119         oldTerm := os.Getenv("TERM")
120         if truecolor {
121                 os.Setenv("TERM", "xterm-truecolor")
122         }
123
124         // Initilize tcell
125         var err error
126         screen, err = tcell.NewScreen()
127         if err != nil {
128                 fmt.Println(err)
129                 os.Exit(1)
130         }
131         if err = screen.Init(); err != nil {
132                 fmt.Println(err)
133                 os.Exit(1)
134         }
135
136         // Now we can put the TERM back to what it was before
137         if truecolor {
138                 os.Setenv("TERM", oldTerm)
139         }
140
141         // Default style
142         defStyle = tcell.StyleDefault.
143                 Foreground(tcell.ColorDefault).
144                 Background(tcell.ColorDefault)
145
146         // There may be another default style defined in the colorscheme
147         if style, ok := colorscheme["default"]; ok {
148                 defStyle = style
149         }
150
151         screen.SetStyle(defStyle)
152         screen.EnableMouse()
153 }
154
155 // Redraw redraws the screen and the given view
156 func Redraw(view *View) {
157         screen.Clear()
158         view.Display()
159         messenger.Display()
160         screen.Show()
161 }
162
163 var flagVersion = flag.Bool("version", false, "Show version number")
164
165 func main() {
166         flag.Parse()
167         if *flagVersion {
168                 fmt.Println("Micro version:", Version)
169                 os.Exit(0)
170         }
171
172         filename, input, err := LoadInput()
173         if err != nil {
174                 fmt.Println(err)
175                 os.Exit(1)
176         }
177
178         encoding.Register()
179         tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
180
181         // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
182         InitConfigDir()
183         // Load the user's settings
184         InitSettings()
185         InitBindings()
186         // Load the syntax files, including the colorscheme
187         LoadSyntaxFiles()
188         // Load the help files
189         LoadHelp()
190
191         buf := NewBuffer(string(input), filename)
192
193         InitScreen()
194
195         // This is just so if we have an error, we can exit cleanly and not completely
196         // mess up the terminal being worked in
197         defer func() {
198                 if err := recover(); err != nil {
199                         screen.Fini()
200                         fmt.Println("Micro encountered an error:", err)
201                         // Print the stack trace too
202                         fmt.Print(errors.Wrap(err, 2).ErrorStack())
203                         os.Exit(1)
204                 }
205         }()
206
207         messenger = new(Messenger)
208         view := NewView(buf)
209
210         for {
211                 // Display everything
212                 Redraw(view)
213
214                 // Wait for the user's action
215                 event := screen.PollEvent()
216
217                 if searching {
218                         HandleSearchEvent(event, view)
219                 } else {
220                         // Check if we should quit
221                         switch e := event.(type) {
222                         case *tcell.EventKey:
223                                 switch e.Key() {
224                                 case tcell.KeyCtrlQ:
225                                         // Make sure not to quit if there are unsaved changes
226                                         if helpOpen {
227                                                 view.OpenBuffer(buf)
228                                                 helpOpen = false
229                                         } else {
230                                                 if view.CanClose("Quit anyway? (yes, no, save) ") {
231                                                         screen.Fini()
232                                                         os.Exit(0)
233                                                 }
234                                         }
235                                 case tcell.KeyCtrlE:
236                                         input, canceled := messenger.Prompt("> ")
237                                         if !canceled {
238                                                 HandleCommand(input, view)
239                                         }
240                                 case tcell.KeyCtrlB:
241                                         input, canceled := messenger.Prompt("$ ")
242                                         if !canceled {
243                                                 HandleShellCommand(input, view, true)
244                                         }
245                                 case tcell.KeyCtrlG:
246                                         if !helpOpen {
247                                                 helpBuffer := NewBuffer(helpTxt, "help.md")
248                                                 helpBuffer.name = "Help"
249                                                 helpOpen = true
250                                                 view.OpenBuffer(helpBuffer)
251                                         } else {
252                                                 view.OpenBuffer(buf)
253                                                 helpOpen = false
254                                         }
255                                 }
256                         }
257
258                         // Send it to the view
259                         view.HandleEvent(event)
260                 }
261         }
262 }