X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fmicro.go;h=066e3200d98ae2c4f7519e6e0ac2d7a6a6309dff;hb=7adcb13c084842822b2991a4d1ee3881535ceb1e;hp=8733aa40b4816701eaea51a28ffefe6b54e5c6f5;hpb=1cb04992ecdd7844ea0475b460c2d1341413273f;p=micro.git diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 8733aa40..066e3200 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -1,12 +1,19 @@ package main import ( + "flag" "fmt" - "github.com/gdamore/tcell" - "github.com/go-errors/errors" - "github.com/mattn/go-isatty" "io/ioutil" "os" + "runtime" + + "github.com/go-errors/errors" + "github.com/layeh/gopher-luar" + "github.com/mattn/go-isatty" + "github.com/mitchellh/go-homedir" + "github.com/yuin/gopher-lua" + "github.com/zyedidia/tcell" + "github.com/zyedidia/tcell/encoding" ) const ( @@ -23,13 +30,34 @@ var ( // Object to send messages and prompts to the user messenger *Messenger - // The default style + // The default highlighting style + // This simply defines the default foreground and background colors defStyle tcell.Style + + // Where the user's configuration is + // This should be $XDG_CONFIG_HOME/micro + // If $XDG_CONFIG_HOME is not set, it is ~/.config/micro + configDir string + + // Version is the version number or commit hash + // This should be set by the linker + Version = "Unknown" + + // L is the lua state + // This is the VM that runs the plugins + L *lua.LState + + // The list of views + views []*View + // This is the currently open view + // It's just an index to the view in the views array + mainView int ) // LoadInput loads the file input for the editor func LoadInput() (string, []byte, error) { // There are a number of ways micro should start given its input + // 1. If it is given a file in os.Args, it should open that // 2. If there is no input file and the input is not a terminal, that means @@ -63,18 +91,40 @@ func LoadInput() (string, []byte, error) { return filename, input, err } -func main() { - filename, input, err := LoadInput() - if err != nil { - fmt.Println(err) - os.Exit(1) +// InitConfigDir finds the configuration directory for micro according to the XDG spec. +// If no directory is found, it creates one. +func InitConfigDir() { + xdgHome := os.Getenv("XDG_CONFIG_HOME") + if xdgHome == "" { + // The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config + home, err := homedir.Dir() + if err != nil { + TermMessage("Error finding your home directory\nCan't load config files") + return + } + xdgHome = home + "/.config" } + configDir = xdgHome + "/micro" - InitSettings() + if _, err := os.Stat(xdgHome); os.IsNotExist(err) { + // If the xdgHome doesn't exist we should create it + err = os.Mkdir(xdgHome, os.ModePerm) + if err != nil { + TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error()) + } + } - // Load the syntax files, including the colorscheme - LoadSyntaxFiles() + if _, err := os.Stat(configDir); os.IsNotExist(err) { + // If the micro specific config directory doesn't exist we should create that too + err = os.Mkdir(configDir, os.ModePerm) + if err != nil { + TermMessage("Error creating configuration directory: " + err.Error()) + } + } +} +// InitScreen creates and initializes the tcell screen +func InitScreen() { // Should we enable true color? truecolor := os.Getenv("MICRO_TRUECOLOR") == "1" @@ -86,6 +136,7 @@ func main() { } // Initilize tcell + var err error screen, err = tcell.NewScreen() if err != nil { fmt.Println(err) @@ -101,73 +152,111 @@ func main() { os.Setenv("TERM", oldTerm) } - // This is just so if we have an error, we can exit cleanly and not completely - // mess up the terminal being worked in - defer func() { - if err := recover(); err != nil { - screen.Fini() - fmt.Println("Micro encountered an error:", err) - // Print the stack trace too - fmt.Print(errors.Wrap(err, 2).ErrorStack()) - os.Exit(1) - } - }() - // Default style defStyle = tcell.StyleDefault. Foreground(tcell.ColorDefault). Background(tcell.ColorDefault) // There may be another default style defined in the colorscheme + // In that case we should use that one if style, ok := colorscheme["default"]; ok { defStyle = style } screen.SetStyle(defStyle) screen.EnableMouse() +} + +// RedrawAll redraws everything -- all the views and the messenger +func RedrawAll() { + screen.Clear() + for _, v := range views { + v.Display() + } + messenger.Display() + screen.Show() +} + +var flagVersion = flag.Bool("version", false, "Show version number") + +func main() { + flag.Parse() + if *flagVersion { + fmt.Println("Micro version:", Version) + os.Exit(0) + } + + filename, input, err := LoadInput() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + L = lua.NewState() + defer L.Close() + + // Some encoding stuff in case the user isn't using UTF-8 + encoding.Register() + tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) + + // Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro) + InitConfigDir() + // Load the user's settings + InitSettings() + InitCommands() + InitBindings() + // Load the syntax files, including the colorscheme + LoadSyntaxFiles() + // Load the help files + LoadHelp() + + buf := NewBuffer(string(input), filename) + + InitScreen() + + // This is just so if we have an error, we can exit cleanly and not completely + // mess up the terminal being worked in + // In other words we need to shut down tcell before the program crashes + defer func() { + if err := recover(); err != nil { + screen.Fini() + fmt.Println("Micro encountered an error:", err) + // Print the stack trace too + fmt.Print(errors.Wrap(err, 2).ErrorStack()) + os.Exit(1) + } + }() messenger = new(Messenger) - view := NewView(NewBuffer(string(input), filename)) + messenger.history = make(map[string][]string) + views = make([]*View, 1) + views[0] = NewView(buf) - for { - // Display everything - screen.Clear() + L.SetGlobal("OS", luar.New(L, runtime.GOOS)) + L.SetGlobal("views", luar.New(L, views)) + L.SetGlobal("mainView", luar.New(L, mainView)) + L.SetGlobal("messenger", luar.New(L, messenger)) + L.SetGlobal("GetOption", luar.New(L, GetOption)) + L.SetGlobal("AddOption", luar.New(L, AddOption)) + L.SetGlobal("BindKey", luar.New(L, BindKey)) + L.SetGlobal("MakeCommand", luar.New(L, MakeCommand)) - view.Display() - messenger.Display() + LoadPlugins() - screen.Show() + for { + // Display everything + RedrawAll() // Wait for the user's action event := screen.PollEvent() if searching { - HandleSearchEvent(event, view) + // Since searching is done in real time, we need to redraw every time + // there is a new event in the search bar + HandleSearchEvent(event, views[mainView]) } else { - // Check if we should quit - switch e := event.(type) { - case *tcell.EventKey: - switch e.Key() { - case tcell.KeyCtrlQ: - // Make sure not to quit if there are unsaved changes - if view.CanClose("Quit anyway? ") { - screen.Fini() - os.Exit(0) - } - case tcell.KeyCtrlE: - input, canceled := messenger.Prompt("> ") - if !canceled { - HandleCommand(input, view) - } - case tcell.KeyCtrlH: - DisplayHelp() - // Make sure to resize the view if the user resized the terminal while looking at the help text - view.Resize(screen.Size()) - } - } - // Send it to the view - view.HandleEvent(event) + views[mainView].HandleEvent(event) } } }