Skip to content

Commit

Permalink
implement :cd, :chdir, :pwd commands to change the working directory
Browse files Browse the repository at this point in the history
  • Loading branch information
itchyny committed Oct 12, 2024
1 parent 4efa31c commit 8ea4b93
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 6 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ So if you have experience with Vim, you will notice most of basic operations of

- File operations
- `:edit`, `:enew`, `:new`, `:vnew`, `:only`
- Current working directory
- `:cd`, `:chdir`, `:pwd`
- Quit and save
- `:quit`, `:qall`, `:write`, `:wq`, `:xit`, `:xall`, `:cquit`
- Window operations
Expand Down
3 changes: 3 additions & 0 deletions cmdline/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ var commands = []command{
{"u[ndo]", event.Undo},
{"red[o]", event.Redo},

{"pw[d]", event.PrintDirectory},
{"cd", event.ChangeDirectory},
{"chd[ir]", event.ChangeDirectory},
{"exi[t]", event.Quit},
{"q[uit]", event.Quit},
{"qa[ll]", event.QuitAll},
Expand Down
14 changes: 10 additions & 4 deletions cmdline/completor.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ func (c *completor) clear() {
func (c *completor) complete(cmdline string, cmd command, prefix string, arg string, forward bool) string {
switch cmd.eventType {
case event.Edit, event.New, event.Vnew, event.Write:
return c.completeFilepaths(cmdline, prefix, arg, forward)
return c.completeFilepaths(cmdline, prefix, arg, forward, false)
case event.ChangeDirectory:
return c.completeFilepaths(cmdline, prefix, arg, forward, true)
case event.Wincmd:
return c.completeWincmd(cmdline, prefix, arg, forward)
default:
Expand All @@ -53,7 +55,9 @@ func (c *completor) completeNext(prefix string, forward bool) string {
return prefix + c.arg + c.results[c.index]
}

func (c *completor) completeFilepaths(cmdline string, prefix string, arg string, forward bool) string {
func (c *completor) completeFilepaths(
cmdline string, prefix string, arg string, forward bool, dirOnly bool,
) string {
if !strings.HasSuffix(prefix, " ") {
prefix += " "
}
Expand All @@ -62,7 +66,7 @@ func (c *completor) completeFilepaths(cmdline string, prefix string, arg string,
}
c.target = cmdline
c.index = 0
c.arg, c.results = c.listFileNames(arg)
c.arg, c.results = c.listFileNames(arg, dirOnly)
if len(c.results) == 1 {
cmdline := prefix + c.arg + c.results[0]
c.results = nil
Expand All @@ -81,7 +85,7 @@ func (c *completor) completeFilepaths(cmdline string, prefix string, arg string,

const separator = string(filepath.Separator)

func (c *completor) listFileNames(arg string) (string, []string) {
func (c *completor) listFileNames(arg string, dirOnly bool) (string, []string) {
var targets []string
path, homedir, hasHomedirPrefix, err := expandHomedir(arg)
if err != nil {
Expand Down Expand Up @@ -132,6 +136,8 @@ func (c *completor) listFileNames(arg string) (string, []string) {
}
if isDir {
name += separator
} else if dirOnly {
continue
}
targets = append(targets, name)
}
Expand Down
34 changes: 34 additions & 0 deletions cmdline/completor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,40 @@ func TestCompletorCompleteFilepathRoot(t *testing.T) {
}
}

func TestCompletorCompleteFilepathChangeDirectory(t *testing.T) {
c := newCompletor(&mockFilesystem{})
cmdline := "cd "
cmd, _, prefix, _, arg, _ := parse([]rune(cmdline))
cmdline = c.complete(cmdline, cmd, prefix, arg, false)
if expected := "cd editor/"; cmdline != expected {
t.Errorf("cmdline should be %q but got %q", expected, cmdline)
}
if expected := "cd "; c.target != expected {
t.Errorf("completion target should be %q but got %q", expected, c.target)
}
if c.index != 3 {
t.Errorf("completion index should be %d but got %d", 3, c.index)
}

c.clear()
cmdline = c.complete(cmdline, cmd, prefix, "~/", false)
if expected := "cd ~/Pictures/"; cmdline != expected {
t.Errorf("cmdline should be %q but got %q", expected, cmdline)
}
if c.index != 2 {
t.Errorf("completion index should be %d but got %d", 0, c.index)
}

c.clear()
cmdline = c.complete(cmdline, cmd, prefix, "/", true)
if expected := "cd /bin/"; cmdline != expected {
t.Errorf("cmdline should be %q but got %q", expected, cmdline)
}
if c.index != 0 {
t.Errorf("completion index should be %d but got %d", 0, c.index)
}
}

func TestCompletorCompleteWincmd(t *testing.T) {
c := newCompletor(&mockFilesystem{})
cmdline := "winc"
Expand Down
57 changes: 55 additions & 2 deletions editor/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
Expand All @@ -24,6 +26,7 @@ type Editor struct {
searchTarget string
searchMode rune
prevEventType event.Type
prevDir string
buffer *buffer.Buffer
err error
errtyp int
Expand Down Expand Up @@ -147,7 +150,7 @@ func (e *Editor) emit(ev event.Event) (redraw bool, finish bool, err error) {
}
switch ev.Type {
case event.QuitAll:
if len(ev.Arg) > 0 {
if ev.Arg != "" {
e.err, e.errtyp = errors.New("too many arguments for "+ev.CmdName), state.MessageError
redraw = true
} else {
Expand All @@ -171,8 +174,35 @@ func (e *Editor) emit(ev event.Event) (redraw bool, finish bool, err error) {
err = &quitErr{1}
finish = true
}
case event.PrintDirectory:
if ev.Arg != "" {
e.err, e.errtyp = errors.New("too many arguments for "+ev.CmdName), state.MessageError
redraw = true
break
}
fallthrough
case event.ChangeDirectory:
if ev.Arg == "-" && e.prevDir == "" {
e.err, e.errtyp = errors.New("no previous working directory"), state.MessageError
} else if dir, err := os.Getwd(); err != nil {
e.err, e.errtyp = err, state.MessageError
} else if ev.Arg == "" {
e.err, e.errtyp = errors.New(dir), state.MessageInfo
} else {
if ev.Arg != "-" {
dir, e.prevDir = ev.Arg, dir
} else {
dir, e.prevDir = e.prevDir, dir
}
if dir, err = e.chdir(dir); err != nil {
e.err, e.errtyp = err, state.MessageError
} else {
e.err, e.errtyp = errors.New(dir), state.MessageInfo
}
}
redraw = true
case event.Suspend:
if len(ev.Arg) > 0 {
if ev.Arg != "" {
e.err, e.errtyp = errors.New("too many arguments for "+ev.CmdName), state.MessageError
} else {
e.mu.Unlock()
Expand Down Expand Up @@ -340,6 +370,18 @@ func (e *Editor) redraw() (err error) {
return e.ui.Redraw(s)
}

func (e *Editor) chdir(dir string) (string, error) {
if dir, err := expandHomedir(dir); err != nil {
return "", err
} else if err = os.Chdir(dir); err != nil {
return "", err
} else if dir, err = os.Getwd(); err != nil {
return "", err
} else {
return dir, nil
}
}

func (e *Editor) suspend() error {
return suspend(e)
}
Expand All @@ -354,3 +396,14 @@ func (e *Editor) Close() error {
e.wm.Close()
return e.ui.Close()
}

func expandHomedir(path string) (string, error) {
if !strings.HasPrefix(path, "~") {
return path, nil
}
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, path[1:]), nil
}
30 changes: 30 additions & 0 deletions editor/editor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,3 +778,33 @@ func TestEditorCmdlineQuitErr(t *testing.T) {
t.Errorf("err should be nil but got: %v", err)
}
}

func TestEditorChangeDirectory(t *testing.T) {
dir, err := os.Getwd()
if err != nil {
t.Errorf("err should be nil but got: %v", err)
}
ui := newTestUI()
editor := NewEditor(ui, window.NewManager(), cmdline.NewCmdline())
if err := editor.Init(); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
if err := editor.OpenEmpty(); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
go func() {
ui.Emit(event.Event{Type: event.PrintDirectory})
ui.Emit(event.Event{Type: event.ChangeDirectory, Arg: "../"})
ui.Emit(event.Event{Type: event.ChangeDirectory, Arg: "-"})
ui.Emit(event.Event{Type: event.Quit})
}()
if err := editor.Run(); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
if err := editor.err; err == nil || err.Error() != dir {
t.Errorf("err should end with %q but got: %v", dir, err)
}
if err := editor.Close(); err != nil {
t.Errorf("err should be nil but got: %v", err)
}
}
3 changes: 3 additions & 0 deletions event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ const (
MoveWindowBottom
MoveWindowLeft
MoveWindowRight

PrintDirectory
ChangeDirectory
Suspend
Quit
QuitAll
Expand Down

0 comments on commit 8ea4b93

Please sign in to comment.