Browse Source

Initial commit.

pull/5/head
Pat Gavlin 2 years ago
commit
31a9964a50
  1. 22
      LICENSE
  2. 25
      LICENSE-THIRD-PARTY
  3. 137
      README.md
  4. 1084
      actions.go
  5. 479
      bindings.go
  6. 461
      buffer.go
  7. 238
      cellview.go
  8. 59
      cmd/femto/femto.go
  9. 241
      colorscheme.go
  10. 397
      cursor.go
  11. 284
      eventhandler.go
  12. 14
      go.mod
  13. 42
      go.sum
  14. 267
      lineArray.go
  15. 171
      loc.go
  16. 142
      rtfiles.go
  17. 22
      runtime/assets_generate.go
  18. 7
      runtime/files.go
  19. 26
      runtime/files/colorschemes/atom-dark-tc.micro
  20. 24
      runtime/files/colorschemes/bubblegum.micro
  21. 40
      runtime/files/colorschemes/cmc-16.micro
  22. 37
      runtime/files/colorschemes/cmc-paper.micro
  23. 36
      runtime/files/colorschemes/cmc-tc.micro
  24. 27
      runtime/files/colorschemes/darcula.micro
  25. 27
      runtime/files/colorschemes/default.micro
  26. 23
      runtime/files/colorschemes/geany.micro
  27. 24
      runtime/files/colorschemes/github-tc.micro
  28. 21
      runtime/files/colorschemes/gruvbox-tc.micro
  29. 19
      runtime/files/colorschemes/gruvbox.micro
  30. 25
      runtime/files/colorschemes/in_progress/codeblocks-paper.micro
  31. 23
      runtime/files/colorschemes/in_progress/codeblocks.micro
  32. 31
      runtime/files/colorschemes/in_progress/funky-cactus.micro
  33. 23
      runtime/files/colorschemes/in_progress/gameboy-tc.micro
  34. 21
      runtime/files/colorschemes/in_progress/geany-alt-tc.micro
  35. 24
      runtime/files/colorschemes/in_progress/github.micro
  36. 25
      runtime/files/colorschemes/in_progress/mc.micro
  37. 5
      runtime/files/colorschemes/in_progress/monochrome-paper.micro
  38. 3
      runtime/files/colorschemes/in_progress/monochrome.micro
  39. 30
      runtime/files/colorschemes/in_progress/nano.micro
  40. 22
      runtime/files/colorschemes/in_progress/paper-tc.micro
  41. 27
      runtime/files/colorschemes/in_progress/paper.micro
  42. 23
      runtime/files/colorschemes/in_progress/symbian-tc.micro
  43. 28
      runtime/files/colorschemes/material-tc.micro
  44. 27
      runtime/files/colorschemes/monokai.micro
  45. 26
      runtime/files/colorschemes/railscast.micro
  46. 25
      runtime/files/colorschemes/simple.micro
  47. 24
      runtime/files/colorschemes/solarized-tc.micro
  48. 23
      runtime/files/colorschemes/solarized.micro
  49. 32
      runtime/files/colorschemes/twilight.micro
  50. 23
      runtime/files/colorschemes/zenburn.micro
  51. 22
      runtime/files/syntax/LICENSE
  52. 129
      runtime/files/syntax/PowerShell.yaml
  53. 68
      runtime/files/syntax/README.md
  54. 44
      runtime/files/syntax/ada.yaml
  55. 59
      runtime/files/syntax/apacheconf.yaml
  56. 101
      runtime/files/syntax/arduino.yaml
  57. 51
      runtime/files/syntax/asciidoc.yaml
  58. 110
      runtime/files/syntax/asm.yaml
  59. 99
      runtime/files/syntax/ats.yaml
  60. 44
      runtime/files/syntax/awk.yaml
  61. 28
      runtime/files/syntax/c++.yaml
  62. 52
      runtime/files/syntax/c.yaml
  63. 23
      runtime/files/syntax/caddyfile.yaml
  64. 38
      runtime/files/syntax/clojure.yaml
  65. 42
      runtime/files/syntax/cmake.yaml
  66. 29
      runtime/files/syntax/coffeescript.yaml
  67. 19
      runtime/files/syntax/colortest.yaml
  68. 17
      runtime/files/syntax/conf.yaml
  69. 17
      runtime/files/syntax/conky.yaml
  70. 55
      runtime/files/syntax/cpp.yaml
  71. 36
      runtime/files/syntax/crontab.yaml
  72. 64
      runtime/files/syntax/crystal.yaml
  73. 51
      runtime/files/syntax/csharp.yaml
  74. 44
      runtime/files/syntax/css.yaml
  75. 52
      runtime/files/syntax/cython.yaml
  76. 121
      runtime/files/syntax/d.yaml
  77. 46
      runtime/files/syntax/dart.yaml
  78. 36
      runtime/files/syntax/dockerfile.yaml
  79. 29
      runtime/files/syntax/dot.yaml
  80. 30
      runtime/files/syntax/elixir.yaml
  81. 38
      runtime/files/syntax/elm.yaml
  82. 42
      runtime/files/syntax/erb.yaml
  83. 41
      runtime/files/syntax/erlang.yaml
  84. 48
      runtime/files/syntax/fish.yaml
  85. 63
      runtime/files/syntax/fortran.yaml
  86. 48
      runtime/files/syntax/fsharp.yaml
  87. 61
      runtime/files/syntax/gdscript.yaml
  88. 48
      runtime/files/syntax/gentoo-ebuild.yaml
  89. 23
      runtime/files/syntax/gentoo-etc-portage.yaml
  90. 25
      runtime/files/syntax/git-commit.yaml
  91. 14
      runtime/files/syntax/git-config.yaml
  92. 19
      runtime/files/syntax/git-rebase-todo.yaml
  93. 26
      runtime/files/syntax/glsl.yaml
  94. 62
      runtime/files/syntax/go.yaml
  95. 73
      runtime/files/syntax/golo.yaml
  96. 47
      runtime/files/syntax/graphql.yaml
  97. 30
      runtime/files/syntax/groff.yaml
  98. 16
      runtime/files/syntax/haml.yaml
  99. 50
      runtime/files/syntax/haskell.yaml
  100. 44
      runtime/files/syntax/html.yaml

