mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-10 14:42:47 +09:00
If you run micro as `micro | cat` for example, micro will disallow you from saving the file, and when you quit the buffer, the contents will be sent to the pipe. This allows one to use micro as part of an interactive unix pipeline. Closes #1524
356 lines
9.1 KiB
Go
356 lines
9.1 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"runtime"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/go-errors/errors"
|
|
isatty "github.com/mattn/go-isatty"
|
|
lua "github.com/yuin/gopher-lua"
|
|
"github.com/zyedidia/micro/internal/action"
|
|
"github.com/zyedidia/micro/internal/buffer"
|
|
"github.com/zyedidia/micro/internal/config"
|
|
"github.com/zyedidia/micro/internal/screen"
|
|
"github.com/zyedidia/micro/internal/shell"
|
|
"github.com/zyedidia/micro/internal/util"
|
|
"github.com/zyedidia/tcell"
|
|
)
|
|
|
|
var (
|
|
// Event channel
|
|
events chan tcell.Event
|
|
autosave chan bool
|
|
|
|
// Command line flags
|
|
flagVersion = flag.Bool("version", false, "Show the version number and information")
|
|
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
|
flagOptions = flag.Bool("options", false, "Show all option help")
|
|
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
|
flagPlugin = flag.String("plugin", "", "Plugin command")
|
|
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
|
optionFlags map[string]*string
|
|
)
|
|
|
|
func InitFlags() {
|
|
flag.Usage = func() {
|
|
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
|
fmt.Println("-clean")
|
|
fmt.Println(" \tCleans the configuration directory")
|
|
fmt.Println("-config-dir dir")
|
|
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
|
fmt.Println("[FILE]:LINE:COL")
|
|
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
|
fmt.Println("-options")
|
|
fmt.Println(" \tShow all option help")
|
|
fmt.Println("-debug")
|
|
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
|
|
fmt.Println("-version")
|
|
fmt.Println(" \tShow the version number and information")
|
|
|
|
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
|
|
fmt.Println("-plugin install [PLUGIN]...")
|
|
fmt.Println(" \tInstall plugin(s)")
|
|
fmt.Println("-plugin remove [PLUGIN]...")
|
|
fmt.Println(" \tRemove plugin(s)")
|
|
fmt.Println("-plugin update [PLUGIN]...")
|
|
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
|
|
fmt.Println("-plugin search [PLUGIN]...")
|
|
fmt.Println(" \tSearch for a plugin")
|
|
fmt.Println("-plugin list")
|
|
fmt.Println(" \tList installed plugins")
|
|
fmt.Println("-plugin available")
|
|
fmt.Println(" \tList available plugins")
|
|
|
|
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)
|
|
|
|
for k, v := range config.DefaultAllSettings() {
|
|
optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
|
|
}
|
|
|
|
flag.Parse()
|
|
|
|
if *flagVersion {
|
|
// If -version was passed
|
|
fmt.Println("Version:", util.Version)
|
|
fmt.Println("Commit hash:", util.CommitHash)
|
|
fmt.Println("Compiled on", util.CompileDate)
|
|
os.Exit(0)
|
|
}
|
|
|
|
if *flagOptions {
|
|
// If -options was passed
|
|
var keys []string
|
|
m := config.DefaultAllSettings()
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
v := m[k]
|
|
fmt.Printf("-%s value\n", k)
|
|
fmt.Printf(" \tDefault value: '%v'\n", v)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
|
|
if util.Debug == "OFF" && *flagDebug {
|
|
util.Debug = "ON"
|
|
}
|
|
}
|
|
|
|
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
|
|
func DoPluginFlags() {
|
|
if *flagClean || *flagPlugin != "" {
|
|
config.LoadAllPlugins()
|
|
|
|
if *flagPlugin != "" {
|
|
args := flag.Args()
|
|
|
|
config.PluginCommand(os.Stdout, *flagPlugin, args)
|
|
} else if *flagClean {
|
|
CleanConfig()
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
// LoadInput determines which files should be loaded into buffers
|
|
// based on the input stored in flag.Args()
|
|
func LoadInput() []*buffer.Buffer {
|
|
// There are a number of ways micro should start given its input
|
|
|
|
// 1. If it is given a files in flag.Args(), it should open those
|
|
|
|
// 2. If there is no input file and the input is not a terminal, that means
|
|
// something is being piped in and the stdin should be opened in an
|
|
// empty buffer
|
|
|
|
// 3. If there is no input file and the input is a terminal, an empty buffer
|
|
// should be opened
|
|
|
|
var filename string
|
|
var input []byte
|
|
var err error
|
|
args := flag.Args()
|
|
buffers := make([]*buffer.Buffer, 0, len(args))
|
|
|
|
btype := buffer.BTDefault
|
|
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
|
btype = buffer.BTStdout
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
// Option 1
|
|
// We go through each file and load it
|
|
for i := 0; i < len(args); i++ {
|
|
buf, err := buffer.NewBufferFromFile(args[i], btype)
|
|
if err != nil {
|
|
screen.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
|
|
// The input is not a terminal, so something is being piped in
|
|
// and we should read from stdin
|
|
input, err = ioutil.ReadAll(os.Stdin)
|
|
if err != nil {
|
|
screen.TermMessage("Error reading from stdin: ", err)
|
|
input = []byte{}
|
|
}
|
|
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
|
|
} else {
|
|
// Option 3, just open an empty buffer
|
|
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, btype))
|
|
}
|
|
|
|
return buffers
|
|
}
|
|
|
|
func main() {
|
|
defer func() {
|
|
if util.Stdout.Len() > 0 {
|
|
fmt.Fprint(os.Stdout, util.Stdout.String())
|
|
}
|
|
os.Exit(0)
|
|
}()
|
|
|
|
// runtime.SetCPUProfileRate(400)
|
|
// f, _ := os.Create("micro.prof")
|
|
// pprof.StartCPUProfile(f)
|
|
// defer pprof.StopCPUProfile()
|
|
|
|
var err error
|
|
|
|
InitFlags()
|
|
|
|
InitLog()
|
|
|
|
err = config.InitConfigDir(*flagConfigDir)
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
}
|
|
|
|
config.InitRuntimeFiles()
|
|
err = config.ReadSettings()
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
}
|
|
config.InitGlobalSettings()
|
|
|
|
// flag options
|
|
for k, v := range optionFlags {
|
|
if *v != "" {
|
|
nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
continue
|
|
}
|
|
config.GlobalSettings[k] = nativeValue
|
|
}
|
|
}
|
|
|
|
DoPluginFlags()
|
|
|
|
screen.Init()
|
|
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
screen.Screen.Fini()
|
|
fmt.Println("Micro encountered an error:", err)
|
|
// backup all open buffers
|
|
for _, b := range buffer.OpenBuffers {
|
|
b.Backup(false)
|
|
}
|
|
// Print the stack trace too
|
|
fmt.Print(errors.Wrap(err, 2).ErrorStack())
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
err = config.LoadAllPlugins()
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
}
|
|
|
|
action.InitBindings()
|
|
action.InitCommands()
|
|
|
|
err = config.InitColorscheme()
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
}
|
|
|
|
b := LoadInput()
|
|
|
|
if len(b) == 0 {
|
|
// No buffers to open
|
|
screen.Screen.Fini()
|
|
runtime.Goexit()
|
|
}
|
|
|
|
action.InitTabs(b)
|
|
action.InitGlobals()
|
|
|
|
err = config.RunPluginFn("init")
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
}
|
|
|
|
events = make(chan tcell.Event)
|
|
|
|
// Here is the event loop which runs in a separate thread
|
|
go func() {
|
|
for {
|
|
screen.Lock()
|
|
e := screen.Screen.PollEvent()
|
|
screen.Unlock()
|
|
if e != nil {
|
|
events <- e
|
|
}
|
|
}
|
|
}()
|
|
|
|
// clear the drawchan so we don't redraw excessively
|
|
// if someone requested a redraw before we started displaying
|
|
for len(screen.DrawChan()) > 0 {
|
|
<-screen.DrawChan()
|
|
}
|
|
|
|
// wait for initial resize event
|
|
select {
|
|
case event := <-events:
|
|
action.Tabs.HandleEvent(event)
|
|
case <-time.After(10 * time.Millisecond):
|
|
// time out after 10ms
|
|
}
|
|
|
|
// Since this loop is very slow (waits for user input every time) it's
|
|
// okay to be inefficient and run it via a function every time
|
|
// We do this so we can recover from panics without crashing the editor
|
|
for {
|
|
DoEvent()
|
|
}
|
|
}
|
|
|
|
// DoEvent runs the main action loop of the editor
|
|
func DoEvent() {
|
|
var event tcell.Event
|
|
|
|
// recover from errors without crashing the editor
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
if e, ok := err.(*lua.ApiError); ok {
|
|
screen.TermMessage("Lua API error:", e)
|
|
} else {
|
|
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
|
}
|
|
}
|
|
}()
|
|
// Display everything
|
|
screen.Screen.Fill(' ', config.DefStyle)
|
|
screen.Screen.HideCursor()
|
|
action.Tabs.Display()
|
|
for _, ep := range action.MainTab().Panes {
|
|
ep.Display()
|
|
}
|
|
action.MainTab().Display()
|
|
action.InfoBar.Display()
|
|
screen.Screen.Show()
|
|
|
|
// Check for new events
|
|
select {
|
|
case f := <-shell.Jobs:
|
|
// If a new job has finished while running in the background we should execute the callback
|
|
f.Function(f.Output, f.Args)
|
|
case <-config.Autosave:
|
|
for _, b := range buffer.OpenBuffers {
|
|
b.Save()
|
|
}
|
|
case <-shell.CloseTerms:
|
|
case event = <-events:
|
|
case <-screen.DrawChan():
|
|
}
|
|
|
|
if action.InfoBar.HasPrompt {
|
|
action.InfoBar.HandleEvent(event)
|
|
} else {
|
|
action.Tabs.HandleEvent(event)
|
|
}
|
|
}
|