]> git.lizzy.rs Git - micro.git/blob - cmd/micro/micro.go
More actions and window organization
[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])
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))
147         } else {
148                 // Option 3, just open an empty buffer
149                 buffers = append(buffers, buffer.NewBufferFromString(string(input), filename))
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         err = config.InitColorscheme()
172         if err != nil {
173                 util.TermMessage(err)
174         }
175
176         screen.Init()
177
178         // If we have an error, we can exit cleanly and not completely
179         // mess up the terminal being worked in
180         // In other words we need to shut down tcell before the program crashes
181         defer func() {
182                 if err := recover(); err != nil {
183                         screen.Screen.Fini()
184                         fmt.Println("Micro encountered an error:", err)
185                         // Print the stack trace too
186                         fmt.Print(errors.Wrap(err, 2).ErrorStack())
187                         os.Exit(1)
188                 }
189         }()
190
191         b := LoadInput()[0]
192         width, height := screen.Screen.Size()
193         ep := NewBufEditPane(0, 0, width, height-1, b)
194
195         // Here is the event loop which runs in a separate thread
196         go func() {
197                 events = make(chan tcell.Event)
198                 for {
199                         screen.Lock()
200                         events <- screen.Screen.PollEvent()
201                         screen.Unlock()
202                 }
203         }()
204
205         for {
206                 // Display everything
207                 screen.Screen.Fill(' ', config.DefStyle)
208                 ep.Display()
209                 screen.Screen.Show()
210
211                 var event tcell.Event
212
213                 // Check for new events
214                 select {
215                 case event = <-events:
216                 }
217
218                 if event != nil {
219                         ep.HandleEvent(event)
220                 }
221         }
222
223         screen.Screen.Fini()
224 }