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/tcell"
16 "github.com/zyedidia/tcell/encoding"
20 synLinesUp = 75 // How many lines up to look to do syntax highlighting
21 synLinesDown = 75 // How many lines down to look to do syntax highlighting
22 doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
23 undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
30 // Object to send messages and prompts to the user
33 // The default highlighting style
34 // This simply defines the default foreground and background colors
37 // Where the user's configuration is
38 // This should be $XDG_CONFIG_HOME/micro
39 // If $XDG_CONFIG_HOME is not set, it is ~/.config/micro
42 // Version is the version number or commit hash
43 // This should be set by the linker
47 // This is the VM that runs the plugins
52 // This is the currently open view
53 // It's just an index to the view in the views array
57 // LoadInput loads the file input for the editor
58 func LoadInput() (string, []byte, error) {
59 // There are a number of ways micro should start given its input
61 // 1. If it is given a file in os.Args, it should open that
63 // 2. If there is no input file and the input is not a terminal, that means
64 // something is being piped in and the stdin should be opened in an
67 // 3. If there is no input file and the input is a terminal, an empty buffer
70 // These are empty by default so if we get to option 3, we can just returns the
79 // Check that the file exists
80 if _, e := os.Stat(filename); e == nil {
81 input, err = ioutil.ReadFile(filename)
83 } else if !isatty.IsTerminal(os.Stdin.Fd()) {
85 // The input is not a terminal, so something is being piped in
86 // and we should read from stdin
87 input, err = ioutil.ReadAll(os.Stdin)
90 // Option 3, or just return whatever we got
91 return filename, input, err
94 // InitConfigDir finds the configuration directory for micro according to the XDG spec.
95 // If no directory is found, it creates one.
96 func InitConfigDir() {
97 xdgHome := os.Getenv("XDG_CONFIG_HOME")
99 // The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
100 home, err := homedir.Dir()
102 TermMessage("Error finding your home directory\nCan't load config files")
105 xdgHome = home + "/.config"
107 configDir = xdgHome + "/micro"
109 if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
110 // If the xdgHome doesn't exist we should create it
111 err = os.Mkdir(xdgHome, os.ModePerm)
113 TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
117 if _, err := os.Stat(configDir); os.IsNotExist(err) {
118 // If the micro specific config directory doesn't exist we should create that too
119 err = os.Mkdir(configDir, os.ModePerm)
121 TermMessage("Error creating configuration directory: " + err.Error())
126 // InitScreen creates and initializes the tcell screen
128 // Should we enable true color?
129 truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
131 // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
132 // initializing tcell, but after that, we can set the TERM back to whatever it was
133 oldTerm := os.Getenv("TERM")
135 os.Setenv("TERM", "xterm-truecolor")
140 screen, err = tcell.NewScreen()
145 if err = screen.Init(); err != nil {
150 // Now we can put the TERM back to what it was before
152 os.Setenv("TERM", oldTerm)
156 defStyle = tcell.StyleDefault.
157 Foreground(tcell.ColorDefault).
158 Background(tcell.ColorDefault)
160 // There may be another default style defined in the colorscheme
161 // In that case we should use that one
162 if style, ok := colorscheme["default"]; ok {
166 screen.SetStyle(defStyle)
170 // RedrawAll redraws everything -- all the views and the messenger
173 for _, v := range views {
180 var flagVersion = flag.Bool("version", false, "Show version number")
185 fmt.Println("Micro version:", Version)
189 filename, input, err := LoadInput()
198 // Some encoding stuff in case the user isn't using UTF-8
200 tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
202 // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
204 // Load the user's settings
208 // Load the syntax files, including the colorscheme
210 // Load the help files
213 buf := NewBuffer(string(input), filename)
217 // This is just so if we have an error, we can exit cleanly and not completely
218 // mess up the terminal being worked in
219 // In other words we need to shut down tcell before the program crashes
221 if err := recover(); err != nil {
223 fmt.Println("Micro encountered an error:", err)
224 // Print the stack trace too
225 fmt.Print(errors.Wrap(err, 2).ErrorStack())
230 messenger = new(Messenger)
231 messenger.history = make(map[string][]string)
232 views = make([]*View, 1)
233 views[0] = NewView(buf)
235 L.SetGlobal("OS", luar.New(L, runtime.GOOS))
236 L.SetGlobal("views", luar.New(L, views))
237 L.SetGlobal("mainView", luar.New(L, mainView))
238 L.SetGlobal("messenger", luar.New(L, messenger))
239 L.SetGlobal("GetOption", luar.New(L, GetOption))
240 L.SetGlobal("AddOption", luar.New(L, AddOption))
241 L.SetGlobal("BindKey", luar.New(L, BindKey))
242 L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
247 // Display everything
250 // Wait for the user's action
251 event := screen.PollEvent()
254 // Since searching is done in real time, we need to redraw every time
255 // there is a new event in the search bar
256 HandleSearchEvent(event, views[mainView])
258 // Send it to the view
259 views[mainView].HandleEvent(event)