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