9 "github.com/atotto/clipboard"
10 "github.com/gdamore/tcell"
11 "github.com/mitchellh/go-homedir"
14 var bindings map[tcell.Key]func(*View) bool
16 // InitBindings initializes the keybindings for micro
18 bindings = make(map[tcell.Key]func(*View) bool)
20 actions := map[string]func(*View) bool{
22 "CursorDown": CursorDown,
23 "CursorLeft": CursorLeft,
24 "CursorRight": CursorRight,
25 "InsertEnter": InsertEnter,
26 "InsertSpace": InsertSpace,
27 "Backspace": Backspace,
29 "InsertTab": InsertTab,
33 "FindPrevious": FindPrevious,
39 "SelectAll": SelectAll,
41 "Beginning": Beginning,
45 "HalfPageUp": HalfPageUp,
46 "HalfPageDown": HalfPageDown,
47 "ToggleRuler": ToggleRuler,
50 keys := map[string]tcell.Key{
52 "Down": tcell.KeyDown,
53 "Right": tcell.KeyRight,
54 "Left": tcell.KeyLeft,
55 "UpLeft": tcell.KeyUpLeft,
56 "UpRight": tcell.KeyUpRight,
57 "DownLeft": tcell.KeyDownLeft,
58 "DownRight": tcell.KeyDownRight,
59 "Center": tcell.KeyCenter,
60 "PgUp": tcell.KeyPgUp,
61 "PgDn": tcell.KeyPgDn,
62 "Home": tcell.KeyHome,
64 "Insert": tcell.KeyInsert,
65 "Delete": tcell.KeyDelete,
66 "Help": tcell.KeyHelp,
67 "Exit": tcell.KeyExit,
68 "Clear": tcell.KeyClear,
69 "Cancel": tcell.KeyCancel,
70 "Print": tcell.KeyPrint,
71 "Pause": tcell.KeyPause,
72 "Backtab": tcell.KeyBacktab,
137 "CtrlSpace": tcell.KeyCtrlSpace,
138 "CtrlA": tcell.KeyCtrlA,
139 "CtrlB": tcell.KeyCtrlB,
140 "CtrlC": tcell.KeyCtrlC,
141 "CtrlD": tcell.KeyCtrlD,
142 "CtrlE": tcell.KeyCtrlE,
143 "CtrlF": tcell.KeyCtrlF,
144 "CtrlG": tcell.KeyCtrlG,
145 "CtrlH": tcell.KeyCtrlH,
146 "CtrlI": tcell.KeyCtrlI,
147 "CtrlJ": tcell.KeyCtrlJ,
148 "CtrlK": tcell.KeyCtrlK,
149 "CtrlL": tcell.KeyCtrlL,
150 "CtrlM": tcell.KeyCtrlM,
151 "CtrlN": tcell.KeyCtrlN,
152 "CtrlO": tcell.KeyCtrlO,
153 "CtrlP": tcell.KeyCtrlP,
154 "CtrlQ": tcell.KeyCtrlQ,
155 "CtrlR": tcell.KeyCtrlR,
156 "CtrlS": tcell.KeyCtrlS,
157 "CtrlT": tcell.KeyCtrlT,
158 "CtrlU": tcell.KeyCtrlU,
159 "CtrlV": tcell.KeyCtrlV,
160 "CtrlW": tcell.KeyCtrlW,
161 "CtrlX": tcell.KeyCtrlX,
162 "CtrlY": tcell.KeyCtrlY,
163 "CtrlZ": tcell.KeyCtrlZ,
164 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
165 "CtrlBackslash": tcell.KeyCtrlBackslash,
166 "CtrlRightSq": tcell.KeyCtrlRightSq,
167 "CtrlCarat": tcell.KeyCtrlCarat,
168 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
169 "Backspace": tcell.KeyBackspace,
172 "Escape": tcell.KeyEscape,
173 "Enter": tcell.KeyEnter,
174 "Space": tcell.KeySpace,
175 "Backspace2": tcell.KeyBackspace2,
178 var parsed map[string]string
180 filename := configDir + "/bindings.json"
181 if _, e := os.Stat(filename); e == nil {
182 input, err := ioutil.ReadFile(filename)
184 TermMessage("Error reading settings.json file: " + err.Error())
188 json.Unmarshal(input, &parsed)
190 parsed = DefaultBindings()
191 if _, e := os.Stat(configDir); e == nil {
192 txt, _ := json.MarshalIndent(parsed, "", " ")
193 err := ioutil.WriteFile(filename, txt, 0644)
195 TermMessage("Error writing bindings.json file: " + err.Error())
200 for k, v := range parsed {
201 bindings[keys[k]] = actions[v]
205 // DefaultBindings returns a map containing micro's default keybindings
206 func DefaultBindings() map[string]string {
207 return map[string]string{
209 "Down": "CursorDown",
210 "Right": "CursorRight",
211 "Left": "CursorLeft",
212 "Enter": "InsertEnter",
213 "Space": "InsertSpace",
214 "Backspace": "Backspace",
215 "Backspace2": "Backspace",
221 "CtrlP": "FindPrevious",
227 "CtrlA": "SelectAll",
231 "PageDown": "PageDown",
232 "CtrlU": "HalfPageUp",
233 "CtrlD": "HalfPageDown",
234 "CtrlR": "ToggleRuler",
239 // CursorUp moves the cursor up
240 func CursorUp(v *View) bool {
241 v.cursor.ResetSelection()
246 // CursorDown moves the cursor down
247 func CursorDown(v *View) bool {
248 v.cursor.ResetSelection()
253 // CursorLeft moves the cursor left
254 func CursorLeft(v *View) bool {
255 v.cursor.ResetSelection()
260 // CursorRight moves the cursor right
261 func CursorRight(v *View) bool {
262 v.cursor.ResetSelection()
267 // InsertSpace inserts a space
268 func InsertSpace(v *View) bool {
270 if v.cursor.HasSelection() {
271 v.cursor.DeleteSelection()
272 v.cursor.ResetSelection()
274 v.eh.Insert(v.cursor.Loc(), " ")
279 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
280 func InsertEnter(v *View) bool {
282 if v.cursor.HasSelection() {
283 v.cursor.DeleteSelection()
284 v.cursor.ResetSelection()
287 v.eh.Insert(v.cursor.Loc(), "\n")
288 ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y])
291 if settings.AutoIndent {
292 v.eh.Insert(v.cursor.Loc(), ws)
293 for i := 0; i < len(ws); i++ {
297 v.cursor.lastVisualX = v.cursor.GetVisualX()
301 // Backspace deletes the previous character
302 func Backspace(v *View) bool {
303 // Delete a character
304 if v.cursor.HasSelection() {
305 v.cursor.DeleteSelection()
306 v.cursor.ResetSelection()
307 } else if v.cursor.Loc() > 0 {
308 // We have to do something a bit hacky here because we want to
309 // delete the line by first moving left and then deleting backwards
310 // but the undo redo would place the cursor in the wrong place
311 // So instead we move left, save the position, move back, delete
312 // and restore the position
314 // If the user is using spaces instead of tabs and they are deleting
315 // whitespace at the start of the line, we should delete as if its a
316 // tab (tabSize number of spaces)
317 lineStart := v.buf.lines[v.cursor.y][:v.cursor.x]
318 if settings.TabsToSpaces && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%settings.TabSize == 0 {
319 loc := v.cursor.Loc()
320 v.cursor.SetLoc(loc - settings.TabSize)
321 cx, cy := v.cursor.x, v.cursor.y
323 v.eh.Remove(loc-settings.TabSize, loc)
324 v.cursor.x, v.cursor.y = cx, cy
327 cx, cy := v.cursor.x, v.cursor.y
329 loc := v.cursor.Loc()
330 v.eh.Remove(loc-1, loc)
331 v.cursor.x, v.cursor.y = cx, cy
334 v.cursor.lastVisualX = v.cursor.GetVisualX()
338 // Delete deletes the next character
339 func Delete(v *View) bool {
340 if v.cursor.HasSelection() {
341 v.cursor.DeleteSelection()
342 v.cursor.ResetSelection()
344 loc := v.cursor.Loc()
345 if loc < len(v.buf.text) {
346 v.eh.Remove(loc, loc+1)
352 // InsertTab inserts a tab or spaces
353 func InsertTab(v *View) bool {
355 if v.cursor.HasSelection() {
356 v.cursor.DeleteSelection()
357 v.cursor.ResetSelection()
359 if settings.TabsToSpaces {
360 v.eh.Insert(v.cursor.Loc(), Spaces(settings.TabSize))
361 for i := 0; i < settings.TabSize; i++ {
365 v.eh.Insert(v.cursor.Loc(), "\t")
371 // Save the buffer to disk
372 func Save(v *View) bool {
373 // If this is an empty buffer, ask for a filename
374 if v.buf.path == "" {
375 filename, canceled := messenger.Prompt("Filename: ")
377 v.buf.path = filename
378 v.buf.name = filename
385 messenger.Error(err.Error())
387 messenger.Message("Saved " + v.buf.path)
392 // Find opens a prompt and searches forward for the input
393 func Find(v *View) bool {
394 if v.cursor.HasSelection() {
395 searchStart = v.cursor.curSelection[1]
397 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
403 // FindNext searches forwards for the last used search term
404 func FindNext(v *View) bool {
405 if v.cursor.HasSelection() {
406 searchStart = v.cursor.curSelection[1]
408 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
410 messenger.Message("Find: " + lastSearch)
411 Search(lastSearch, v, true)
415 // FindPrevious searches backwards for the last used search term
416 func FindPrevious(v *View) bool {
417 if v.cursor.HasSelection() {
418 searchStart = v.cursor.curSelection[0]
420 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
422 messenger.Message("Find: " + lastSearch)
423 Search(lastSearch, v, false)
427 // Undo undoes the last action
428 func Undo(v *View) bool {
433 // Redo redoes the last action
434 func Redo(v *View) bool {
439 // Copy the selection to the system clipboard
440 func Copy(v *View) bool {
441 if v.cursor.HasSelection() {
442 if !clipboard.Unsupported {
443 clipboard.WriteAll(v.cursor.GetSelection())
445 messenger.Error("Clipboard is not supported on your system")
451 // Cut the selection to the system clipboard
452 func Cut(v *View) bool {
453 if v.cursor.HasSelection() {
454 if !clipboard.Unsupported {
455 clipboard.WriteAll(v.cursor.GetSelection())
456 v.cursor.DeleteSelection()
457 v.cursor.ResetSelection()
459 messenger.Error("Clipboard is not supported on your system")
465 // Paste whatever is in the system clipboard into the buffer
466 // Delete and paste if the user has a selection
467 func Paste(v *View) bool {
468 if !clipboard.Unsupported {
469 if v.cursor.HasSelection() {
470 v.cursor.DeleteSelection()
471 v.cursor.ResetSelection()
473 clip, _ := clipboard.ReadAll()
474 v.eh.Insert(v.cursor.Loc(), clip)
475 v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
477 messenger.Error("Clipboard is not supported on your system")
482 // SelectAll selects the entire buffer
483 func SelectAll(v *View) bool {
484 v.cursor.curSelection[1] = 0
485 v.cursor.curSelection[0] = v.buf.Len()
486 // Put the cursor at the beginning
492 // OpenFile opens a new file in the buffer
493 func OpenFile(v *View) bool {
494 if v.CanClose("Continue? (yes, no, save) ") {
495 filename, canceled := messenger.Prompt("File to open: ")
499 home, _ := homedir.Dir()
500 filename = strings.Replace(filename, "~", home, 1)
501 file, err := ioutil.ReadFile(filename)
504 messenger.Error(err.Error())
507 buf := NewBuffer(string(file), filename)
513 // Beginning moves the viewport to the start of the buffer
514 func Beginning(v *View) bool {
519 // End moves the viewport to the end of the buffer
520 func End(v *View) bool {
521 if v.height > len(v.buf.lines) {
524 v.topline = len(v.buf.lines) - v.height
529 // PageUp scrolls the view up a page
530 func PageUp(v *View) bool {
531 if v.topline > v.height {
539 // PageDown scrolls the view down a page
540 func PageDown(v *View) bool {
541 if len(v.buf.lines)-(v.topline+v.height) > v.height {
542 v.ScrollDown(v.height)
544 if len(v.buf.lines) >= v.height {
545 v.topline = len(v.buf.lines) - v.height
551 // HalfPageUp scrolls the view up half a page
552 func HalfPageUp(v *View) bool {
553 if v.topline > v.height/2 {
554 v.ScrollUp(v.height / 2)
561 // HalfPageDown scrolls the view down half a page
562 func HalfPageDown(v *View) bool {
563 if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
564 v.ScrollDown(v.height / 2)
566 if len(v.buf.lines) >= v.height {
567 v.topline = len(v.buf.lines) - v.height
573 // ToggleRuler turns line numbers off and on
574 func ToggleRuler(v *View) bool {
575 if settings.Ruler == false {
576 settings.Ruler = true
578 settings.Ruler = false