22
LICENSE

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2019: Pat Gavlin, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

25
LICENSE-THIRD-PARTY

@ -0,0 +1,25 @@
github.com/zyedidia/micro/LICENSE
================
MIT License
Copyright (c) 2016-2017: Zachary Yedidia, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

137
README.md

@ -0,0 +1,137 @@
## femto, an editor component for tview
`femto` is a text editor component for tview. The vast majority of the code is derived from
[the micro editor](github.com/zyedidia/micro).
**Note** The shape of the `femto` API is a work-in-progress, and should not be considered stable.
### Default keybindings
```
Up: CursorUp
Down: CursorDown
Right: CursorRight
Left: CursorLeft
ShiftUp: SelectUp
ShiftDown: SelectDown
ShiftLeft: SelectLeft
ShiftRight: SelectRight
AltLeft: WordLeft
AltRight: WordRight
AltUp: MoveLinesUp
AltDown: MoveLinesDown
AltShiftRight: SelectWordRight
AltShiftLeft: SelectWordLeft
CtrlLeft: StartOfLine
CtrlRight: EndOfLine
CtrlShiftLeft: SelectToStartOfLine
ShiftHome: SelectToStartOfLine
CtrlShiftRight: SelectToEndOfLine
ShiftEnd: SelectToEndOfLine
CtrlUp: CursorStart
CtrlDown: CursorEnd
CtrlShiftUp: SelectToStart
CtrlShiftDown: SelectToEnd
Alt-{: ParagraphPrevious
Alt-}: ParagraphNext
Enter: InsertNewline
CtrlH: Backspace
Backspace: Backspace
Alt-CtrlH: DeleteWordLeft
Alt-Backspace: DeleteWordLeft
Tab: IndentSelection,InsertTab
Backtab: OutdentSelection,OutdentLine
CtrlZ: Undo
CtrlY: Redo
CtrlC: Copy
CtrlX: Cut
CtrlK: CutLine
CtrlD: DuplicateLine
CtrlV: Paste
CtrlA: SelectAll
Home: StartOfLine
End: EndOfLine
CtrlHome: CursorStart
CtrlEnd: CursorEnd
PageUp: CursorPageUp
PageDown: CursorPageDown
CtrlR: ToggleRuler
Delete: Delete
Insert: ToggleOverwriteMode
Alt-f: WordRight
Alt-b: WordLeft
Alt-a: StartOfLine
Alt-e: EndOfLine
Esc: Escape
Alt-n: SpawnMultiCursor
Alt-m: SpawnMultiCursorSelect
Alt-p: RemoveMultiCursor
Alt-c: RemoveAllMultiCursors
Alt-x: SkipMultiCursor
```
### Example Usage
The code below (also found in `cmd/femto/femto.go`) creates a `tview` application with a single full-screen editor
that operates on one file at a time. Ctrl-s saves any edits; Ctrl-q quits.
```go
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"github.com/gdamore/tcell"
"github.com/pgavlin/femto"
"github.com/pgavlin/femto/runtime"
"github.com/rivo/tview"
)
func saveBuffer(b *femto.Buffer, path string) error {
return ioutil.WriteFile(path, []byte(b.String()), 0600)
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: femto [filename]\n")
os.Exit(1)
}
path := os.Args[1]
content, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("could not read %v: %v", path, err)
}
var colorscheme femto.Colorscheme
if monokai := runtime.Files.FindFile(femto.RTColorscheme, "monokai"); monokai != nil {
if data, err := monokai.Data(); err == nil {
colorscheme = femto.ParseColorscheme(string(data))
}
}
app := tview.NewApplication()
buffer := femto.NewBufferFromString(string(content), path)
root := femto.NewView(buffer)
root.SetRuntimeFiles(runtime.Files)
root.SetColorscheme(colorscheme)
root.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlS:
saveBuffer(buffer, path)
return nil
case tcell.KeyCtrlQ:
app.Stop()
return nil
}
return event
})
app.SetRoot(root, true)
if err := app.Run(); err != nil {
log.Fatalf("%v", err)
}
}
```

1084
actions.go

File diff suppressed because it is too large

479
bindings.go

