X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fmicro.go;h=dc35ed2708f853b0a9c45bbc1b076200fe067892;hb=71af765b4e4f368c4bbbcb3947f3497e17271b62;hp=22a3d6f41714414a04f60d31225fa164bd5188da;hpb=63b4848bb0dc0be7f4e6bf4c6aaabdc4a03b2b9a;p=micro.git diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 22a3d6f4..dc35ed27 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -15,14 +15,13 @@ import ( "github.com/mitchellh/go-homedir" "github.com/yuin/gopher-lua" "github.com/zyedidia/clipboard" + "github.com/zyedidia/micro/cmd/micro/terminfo" "github.com/zyedidia/tcell" "github.com/zyedidia/tcell/encoding" "layeh.com/gopher-luar" ) const ( - synLinesUp = 75 // How many lines up to look to do syntax highlighting - synLinesDown = 75 // How many lines down to look to do syntax highlighting doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them autosaveTime = 8 // Number of seconds to wait before autosaving @@ -46,14 +45,12 @@ var ( // Version is the version number or commit hash // These variables should be set by the linker when compiling - Version = "0.0.0-unknown" - CommitHash = "Unknown" + Version = "0.0.0-unknown" + // CommitHash is the commit this version was built on + CommitHash = "Unknown" + // CompileDate is the date this binary was compiled on CompileDate = "Unknown" - // L is the lua state - // This is the VM that runs the plugins - L *lua.LState - // The list of views tabs []*Tab // This is the currently open tab @@ -62,9 +59,17 @@ var ( // Channel of jobs running in the background jobs chan JobFunction + // Event channel events chan tcell.Event autosave chan bool + + // Channels for the terminal emulator + updateterm chan bool + closeterm chan int + + // How many redraws have happened + numRedraw uint ) // LoadInput determines which files should be loaded into buffers @@ -91,30 +96,23 @@ func LoadInput() []*Buffer { // Option 1 // We go through each file and load it for i := 0; i < len(args); i++ { - filename = args[i] - - // Check that the file exists - var input *os.File - if _, e := os.Stat(filename); e == nil { - // If it exists we load it into a buffer - input, err = os.Open(filename) - stat, _ := input.Stat() - defer input.Close() - if err != nil { - TermMessage(err) - continue - } - if stat.IsDir() { - TermMessage("Cannot read", filename, "because it is a directory") - continue + if strings.HasPrefix(args[i], "+") { + if strings.Contains(args[i], ":") { + split := strings.Split(args[i], ":") + *flagStartPos = split[0][1:] + "," + split[1] + } else { + *flagStartPos = args[i][1:] + ",0" } + continue } - // If the file didn't exist, input will be empty, and we'll open an empty buffer - if input != nil { - buffers = append(buffers, NewBuffer(input, FSize(input), filename)) - } else { - buffers = append(buffers, NewBufferFromString("", filename)) + + buf, err := NewBufferFromFile(args[i]) + if err != nil { + TermMessage(err) + continue } + // If the file didn't exist, input will be empty, and we'll open an empty buffer + buffers = append(buffers, buf) } } else if !isatty.IsTerminal(os.Stdin.Fd()) { // Option 2 @@ -149,6 +147,15 @@ func InitConfigDir() { } configDir = xdgHome + "/micro" + if len(*flagConfigDir) > 0 { + if _, err := os.Stat(*flagConfigDir); os.IsNotExist(err) { + TermMessage("Error: " + *flagConfigDir + " does not exist. Defaulting to " + configDir + ".") + } else { + configDir = *flagConfigDir + return + } + } + if _, err := os.Stat(xdgHome); os.IsNotExist(err) { // If the xdgHome doesn't exist we should create it err = os.Mkdir(xdgHome, os.ModePerm) @@ -171,6 +178,9 @@ func InitScreen() { // Should we enable true color? truecolor := os.Getenv("MICRO_TRUECOLOR") == "1" + tcelldb := os.Getenv("TCELLDB") + os.Setenv("TCELLDB", configDir+"/.tcelldb") + // In order to enable true color, we have to set the TERM to `xterm-truecolor` when // initializing tcell, but after that, we can set the TERM back to whatever it was oldTerm := os.Getenv("TERM") @@ -182,12 +192,19 @@ func InitScreen() { var err error screen, err = tcell.NewScreen() if err != nil { - fmt.Println(err) if err == tcell.ErrTermNotFound { - fmt.Println("Micro does not recognize your terminal:", oldTerm) - fmt.Println("Please go to https://github.com/zyedidia/mkinfo to read about how to fix this problem (it should be easy to fix).") + terminfo.WriteDB(configDir + "/.tcelldb") + screen, err = tcell.NewScreen() + if err != nil { + fmt.Println(err) + fmt.Println("Fatal: Micro could not initialize a screen.") + os.Exit(1) + } + } else { + fmt.Println(err) + fmt.Println("Fatal: Micro could not initialize a screen.") + os.Exit(1) } - os.Exit(1) } if err = screen.Init(); err != nil { fmt.Println(err) @@ -199,8 +216,13 @@ func InitScreen() { os.Setenv("TERM", oldTerm) } - screen.SetStyle(defStyle) - screen.EnableMouse() + if GetGlobalOption("mouse").(bool) { + screen.EnableMouse() + } + + os.Setenv("TCELLDB", tcelldb) + + // screen.SetStyle(defStyle) } // RedrawAll redraws everything -- all the views and the messenger @@ -214,12 +236,20 @@ func RedrawAll() { } } - for _, v := range tabs[curTab].views { + for _, v := range tabs[curTab].Views { v.Display() } DisplayTabs() messenger.Display() + if globalSettings["keymenu"].(bool) { + DisplayKeyMenu() + } screen.Show() + + if numRedraw%50 == 0 { + runtime.GC() + } + numRedraw++ } func LoadAll() { @@ -238,22 +268,37 @@ func LoadAll() { InitColorscheme() for _, tab := range tabs { - for _, v := range tab.views { + for _, v := range tab.Views { v.Buf.UpdateRules() } } } -// Passing -version as a flag will have micro print out the version number +// Command line flags var flagVersion = flag.Bool("version", false, "Show the version number and information") var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.") +var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory") +var flagOptions = flag.Bool("options", false, "Show all option help") func main() { flag.Usage = func() { fmt.Println("Usage: micro [OPTIONS] [FILE]...") - fmt.Print("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n\n") - flag.CommandLine.SetOutput(os.Stdout) - flag.PrintDefaults() + fmt.Println("-config-dir dir") + fmt.Println(" \tSpecify a custom location for the configuration directory") + fmt.Println("-startpos LINE,COL") + fmt.Println("+LINE:COL") + fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer") + fmt.Println(" \tThis can also be done by opening file:LINE:COL") + fmt.Println("-options") + fmt.Println(" \tShow all option help") + fmt.Println("-version") + fmt.Println(" \tShow the version number and information") + + fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n") + fmt.Println("-option value") + fmt.Println(" \tSet `option` to `value` for this session") + fmt.Println(" \tFor example: `micro -syntax off file.c`") + fmt.Println("\nUse `micro -options` to see the full list of configuration options") } optionFlags := make(map[string]*string) @@ -272,6 +317,15 @@ func main() { os.Exit(0) } + if *flagOptions { + // If -options was passed + for k, v := range DefaultGlobalSettings() { + fmt.Printf("-%s value\n", k) + fmt.Printf(" \tThe %s option. Default value: '%v'\n", k, v) + } + os.Exit(0) + } + // Start the Lua VM for running plugins L = lua.NewState() defer L.Close() @@ -311,7 +365,7 @@ func main() { // Create a new messenger // This is used for sending the user messages in the bottom of the editor messenger = new(Messenger) - messenger.history = make(map[string][]string) + messenger.LoadHistory() // Now we load the input buffers := LoadInput() @@ -326,7 +380,7 @@ func main() { tab.SetNum(len(tabs)) tabs = append(tabs, tab) for _, t := range tabs { - for _, v := range t.views { + for _, v := range t.Views { v.Center(false) } @@ -356,15 +410,23 @@ func main() { L.SetGlobal("IsWordChar", luar.New(L, IsWordChar)) L.SetGlobal("HandleCommand", luar.New(L, HandleCommand)) L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand)) + L.SetGlobal("ExecCommand", luar.New(L, ExecCommand)) + L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand)) + L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell)) + L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell)) + L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported)) + L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator)) L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace)) L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion)) L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString)) + L.SetGlobal("NewBufferFromFile", luar.New(L, NewBufferFromFile)) L.SetGlobal("RuneStr", luar.New(L, func(r rune) string { return string(r) })) L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc { return Loc{x, y} })) + L.SetGlobal("WorkingDirectory", luar.New(L, os.Getwd)) L.SetGlobal("JoinPaths", luar.New(L, filepath.Join)) L.SetGlobal("DirectoryName", luar.New(L, filepath.Dir)) L.SetGlobal("configDir", luar.New(L, configDir)) @@ -385,25 +447,26 @@ func main() { L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory)) L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory)) + // Access to Go stdlib + L.SetGlobal("import", luar.New(L, Import)) + jobs = make(chan JobFunction, 100) events = make(chan tcell.Event, 100) autosave = make(chan bool) + updateterm = make(chan bool) + closeterm = make(chan int) LoadPlugins() for _, t := range tabs { - for _, v := range t.views { - for pl := range loadedPlugins { - _, err := Call(pl+".onViewOpen", v) - if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { - TermMessage(err) - continue - } - } + for _, v := range t.Views { + GlobalPluginCall("onViewOpen", v) + GlobalPluginCall("onBufferOpen", v.Buf) } } InitColorscheme() + messenger.style = defStyle // Here is the event loop which runs in a separate thread go func() { @@ -436,11 +499,19 @@ func main() { f.function(f.output, f.args...) continue case <-autosave: - CurView().Save(true) + if CurView().Buf.Path != "" { + CurView().Save(true) + } + case <-updateterm: + continue + case vnum := <-closeterm: + tabs[curTab].Views[vnum].CloseTerminal() case event = <-events: } for event != nil { + didAction := false + switch e := event.(type) { case *tcell.EventResize: for _, t := range tabs { @@ -464,30 +535,44 @@ func main() { if CurView().mouseReleased { // We loop through each view in the current tab and make sure the current view // is the one being clicked in - for _, v := range tabs[curTab].views { + for _, v := range tabs[curTab].Views { if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height { tabs[curTab].CurView = v.Num } } } + } else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown { + var view *View + x, y := e.Position() + for _, v := range tabs[curTab].Views { + if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height { + view = tabs[curTab].Views[v.Num] + } + } + if view != nil { + view.HandleEvent(e) + didAction = true + } } } } - // This function checks the mouse event for the possibility of changing the current tab - // If the tab was changed it returns true - if TabbarHandleMouseEvent(event) { - break - } + if !didAction { + // This function checks the mouse event for the possibility of changing the current tab + // If the tab was changed it returns true + if TabbarHandleMouseEvent(event) { + break + } - if searching { - // Since searching is done in real time, we need to redraw every time - // there is a new event in the search bar so we need a special function - // to run instead of the standard HandleEvent. - HandleSearchEvent(event, CurView()) - } else { - // Send it to the view - CurView().HandleEvent(event) + if searching { + // Since searching is done in real time, we need to redraw every time + // there is a new event in the search bar so we need a special function + // to run instead of the standard HandleEvent. + HandleSearchEvent(event, CurView()) + } else { + // Send it to the view + CurView().HandleEvent(event) + } } select {