added vi commands

This commit is contained in:
Dongdong Zhou 2018-02-20 09:05:44 +00:00
parent f8103a2c5b
commit a4a42b3324
4 changed files with 596 additions and 4 deletions

View File

@ -48,6 +48,23 @@ type Editor struct {
init chan struct{} init chan struct{}
initOnce sync.Once initOnce sync.Once
vimNormalState *NormalState
vimStates map[int]VimState
vimMode int
vimPending bool
vimPendingBuffer string
specialKeys map[core.Qt__Key]string
controlModifier core.Qt__KeyboardModifier
cmdModifier core.Qt__KeyboardModifier
shiftModifier core.Qt__KeyboardModifier
altModifier core.Qt__KeyboardModifier
metaModifier core.Qt__KeyboardModifier
keyControl core.Qt__Key
keyCmd core.Qt__Key
keyAlt core.Qt__Key
keyShift core.Qt__Key
} }
type editorSignal struct { type editorSignal struct {
@ -66,6 +83,8 @@ func NewEditor() (*Editor, error) {
bgBrush: gui.NewQBrush(), bgBrush: gui.NewQBrush(),
fgBrush: gui.NewQBrush(), fgBrush: gui.NewQBrush(),
} }
e.initSpecialKeys()
e.vimStates = newVimStates(e)
xiClient, err := xi.New(e.handleXiNotification) xiClient, err := xi.New(e.handleXiNotification)
if err != nil { if err != nil {
@ -105,7 +124,7 @@ func NewEditor() (*Editor, error) {
case *xi.Theme: case *xi.Theme:
e.theme = u e.theme = u
fg := u.Theme.Foreground fg := u.Theme.Foreground
e.cursor.SetStyleSheet(fmt.Sprintf("background-color: rgba(%d, %d, %d, 1);", fg.R, fg.G, fg.B)) e.cursor.SetStyleSheet(fmt.Sprintf("background-color: rgba(%d, %d, %d, 0.6);", fg.R, fg.G, fg.B))
scrollBarStyleSheet := e.getScrollbarStylesheet() scrollBarStyleSheet := e.getScrollbarStylesheet()
e.winsRWMutext.RLock() e.winsRWMutext.RLock()
defer e.winsRWMutext.RUnlock() defer e.winsRWMutext.RUnlock()
@ -247,7 +266,7 @@ func (e *Editor) initMainWindow() {
e.cursor = widgets.NewQWidget(nil, 0) e.cursor = widgets.NewQWidget(nil, 0)
e.cursor.Resize2(1, 20) e.cursor.Resize2(1, 20)
e.cursor.SetStyleSheet("background-color: rgba(0, 0, 0, 0.5);") e.cursor.SetStyleSheet("background-color: rgba(0, 0, 0, 0.1);")
e.cursor.Show() e.cursor.Show()
e.topFrame.setFocus() e.topFrame.setFocus()
e.window.Show() e.window.Show()

524
editor/vi.go Normal file
View File

@ -0,0 +1,524 @@
package editor
import (
"fmt"
"runtime"
"strconv"
"strings"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/gui"
)
//
const (
Normal = iota
Insert
Replace
)
//
const (
Nomatch string = "NOMATCH"
Digit string = "DIGIT"
)
// VimAction is
type VimAction func(key string)
// VimCommand is
type VimCommand func()
// VimOutcome is
type VimOutcome struct {
mode int
action VimAction
}
// VimState is
type VimState interface {
execute()
setCmd(key string)
}
func newVimStates(e *Editor) map[int]VimState {
states := map[int]VimState{}
states[Normal] = newVimNormalState(e)
states[Insert] = newVimInsertState(e)
return states
}
// NormalState is
type NormalState struct {
editor *Editor
wincmd bool
gcmd bool
cmdArg *VimCmdArg
cmds map[string]VimCommand
}
// VimCmdArg is
type VimCmdArg struct {
cmd string
opcount int // count before an operator
count int
}
func newVimNormalState(e *Editor) VimState {
s := &NormalState{
editor: e,
cmdArg: &VimCmdArg{},
}
s.cmds = map[string]VimCommand{
"<Esc>": s.reset,
"i": s.toInsert,
"a": s.toInsertRight,
"h": s.left,
"l": s.right,
"j": s.down,
"k": s.up,
"0": s.startOfLine,
"$": s.endOfLine,
"G": s.goTo,
"<C-e>": s.scrollDown,
"<C-y>": s.scrollUp,
}
return s
}
func (s *NormalState) setCmd(key string) {
s.cmdArg.cmd = key
}
func (s *NormalState) execute() {
fmt.Println("normal execute", s.cmdArg.cmd)
i, err := strconv.Atoi(s.cmdArg.cmd)
if err == nil {
s.cmdArg.count = s.cmdArg.count*10 + i
if s.cmdArg.count > 0 {
return
}
}
if s.cmdArg.cmd == "<C-w>" {
s.wincmd = true
return
}
if !s.gcmd {
if s.cmdArg.cmd == "g" {
s.gcmd = true
return
}
} else {
s.doGcmd()
s.reset()
return
}
if s.wincmd {
s.doWincmd()
s.reset()
return
}
cmd, ok := s.cmds[s.cmdArg.cmd]
if !ok {
return
}
cmd()
s.reset()
}
func (s *NormalState) toInsert() {
s.editor.vimMode = Insert
s.editor.updateCursorShape()
}
func (s *NormalState) toInsertRight() {
s.editor.vimMode = Insert
s.editor.updateCursorShape()
if s.editor.activeWin.col < len(s.editor.activeWin.buffer.lines[s.editor.activeWin.row].text)-1 {
s.editor.activeWin.scrollto(s.editor.activeWin.col+1, s.editor.activeWin.row, true)
s.editor.activeWin.buffer.xiView.MoveRight()
}
}
func (s *NormalState) reset() {
s.cmdArg.opcount = 0
s.cmdArg.count = 0
s.wincmd = false
s.gcmd = false
}
func (s *NormalState) doGcmd() {
cmd := s.cmdArg.cmd
switch cmd {
case "g":
s.goTo()
return
}
}
func (s *NormalState) doWincmd() {
cmd := s.cmdArg.cmd
switch cmd {
case "l":
s.editor.activeWin.frame.focusRight()
return
case "h":
s.editor.activeWin.frame.focusLeft()
return
case "k":
s.editor.activeWin.frame.focusAbove()
return
case "j":
s.editor.activeWin.frame.focusBelow()
return
case "v":
s.editor.activeWin.frame.split(true)
return
case "s":
s.editor.activeWin.frame.split(false)
return
case "c":
s.editor.activeWin.frame.close()
return
case "x":
s.editor.activeWin.frame.exchange()
return
}
}
func (s *NormalState) down() {
count := 1
if s.cmdArg.count > 0 {
count = s.cmdArg.count
}
for i := 0; i < count; i++ {
s.editor.activeWin.buffer.xiView.MoveDown()
}
}
func (s *NormalState) up() {
count := 1
if s.cmdArg.count > 0 {
count = s.cmdArg.count
}
for i := 0; i < count; i++ {
s.editor.activeWin.buffer.xiView.MoveUp()
}
}
func (s *NormalState) left() {
count := 1
if s.cmdArg.count > 0 {
count = s.cmdArg.count
}
for i := 0; i < count; i++ {
s.editor.activeWin.buffer.xiView.MoveLeft()
}
}
func (s *NormalState) right() {
count := 1
if s.cmdArg.count > 0 {
count = s.cmdArg.count
}
for i := 0; i < count; i++ {
s.editor.activeWin.buffer.xiView.MoveRight()
}
}
func (s *NormalState) goTo() {
if s.cmdArg.count == 0 {
if s.cmdArg.cmd == "G" {
s.editor.activeWin.buffer.xiView.MoveToEndOfDocument()
} else {
s.editor.activeWin.buffer.xiView.MoveToBeginningOfDocument()
}
return
}
s.editor.activeWin.buffer.xiView.GotoLine(s.cmdArg.count)
}
func (s *NormalState) scrollUp() {
count := 1
if s.cmdArg.count > 0 {
count = s.cmdArg.count
}
y := int(float64(count)*s.editor.activeWin.buffer.font.lineHeight + 0.5)
scrollBar := s.editor.activeWin.view.VerticalScrollBar()
scrollBar.SetValue(scrollBar.Value() - y)
}
func (s *NormalState) scrollDown() {
count := 1
if s.cmdArg.count > 0 {
count = s.cmdArg.count
}
y := int(float64(count)*s.editor.activeWin.buffer.font.lineHeight + 0.5)
scrollBar := s.editor.activeWin.view.VerticalScrollBar()
scrollBar.SetValue(scrollBar.Value() + y)
}
func (s *NormalState) startOfLine() {
s.editor.activeWin.buffer.xiView.MoveToLeftEndOfLine()
}
func (s *NormalState) endOfLine() {
s.editor.activeWin.buffer.xiView.MoveToRightEndOfLine()
}
// InsertState is
type InsertState struct {
editor *Editor
cmdArg *VimCmdArg
cmds map[string]VimCommand
}
func newVimInsertState(e *Editor) VimState {
s := &InsertState{
editor: e,
cmdArg: &VimCmdArg{},
}
s.cmds = map[string]VimCommand{
"<Esc>": s.toNormal,
"<Enter>": s.newLine,
"<BS>": s.deleteBackward,
}
return s
}
func (s *InsertState) setCmd(key string) {
s.cmdArg.cmd = key
}
func (s *InsertState) execute() {
cmd, ok := s.cmds[s.cmdArg.cmd]
if !ok {
if strings.HasPrefix(s.cmdArg.cmd, "<") && strings.HasSuffix(s.cmdArg.cmd, ">") {
fmt.Println(s.cmdArg.cmd)
return
}
s.editor.activeWin.buffer.xiView.Insert(s.cmdArg.cmd)
return
}
cmd()
}
func (s *InsertState) toNormal() {
s.editor.vimMode = Normal
s.editor.updateCursorShape()
if s.editor.activeWin.col > 0 {
s.editor.activeWin.scrollto(s.editor.activeWin.col-1, s.editor.activeWin.row, true)
s.editor.activeWin.buffer.xiView.MoveLeft()
}
}
func (s *InsertState) newLine() {
s.editor.activeWin.buffer.xiView.InsertNewline()
}
func (s *InsertState) deleteBackward() {
s.editor.activeWin.buffer.xiView.DeleteBackward()
}
func (e *Editor) updateCursorShape() {
if e.activeWin == nil {
return
}
font := e.activeWin.buffer.font
if e.vimMode == Insert {
e.cursor.Resize2(1, int(font.lineHeight+0.5))
} else {
e.cursor.Resize2(int(font.width+0.5), int(font.lineHeight+0.5))
}
}
func (e *Editor) convertKey(keyEvent *gui.QKeyEvent) string {
key := keyEvent.Key()
text := keyEvent.Text()
mod := keyEvent.Modifiers()
if mod&core.Qt__KeypadModifier > 0 {
switch core.Qt__Key(key) {
case core.Qt__Key_Home:
return fmt.Sprintf("<%sHome>", e.modPrefix(mod))
case core.Qt__Key_End:
return fmt.Sprintf("<%sEnd>", e.modPrefix(mod))
case core.Qt__Key_PageUp:
return fmt.Sprintf("<%sPageUp>", e.modPrefix(mod))
case core.Qt__Key_PageDown:
return fmt.Sprintf("<%sPageDown>", e.modPrefix(mod))
case core.Qt__Key_Plus:
return fmt.Sprintf("<%sPlus>", e.modPrefix(mod))
case core.Qt__Key_Minus:
return fmt.Sprintf("<%sMinus>", e.modPrefix(mod))
case core.Qt__Key_multiply:
return fmt.Sprintf("<%sMultiply>", e.modPrefix(mod))
case core.Qt__Key_division:
return fmt.Sprintf("<%sDivide>", e.modPrefix(mod))
case core.Qt__Key_Enter:
return fmt.Sprintf("<%sEnter>", e.modPrefix(mod))
case core.Qt__Key_Period:
return fmt.Sprintf("<%sPoint>", e.modPrefix(mod))
case core.Qt__Key_0:
return fmt.Sprintf("<%s0>", e.modPrefix(mod))
case core.Qt__Key_1:
return fmt.Sprintf("<%s1>", e.modPrefix(mod))
case core.Qt__Key_2:
return fmt.Sprintf("<%s2>", e.modPrefix(mod))
case core.Qt__Key_3:
return fmt.Sprintf("<%s3>", e.modPrefix(mod))
case core.Qt__Key_4:
return fmt.Sprintf("<%s4>", e.modPrefix(mod))
case core.Qt__Key_5:
return fmt.Sprintf("<%s5>", e.modPrefix(mod))
case core.Qt__Key_6:
return fmt.Sprintf("<%s6>", e.modPrefix(mod))
case core.Qt__Key_7:
return fmt.Sprintf("<%s7>", e.modPrefix(mod))
case core.Qt__Key_8:
return fmt.Sprintf("<%s8>", e.modPrefix(mod))
case core.Qt__Key_9:
return fmt.Sprintf("<%s9>", e.modPrefix(mod))
}
}
if text == "<" {
return "<lt>"
}
specialKey, ok := e.specialKeys[core.Qt__Key(key)]
if ok {
return fmt.Sprintf("<%s%s>", e.modPrefix(mod), specialKey)
}
if text == "\\" {
return fmt.Sprintf("<%s%s>", e.modPrefix(mod), "Bslash")
}
c := ""
if mod&e.controlModifier > 0 || mod&e.cmdModifier > 0 {
if int(e.keyControl) == key || int(e.keyCmd) == key || int(e.keyAlt) == key || int(e.keyShift) == key {
return ""
}
c = string(key)
if !(mod&e.shiftModifier > 0) {
c = strings.ToLower(c)
}
} else {
c = text
}
if c == "" {
return ""
}
char := core.NewQChar11(c)
if char.Unicode() < 0x100 && !char.IsNumber() && char.IsPrint() {
mod &= ^e.shiftModifier
}
prefix := e.modPrefix(mod)
if prefix != "" {
return fmt.Sprintf("<%s%s>", prefix, c)
}
return c
}
func (e *Editor) modPrefix(mod core.Qt__KeyboardModifier) string {
prefix := ""
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
if mod&e.cmdModifier > 0 {
prefix += "D-"
}
}
if mod&e.controlModifier > 0 {
prefix += "C-"
}
if mod&e.shiftModifier > 0 {
prefix += "S-"
}
if mod&e.altModifier > 0 {
prefix += "A-"
}
return prefix
}
func (e *Editor) initSpecialKeys() {
e.specialKeys = map[core.Qt__Key]string{}
e.specialKeys[core.Qt__Key_Up] = "Up"
e.specialKeys[core.Qt__Key_Down] = "Down"
e.specialKeys[core.Qt__Key_Left] = "Left"
e.specialKeys[core.Qt__Key_Right] = "Right"
e.specialKeys[core.Qt__Key_F1] = "F1"
e.specialKeys[core.Qt__Key_F2] = "F2"
e.specialKeys[core.Qt__Key_F3] = "F3"
e.specialKeys[core.Qt__Key_F4] = "F4"
e.specialKeys[core.Qt__Key_F5] = "F5"
e.specialKeys[core.Qt__Key_F6] = "F6"
e.specialKeys[core.Qt__Key_F7] = "F7"
e.specialKeys[core.Qt__Key_F8] = "F8"
e.specialKeys[core.Qt__Key_F9] = "F9"
e.specialKeys[core.Qt__Key_F10] = "F10"
e.specialKeys[core.Qt__Key_F11] = "F11"
e.specialKeys[core.Qt__Key_F12] = "F12"
e.specialKeys[core.Qt__Key_F13] = "F13"
e.specialKeys[core.Qt__Key_F14] = "F14"
e.specialKeys[core.Qt__Key_F15] = "F15"
e.specialKeys[core.Qt__Key_F16] = "F16"
e.specialKeys[core.Qt__Key_F17] = "F17"
e.specialKeys[core.Qt__Key_F18] = "F18"
e.specialKeys[core.Qt__Key_F19] = "F19"
e.specialKeys[core.Qt__Key_F20] = "F20"
e.specialKeys[core.Qt__Key_F21] = "F21"
e.specialKeys[core.Qt__Key_F22] = "F22"
e.specialKeys[core.Qt__Key_F23] = "F23"
e.specialKeys[core.Qt__Key_F24] = "F24"
e.specialKeys[core.Qt__Key_Backspace] = "BS"
e.specialKeys[core.Qt__Key_Delete] = "Del"
e.specialKeys[core.Qt__Key_Insert] = "Insert"
e.specialKeys[core.Qt__Key_Home] = "Home"
e.specialKeys[core.Qt__Key_End] = "End"
e.specialKeys[core.Qt__Key_PageUp] = "PageUp"
e.specialKeys[core.Qt__Key_PageDown] = "PageDown"
e.specialKeys[core.Qt__Key_Return] = "Enter"
e.specialKeys[core.Qt__Key_Enter] = "Enter"
e.specialKeys[core.Qt__Key_Tab] = "Tab"
e.specialKeys[core.Qt__Key_Backtab] = "Tab"
e.specialKeys[core.Qt__Key_Escape] = "Esc"
e.specialKeys[core.Qt__Key_Backslash] = "Bslash"
e.specialKeys[core.Qt__Key_Space] = "Space"
goos := runtime.GOOS
e.shiftModifier = core.Qt__ShiftModifier
e.altModifier = core.Qt__AltModifier
e.keyAlt = core.Qt__Key_Alt
e.keyShift = core.Qt__Key_Shift
if goos == "darwin" {
e.controlModifier = core.Qt__MetaModifier
e.cmdModifier = core.Qt__ControlModifier
e.metaModifier = core.Qt__AltModifier
e.keyControl = core.Qt__Key_Meta
e.keyCmd = core.Qt__Key_Control
} else {
e.controlModifier = core.Qt__ControlModifier
e.metaModifier = core.Qt__MetaModifier
e.keyControl = core.Qt__Key_Control
if goos == "linux" {
e.cmdModifier = core.Qt__MetaModifier
e.keyCmd = core.Qt__Key_Meta
}
}
}

View File

@ -359,6 +359,15 @@ func NewWindow(editor *Editor, frame *Frame) *Window {
if w.buffer == nil { if w.buffer == nil {
return return
} }
state, ok := editor.vimStates[editor.vimMode]
if !ok {
return
}
key := editor.convertKey(event)
state.setCmd(key)
state.execute()
return
if event.Modifiers()&core.Qt__ControlModifier > 0 { if event.Modifiers()&core.Qt__ControlModifier > 0 {
switch string(event.Key()) { switch string(event.Key()) {
case "V": case "V":
@ -462,9 +471,9 @@ func (w *Window) updateCline() {
} }
func (w *Window) updateCursor() { func (w *Window) updateCursor() {
w.editor.updateCursorShape()
cursor := w.editor.cursor cursor := w.editor.cursor
cursor.Move2(w.cursorX, w.cursorY) cursor.Move2(w.cursorX, w.cursorY)
cursor.Resize2(1, int(w.buffer.font.lineHeight+0.5))
cursor.Hide() cursor.Hide()
cursor.Show() cursor.Show()
} }
@ -496,7 +505,7 @@ func (w *Window) updatePos() {
col = len(text) col = len(text)
w.col = col w.col = col
} }
w.x = b.font.fontMetrics.Width(text[:col]) - 0.5 w.x = b.font.fontMetrics.Width(text[:col]) + 0.5
w.y = float64(row) * b.font.lineHeight w.y = float64(row) * b.font.lineHeight
} }

View File

@ -301,6 +301,19 @@ func (v *View) Insert(chars string) {
v.xi.Conn.Notify(context.Background(), "edit", &cmd) v.xi.Conn.Notify(context.Background(), "edit", &cmd)
} }
// GotoLine sets
func (v *View) GotoLine(line int) {
params := map[string]int{}
params["line"] = line
cmd := &EditCommand{
Method: "goto_line",
ViewID: v.ID,
Params: params,
}
v.xi.Conn.Notify(context.Background(), "edit", &cmd)
}
// Scroll sets // Scroll sets
func (v *View) Scroll(start, end int) { func (v *View) Scroll(start, end int) {
cmd := &EditCommand{ cmd := &EditCommand{
@ -367,6 +380,24 @@ func (v *View) MoveRight() {
v.xi.Conn.Notify(context.Background(), "edit", &cmd) v.xi.Conn.Notify(context.Background(), "edit", &cmd)
} }
// MoveToLeftEndOfLine is
func (v *View) MoveToLeftEndOfLine() {
cmd := &EditCommand{
Method: "move_to_left_end_of_line",
ViewID: v.ID,
}
v.xi.Conn.Notify(context.Background(), "edit", &cmd)
}
// MoveToRightEndOfLine is
func (v *View) MoveToRightEndOfLine() {
cmd := &EditCommand{
Method: "move_to_right_end_of_line",
ViewID: v.ID,
}
v.xi.Conn.Notify(context.Background(), "edit", &cmd)
}
// MoveToEndOfDocument is // MoveToEndOfDocument is
func (v *View) MoveToEndOfDocument() { func (v *View) MoveToEndOfDocument() {
cmd := &EditCommand{ cmd := &EditCommand{
@ -376,6 +407,15 @@ func (v *View) MoveToEndOfDocument() {
v.xi.Conn.Notify(context.Background(), "edit", &cmd) v.xi.Conn.Notify(context.Background(), "edit", &cmd)
} }
// MoveToBeginningOfDocument is
func (v *View) MoveToBeginningOfDocument() {
cmd := &EditCommand{
Method: "move_to_beginning_of_document",
ViewID: v.ID,
}
v.xi.Conn.Notify(context.Background(), "edit", &cmd)
}
// ScrollPageUp is // ScrollPageUp is
func (v *View) ScrollPageUp() { func (v *View) ScrollPageUp() {
cmd := &EditCommand{ cmd := &EditCommand{