@ -0,0 +1,479 @@
package femto
import (
"strings"
"unicode"
"github.com/gdamore/tcell"
)
// Actions
const (
ActionCursorUp = "CursorUp"
ActionCursorDown = "CursorDown"
ActionCursorPageUp = "CursorPageUp"
ActionCursorPageDown = "CursorPageDown"
ActionCursorLeft = "CursorLeft"
ActionCursorRight = "CursorRight"
ActionCursorStart = "CursorStart"
ActionCursorEnd = "CursorEnd"
ActionSelectToStart = "SelectToStart"
ActionSelectToEnd = "SelectToEnd"
ActionSelectUp = "SelectUp"
ActionSelectDown = "SelectDown"
ActionSelectLeft = "SelectLeft"
ActionSelectRight = "SelectRight"
ActionWordRight = "WordRight"
ActionWordLeft = "WordLeft"
ActionSelectWordRight = "SelectWordRight"
ActionSelectWordLeft = "SelectWordLeft"
ActionDeleteWordRight = "DeleteWordRight"
ActionDeleteWordLeft = "DeleteWordLeft"
ActionSelectLine = "SelectLine"
ActionSelectToStartOfLine = "SelectToStartOfLine"
ActionSelectToEndOfLine = "SelectToEndOfLine"
ActionParagraphPrevious = "ParagraphPrevious"
ActionParagraphNext = "ParagraphNext"
ActionInsertNewline = "InsertNewline"
ActionInsertSpace = "InsertSpace"
ActionBackspace = "Backspace"
ActionDelete = "Delete"
ActionInsertTab = "InsertTab"
ActionCenter = "Center"
ActionUndo = "Undo"
ActionRedo = "Redo"
ActionCopy = "Copy"
ActionCut = "Cut"
ActionCutLine = "CutLine"
ActionDuplicateLine = "DuplicateLine"
ActionDeleteLine = "DeleteLine"
ActionMoveLinesUp = "MoveLinesUp"
ActionMoveLinesDown = "MoveLinesDown"
ActionIndentSelection = "IndentSelection"
ActionOutdentSelection = "OutdentSelection"
ActionOutdentLine = "OutdentLine"
ActionPaste = "Paste"
ActionSelectAll = "SelectAll"
ActionStart = "Start"
ActionEnd = "End"
ActionPageUp = "PageUp"
ActionPageDown = "PageDown"
ActionSelectPageUp = "SelectPageUp"
ActionSelectPageDown = "SelectPageDown"
ActionHalfPageUp = "HalfPageUp"
ActionHalfPageDown = "HalfPageDown"
ActionStartOfLine = "StartOfLine"
ActionEndOfLine = "EndOfLine"
ActionToggleRuler = "ToggleRuler"
ActionToggleOverwriteMode = "ToggleOverwriteMode"
ActionEscape = "Escape"
ActionScrollUp = "ScrollUp"
ActionScrollDown = "ScrollDown"
ActionSpawnMultiCursor = "SpawnMultiCursor"
ActionSpawnMultiCursorSelect = "SpawnMultiCursorSelect"
ActionRemoveMultiCursor = "RemoveMultiCursor"
ActionRemoveAllMultiCursors = "RemoveAllMultiCursors"
ActionSkipMultiCursor = "SkipMultiCursor"
ActionJumpToMatchingBrace = "JumpToMatchingBrace"
ActionInsertEnter = "InsertEnter"
ActionUnbindKey = "UnbindKey"
)
// keyDesc holds the data for a keypress (keycode + modifiers)
type keyDesc struct {
keyCode tcell.Key
modifiers tcell.ModMask
r rune
}
// KeyBindings associates key presses with view actions.
type KeyBindings map[keyDesc][]func(*View) bool
// NewKeyBindings returns a new set of keybindings from the given set of binding descriptions.
func NewKeyBindings(bindings map[string]string) KeyBindings {
return make(KeyBindings).BindKeys(bindings)
}
// BindKey binds a key to a list of actions. If the key is not found or if any action is not found, this function has
// no effect.
func (bindings KeyBindings) BindKey(key string, actions string) KeyBindings {
k, ok := findKey(key)
if !ok {
return bindings
}
actionNames := strings.Split(actions, ",")
if actionNames[0] == "UnbindKey" {
delete(bindings, k)
if len(actionNames) == 1 {
return bindings
}
actionNames = append(actionNames[:0], actionNames[1:]...)
}
acts := make([]func(*View) bool, 0, len(actionNames))
for _, actionName := range actionNames {
action := findAction(actionName)
if action == nil {
return bindings
}
acts = append(acts, action)
}
if len(acts) > 0 {
bindings[k] = acts
}
return bindings
}
// BindKeys binds a set of keys to actions.
func (bindings KeyBindings) BindKeys(keys map[string]string) KeyBindings {
for k, v := range keys {
bindings.BindKey(k, v)
}
return bindings
}
var bindingActions = map[string]func(*View) bool{
ActionCursorUp: (*View).CursorUp,
ActionCursorDown: (*View).CursorDown,
ActionCursorPageUp: (*View).CursorPageUp,
ActionCursorPageDown: (*View).CursorPageDown,
ActionCursorLeft: (*View).CursorLeft,
ActionCursorRight: (*View).CursorRight,
ActionCursorStart: (*View).CursorStart,
ActionCursorEnd: (*View).CursorEnd,
ActionSelectToStart: (*View).SelectToStart,
ActionSelectToEnd: (*View).SelectToEnd,
ActionSelectUp: (*View).SelectUp,
ActionSelectDown: (*View).SelectDown,
ActionSelectLeft: (*View).SelectLeft,
ActionSelectRight: (*View).SelectRight,
ActionWordRight: (*View).WordRight,
ActionWordLeft: (*View).WordLeft,
ActionSelectWordRight: (*View).SelectWordRight,
ActionSelectWordLeft: (*View).SelectWordLeft,
ActionDeleteWordRight: (*View).DeleteWordRight,
ActionDeleteWordLeft: (*View).DeleteWordLeft,
ActionSelectLine: (*View).SelectLine,
ActionSelectToStartOfLine: (*View).SelectToStartOfLine,
ActionSelectToEndOfLine: (*View).SelectToEndOfLine,
ActionParagraphPrevious: (*View).ParagraphPrevious,
ActionParagraphNext: (*View).ParagraphNext,
ActionInsertNewline: (*View).InsertNewline,
ActionInsertSpace: (*View).InsertSpace,
ActionBackspace: (*View).Backspace,
ActionDelete: (*View).Delete,
ActionInsertTab: (*View).InsertTab,
ActionCenter: (*View).Center,
ActionUndo: (*View).Undo,
ActionRedo: (*View).Redo,
ActionCopy: (*View).Copy,
ActionCut: (*View).Cut,
ActionCutLine: (*View).CutLine,
ActionDuplicateLine: (*View).DuplicateLine,
ActionDeleteLine: (*View).DeleteLine,
ActionMoveLinesUp: (*View).MoveLinesUp,
ActionMoveLinesDown: (*View).MoveLinesDown,
ActionIndentSelection: (*View).IndentSelection,
ActionOutdentSelection: (*View).OutdentSelection,
ActionOutdentLine: (*View).OutdentLine,
ActionPaste: (*View).Paste,
ActionSelectAll: (*View).SelectAll,
ActionStart: (*View).Start,
ActionEnd: (*View).End,
ActionPageUp: (*View).PageUp,
ActionPageDown: (*View).PageDown,
ActionSelectPageUp: (*View).SelectPageUp,
ActionSelectPageDown: (*View).SelectPageDown,
ActionHalfPageUp: (*View).HalfPageUp,
ActionHalfPageDown: (*View).HalfPageDown,
ActionStartOfLine: (*View).StartOfLine,
ActionEndOfLine: (*View).EndOfLine,
ActionToggleRuler: (*View).ToggleRuler,
ActionToggleOverwriteMode: (*View).ToggleOverwriteMode,
ActionEscape: (*View).Escape,
ActionScrollUp: (*View).ScrollUpAction,
ActionScrollDown: (*View).ScrollDownAction,
ActionSpawnMultiCursor: (*View).SpawnMultiCursor,
ActionSpawnMultiCursorSelect: (*View).SpawnMultiCursorSelect,
ActionRemoveMultiCursor: (*View).RemoveMultiCursor,
ActionRemoveAllMultiCursors: (*View).RemoveAllMultiCursors,
ActionSkipMultiCursor: (*View).SkipMultiCursor,
ActionJumpToMatchingBrace: (*View).JumpToMatchingBrace,
ActionInsertEnter: (*View).InsertNewline,
}
var bindingKeys = map[string]tcell.Key{
"Up": tcell.KeyUp,
"Down": tcell.KeyDown,
"Right": tcell.KeyRight,
"Left": tcell.KeyLeft,
"UpLeft": tcell.KeyUpLeft,
"UpRight": tcell.KeyUpRight,
"DownLeft": tcell.KeyDownLeft,
"DownRight": tcell.KeyDownRight,
"Center": tcell.KeyCenter,
"PageUp": tcell.KeyPgUp,
"PageDown": tcell.KeyPgDn,
"Home": tcell.KeyHome,
"End": tcell.KeyEnd,
"Insert": tcell.KeyInsert,
"Delete": tcell.KeyDelete,
"Help": tcell.KeyHelp,
"Exit": tcell.KeyExit,
"Clear": tcell.KeyClear,
"Cancel": tcell.KeyCancel,
"Print": tcell.KeyPrint,
"Pause": tcell.KeyPause,
"Backtab": tcell.KeyBacktab,
"F1": tcell.KeyF1,
"F2": tcell.KeyF2,
"F3": tcell.KeyF3,
"F4": tcell.KeyF4,
"F5": tcell.KeyF5,
"F6": tcell.KeyF6,
"F7": tcell.KeyF7,
"F8": tcell.KeyF8,
"F9": tcell.KeyF9,
"F10": tcell.KeyF10,
"F11": tcell.KeyF11,
"F12": tcell.KeyF12,
"F13": tcell.KeyF13,
"F14": tcell.KeyF14,
"F15": tcell.KeyF15,
"F16": tcell.KeyF16,
"F17": tcell.KeyF17,
"F18": tcell.KeyF18,
"F19": tcell.KeyF19,
"F20": tcell.KeyF20,
"F21": tcell.KeyF21,
"F22": tcell.KeyF22,
"F23": tcell.KeyF23,
"F24": tcell.KeyF24,
"F25": tcell.KeyF25,
"F26": tcell.KeyF26,
"F27": tcell.KeyF27,
"F28": tcell.KeyF28,
"F29": tcell.KeyF29,
"F30": tcell.KeyF30,
"F31": tcell.KeyF31,
"F32": tcell.KeyF32,
"F33": tcell.KeyF33,
"F34": tcell.KeyF34,
"F35": tcell.KeyF35,
"F36": tcell.KeyF36,
"F37": tcell.KeyF37,
"F38": tcell.KeyF38,
"F39": tcell.KeyF39,
"F40": tcell.KeyF40,
"F41": tcell.KeyF41,
"F42": tcell.KeyF42,
"F43": tcell.KeyF43,
"F44": tcell.KeyF44,
"F45": tcell.KeyF45,
"F46": tcell.KeyF46,
"F47": tcell.KeyF47,
"F48": tcell.KeyF48,
"F49": tcell.KeyF49,
"F50": tcell.KeyF50,
"F51": tcell.KeyF51,
"F52": tcell.KeyF52,
"F53": tcell.KeyF53,
"F54": tcell.KeyF54,
"F55": tcell.KeyF55,
"F56": tcell.KeyF56,
"F57": tcell.KeyF57,
"F58": tcell.KeyF58,
"F59": tcell.KeyF59,
"F60": tcell.KeyF60,
"F61": tcell.KeyF61,
"F62": tcell.KeyF62,
"F63": tcell.KeyF63,
"F64": tcell.KeyF64,
"CtrlSpace": tcell.KeyCtrlSpace,
"CtrlA": tcell.KeyCtrlA,
"CtrlB": tcell.KeyCtrlB,
"CtrlC": tcell.KeyCtrlC,
"CtrlD": tcell.KeyCtrlD,
"CtrlE": tcell.KeyCtrlE,
"CtrlF": tcell.KeyCtrlF,
"CtrlG": tcell.KeyCtrlG,
"CtrlH": tcell.KeyCtrlH,
"CtrlI": tcell.KeyCtrlI,
"CtrlJ": tcell.KeyCtrlJ,
"CtrlK": tcell.KeyCtrlK,
"CtrlL": tcell.KeyCtrlL,
"CtrlM": tcell.KeyCtrlM,
"CtrlN": tcell.KeyCtrlN,
"CtrlO": tcell.KeyCtrlO,
"CtrlP": tcell.KeyCtrlP,
"CtrlQ": tcell.KeyCtrlQ,
"CtrlR": tcell.KeyCtrlR,
"CtrlS": tcell.KeyCtrlS,
"CtrlT": tcell.KeyCtrlT,
"CtrlU": tcell.KeyCtrlU,
"CtrlV": tcell.KeyCtrlV,
"CtrlW": tcell.KeyCtrlW,
"CtrlX": tcell.KeyCtrlX,
"CtrlY": tcell.KeyCtrlY,
"CtrlZ": tcell.KeyCtrlZ,
"CtrlLeftSq": tcell.KeyCtrlLeftSq,
"CtrlBackslash": tcell.KeyCtrlBackslash,
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace": tcell.KeyBackspace2,
"OldBackspace": tcell.KeyBackspace,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
"PgDown": tcell.KeyPgDn,
}
var DefaultKeyBindings KeyBindings
// InitBindings initializes the keybindings for micro
func init() {
DefaultKeyBindings = NewKeyBindings(map[string]string{
"Up": ActionCursorUp,
"Down": ActionCursorDown,
"Right": ActionCursorRight,
"Left": ActionCursorLeft,
"ShiftUp": ActionSelectUp,
"ShiftDown": ActionSelectDown,
"ShiftLeft": ActionSelectLeft,
"ShiftRight": ActionSelectRight,
"AltLeft": ActionWordLeft,
"AltRight": ActionWordRight,
"AltUp": ActionMoveLinesUp,
"AltDown": ActionMoveLinesDown,
"AltShiftRight": ActionSelectWordRight,
"AltShiftLeft": ActionSelectWordLeft,
"CtrlLeft": ActionStartOfLine,
"CtrlRight": ActionEndOfLine,
"CtrlShiftLeft": ActionSelectToStartOfLine,
"ShiftHome": ActionSelectToStartOfLine,
"CtrlShiftRight": ActionSelectToEndOfLine,
"ShiftEnd": ActionSelectToEndOfLine,
"CtrlUp": ActionCursorStart,
"CtrlDown": ActionCursorEnd,
"CtrlShiftUp": ActionSelectToStart,
"CtrlShiftDown": ActionSelectToEnd,
"Alt-{": ActionParagraphPrevious,
"Alt-}": ActionParagraphNext,
"Enter": ActionInsertNewline,
"CtrlH": ActionBackspace,
"Backspace": ActionBackspace,
"Alt-CtrlH": ActionDeleteWordLeft,
"Alt-Backspace": ActionDeleteWordLeft,
"Tab": ActionIndentSelection + "," + ActionInsertTab,
"Backtab": ActionOutdentSelection + "," + ActionOutdentLine,
"CtrlZ": ActionUndo,
"CtrlY": ActionRedo,
"CtrlC": ActionCopy,
"CtrlX": ActionCut,
"CtrlK": ActionCutLine,
"CtrlD": ActionDuplicateLine,
"CtrlV": ActionPaste,
"CtrlA": ActionSelectAll,
"Home": ActionStartOfLine,
"End": ActionEndOfLine,
"CtrlHome": ActionCursorStart,
"CtrlEnd": ActionCursorEnd,
"PageUp": ActionCursorPageUp,
"PageDown": ActionCursorPageDown,
"CtrlR": ActionToggleRuler,
"Delete": ActionDelete,
"Insert": ActionToggleOverwriteMode,
"Alt-f": ActionWordRight,
"Alt-b": ActionWordLeft,
"Alt-a": ActionStartOfLine,
"Alt-e": ActionEndOfLine,
"Esc": ActionEscape,
"Alt-n": ActionSpawnMultiCursor,
"Alt-m": ActionSpawnMultiCursorSelect,
"Alt-p": ActionRemoveMultiCursor,
"Alt-c": ActionRemoveAllMultiCursors,
"Alt-x": ActionSkipMultiCursor,
})
}
// findKey will find binding Key 'b' using string 'k'
func findKey(k string) (b keyDesc, ok bool) {
modifiers := tcell.ModNone
// First, we'll strip off all the modifiers in the name and add them to the
// ModMask
modSearch:
for {
switch {
case strings.HasPrefix(k, "-"):
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
k = k[4:]
modifiers |= tcell.ModCtrl
case strings.HasPrefix(k, "Alt"):
k = k[3:]
modifiers |= tcell.ModAlt
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
default:
break modSearch
}
}
if len(k) == 0 {
return keyDesc{}, false
}
// Control is handled specially, since some character codes in bindingKeys
// are different when Control is depressed. We should check for Control keys
// first.
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := bindingKeys["Ctrl"+k]; ok {
// It is, we're done.
return keyDesc{
keyCode: code,
modifiers: modifiers,
r: 0,
}, true
}
}
// See if we can find the key in bindingKeys
if code, ok := bindingKeys[k]; ok {
return keyDesc{
keyCode: code,
modifiers: modifiers,
r: 0,
}, true
}
// If we were given one character, then we've got a rune.
if len(k) == 1 {
return keyDesc{
keyCode: tcell.KeyRune,
modifiers: modifiers,
r: rune(k[0]),
}, true
}
// We don't know what happened.
return keyDesc{}, false
}
// findAction will find 'action' using string 'v'
func findAction(v string) (action func(*View) bool) {
action, _ = bindingActions[v]
return action
}

