10 "github.com/go-errors/errors"
11 "github.com/layeh/gopher-luar"
12 "github.com/mattn/go-isatty"
13 "github.com/mitchellh/go-homedir"
14 "github.com/yuin/gopher-lua"
15 "github.com/zyedidia/clipboard"
16 "github.com/zyedidia/tcell"
17 "github.com/zyedidia/tcell/encoding"
21 synLinesUp = 75 // How many lines up to look to do syntax highlighting
22 synLinesDown = 75 // How many lines down to look to do syntax highlighting
23 doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
24 undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
31 // Object to send messages and prompts to the user
34 // The default highlighting style
35 // This simply defines the default foreground and background colors
38 // Where the user's configuration is
39 // This should be $XDG_CONFIG_HOME/micro
40 // If $XDG_CONFIG_HOME is not set, it is ~/.config/micro
43 // Version is the version number or commit hash
44 // This should be set by the linker when compiling
48 // This is the VM that runs the plugins
53 // This is the currently open tab
54 // It's just an index to the tab in the tabs array
57 // Channel of jobs running in the background
60 events chan tcell.Event
63 // LoadInput determines which files should be loaded into buffers
64 // based on the input stored in os.Args
65 func LoadInput() []*Buffer {
66 // There are a number of ways micro should start given its input
68 // 1. If it is given a files in os.Args, it should open those
70 // 2. If there is no input file and the input is not a terminal, that means
71 // something is being piped in and the stdin should be opened in an
74 // 3. If there is no input file and the input is a terminal, an empty buffer
84 // We go through each file and load it
85 for i := 1; i < len(os.Args); i++ {
87 // Check that the file exists
88 if _, e := os.Stat(filename); e == nil {
89 // If it exists we load it into a buffer
90 input, err = ioutil.ReadFile(filename)
96 // If the file didn't exist, input will be empty, and we'll open an empty buffer
97 buffers = append(buffers, NewBuffer(input, filename))
99 } else if !isatty.IsTerminal(os.Stdin.Fd()) {
101 // The input is not a terminal, so something is being piped in
102 // and we should read from stdin
103 input, err = ioutil.ReadAll(os.Stdin)
104 buffers = append(buffers, NewBuffer(input, filename))
106 // Option 3, just open an empty buffer
107 buffers = append(buffers, NewBuffer(input, filename))
113 // InitConfigDir finds the configuration directory for micro according to the XDG spec.
114 // If no directory is found, it creates one.
115 func InitConfigDir() {
116 xdgHome := os.Getenv("XDG_CONFIG_HOME")
118 // The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
119 home, err := homedir.Dir()
121 TermMessage("Error finding your home directory\nCan't load config files")
124 xdgHome = home + "/.config"
126 configDir = xdgHome + "/micro"
128 if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
129 // If the xdgHome doesn't exist we should create it
130 err = os.Mkdir(xdgHome, os.ModePerm)
132 TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
136 if _, err := os.Stat(configDir); os.IsNotExist(err) {
137 // If the micro specific config directory doesn't exist we should create that too
138 err = os.Mkdir(configDir, os.ModePerm)
140 TermMessage("Error creating configuration directory: " + err.Error())
145 // InitScreen creates and initializes the tcell screen
147 // Should we enable true color?
148 truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
150 // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
151 // initializing tcell, but after that, we can set the TERM back to whatever it was
152 oldTerm := os.Getenv("TERM")
154 os.Setenv("TERM", "xterm-truecolor")
159 screen, err = tcell.NewScreen()
164 if err = screen.Init(); err != nil {
169 // Now we can put the TERM back to what it was before
171 os.Setenv("TERM", oldTerm)
174 screen.SetStyle(defStyle)
178 // RedrawAll redraws everything -- all the views and the messenger
181 for _, v := range tabs[curTab].views {
189 // Passing -version as a flag will have micro print out the version number
190 var flagVersion = flag.Bool("version", false, "Show the version number")
195 // If -version was passed
196 fmt.Println("Micro version:", Version)
200 // Start the Lua VM for running plugins
204 // Some encoding stuff in case the user isn't using UTF-8
206 tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
208 // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
211 // Load the user's settings
216 // Load the syntax files, including the colorscheme
219 // Load the help files
225 // This is just so if we have an error, we can exit cleanly and not completely
226 // mess up the terminal being worked in
227 // In other words we need to shut down tcell before the program crashes
229 if err := recover(); err != nil {
231 fmt.Println("Micro encountered an error:", err)
232 // Print the stack trace too
233 fmt.Print(errors.Wrap(err, 2).ErrorStack())
238 // Create a new messenger
239 // This is used for sending the user messages in the bottom of the editor
240 messenger = new(Messenger)
241 messenger.history = make(map[string][]string)
243 // Now we load the input
244 buffers := LoadInput()
245 for _, buf := range buffers {
246 // For each buffer we create a new tab and place the view in that tab
247 tab := NewTabFromView(NewView(buf))
248 tab.SetNum(len(tabs))
249 tabs = append(tabs, tab)
250 for _, t := range tabs {
251 for _, v := range t.views {
253 if settings["syntax"].(bool) {
260 // Load all the plugin stuff
261 // We give plugins access to a bunch of variables here which could be useful to them
262 L.SetGlobal("OS", luar.New(L, runtime.GOOS))
263 L.SetGlobal("tabs", luar.New(L, tabs))
264 L.SetGlobal("curTab", luar.New(L, curTab))
265 L.SetGlobal("messenger", luar.New(L, messenger))
266 L.SetGlobal("GetOption", luar.New(L, GetOption))
267 L.SetGlobal("AddOption", luar.New(L, AddOption))
268 L.SetGlobal("SetOption", luar.New(L, SetOption))
269 L.SetGlobal("BindKey", luar.New(L, BindKey))
270 L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
271 L.SetGlobal("CurView", luar.New(L, CurView))
272 L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
273 L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
274 L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
275 L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
277 // Used for asynchronous jobs
278 L.SetGlobal("JobStart", luar.New(L, JobStart))
279 L.SetGlobal("JobSend", luar.New(L, JobSend))
280 L.SetGlobal("JobStop", luar.New(L, JobStop))
284 jobs = make(chan JobFunction, 100)
285 events = make(chan tcell.Event)
287 // Here is the event loop which runs in a separate thread
290 events <- screen.PollEvent()
295 // Display everything
298 var event tcell.Event
300 // Check for new events
303 // If a new job has finished while running in the background we should execute the callback
304 f.function(f.output, f.args...)
306 case event = <-events:
309 switch e := event.(type) {
310 case *tcell.EventMouse:
311 if e.Buttons() == tcell.Button1 {
312 // If the user left clicked we check a couple things
313 _, h := screen.Size()
315 if y == h-1 && messenger.message != "" {
316 // If the user clicked in the bottom bar, and there is a message down there
317 // we copy it to the clipboard.
318 // Often error messages are displayed down there so it can be useful to easily
320 clipboard.WriteAll(messenger.message)
324 // We loop through each view in the current tab and make sure the current view
325 // it the one being clicked in
326 for _, v := range tabs[curTab].views {
327 if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
328 tabs[curTab].curView = v.Num
334 // This function checks the mouse event for the possibility of changing the current tab
335 // If the tab was changed it returns true
336 if TabbarHandleMouseEvent(event) {
341 // Since searching is done in real time, we need to redraw every time
342 // there is a new event in the search bar so we need a special function
343 // to run instead of the standard HandleEvent.
344 HandleSearchEvent(event, CurView())
346 // Send it to the view
347 CurView().HandleEvent(event)