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