461
buffer.go

@ -0,0 +1,461 @@
package femto
import (
"crypto/md5"
"io"
"strings"
"unicode/utf8"
"github.com/zyedidia/micro/cmd/micro/highlight"
)
const LargeFileThreshold = 50000
var (
// 0 - no line type detected
// 1 - lf detected
// 2 - crlf detected
fileformat = 0
)
// Buffer stores the text for files that are loaded into the text editor
// It uses a rope to efficiently store the string and contains some
// simple functions for saving and wrapper functions for modifying the rope
type Buffer struct {
// The eventhandler for undo/redo
*EventHandler
// This stores all the text in the buffer as an array of lines
*LineArray
// The path to the loaded file, if any
Path string
Cursor Cursor
cursors []*Cursor // for multiple cursors
curCursor int // the current cursor
// Name of the buffer on the status line
name string
// Whether or not the buffer has been modified since it was opened
IsModified bool
// NumLines is the number of lines in the buffer
NumLines int
syntaxDef *highlight.Def
highlighter *highlight.Highlighter
// Hash of the original buffer -- empty if fastdirty is on
origHash [md5.Size]byte
// Buffer local settings
Settings map[string]interface{}
}
// NewBufferFromString creates a new buffer containing the given string
func NewBufferFromString(text, path string) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
}
// NewBuffer creates a new buffer from a given reader
func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
b := new(Buffer)
b.LineArray = NewLineArray(size, reader)
b.Settings = DefaultLocalSettings()
// for k, v := range globalSettings {
// if _, ok := b.Settings[k]; ok {
// b.Settings[k] = v
// }
// }
if fileformat == 1 {
b.Settings["fileformat"] = "unix"
} else if fileformat == 2 {
b.Settings["fileformat"] = "dos"
}
b.Path = path
b.EventHandler = NewEventHandler(b)
b.update()
b.Cursor = Cursor{
Loc: Loc{0, 0},
buf: b,
}
//InitLocalSettings(b)
if !b.Settings["fastdirty"].(bool) {
if size > LargeFileThreshold {
// If the file is larger than a megabyte fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
calcHash(b, &b.origHash)
}
}
b.cursors = []*Cursor{&b.Cursor}
return b
}
// GetName returns the name that should be displayed in the statusline
// for this buffer
func (b *Buffer) GetName() string {
return b.name
}
// updateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func (b *Buffer) updateRules(runtimeFiles *RuntimeFiles) {
if runtimeFiles == nil {
return
}
rehighlight := false
var files []*highlight.File
for _, f := range runtimeFiles.ListRuntimeFiles(RTSyntax) {
data, err := f.Data()
if err == nil {
file, err := highlight.ParseFile(data)
if err != nil {
continue
}
ftdetect, err := highlight.ParseFtDetect(file)
if err != nil {
continue
}
ft := b.Settings["filetype"].(string)
if (ft == "Unknown" || ft == "") && !rehighlight {
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.syntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
continue
}
rehighlight = true
}
} else {
if file.FileType == ft && !rehighlight {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.syntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
continue
}
rehighlight = true
}
}
files = append(files, file)
}
}
if b.syntaxDef != nil {
highlight.ResolveIncludes(b.syntaxDef, files)
}
if b.highlighter == nil || rehighlight {
if b.syntaxDef != nil {
b.Settings["filetype"] = b.syntaxDef.FileType
b.highlighter = highlight.NewHighlighter(b.syntaxDef)
if b.Settings["syntax"].(bool) {
b.highlighter.HighlightStates(b)
}
}
}
}
// FileType returns the buffer's filetype
func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
}
// IndentString returns a string representing one level of indentation
func (b *Buffer) IndentString() string {
if b.Settings["tabstospaces"].(bool) {
return Spaces(int(b.Settings["tabsize"].(float64)))
}
return "\t"
}
// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
func (b *Buffer) update() {
b.NumLines = len(b.lines)
}
// MergeCursors merges any cursors that are at the same position
// into one cursor
func (b *Buffer) MergeCursors() {
var cursors []*Cursor
for i := 0; i < len(b.cursors); i++ {
c1 := b.cursors[i]
if c1 != nil {
for j := 0; j < len(b.cursors); j++ {
c2 := b.cursors[j]
if c2 != nil && i != j && c1.Loc == c2.Loc {
b.cursors[j] = nil
}
}
cursors = append(cursors, c1)
}
}
b.cursors = cursors
for i := range b.cursors {
b.cursors[i].Num = i
}
if b.curCursor >= len(b.cursors) {
b.curCursor = len(b.cursors) - 1
}
}
// UpdateCursors updates all the cursors indicies
func (b *Buffer) UpdateCursors() {
for i, c := range b.cursors {
c.Num = i
}
}
// calcHash calculates md5 hash of all lines in the buffer
func calcHash(b *Buffer, out *[md5.Size]byte) {
h := md5.New()
if len(b.lines) > 0 {
h.Write(b.lines[0].data)
for _, l := range b.lines[1:] {
h.Write([]byte{'\n'})
h.Write(l.data)
}
}
h.Sum((*out)[:0])
}
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Settings["fastdirty"].(bool) {
return b.IsModified
}
var buff [md5.Size]byte
calcHash(b, &buff)
return buff != b.origHash
}
func (b *Buffer) insert(pos Loc, value []byte) {
b.IsModified = true
b.LineArray.insert(pos, value)
b.update()
}
func (b *Buffer) remove(start, end Loc) string {
b.IsModified = true
sub := b.LineArray.remove(start, end)
b.update()
return sub
}
func (b *Buffer) deleteToEnd(start Loc) {
b.IsModified = true
b.LineArray.DeleteToEnd(start)
b.update()
}
// Start returns the location of the first character in the buffer
func (b *Buffer) Start() Loc {
return Loc{0, 0}
}
// End returns the location of the last character in the buffer
func (b *Buffer) End() Loc {
return Loc{utf8.RuneCount(b.lines[b.NumLines-1].data), b.NumLines - 1}
}
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
line := b.LineRunes(loc.Y)
if len(line) > 0 {
return line[loc.X]
}
return '\n'
}
// LineBytes returns a single line as an array of runes
func (b *Buffer) LineBytes(n int) []byte {
if n >= len(b.lines) {
return []byte{}
}
return b.lines[n].data
}
// LineRunes returns a single line as an array of runes
func (b *Buffer) LineRunes(n int) []rune {
if n >= len(b.lines) {
return []rune{}
}
return toRunes(b.lines[n].data)
}
// Line returns a single line
func (b *Buffer) Line(n int) string {
if n >= len(b.lines) {
return ""
}
return string(b.lines[n].data)
}
// LinesNum returns the number of lines in the buffer
func (b *Buffer) LinesNum() int {
return len(b.lines)
}
// Lines returns an array of strings containing the lines from start to end
func (b *Buffer) Lines(start, end int) []string {
lines := b.lines[start:end]
var slice []string
for _, line := range lines {
slice = append(slice, string(line.data))
}
return slice
}
// Len gives the length of the buffer
func (b *Buffer) Len() (n int) {
for _, l := range b.lines {
n += utf8.RuneCount(l.data)
}
if len(b.lines) > 1 {
n += len(b.lines) - 1 // account for newlines
}
return
}
// MoveLinesUp moves the range of lines up one row
func (b *Buffer) MoveLinesUp(start int, end int) {
// 0 < start < end <= len(b.lines)
if start < 1 || start >= end || end > len(b.lines) {
return // what to do? FIXME
}
if end == len(b.lines) {
b.Insert(
Loc{
utf8.RuneCount(b.lines[end-1].data),
end - 1,
},
"\n"+b.Line(start-1),
)
} else {
b.Insert(
Loc{0, end},
b.Line(start-1)+"\n",
)
}
b.Remove(
Loc{0, start - 1},
Loc{0, start},
)
}
// MoveLinesDown moves the range of lines down one row
func (b *Buffer) MoveLinesDown(start int, end int) {
// 0 <= start < end < len(b.lines)
// if end == len(b.lines), we can't do anything here because the
// last line is unaccessible, FIXME
if start < 0 || start >= end || end >= len(b.lines)-1 {
return // what to do? FIXME
}
b.Insert(
Loc{0, start},
b.Line(end)+"\n",
)
end++
b.Remove(
Loc{0, end},
Loc{0, end + 1},
)
}
// ClearMatches clears all of the syntax highlighting for this buffer
func (b *Buffer) ClearMatches() {
for i := range b.lines {
b.SetMatch(i, nil)
b.SetState(i, nil)
}
}
func (b *Buffer) clearCursors() {
for i := 1; i < len(b.cursors); i++ {
b.cursors[i] = nil
}
b.cursors = b.cursors[:1]
b.UpdateCursors()
b.Cursor.ResetSelection()
}
var bracePairs = [][2]rune{
{'(', ')'},
{'{', '}'},
{'[', ']'},
}
// FindMatchingBrace returns the location in the buffer of the matching bracket
// It is given a brace type containing the open and closing character, (for example
// '{' and '}') as well as the location to match from
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
curLine := b.LineRunes(start.Y)
startChar := curLine[start.X]
var i int
if startChar == braceType[0] {
for y := start.Y; y < b.NumLines; y++ {
l := b.LineRunes(y)
xInit := 0
if y == start.Y {
xInit = start.X
}
for x := xInit; x < len(l); x++ {
r := l[x]
if r == braceType[0] {
i++
} else if r == braceType[1] {
i--
if i == 0 {
return Loc{x, y}
}
}
}
}
} else if startChar == braceType[1] {
for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
xInit := len(l) - 1
if y == start.Y {
xInit = start.X
}
for x := xInit; x >= 0; x-- {
r := l[x]
if r == braceType[0] {
i--
if i == 0 {
return Loc{x, y}
}
} else if r == braceType[1] {
i++
}
}
}
}
return start
}

