10 "github.com/atotto/clipboard"
11 "github.com/go-errors/errors"
12 "github.com/layeh/gopher-luar"
13 "github.com/mattn/go-isatty"
14 "github.com/mitchellh/go-homedir"
15 "github.com/yuin/gopher-lua"
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
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
58 events chan tcell.Event
61 // LoadInput loads the file input for the editor
62 func LoadInput() []*Buffer {
63 // There are a number of ways micro should start given its input
65 // 1. If it is given a file in os.Args, it should open that
67 // 2. If there is no input file and the input is not a terminal, that means
68 // something is being piped in and the stdin should be opened in an
71 // 3. If there is no input file and the input is a terminal, an empty buffer
74 // These are empty by default so if we get to option 3, we can just returns the
83 for i := 1; i < len(os.Args); i++ {
85 // Check that the file exists
86 if _, e := os.Stat(filename); e == nil {
87 input, err = ioutil.ReadFile(filename)
93 buffers = append(buffers, NewBuffer(input, filename))
95 } else if !isatty.IsTerminal(os.Stdin.Fd()) {
97 // The input is not a terminal, so something is being piped in
98 // and we should read from stdin
99 input, err = ioutil.ReadAll(os.Stdin)
100 buffers = append(buffers, NewBuffer(input, filename))
102 // Option 3, just open an empty buffer
103 buffers = append(buffers, NewBuffer(input, filename))
109 // InitConfigDir finds the configuration directory for micro according to the XDG spec.
110 // If no directory is found, it creates one.
111 func InitConfigDir() {
112 xdgHome := os.Getenv("XDG_CONFIG_HOME")
114 // The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
115 home, err := homedir.Dir()
117 TermMessage("Error finding your home directory\nCan't load config files")
120 xdgHome = home + "/.config"
122 configDir = xdgHome + "/micro"
124 if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
125 // If the xdgHome doesn't exist we should create it
126 err = os.Mkdir(xdgHome, os.ModePerm)
128 TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
132 if _, err := os.Stat(configDir); os.IsNotExist(err) {
133 // If the micro specific config directory doesn't exist we should create that too
134 err = os.Mkdir(configDir, os.ModePerm)
136 TermMessage("Error creating configuration directory: " + err.Error())
141 // InitScreen creates and initializes the tcell screen
143 // Should we enable true color?
144 truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
146 // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
147 // initializing tcell, but after that, we can set the TERM back to whatever it was
148 oldTerm := os.Getenv("TERM")
150 os.Setenv("TERM", "xterm-truecolor")
155 screen, err = tcell.NewScreen()
160 if err = screen.Init(); err != nil {
165 // Now we can put the TERM back to what it was before
167 os.Setenv("TERM", oldTerm)
170 screen.SetStyle(defStyle)
174 // RedrawAll redraws everything -- all the views and the messenger
179 for _, v := range tabs[curTab].views {
185 var flagVersion = flag.Bool("version", false, "Show version number")
190 fmt.Println("Micro version:", Version)
197 // Some encoding stuff in case the user isn't using UTF-8
199 tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
201 // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
203 // Load the user's settings
207 // Load the syntax files, including the colorscheme
209 // Load the help files
214 // This is just so if we have an error, we can exit cleanly and not completely
215 // mess up the terminal being worked in
216 // In other words we need to shut down tcell before the program crashes
218 if err := recover(); err != nil {
220 fmt.Println("Micro encountered an error:", err)
221 // Print the stack trace too
222 fmt.Print(errors.Wrap(err, 2).ErrorStack())
227 messenger = new(Messenger)
228 messenger.history = make(map[string][]string)
230 buffers := LoadInput()
231 for _, buf := range buffers {
232 tab := NewTabFromView(NewView(buf))
233 tab.SetNum(len(tabs))
234 tabs = append(tabs, tab)
235 for _, t := range tabs {
236 for _, v := range t.views {
237 v.Resize(screen.Size())
242 L.SetGlobal("OS", luar.New(L, runtime.GOOS))
243 L.SetGlobal("tabs", luar.New(L, tabs))
244 L.SetGlobal("curTab", luar.New(L, curTab))
245 L.SetGlobal("messenger", luar.New(L, messenger))
246 L.SetGlobal("GetOption", luar.New(L, GetOption))
247 L.SetGlobal("AddOption", luar.New(L, AddOption))
248 L.SetGlobal("BindKey", luar.New(L, BindKey))
249 L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
250 L.SetGlobal("CurView", luar.New(L, CurView))
251 L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
253 L.SetGlobal("JobStart", luar.New(L, JobStart))
254 L.SetGlobal("JobSend", luar.New(L, JobSend))
255 L.SetGlobal("JobStop", luar.New(L, JobStop))
259 jobs = make(chan JobFunction, 100)
260 events = make(chan tcell.Event)
264 events <- screen.PollEvent()
269 // Display everything
272 var event tcell.Event
275 f.function(f.output, f.args...)
277 case event = <-events:
280 switch e := event.(type) {
281 case *tcell.EventMouse:
282 if e.Buttons() == tcell.Button1 {
283 _, h := screen.Size()
285 if y == h-1 && messenger.message != "" {
286 clipboard.WriteAll(messenger.message)
290 for _, v := range tabs[curTab].views {
291 if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
292 tabs[curTab].curView = v.Num
298 if TabbarHandleMouseEvent(event) {
303 // Since searching is done in real time, we need to redraw every time
304 // there is a new event in the search bar
305 HandleSearchEvent(event, CurView())
307 // Send it to the view
308 CurView().HandleEvent(event)