2016-03-17 21:27:57 +00:00
package main
2018-09-03 20:54:56 +00:00
2016-03-17 21:27:57 +00:00
import (
2016-04-19 17:40:30 +00:00
"flag"
2016-03-17 21:27:57 +00:00
"fmt"
2023-03-20 20:17:43 +00:00
"io"
2018-08-28 22:44:52 +00:00
"io/ioutil"
2020-12-17 02:35:07 +00:00
"log"
2016-04-19 04:33:54 +00:00
"os"
2020-08-02 00:18:07 +00:00
"os/signal"
2020-03-24 14:10:44 +00:00
"regexp"
2020-01-02 20:25:07 +00:00
"runtime"
2023-01-30 02:10:57 +00:00
"runtime/pprof"
2019-06-15 18:44:03 +00:00
"sort"
2020-05-30 02:48:23 +00:00
"strconv"
2020-08-02 00:18:07 +00:00
"syscall"
2020-01-29 01:54:14 +00:00
"time"
2016-04-19 04:33:54 +00:00
2016-03-20 19:24:40 +00:00
"github.com/go-errors/errors"
2018-08-28 22:44:52 +00:00
isatty "github.com/mattn/go-isatty"
2020-02-11 05:50:24 +00:00
lua "github.com/yuin/gopher-lua"
2020-05-04 14:16:15 +00:00
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
2020-07-05 00:00:39 +00:00
"github.com/zyedidia/micro/v2/internal/clipboard"
2020-05-04 14:16:15 +00:00
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
2020-09-05 18:52:35 +00:00
"github.com/zyedidia/tcell/v2"
2016-03-17 21:27:57 +00:00
)
2016-04-16 13:55:40 +00:00
var (
2018-08-26 03:06:44 +00:00
// 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" )
2020-01-16 03:25:08 +00:00
flagDebug = flag . Bool ( "debug" , false , "Enable debug mode (prints debug info to ./log.txt)" )
2023-01-30 02:10:57 +00:00
flagProfile = flag . Bool ( "profile" , false , "Enable CPU profiling (writes profile info to ./micro.prof)" )
2020-02-02 04:54:38 +00:00
flagPlugin = flag . String ( "plugin" , "" , "Plugin command" )
2020-02-02 20:30:06 +00:00
flagClean = flag . Bool ( "clean" , false , "Clean configuration directory" )
2019-03-19 22:28:51 +00:00
optionFlags map [ string ] * string
2021-04-21 01:27:59 +00:00
sigterm chan os . Signal
sighup chan os . Signal
2023-11-13 16:51:19 +00:00
timerChan chan func ( )
2018-08-26 03:06:44 +00:00
)
2016-03-25 16:14:22 +00:00
2018-08-26 03:06:44 +00:00
func InitFlags ( ) {
2016-09-03 15:26:01 +00:00
flag . Usage = func ( ) {
fmt . Println ( "Usage: micro [OPTIONS] [FILE]..." )
2020-02-02 20:30:06 +00:00
fmt . Println ( "-clean" )
fmt . Println ( " \tCleans the configuration directory" )
2017-09-18 02:11:26 +00:00
fmt . Println ( "-config-dir dir" )
fmt . Println ( " \tSpecify a custom location for the configuration directory" )
2020-05-30 16:23:29 +00:00
fmt . Println ( "[FILE]:LINE:COL (if the `parsecursor` option is enabled)" )
2020-03-24 14:10:44 +00:00
fmt . Println ( "+LINE:COL" )
2017-09-18 02:11:26 +00:00
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" )
2020-01-16 03:25:08 +00:00
fmt . Println ( "-debug" )
fmt . Println ( " \tEnable debug mode (enables logging to ./log.txt)" )
2023-01-30 02:10:57 +00:00
fmt . Println ( "-profile" )
fmt . Println ( " \tEnable CPU profiling (writes profile info to ./micro.prof" )
fmt . Println ( " \tso it can be analyzed later with \"go tool pprof micro.prof\")" )
2017-09-18 02:11:26 +00:00
fmt . Println ( "-version" )
fmt . Println ( " \tShow the version number and information" )
2024-03-17 15:48:41 +00:00
fmt . Print ( "\nMicro's plugins can be managed at the command line with the following commands.\n" )
2020-02-02 04:54:38 +00:00
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)" )
2020-02-02 19:20:39 +00:00
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" )
2020-02-02 04:54:38 +00:00
2017-12-28 21:05:35 +00:00
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" )
2017-09-18 02:11:26 +00:00
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" )
2016-09-03 15:26:01 +00:00
}
2019-03-19 22:28:51 +00:00
optionFlags = make ( map [ string ] * string )
2016-09-03 15:26:01 +00:00
2019-06-17 21:45:38 +00:00
for k , v := range config . DefaultAllSettings ( ) {
2019-06-17 15:26:37 +00:00
optionFlags [ k ] = flag . String ( k , "" , fmt . Sprintf ( "The %s option. Default value: '%v'." , k , v ) )
2016-09-03 15:26:01 +00:00
}
2016-04-20 01:25:13 +00:00
flag . Parse ( )
2016-09-03 15:26:01 +00:00
2016-04-20 01:25:13 +00:00
if * flagVersion {
2016-07-10 17:26:05 +00:00
// If -version was passed
2019-02-05 03:26:06 +00:00
fmt . Println ( "Version:" , util . Version )
fmt . Println ( "Commit hash:" , util . CommitHash )
fmt . Println ( "Compiled on" , util . CompileDate )
2016-04-20 01:25:13 +00:00
os . Exit ( 0 )
}
2017-09-18 02:11:26 +00:00
if * flagOptions {
// If -options was passed
2019-06-15 18:44:03 +00:00
var keys [ ] string
2019-06-17 21:45:38 +00:00
m := config . DefaultAllSettings ( )
2019-12-22 18:43:29 +00:00
for k := range m {
2019-06-15 18:44:03 +00:00
keys = append ( keys , k )
}
sort . Strings ( keys )
for _ , k := range keys {
v := m [ k ]
2017-09-18 02:11:26 +00:00
fmt . Printf ( "-%s value\n" , k )
2018-08-26 03:06:44 +00:00
fmt . Printf ( " \tDefault value: '%v'\n" , v )
2017-09-18 02:11:26 +00:00
}
os . Exit ( 0 )
}
2020-01-16 03:25:08 +00:00
if util . Debug == "OFF" && * flagDebug {
util . Debug = "ON"
}
2018-08-26 03:06:44 +00:00
}
2017-09-18 02:11:26 +00:00
2020-02-02 20:30:06 +00:00
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
2020-02-02 04:54:38 +00:00
func DoPluginFlags ( ) {
2020-02-02 20:30:06 +00:00
if * flagClean || * flagPlugin != "" {
2020-02-02 04:54:38 +00:00
config . LoadAllPlugins ( )
2020-02-02 20:30:06 +00:00
if * flagPlugin != "" {
args := flag . Args ( )
2020-02-02 04:54:38 +00:00
2020-02-02 20:30:06 +00:00
config . PluginCommand ( os . Stdout , * flagPlugin , args )
} else if * flagClean {
CleanConfig ( )
}
2020-02-02 04:54:38 +00:00
os . Exit ( 0 )
}
}
2018-08-28 22:44:52 +00:00
// LoadInput determines which files should be loaded into buffers
// based on the input stored in flag.Args()
2020-06-20 22:24:12 +00:00
func LoadInput ( args [ ] string ) [ ] * buffer . Buffer {
2018-08-28 22:44:52 +00:00
// 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
buffers := make ( [ ] * buffer . Buffer , 0 , len ( args ) )
2020-02-27 17:39:19 +00:00
btype := buffer . BTDefault
if ! isatty . IsTerminal ( os . Stdout . Fd ( ) ) {
btype = buffer . BTStdout
}
2020-03-24 14:10:44 +00:00
files := make ( [ ] string , 0 , len ( args ) )
2020-05-30 02:48:23 +00:00
flagStartPos := buffer . Loc { - 1 , - 1 }
flagr := regexp . MustCompile ( ` ^\+(\d+)(?::(\d+))?$ ` )
2020-03-24 14:10:44 +00:00
for _ , a := range args {
2020-05-30 02:48:23 +00:00
match := flagr . FindStringSubmatch ( a )
if len ( match ) == 3 && match [ 2 ] != "" {
line , err := strconv . Atoi ( match [ 1 ] )
if err != nil {
screen . TermMessage ( err )
continue
2020-03-24 14:10:44 +00:00
}
2020-05-30 02:48:23 +00:00
col , err := strconv . Atoi ( match [ 2 ] )
if err != nil {
screen . TermMessage ( err )
continue
}
2020-05-30 16:23:29 +00:00
flagStartPos = buffer . Loc { col - 1 , line - 1 }
2020-05-30 02:48:23 +00:00
} else if len ( match ) == 3 && match [ 2 ] == "" {
line , err := strconv . Atoi ( match [ 1 ] )
if err != nil {
screen . TermMessage ( err )
continue
}
flagStartPos = buffer . Loc { 0 , line - 1 }
} else {
files = append ( files , a )
2020-03-24 14:10:44 +00:00
}
}
if len ( files ) > 0 {
2018-08-28 22:44:52 +00:00
// Option 1
// We go through each file and load it
2020-03-24 14:10:44 +00:00
for i := 0 ; i < len ( files ) ; i ++ {
2020-05-30 02:48:23 +00:00
buf , err := buffer . NewBufferFromFileAtLoc ( files [ i ] , btype , flagStartPos )
2018-08-28 22:44:52 +00:00
if err != nil {
2019-01-14 02:06:58 +00:00
screen . TermMessage ( err )
2018-08-28 22:44:52 +00:00
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 {
2019-01-14 02:06:58 +00:00
screen . TermMessage ( "Error reading from stdin: " , err )
2018-08-28 22:44:52 +00:00
input = [ ] byte { }
}
2020-05-30 02:48:23 +00:00
buffers = append ( buffers , buffer . NewBufferFromStringAtLoc ( string ( input ) , filename , btype , flagStartPos ) )
2018-08-28 22:44:52 +00:00
} else {
// Option 3, just open an empty buffer
2020-05-30 02:48:23 +00:00
buffers = append ( buffers , buffer . NewBufferFromStringAtLoc ( string ( input ) , filename , btype , flagStartPos ) )
2018-08-28 22:44:52 +00:00
}
return buffers
}
2018-08-26 03:06:44 +00:00
func main ( ) {
2020-02-27 17:39:19 +00:00
defer func ( ) {
if util . Stdout . Len ( ) > 0 {
fmt . Fprint ( os . Stdout , util . Stdout . String ( ) )
}
os . Exit ( 0 )
} ( )
2019-12-22 18:43:29 +00:00
2018-08-26 03:06:44 +00:00
var err error
2016-04-20 01:25:13 +00:00
2019-03-19 22:28:51 +00:00
InitFlags ( )
2023-01-30 02:10:57 +00:00
if * flagProfile {
f , err := os . Create ( "micro.prof" )
if err != nil {
log . Fatal ( "error creating CPU profile: " , err )
}
if err := pprof . StartCPUProfile ( f ) ; err != nil {
log . Fatal ( "error starting CPU profile: " , err )
}
defer pprof . StopCPUProfile ( )
}
2020-01-16 03:25:08 +00:00
InitLog ( )
2019-03-19 22:28:51 +00:00
err = config . InitConfigDir ( * flagConfigDir )
if err != nil {
screen . TermMessage ( err )
}
2018-08-27 19:53:10 +00:00
config . InitRuntimeFiles ( )
err = config . ReadSettings ( )
2018-08-26 03:06:44 +00:00
if err != nil {
2019-01-14 02:06:58 +00:00
screen . TermMessage ( err )
2018-08-26 03:06:44 +00:00
}
2020-06-07 21:31:16 +00:00
err = config . InitGlobalSettings ( )
if err != nil {
screen . TermMessage ( err )
}
2019-01-25 03:46:07 +00:00
2019-03-19 22:28:51 +00:00
// flag options
for k , v := range optionFlags {
2019-06-17 21:45:38 +00:00
if * v != "" {
nativeValue , err := config . GetNativeValue ( k , config . DefaultAllSettings ( ) [ k ] , * v )
2019-03-19 22:28:51 +00:00
if err != nil {
screen . TermMessage ( err )
continue
}
config . GlobalSettings [ k ] = nativeValue
2024-03-14 03:43:40 +00:00
config . VolatileSettings [ k ] = true
2019-03-19 22:28:51 +00:00
}
2019-03-17 23:03:37 +00:00
}
2020-02-02 04:54:38 +00:00
DoPluginFlags ( )
2020-06-20 22:24:12 +00:00
err = screen . Init ( )
if err != nil {
fmt . Println ( err )
fmt . Println ( "Fatal: Micro could not initialize a Screen." )
os . Exit ( 1 )
}
2020-07-05 00:00:39 +00:00
m := clipboard . SetMethod ( config . GetGlobalOption ( "clipboard" ) . ( string ) )
clipErr := clipboard . Initialize ( m )
2016-04-20 01:25:13 +00:00
defer func ( ) {
if err := recover ( ) ; err != nil {
2020-08-02 00:18:07 +00:00
if screen . Screen != nil {
screen . Screen . Fini ( )
}
2020-09-05 18:52:35 +00:00
if e , ok := err . ( * lua . ApiError ) ; ok {
fmt . Println ( "Lua API error:" , e )
} else {
fmt . Println ( "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" )
}
2019-12-22 04:26:53 +00:00
// backup all open buffers
for _ , b := range buffer . OpenBuffers {
2020-06-22 21:54:56 +00:00
b . Backup ( )
2019-12-22 04:26:53 +00:00
}
2016-04-20 01:25:13 +00:00
os . Exit ( 1 )
}
} ( )
2016-03-17 21:27:57 +00:00
2020-02-02 19:20:39 +00:00
err = config . LoadAllPlugins ( )
2020-01-03 00:00:42 +00:00
if err != nil {
screen . TermMessage ( err )
}
2020-02-02 19:20:39 +00:00
action . InitBindings ( )
action . InitCommands ( )
err = config . InitColorscheme ( )
2020-01-03 00:00:42 +00:00
if err != nil {
screen . TermMessage ( err )
}
2020-07-13 17:28:26 +00:00
err = config . RunPluginFn ( "preinit" )
if err != nil {
screen . TermMessage ( err )
}
2021-05-19 18:58:00 +00:00
action . InitGlobals ( )
buffer . SetMessager ( action . InfoBar )
2020-06-20 22:24:12 +00:00
args := flag . Args ( )
b := LoadInput ( args )
2020-01-02 20:25:07 +00:00
if len ( b ) == 0 {
// No buffers to open
screen . Screen . Fini ( )
runtime . Goexit ( )
}
2019-01-10 01:07:18 +00:00
action . InitTabs ( b )
2019-01-01 03:07:01 +00:00
2020-01-31 05:56:15 +00:00
err = config . RunPluginFn ( "init" )
if err != nil {
screen . TermMessage ( err )
}
2020-07-13 17:28:26 +00:00
err = config . RunPluginFn ( "postinit" )
if err != nil {
screen . TermMessage ( err )
}
2020-07-05 00:00:39 +00:00
if clipErr != nil {
2020-12-17 02:35:07 +00:00
log . Println ( clipErr , " or change 'clipboard' option" )
2020-07-05 00:00:39 +00:00
}
2020-10-09 03:33:34 +00:00
if a := config . GetGlobalOption ( "autosave" ) . ( float64 ) ; a > 0 {
config . SetAutoTime ( int ( a ) )
config . StartAutoSave ( )
}
2020-07-05 00:54:27 +00:00
screen . Events = make ( chan tcell . Event )
2019-12-29 23:53:59 +00:00
2021-08-03 01:05:22 +00:00
sigterm = make ( chan os . Signal , 1 )
sighup = make ( chan os . Signal , 1 )
2023-10-20 06:51:17 +00:00
signal . Notify ( sigterm , syscall . SIGTERM , syscall . SIGINT , syscall . SIGQUIT , syscall . SIGABRT )
2021-08-03 01:05:22 +00:00
signal . Notify ( sighup , syscall . SIGHUP )
2023-11-13 16:51:19 +00:00
timerChan = make ( chan func ( ) )
2018-08-27 19:53:10 +00:00
// Here is the event loop which runs in a separate thread
2019-01-10 21:37:05 +00:00
go func ( ) {
for {
screen . Lock ( )
e := screen . Screen . PollEvent ( )
screen . Unlock ( )
if e != nil {
2020-07-05 00:54:27 +00:00
screen . Events <- e
2019-01-10 21:37:05 +00:00
}
}
} ( )
2016-09-03 15:26:01 +00:00
2020-01-29 01:54:14 +00:00
// clear the drawchan so we don't redraw excessively
// if someone requested a redraw before we started displaying
2020-02-12 01:39:26 +00:00
for len ( screen . DrawChan ( ) ) > 0 {
<- screen . DrawChan ( )
2020-01-29 01:54:14 +00:00
}
// wait for initial resize event
select {
2020-07-05 00:54:27 +00:00
case event := <- screen . Events :
2020-01-29 01:54:14 +00:00
action . Tabs . HandleEvent ( event )
2020-02-02 04:54:38 +00:00
case <- time . After ( 10 * time . Millisecond ) :
2020-01-29 01:54:14 +00:00
// time out after 10ms
}
2018-08-27 19:53:10 +00:00
for {
2020-02-11 05:50:24 +00:00
DoEvent ( )
}
}
// DoEvent runs the main action loop of the editor
func DoEvent ( ) {
var event tcell . Event
// 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 ( )
2018-08-27 19:53:10 +00:00
2020-02-11 05:50:24 +00:00
// 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 {
2024-03-15 17:46:51 +00:00
b . AutoSave ( )
2018-08-27 19:53:10 +00:00
}
2020-02-11 05:50:24 +00:00
case <- shell . CloseTerms :
2020-07-05 00:54:27 +00:00
case event = <- screen . Events :
2020-02-12 01:39:26 +00:00
case <- screen . DrawChan ( ) :
2020-05-23 18:59:23 +00:00
for len ( screen . DrawChan ( ) ) > 0 {
<- screen . DrawChan ( )
}
2023-11-13 16:51:19 +00:00
case f := <- timerChan :
f ( )
2021-04-21 01:27:59 +00:00
case <- sighup :
for _ , b := range buffer . OpenBuffers {
if ! b . Modified ( ) {
b . Fini ( )
}
}
os . Exit ( 0 )
case <- sigterm :
for _ , b := range buffer . OpenBuffers {
if ! b . Modified ( ) {
b . Fini ( )
}
}
if screen . Screen != nil {
screen . Screen . Fini ( )
}
os . Exit ( 0 )
2020-02-11 05:50:24 +00:00
}
2024-03-12 17:35:33 +00:00
if event == nil {
return
}
2023-03-20 20:17:43 +00:00
if e , ok := event . ( * tcell . EventError ) ; ok {
log . Println ( "tcell event error: " , e . Error ( ) )
if e . Err ( ) == io . EOF {
// shutdown due to terminal closing/becoming inaccessible
for _ , b := range buffer . OpenBuffers {
if ! b . Modified ( ) {
b . Fini ( )
}
}
if screen . Screen != nil {
screen . Screen . Fini ( )
}
os . Exit ( 0 )
}
return
}
2024-03-12 17:49:24 +00:00
_ , resize := event . ( * tcell . EventResize )
if action . InfoBar . HasPrompt && ! resize {
2020-02-11 05:50:24 +00:00
action . InfoBar . HandleEvent ( event )
} else {
action . Tabs . HandleEvent ( event )
2018-08-27 19:53:10 +00:00
}
2016-03-17 21:27:57 +00:00
}