238
cellview.go

@ -0,0 +1,238 @@
package femto
import (
"github.com/gdamore/tcell"
"github.com/mattn/go-runewidth"
)
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func visualToCharPos(visualIndex int, lineN int, str string, buf *Buffer, colorscheme Colorscheme, tabsize int) (int, int, *tcell.Style) {
charPos := 0
var lineIdx int
var lastWidth int
var style *tcell.Style
var width int
var rw int
for i, c := range str {
// width := StringWidth(str[:i], tabsize)
if group, ok := buf.Match(lineN)[charPos]; ok {
s := colorscheme.GetColor(group.String())
style = &s
}
if width >= visualIndex {
return charPos, visualIndex - lastWidth, style
}
if i != 0 {
charPos++
lineIdx += rw
}
lastWidth = width
rw = 0
if c == '\t' {
rw = tabsize - (lineIdx % tabsize)
width += rw
} else {
rw = runewidth.RuneWidth(c)
width += rw
}
}
return -1, -1, style
}
type Char struct {
visualLoc Loc
realLoc Loc
char rune
// The actual character that is drawn
// This is only different from char if it's for example hidden character
drawChar rune
style tcell.Style
width int
}
type CellView struct {
lines [][]*Char
}
func (c *CellView) Draw(buf *Buffer, colorscheme Colorscheme, top, height, left, width int) {
if width <= 0 {
return
}
matchingBrace := Loc{-1, -1}
// bracePairs is defined in buffer.go
if buf.Settings["matchbrace"].(bool) {
for _, bp := range bracePairs {
curX := buf.Cursor.X
curLoc := buf.Cursor.Loc
if buf.Settings["matchbraceleft"].(bool) {
if curX > 0 {
curX--
curLoc = curLoc.Move(-1, buf)
}
}
r := buf.Cursor.RuneUnder(curX)
if r == bp[0] || r == bp[1] {
matchingBrace = buf.FindMatchingBrace(bp, curLoc)
}
}
}
tabsize := int(buf.Settings["tabsize"].(float64))
softwrap := buf.Settings["softwrap"].(bool)
indentrunes := []rune(buf.Settings["indentchar"].(string))
// if empty indentchar settings, use space
if indentrunes == nil || len(indentrunes) == 0 {
indentrunes = []rune{' '}
}
indentchar := indentrunes[0]
start := buf.Cursor.Y
if buf.Settings["syntax"].(bool) && buf.syntaxDef != nil {
if start > 0 && buf.lines[start-1].rehighlight {
buf.highlighter.ReHighlightLine(buf, start-1)
buf.lines[start-1].rehighlight = false
}
buf.highlighter.ReHighlightStates(buf, start)
buf.highlighter.HighlightMatches(buf, top, top+height)
}
c.lines = make([][]*Char, 0)
viewLine := 0
lineN := top
curStyle := defStyle
for viewLine < height {
if lineN >= len(buf.lines) {
break
}
lineStr := buf.Line(lineN)
line := []rune(lineStr)
colN, startOffset, startStyle := visualToCharPos(left, lineN, lineStr, buf, colorscheme, tabsize)
if colN < 0 {
colN = len(line)
}
viewCol := -startOffset
if startStyle != nil {
curStyle = *startStyle
}
// We'll either draw the length of the line, or the width of the screen
// whichever is smaller
lineLength := min(StringWidth(lineStr, tabsize), width)
c.lines = append(c.lines, make([]*Char, lineLength))
wrap := false
// We only need to wrap if the length of the line is greater than the width of the terminal screen
if softwrap && StringWidth(lineStr, tabsize) > width {
wrap = true
// We're going to draw the entire line now
lineLength = StringWidth(lineStr, tabsize)
}
for viewCol < lineLength {
if colN >= len(line) {
break
}
if group, ok := buf.Match(lineN)[colN]; ok {
curStyle = colorscheme.GetColor(group.String())
}
char := line[colN]
if viewCol >= 0 {
st := curStyle
if colN == matchingBrace.X && lineN == matchingBrace.Y && !buf.Cursor.HasSelection() {
st = curStyle.Reverse(true)
}
if viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, st, 1}
}
}
if char == '\t' {
charWidth := tabsize - (viewCol+left)%tabsize
if viewCol >= 0 {
c.lines[viewLine][viewCol].drawChar = indentchar
c.lines[viewLine][viewCol].width = charWidth
indentStyle := curStyle
ch := buf.Settings["indentchar"].(string)
if group, ok := colorscheme["indent-char"]; ok && !IsStrWhitespace(ch) && ch != "" {
indentStyle = group
}
c.lines[viewLine][viewCol].style = indentStyle
}
for i := 1; i < charWidth; i++ {
viewCol++
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
}
}
viewCol++
} else if runewidth.RuneWidth(char) > 1 {
charWidth := runewidth.RuneWidth(char)
if viewCol >= 0 {
c.lines[viewLine][viewCol].width = charWidth
}
for i := 1; i < charWidth; i++ {
viewCol++
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
}
}
viewCol++
} else {
viewCol++
}
colN++
if wrap && viewCol >= width {
viewLine++
// If we go too far soft wrapping we have to cut off
if viewLine >= height {
break
}
nextLine := line[colN:]
lineLength := min(StringWidth(string(nextLine), tabsize), width)
c.lines = append(c.lines, make([]*Char, lineLength))
viewCol = 0
}
}
if group, ok := buf.Match(lineN)[len(line)]; ok {
curStyle = colorscheme.GetColor(group.String())
}
// newline
viewLine++
lineN++
}
for i := top; i < top+height; i++ {
if i >= buf.NumLines {
break
}
buf.SetMatch(i, nil)
}
}

