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