package cmdline import ( "reflect" "runtime" "strings" "testing" "b612.me/apps/b612/bed/event" ) func TestNewCmdline(t *testing.T) { c := NewCmdline() cmdline, cursor, _, _ := c.Get() if len(cmdline) != 0 { t.Errorf("cmdline should be empty but got %v", cmdline) } if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } } func TestCmdlineRun(t *testing.T) { c := NewCmdline() eventCh, cmdlineCh, redrawCh := make(chan event.Event), make(chan event.Event), make(chan struct{}) c.Init(eventCh, cmdlineCh, redrawCh) go c.Run() events := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.Rune, Rune: 't'}, {Type: event.Rune, Rune: 'e'}, {Type: event.CursorLeft}, {Type: event.CursorRight}, {Type: event.CursorHead}, {Type: event.CursorEnd}, {Type: event.BackspaceCmdline}, {Type: event.DeleteCmdline}, {Type: event.DeleteWordCmdline}, {Type: event.ClearToHeadCmdline}, {Type: event.ClearCmdline}, {Type: event.Rune, Rune: 't'}, {Type: event.Rune, Rune: 'e'}, {Type: event.ExecuteCmdline}, {Type: event.StartCmdlineCommand}, {Type: event.ExecuteCmdline}, } go func() { for _, e := range events { cmdlineCh <- e } }() for range len(events) - 3 { <-redrawCh } e := <-eventCh if e.Type != event.Error { t.Errorf("cmdline should emit Error event but got %v", e) } cmdline, cursor, _, _ := c.Get() if expected := "te"; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } if cursor != 2 { t.Errorf("cursor should be 2 but got %v", cursor) } for range 3 { <-redrawCh } cmdline, _, _, _ = c.Get() if expected := ""; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } } func TestCmdlineCursorMotion(t *testing.T) { c := NewCmdline() for _, ch := range "abcde" { c.insert(ch) } cmdline, cursor, _, _ := c.Get() if expected := "abcde"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 5 { t.Errorf("cursor should be 5 but got %v", cursor) } c.cursorLeft() _, cursor, _, _ = c.Get() if cursor != 4 { t.Errorf("cursor should be 4 but got %v", cursor) } for range 10 { c.cursorLeft() } _, cursor, _, _ = c.Get() if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } c.cursorRight() _, cursor, _, _ = c.Get() if cursor != 1 { t.Errorf("cursor should be 1 but got %v", cursor) } for range 10 { c.cursorRight() } _, cursor, _, _ = c.Get() if cursor != 5 { t.Errorf("cursor should be 5 but got %v", cursor) } c.cursorHead() _, cursor, _, _ = c.Get() if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } c.cursorEnd() _, cursor, _, _ = c.Get() if cursor != 5 { t.Errorf("cursor should be 5 but got %v", cursor) } } func TestCmdlineCursorBackspaceDelete(t *testing.T) { c := NewCmdline() for _, ch := range "abcde" { c.insert(ch) } cmdline, cursor, _, _ := c.Get() if expected := "abcde"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 5 { t.Errorf("cursor should be 5 but got %v", cursor) } c.cursorLeft() c.backspace() cmdline, cursor, _, _ = c.Get() if expected := "abce"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 3 { t.Errorf("cursor should be 3 but got %v", cursor) } c.deleteRune() cmdline, cursor, _, _ = c.Get() if expected := "abc"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 3 { t.Errorf("cursor should be 3 but got %v", cursor) } c.deleteRune() cmdline, cursor, _, _ = c.Get() if expected := "abc"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 3 { t.Errorf("cursor should be 3 but got %v", cursor) } c.cursorLeft() c.cursorLeft() c.backspace() c.backspace() cmdline, cursor, _, _ = c.Get() if expected := "bc"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } } func TestCmdlineCursorDeleteWord(t *testing.T) { c := NewCmdline() for _, ch := range "abcde" { c.insert(ch) } c.cursorLeft() c.cursorLeft() c.deleteWord() cmdline, cursor, _, _ := c.Get() if expected := "de"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } for _, ch := range "x0z!123 " { c.insert(ch) } c.cursorLeft() c.deleteWord() cmdline, cursor, _, _ = c.Get() if expected := "x0z! de"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 4 { t.Errorf("cursor should be 4 but got %v", cursor) } c.deleteWord() cmdline, cursor, _, _ = c.Get() if expected := "x0z de"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 3 { t.Errorf("cursor should be 3 but got %v", cursor) } } func TestCmdlineCursorClear(t *testing.T) { c := NewCmdline() for _, ch := range "abcde" { c.insert(ch) } cmdline, cursor, _, _ := c.Get() if expected := "abcde"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 5 { t.Errorf("cursor should be 5 but got %v", cursor) } c.cursorLeft() c.clear() cmdline, cursor, _, _ = c.Get() if expected := ""; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } } func TestCmdlineCursorClearToHead(t *testing.T) { c := NewCmdline() for _, ch := range "abcde" { c.insert(ch) } cmdline, cursor, _, _ := c.Get() if expected := "abcde"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 5 { t.Errorf("cursor should be 5 but got %v", cursor) } c.cursorLeft() c.cursorLeft() c.clearToHead() cmdline, cursor, _, _ = c.Get() if expected := "de"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 0 { t.Errorf("cursor should be 0 but got %v", cursor) } } func TestCmdlineCursorInsert(t *testing.T) { c := NewCmdline() for _, ch := range "abcde" { c.insert(ch) } c.cursorLeft() c.cursorLeft() c.backspace() c.insert('x') c.insert('y') cmdline, cursor, _, _ := c.Get() if expected := "abxyde"; string(cmdline) != expected { t.Errorf("cmdline should be %q but got %q", expected, string(cmdline)) } if cursor != 4 { t.Errorf("cursor should be 4 but got %v", cursor) } } func TestCmdlineQuit(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string name string }{ {"exi", "exi[t]"}, {"quit", "q[uit]"}, {"q", "q[uit]"}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch if e.CmdName != cmd.name { t.Errorf("cmdline should report command name %q but got %q", cmd.name, e.CmdName) } if e.Type != event.Quit { t.Errorf("cmdline should emit quit event with %q", cmd.cmd) } if e.Bang { t.Errorf("cmdline should emit quit event without bang") } } } func TestCmdlineForceQuit(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string name string }{ {"exit!", "exi[t]"}, {"q!", "q[uit]"}, {"quit!", "q[uit]"}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch if e.CmdName != cmd.name { t.Errorf("cmdline should report command name %q but got %q", cmd.name, e.CmdName) } if e.Type != event.Quit { t.Errorf("cmdline should emit quit event with %q", cmd.cmd) } if !e.Bang { t.Errorf("cmdline should emit quit event with bang") } } } func TestCmdlineExecuteQuitAll(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string name string }{ {"qall", "qa[ll]"}, {"qa", "qa[ll]"}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch if e.CmdName != cmd.name { t.Errorf("cmdline should report command name %q but got %q", cmd.name, e.CmdName) } if e.Type != event.QuitAll { t.Errorf("cmdline should emit QuitAll event with %q", cmd.cmd) } } } func TestCmdlineExecuteQuitErr(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string name string }{ {"cquit", "cq[uit]"}, {"cq", "cq[uit]"}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch if e.CmdName != cmd.name { t.Errorf("cmdline should report command name %q but got %q", cmd.name, e.CmdName) } if e.Type != event.QuitErr { t.Errorf("cmdline should emit QuitErr event with %q", cmd.cmd) } } } func TestCmdlineExecuteWrite(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string name string }{ {"w", "w[rite]"}, {" : : write sample.txt", "w[rite]"}, {"'<,'>write sample.txt", "w[rite]"}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch if e.CmdName != cmd.name { t.Errorf("cmdline should report command name %q but got %q", cmd.name, e.CmdName) } if e.Type != event.Write { t.Errorf("cmdline should emit Write event with %q", cmd.cmd) } } } func TestCmdlineExecuteWriteQuit(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string name string }{ {"wq", "wq"}, {"x", "x[it]"}, {"xit", "x[it]"}, {"xa", "xa[ll]"}, {"xall", "xa[ll]"}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch if e.CmdName != cmd.name { t.Errorf("cmdline should report command name %q but got %q", cmd.name, e.CmdName) } if e.Type != event.WriteQuit { t.Errorf("cmdline should emit WriteQuit event with %q", cmd.cmd) } } } func TestCmdlineExecuteGoto(t *testing.T) { c := NewCmdline() ch := make(chan event.Event, 1) c.Init(ch, make(chan event.Event), make(chan struct{})) for _, cmd := range []struct { cmd string pos event.Position typ event.Type }{ {" : : $ ", event.End{}, event.CursorGoto}, {" :123456789 ", event.Absolute{Offset: 123456789}, event.CursorGoto}, {" +16777216 ", event.Relative{Offset: 16777216}, event.CursorGoto}, {" -256 ", event.Relative{Offset: -256}, event.CursorGoto}, {" : 0x123456789abcdef ", event.Absolute{Offset: 0x123456789abcdef}, event.CursorGoto}, {" 0xfedcba ", event.Absolute{Offset: 0xfedcba}, event.CursorGoto}, {" +0x44ef ", event.Relative{Offset: 0x44ef}, event.CursorGoto}, {" -0xff ", event.Relative{Offset: -0xff}, event.CursorGoto}, {"10go", event.Absolute{Offset: 10}, event.CursorGoto}, {"+10 got", event.Relative{Offset: 10}, event.CursorGoto}, {"$-10 goto", event.End{Offset: -10}, event.CursorGoto}, {"10%", event.Absolute{Offset: 10}, event.CursorGoto}, {"+10%", event.Relative{Offset: 10}, event.CursorGoto}, {"$-10%", event.End{Offset: -10}, event.CursorGoto}, } { c.clear() c.cmdline = []rune(cmd.cmd) c.typ = ':' c.execute() e := <-ch expected := "goto" if strings.HasSuffix(cmd.cmd, "%") { expected = "%" } else if strings.Contains(cmd.cmd, "go") { expected = "go[to]" } if e.CmdName != expected { t.Errorf("cmdline should report command name %q but got %q", expected, e.CmdName) } if !reflect.DeepEqual(e.Range.From, cmd.pos) { t.Errorf("cmdline should report command with position %#v but got %#v", cmd.pos, e.Range.From) } if e.Type != cmd.typ { t.Errorf("cmdline should emit %d but got %d with %q", cmd.typ, e.Type, cmd.cmd) } } } func TestCmdlineComplete(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skip on Windows") } c := NewCmdline() c.completor = newCompletor(&mockFilesystem{}, nil) eventCh, cmdlineCh, redrawCh := make(chan event.Event), make(chan event.Event), make(chan struct{}) c.Init(eventCh, cmdlineCh, redrawCh) waitCh := make(chan struct{}) go c.Run() go func() { cmdlineCh <- event.Event{Type: event.StartCmdlineCommand} cmdlineCh <- event.Event{Type: event.Rune, Rune: 'e'} cmdlineCh <- event.Event{Type: event.Rune, Rune: ' '} cmdlineCh <- event.Event{Type: event.Rune, Rune: '/'} cmdlineCh <- event.Event{Type: event.CompleteForwardCmdline} <-waitCh cmdlineCh <- event.Event{Type: event.CompleteForwardCmdline} <-waitCh cmdlineCh <- event.Event{Type: event.CompleteBackCmdline} <-waitCh cmdlineCh <- event.Event{Type: event.CursorEnd} cmdlineCh <- event.Event{Type: event.CompleteForwardCmdline} cmdlineCh <- event.Event{Type: event.CompleteForwardCmdline} <-waitCh cmdlineCh <- event.Event{Type: event.ExecuteCmdline} }() for range 5 { <-redrawCh } cmdline, cursor, _, _ := c.Get() if expected := "e /bin/"; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } if cursor != 7 { t.Errorf("cursor should be 7 but got %v", cursor) } waitCh <- struct{}{} <-redrawCh cmdline, cursor, _, _ = c.Get() if expected := "e /tmp/"; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } if cursor != 7 { t.Errorf("cursor should be 7 but got %v", cursor) } waitCh <- struct{}{} <-redrawCh cmdline, cursor, _, _ = c.Get() if expected := "e /bin/"; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } if cursor != 7 { t.Errorf("cursor should be 7 but got %v", cursor) } waitCh <- struct{}{} <-redrawCh <-redrawCh <-redrawCh cmdline, cursor, _, _ = c.Get() if expected := "e /bin/echo"; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } if cursor != 11 { t.Errorf("cursor should be 11 but got %v", cursor) } waitCh <- struct{}{} go func() { <-redrawCh }() e := <-eventCh cmdline, cursor, _, _ = c.Get() if expected := "e /bin/echo"; string(cmdline) != expected { t.Errorf("cmdline should be %q got %q", expected, string(cmdline)) } if cursor != 11 { t.Errorf("cursor should be 11 but got %v", cursor) } if e.Type != event.Edit { t.Errorf("cmdline should emit Edit event but got %v", e) } if expected := "/bin/echo"; e.Arg != expected { t.Errorf("cmdline should emit event with arg %q but got %v", expected, e) } } func TestCmdlineSearch(t *testing.T) { c := NewCmdline() eventCh, cmdlineCh, redrawCh := make(chan event.Event), make(chan event.Event), make(chan struct{}) waitCh := make(chan struct{}) c.Init(eventCh, cmdlineCh, redrawCh) defer func() { close(eventCh) close(cmdlineCh) close(redrawCh) }() go c.Run() events1 := []event.Event{ {Type: event.StartCmdlineSearchForward}, {Type: event.Rune, Rune: 't'}, {Type: event.Rune, Rune: 't'}, {Type: event.CursorLeft}, {Type: event.Rune, Rune: 'e'}, {Type: event.Rune, Rune: 's'}, {Type: event.ExecuteCmdline}, } events2 := []event.Event{ {Type: event.StartCmdlineSearchBackward}, {Type: event.Rune, Rune: 'x'}, {Type: event.Rune, Rune: 'y'}, {Type: event.Rune, Rune: 'z'}, {Type: event.ExecuteCmdline}, } go func() { for _, e := range events1 { cmdlineCh <- e } <-waitCh for _, e := range events2 { cmdlineCh <- e } }() for range len(events1) - 1 { <-redrawCh } e := <-eventCh <-redrawCh if e.Type != event.ExecuteSearch { t.Errorf("cmdline should emit ExecuteSearch event but got %v", e) } if expected := "test"; e.Arg != expected { t.Errorf("cmdline should emit search event with Arg %q but got %q", expected, e.Arg) } if e.Rune != '/' { t.Errorf("cmdline should emit search event with Rune %q but got %q", '/', e.Rune) } waitCh <- struct{}{} for range len(events2) - 1 { <-redrawCh } e = <-eventCh <-redrawCh if e.Type != event.ExecuteSearch { t.Errorf("cmdline should emit ExecuteSearch event but got %v", e) } if expected := "xyz"; e.Arg != expected { t.Errorf("cmdline should emit search event with Arg %q but got %q", expected, e.Arg) } if e.Rune != '?' { t.Errorf("cmdline should emit search event with Rune %q but got %q", '?', e.Rune) } } func TestCmdlineHistory(t *testing.T) { c := NewCmdline() eventCh, cmdlineCh, redrawCh := make(chan event.Event), make(chan event.Event), make(chan struct{}) c.Init(eventCh, cmdlineCh, redrawCh) go c.Run() events0 := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.Rune, Rune: 'n'}, {Type: event.Rune, Rune: 'e'}, {Type: event.Rune, Rune: 'w'}, {Type: event.ExecuteCmdline}, } events1 := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.Rune, Rune: 'v'}, {Type: event.Rune, Rune: 'n'}, {Type: event.Rune, Rune: 'e'}, {Type: event.Rune, Rune: 'w'}, {Type: event.ExecuteCmdline}, } events2 := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.CursorUp}, {Type: event.ExecuteCmdline}, } events3 := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.CursorUp}, {Type: event.CursorUp}, {Type: event.CursorUp}, {Type: event.CursorDown}, {Type: event.ExecuteCmdline}, } events4 := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.CursorUp}, {Type: event.ExecuteCmdline}, } events5 := []event.Event{ {Type: event.StartCmdlineSearchForward}, {Type: event.Rune, Rune: 't'}, {Type: event.Rune, Rune: 'e'}, {Type: event.Rune, Rune: 's'}, {Type: event.Rune, Rune: 't'}, {Type: event.ExecuteCmdline}, } events6 := []event.Event{ {Type: event.StartCmdlineSearchForward}, {Type: event.CursorUp}, {Type: event.CursorDown}, {Type: event.Rune, Rune: 'n'}, {Type: event.Rune, Rune: 'e'}, {Type: event.Rune, Rune: 'w'}, {Type: event.ExecuteCmdline}, } events7 := []event.Event{ {Type: event.StartCmdlineSearchBackward}, {Type: event.CursorUp}, {Type: event.CursorUp}, {Type: event.ExecuteCmdline}, } events8 := []event.Event{ {Type: event.StartCmdlineCommand}, {Type: event.CursorUp}, {Type: event.CursorUp}, {Type: event.ExecuteCmdline}, } events9 := []event.Event{ {Type: event.StartCmdlineSearchForward}, {Type: event.CursorUp}, {Type: event.ExecuteCmdline}, } go func() { for _, events := range [][]event.Event{ events0, events1, events2, events3, events4, events5, events6, events7, events8, events9, } { for _, e := range events { cmdlineCh <- e } } }() for range len(events0) - 1 { <-redrawCh } e := <-eventCh if e.Type != event.New { t.Errorf("cmdline should emit New event but got %v", e) } for range len(events1) { <-redrawCh } e = <-eventCh if e.Type != event.Vnew { t.Errorf("cmdline should emit Vnew event but got %v", e) } for range len(events2) { <-redrawCh } e = <-eventCh if e.Type != event.Vnew { t.Errorf("cmdline should emit Vnew event but got %v", e) } for range len(events3) { <-redrawCh } e = <-eventCh if e.Type != event.New { t.Errorf("cmdline should emit New event but got %v", e.Type) } for range len(events4) { <-redrawCh } e = <-eventCh if e.Type != event.New { t.Errorf("cmdline should emit New event but got %v", e.Type) } for range len(events5) { <-redrawCh } e = <-eventCh if e.Type != event.ExecuteSearch { t.Errorf("cmdline should emit ExecuteSearch event but got %v", e) } if expected := "test"; e.Arg != expected { t.Errorf("cmdline should emit search event with Arg %q but got %q", expected, e.Arg) } for range len(events6) { <-redrawCh } e = <-eventCh if e.Type != event.ExecuteSearch { t.Errorf("cmdline should emit ExecuteSearch event but got %v", e) } if expected := "new"; e.Arg != expected { t.Errorf("cmdline should emit search event with Arg %q but got %q", expected, e.Arg) } for range len(events7) { <-redrawCh } e = <-eventCh if e.Type != event.ExecuteSearch { t.Errorf("cmdline should emit ExecuteSearch event but got %v", e) } if expected := "test"; e.Arg != expected { t.Errorf("cmdline should emit search event with Arg %q but got %q", expected, e.Arg) } for range len(events8) { <-redrawCh } e = <-eventCh if e.Type != event.Vnew { t.Errorf("cmdline should emit Vnew event but got %v", e.Type) } for range len(events9) { <-redrawCh } e = <-eventCh if e.Type != event.ExecuteSearch { t.Errorf("cmdline should emit ExecuteSearch event but got %v", e) } if expected := "test"; e.Arg != expected { t.Errorf("cmdline should emit search event with Arg %q but got %q", expected, e.Arg) } <-redrawCh }