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() []*Buffer {
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 for i := 1; i < len(os.Args); i++ {
81 // Check that the file exists
82 if _, e := os.Stat(filename); e == nil {
83 input, err = ioutil.ReadFile(filename)
89 buffers = append(buffers, NewBuffer(input, filename))
91 } else if !isatty.IsTerminal(os.Stdin.Fd()) {
93 // The input is not a terminal, so something is being piped in
94 // and we should read from stdin
95 input, err = ioutil.ReadAll(os.Stdin)
96 buffers = append(buffers, NewBuffer(input, filename))
98 // Option 3, just open an empty buffer
99 buffers = append(buffers, NewBuffer(input, filename))
105 // InitConfigDir finds the configuration directory for micro according to the XDG spec.
106 // If no directory is found, it creates one.
107 func InitConfigDir() {
108 xdgHome := os.Getenv("XDG_CONFIG_HOME")
110 // The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
111 home, err := homedir.Dir()
113 TermMessage("Error finding your home directory\nCan't load config files")
116 xdgHome = home + "/.config"
118 configDir = xdgHome + "/micro"
120 if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
121 // If the xdgHome doesn't exist we should create it
122 err = os.Mkdir(xdgHome, os.ModePerm)
124 TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
128 if _, err := os.Stat(configDir); os.IsNotExist(err) {
129 // If the micro specific config directory doesn't exist we should create that too
130 err = os.Mkdir(configDir, os.ModePerm)
132 TermMessage("Error creating configuration directory: " + err.Error())
137 // InitScreen creates and initializes the tcell screen
139 // Should we enable true color?
140 truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
142 // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
143 // initializing tcell, but after that, we can set the TERM back to whatever it was
144 oldTerm := os.Getenv("TERM")
146 os.Setenv("TERM", "xterm-truecolor")
151 screen, err = tcell.NewScreen()
156 if err = screen.Init(); err != nil {
161 // Now we can put the TERM back to what it was before
163 os.Setenv("TERM", oldTerm)
167 defStyle = tcell.StyleDefault.
168 Foreground(tcell.ColorDefault).
169 Background(tcell.ColorDefault)
171 // There may be another default style defined in the colorscheme
172 // In that case we should use that one
173 if style, ok := colorscheme["default"]; ok {
177 screen.SetStyle(defStyle)
181 // RedrawAll redraws everything -- all the views and the messenger
184 // for _, v := range views {
185 views[mainView].Display()
192 var flagVersion = flag.Bool("version", false, "Show version number")
197 fmt.Println("Micro version:", Version)
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)
210 // Load the user's settings
214 // Load the syntax files, including the colorscheme
216 // Load the help files
221 // This is just so if we have an error, we can exit cleanly and not completely
222 // mess up the terminal being worked in
223 // In other words we need to shut down tcell before the program crashes
225 if err := recover(); err != nil {
227 fmt.Println("Micro encountered an error:", err)
228 // Print the stack trace too
229 fmt.Print(errors.Wrap(err, 2).ErrorStack())
234 messenger = new(Messenger)
235 messenger.history = make(map[string][]string)
236 // views = make([]*View, 1)
237 buffers := LoadInput()
238 for i, buf := range buffers {
239 views = append(views, NewView(buf))
243 L.SetGlobal("OS", luar.New(L, runtime.GOOS))
244 L.SetGlobal("views", luar.New(L, views))
245 L.SetGlobal("mainView", luar.New(L, mainView))
246 L.SetGlobal("messenger", luar.New(L, messenger))
247 L.SetGlobal("GetOption", luar.New(L, GetOption))
248 L.SetGlobal("AddOption", luar.New(L, AddOption))
249 L.SetGlobal("BindKey", luar.New(L, BindKey))
250 L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
255 // Display everything
258 // Wait for the user's action
259 event := screen.PollEvent()
262 // Since searching is done in real time, we need to redraw every time
263 // there is a new event in the search bar
264 HandleSearchEvent(event, views[mainView])
266 // Send it to the view
267 views[mainView].HandleEvent(event)