59
cmd/femto/femto.go

@ -0,0 +1,59 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"github.com/gdamore/tcell"
"github.com/pgavlin/femto"
"github.com/pgavlin/femto/runtime"
"github.com/rivo/tview"
)
func saveBuffer(b *femto.Buffer, path string) error {
return ioutil.WriteFile(path, []byte(b.String()), 0600)
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: femto [filename]\n")
os.Exit(1)
}
path := os.Args[1]
content, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("could not read %v: %v", path, err)
}
var colorscheme femto.Colorscheme
if monokai := runtime.Files.FindFile(femto.RTColorscheme, "monokai"); monokai != nil {
if data, err := monokai.Data(); err == nil {
colorscheme = femto.ParseColorscheme(string(data))
}
}
app := tview.NewApplication()
buffer := femto.NewBufferFromString(string(content), path)
root := femto.NewView(buffer)
root.SetRuntimeFiles(runtime.Files)
root.SetColorscheme(colorscheme)
root.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlS:
saveBuffer(buffer, path)
return nil
case tcell.KeyCtrlQ:
app.Stop()
return nil
}
return event
})
app.SetRoot(root, true)
if err := app.Run(); err != nil {
log.Fatalf("%v", err)
}
}

241
colorscheme.go

@ -0,0 +1,241 @@
package femto
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/gdamore/tcell"
)
// Colorscheme is a map from string to style -- it represents a colorscheme
type Colorscheme map[string]tcell.Style
// The current default colorscheme
var colorscheme Colorscheme
// The default cell style
var defStyle tcell.Style
// GetColor takes in a syntax group and returns the colorscheme's style for that group
func GetColor(color string) tcell.Style {
return colorscheme.GetColor(color)
}
// GetColor takes in a syntax group and returns the colorscheme's style for that group
func (colorscheme Colorscheme) GetColor(color string) tcell.Style {
st := defStyle
if color == "" {
return st
}
groups := strings.Split(color, ".")
if len(groups) > 1 {
curGroup := ""
for i, g := range groups {
if i != 0 {
curGroup += "."
}
curGroup += g
if style, ok := colorscheme[curGroup]; ok {
st = style
}
}
} else if style, ok := colorscheme[color]; ok {
st = style
} else {
st = StringToStyle(color)
}
return st
}
// init picks and initializes the colorscheme when micro starts
func init() {
colorscheme = make(Colorscheme)
defStyle = tcell.StyleDefault.
Foreground(tcell.ColorDefault).
Background(tcell.ColorDefault)
}
// SetDefaultColorscheme sets the current default colorscheme for new Views.
func SetDefaultColorscheme(scheme Colorscheme) {
colorscheme = scheme
}
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
// Colorschemes are made up of color-link statements linking a color group to a list of colors
// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
// red background
func ParseColorscheme(text string) Colorscheme {
parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
lines := strings.Split(text, "\n")
c := make(Colorscheme)
for _, line := range lines {
if strings.TrimSpace(line) == "" ||
strings.TrimSpace(line)[0] == '#' {
// Ignore this line
continue
}
matches := parser.FindSubmatch([]byte(line))
if len(matches) == 3 {
link := string(matches[1])
colors := string(matches[2])
style := StringToStyle(colors)
c[link] = style
if link == "default" {
defStyle = style
}
} else {
fmt.Println("Color-link statement is not valid:", line)
}
}
return c
}
// StringToStyle returns a style from a string
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
// The 'extra' can be bold, reverse, or underline
func StringToStyle(str string) tcell.Style {
var fg, bg string
spaceSplit := strings.Split(str, " ")
var split []string
if len(spaceSplit) > 1 {
split = strings.Split(spaceSplit[1], ",")
} else {
split = strings.Split(str, ",")
}
if len(split) > 1 {
fg, bg = split[0], split[1]
} else {
fg = split[0]
}
fg = strings.TrimSpace(fg)
bg = strings.TrimSpace(bg)
var fgColor, bgColor tcell.Color
if fg == "" {
fgColor, _, _ = defStyle.Decompose()
} else {
fgColor = StringToColor(fg)
}
if bg == "" {
_, bgColor, _ = defStyle.Decompose()
} else {
bgColor = StringToColor(bg)
}
style := defStyle.Foreground(fgColor).Background(bgColor)
if strings.Contains(str, "bold") {
style = style.Bold(true)
}
if strings.Contains(str, "reverse") {
style = style.Reverse(true)
}
if strings.Contains(str, "underline") {
style = style.Underline(true)
}
return style
}
// StringToColor returns a tcell color from a string representation of a color
// We accept either bright... or light... to mean the brighter version of a color
func StringToColor(str string) tcell.Color {