]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
ecee684771c1ab6adf730deb7e413650847670d3
[micro.git] / cmd / micro / micro.go
1 package main
2
3 import (
4         "flag"
5         "fmt"
6         "io/ioutil"
7         "os"
8         "strings"
9
10         "github.com/go-errors/errors"
11         isatty "github.com/mattn/go-isatty"
12         "github.com/zyedidia/micro/cmd/micro/action"
13         "github.com/zyedidia/micro/cmd/micro/buffer"
14         "github.com/zyedidia/micro/cmd/micro/config"
15         "github.com/zyedidia/micro/cmd/micro/screen"
16         "github.com/zyedidia/micro/cmd/micro/util"
17         "github.com/zyedidia/tcell"
18 )
19
20 const (
21         doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
22         autosaveTime         = 8   // Number of seconds to wait before autosaving
23 )
24
25 var (
26         // These variables should be set by the linker when compiling
27
28         // Version is the version number or commit hash
29         Version = "0.0.0-unknown"
30         // CommitHash is the commit this version was built on
31         CommitHash = "Unknown"
32         // CompileDate is the date this binary was compiled on
33         CompileDate = "Unknown"
34         // Debug logging
35         Debug = "ON"
36
37         // Event channel
38         events   chan tcell.Event
39         autosave chan bool
40
41         // Command line flags
42         flagVersion   = flag.Bool("version", false, "Show the version number and information")
43         flagStartPos  = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
44         flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
45         flagOptions   = flag.Bool("options", false, "Show all option help")
46 )
47
48 func InitFlags() {
49         flag.Usage = func() {
50                 fmt.Println("Usage: micro [OPTIONS] [FILE]...")
51                 fmt.Println("-config-dir dir")
52                 fmt.Println("    \tSpecify a custom location for the configuration directory")
53                 fmt.Println("-startpos LINE,COL")
54                 fmt.Println("+LINE:COL")
55                 fmt.Println("    \tSpecify a line and column to start the cursor at when opening a buffer")
56                 fmt.Println("    \tThis can also be done by opening file:LINE:COL")
57                 fmt.Println("-options")
58                 fmt.Println("    \tShow all option help")
59                 fmt.Println("-version")
60                 fmt.Println("    \tShow the version number and information")
61
62                 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")
63                 fmt.Println("-option value")
64                 fmt.Println("    \tSet `option` to `value` for this session")
65                 fmt.Println("    \tFor example: `micro -syntax off file.c`")
66                 fmt.Println("\nUse `micro -options` to see the full list of configuration options")
67         }
68
69         optionFlags := make(map[string]*string)
70
71         for k, v := range config.DefaultGlobalSettings() {
72                 optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'", k, v))
73         }
74
75         flag.Parse()
76
77         if *flagVersion {
78                 // If -version was passed
79                 fmt.Println("Version:", Version)
80                 fmt.Println("Commit hash:", CommitHash)
81                 fmt.Println("Compiled on", CompileDate)
82                 os.Exit(0)
83         }
84
85         if *flagOptions {
86                 // If -options was passed
87                 for k, v := range config.DefaultGlobalSettings() {
88                         fmt.Printf("-%s value\n", k)
89                         fmt.Printf("    \tDefault value: '%v'\n", v)
90                 }
91                 os.Exit(0)
92         }
93 }
94
95 // LoadInput determines which files should be loaded into buffers
96 // based on the input stored in flag.Args()
97 func LoadInput() []*buffer.Buffer {
98         // There are a number of ways micro should start given its input
99
100         // 1. If it is given a files in flag.Args(), it should open those
101
102         // 2. If there is no input file and the input is not a terminal, that means
103         // something is being piped in and the stdin should be opened in an
104         // empty buffer
105
106         // 3. If there is no input file and the input is a terminal, an empty buffer
107         // should be opened
108
109         var filename string
110         var input []byte
111         var err error
112         args := flag.Args()
113         buffers := make([]*buffer.Buffer, 0, len(args))
114
115         if len(args) > 0 {
116                 // Option 1
117                 // We go through each file and load it
118                 for i := 0; i < len(args); i++ {
119                         if strings.HasPrefix(args[i], "+") {
120                                 if strings.Contains(args[i], ":") {
121                                         split := strings.Split(args[i], ":")
122                                         *flagStartPos = split[0][1:] + "," + split[1]
123                                 } else {
124                                         *flagStartPos = args[i][1:] + ",0"
125                                 }
126                                 continue
127                         }
128
129                         buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
130                         if err != nil {
131                                 util.TermMessage(err)
132                                 continue
133                         }
134                         // If the file didn't exist, input will be empty, and we'll open an empty buffer
135                         buffers = append(buffers, buf)
136                 }
137         } else if !isatty.IsTerminal(os.Stdin.Fd()) {
138                 // Option 2
139                 // The input is not a terminal, so something is being piped in
140                 // and we should read from stdin
141                 input, err = ioutil.ReadAll(os.Stdin)
142                 if err != nil {
143                         util.TermMessage("Error reading from stdin: ", err)
144                         input = []byte{}
145                 }
146                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
147         } else {
148                 // Option 3, just open an empty buffer
149                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
150         }
151
152         return buffers
153 }
154
155 func main() {
156         var err error
157
158         InitLog()
159         InitFlags()
160         err = config.InitConfigDir(*flagConfigDir)
161         if err != nil {
162                 util.TermMessage(err)
163         }
164         config.InitRuntimeFiles()
165         err = config.ReadSettings()
166         if err != nil {
167                 util.TermMessage(err)
168         }
169         config.InitGlobalSettings()
170         action.InitBindings()
171         action.InitCommands()
172
173         err = config.InitColorscheme()
174         if err != nil {
175                 util.TermMessage(err)
176         }
177
178         screen.Init()
179
180         // If we have an error, we can exit cleanly and not completely
181         // mess up the terminal being worked in
182         // In other words we need to shut down tcell before the program crashes
183         defer func() {
184                 if err := recover(); err != nil {
185                         screen.Screen.Fini()
186                         fmt.Println("Micro encountered an error:", err)
187                         // Print the stack trace too
188                         fmt.Print(errors.Wrap(err, 2).ErrorStack())
189                         os.Exit(1)
190                 }
191         }()
192
193         b := LoadInput()[0]
194         width, height := screen.Screen.Size()
195         ep := action.NewBufEditPane(0, 0, width, height-1, b)
196
197         action.InitGlobals()
198
199         // Here is the event loop which runs in a separate thread
200         go func() {
201                 events = make(chan tcell.Event)
202                 for {
203                         screen.Lock()
204                         events <- screen.Screen.PollEvent()
205                         screen.Unlock()
206                 }
207         }()
208
209         for {
210                 // Display everything
211                 screen.Screen.Fill(' ', config.DefStyle)
212                 screen.Screen.HideCursor()
213                 ep.Display()
214                 action.InfoBar.Display()
215                 screen.Screen.Show()
216
217                 var event tcell.Event
218
219                 // Check for new events
220                 select {
221                 case event = <-events:
222                 }
223
224                 if event != nil {
225                         if action.InfoBar.HasPrompt {
226                                 action.InfoBar.HandleEvent(event)
227                         } else {
228                                 ep.HandleEvent(event)
229                         }
230                 }
231         }
232 }