Skip to content

Commit 75861e7

Browse files
committed
implement :cd, :chdir, :pwd commands to change the working directory
1 parent 463c581 commit 75861e7

File tree

7 files changed

+137
-6
lines changed

7 files changed

+137
-6
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ So if you have experience with Vim, you will notice most of basic operations of
4848

4949
- File operations
5050
- `:edit`, `:enew`, `:new`, `:vnew`, `:only`
51+
- Current working directory
52+
- `:cd`, `:chdir`, `:pwd`
5153
- Quit and save
5254
- `:quit`, `:qall`, `:write`, `:wq`, `:xit`, `:xall`, `:cquit`
5355
- Window operations

cmdline/command.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ var commands = []command{
2121
{"u[ndo]", event.Undo},
2222
{"red[o]", event.Redo},
2323

24+
{"pw[d]", event.PrintDirectory},
25+
{"cd", event.ChangeDirectory},
26+
{"chd[ir]", event.ChangeDirectory},
2427
{"exi[t]", event.Quit},
2528
{"q[uit]", event.Quit},
2629
{"qa[ll]", event.QuitAll},

cmdline/completor.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ func (c *completor) clear() {
3131
func (c *completor) complete(cmdline string, cmd command, prefix string, arg string, forward bool) string {
3232
switch cmd.eventType {
3333
case event.Edit, event.New, event.Vnew, event.Write:
34-
return c.completeFilepaths(cmdline, prefix, arg, forward)
34+
return c.completeFilepaths(cmdline, prefix, arg, forward, false)
35+
case event.ChangeDirectory:
36+
return c.completeFilepaths(cmdline, prefix, arg, forward, true)
3537
case event.Wincmd:
3638
return c.completeWincmd(cmdline, prefix, arg, forward)
3739
default:
@@ -53,7 +55,9 @@ func (c *completor) completeNext(prefix string, forward bool) string {
5355
return prefix + c.arg + c.results[c.index]
5456
}
5557

56-
func (c *completor) completeFilepaths(cmdline string, prefix string, arg string, forward bool) string {
58+
func (c *completor) completeFilepaths(
59+
cmdline string, prefix string, arg string, forward bool, dirOnly bool,
60+
) string {
5761
if !strings.HasSuffix(prefix, " ") {
5862
prefix += " "
5963
}
@@ -62,7 +66,7 @@ func (c *completor) completeFilepaths(cmdline string, prefix string, arg string,
6266
}
6367
c.target = cmdline
6468
c.index = 0
65-
c.arg, c.results = c.listFileNames(arg)
69+
c.arg, c.results = c.listFileNames(arg, dirOnly)
6670
if len(c.results) == 1 {
6771
cmdline := prefix + c.arg + c.results[0]
6872
c.results = nil
@@ -81,7 +85,7 @@ func (c *completor) completeFilepaths(cmdline string, prefix string, arg string,
8185

8286
const separator = string(filepath.Separator)
8387

84-
func (c *completor) listFileNames(arg string) (string, []string) {
88+
func (c *completor) listFileNames(arg string, dirOnly bool) (string, []string) {
8589
var targets []string
8690
path, homedir, hasHomedirPrefix, err := expandHomedir(arg)
8791
if err != nil {
@@ -132,6 +136,8 @@ func (c *completor) listFileNames(arg string) (string, []string) {
132136
}
133137
if isDir {
134138
name += separator
139+
} else if dirOnly {
140+
continue
135141
}
136142
targets = append(targets, name)
137143
}

cmdline/completor_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,40 @@ func TestCompletorCompleteFilepathRoot(t *testing.T) {
255255
}
256256
}
257257

258+
func TestCompletorCompleteFilepathChangeDirectory(t *testing.T) {
259+
c := newCompletor(&mockFilesystem{})
260+
cmdline := "cd "
261+
cmd, _, prefix, _, arg, _ := parse([]rune(cmdline))
262+
cmdline = c.complete(cmdline, cmd, prefix, arg, false)
263+
if expected := "cd editor/"; cmdline != expected {
264+
t.Errorf("cmdline should be %q but got %q", expected, cmdline)
265+
}
266+
if expected := "cd "; c.target != expected {
267+
t.Errorf("completion target should be %q but got %q", expected, c.target)
268+
}
269+
if c.index != 3 {
270+
t.Errorf("completion index should be %d but got %d", 3, c.index)
271+
}
272+
273+
c.clear()
274+
cmdline = c.complete(cmdline, cmd, prefix, "~/", false)
275+
if expected := "cd ~/Pictures/"; cmdline != expected {
276+
t.Errorf("cmdline should be %q but got %q", expected, cmdline)
277+
}
278+
if c.index != 2 {
279+
t.Errorf("completion index should be %d but got %d", 0, c.index)
280+
}
281+
282+
c.clear()
283+
cmdline = c.complete(cmdline, cmd, prefix, "/", true)
284+
if expected := "cd /bin/"; cmdline != expected {
285+
t.Errorf("cmdline should be %q but got %q", expected, cmdline)
286+
}
287+
if c.index != 0 {
288+
t.Errorf("completion index should be %d but got %d", 0, c.index)
289+
}
290+
}
291+
258292
func TestCompletorCompleteWincmd(t *testing.T) {
259293
c := newCompletor(&mockFilesystem{})
260294
cmdline := "winc"

editor/editor.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"errors"
55
"fmt"
66
"io"
7+
"os"
8+
"path/filepath"
79
"strconv"
810
"strings"
911
"sync"
@@ -24,6 +26,7 @@ type Editor struct {
2426
searchTarget string
2527
searchMode rune
2628
prevEventType event.Type
29+
prevDir string
2730
buffer *buffer.Buffer
2831
err error
2932
errtyp int
@@ -147,7 +150,7 @@ func (e *Editor) emit(ev event.Event) (redraw bool, finish bool, err error) {
147150
}
148151
switch ev.Type {
149152
case event.QuitAll:
150-
if len(ev.Arg) > 0 {
153+
if ev.Arg != "" {
151154
e.err, e.errtyp = errors.New("too many arguments for "+ev.CmdName), state.MessageError
152155
redraw = true
153156
} else {
@@ -171,8 +174,35 @@ func (e *Editor) emit(ev event.Event) (redraw bool, finish bool, err error) {
171174
err = &quitErr{1}
172175
finish = true
173176
}
177+
case event.PrintDirectory:
178+
if ev.Arg != "" {
179+
e.err, e.errtyp = errors.New("too many arguments for "+ev.CmdName), state.MessageError
180+
redraw = true
181+
break
182+
}
183+
fallthrough
184+
case event.ChangeDirectory:
185+
if ev.Arg == "-" && e.prevDir == "" {
186+
e.err, e.errtyp = errors.New("no previous working directory"), state.MessageError
187+
} else if dir, err := os.Getwd(); err != nil {
188+
e.err, e.errtyp = err, state.MessageError
189+
} else if ev.Arg == "" {
190+
e.err, e.errtyp = errors.New(dir), state.MessageInfo
191+
} else {
192+
if ev.Arg != "-" {
193+
dir, e.prevDir = ev.Arg, dir
194+
} else {
195+
dir, e.prevDir = e.prevDir, dir
196+
}
197+
if dir, err = e.chdir(dir); err != nil {
198+
e.err, e.errtyp = err, state.MessageError
199+
} else {
200+
e.err, e.errtyp = errors.New(dir), state.MessageInfo
201+
}
202+
}
203+
redraw = true
174204
case event.Suspend:
175-
if len(ev.Arg) > 0 {
205+
if ev.Arg != "" {
176206
e.err, e.errtyp = errors.New("too many arguments for "+ev.CmdName), state.MessageError
177207
} else {
178208
e.mu.Unlock()
@@ -340,6 +370,18 @@ func (e *Editor) redraw() (err error) {
340370
return e.ui.Redraw(s)
341371
}
342372

373+
func (e *Editor) chdir(dir string) (string, error) {
374+
if dir, err := expandHomedir(dir); err != nil {
375+
return "", err
376+
} else if err = os.Chdir(dir); err != nil {
377+
return "", err
378+
} else if dir, err = os.Getwd(); err != nil {
379+
return "", err
380+
} else {
381+
return dir, nil
382+
}
383+
}
384+
343385
func (e *Editor) suspend() error {
344386
return suspend(e)
345387
}
@@ -354,3 +396,14 @@ func (e *Editor) Close() error {
354396
e.wm.Close()
355397
return e.ui.Close()
356398
}
399+
400+
func expandHomedir(path string) (string, error) {
401+
if !strings.HasPrefix(path, "~") {
402+
return path, nil
403+
}
404+
homeDir, err := os.UserHomeDir()
405+
if err != nil {
406+
return "", err
407+
}
408+
return filepath.Join(homeDir, path[1:]), nil
409+
}

editor/editor_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,3 +778,33 @@ func TestEditorCmdlineQuitErr(t *testing.T) {
778778
t.Errorf("err should be nil but got: %v", err)
779779
}
780780
}
781+
782+
func TestEditorChangeDirectory(t *testing.T) {
783+
dir, err := os.Getwd()
784+
if err != nil {
785+
t.Errorf("err should be nil but got: %v", err)
786+
}
787+
ui := newTestUI()
788+
editor := NewEditor(ui, window.NewManager(), cmdline.NewCmdline())
789+
if err := editor.Init(); err != nil {
790+
t.Errorf("err should be nil but got: %v", err)
791+
}
792+
if err := editor.OpenEmpty(); err != nil {
793+
t.Errorf("err should be nil but got: %v", err)
794+
}
795+
go func() {
796+
ui.Emit(event.Event{Type: event.PrintDirectory})
797+
ui.Emit(event.Event{Type: event.ChangeDirectory, Arg: "../"})
798+
ui.Emit(event.Event{Type: event.ChangeDirectory, Arg: "-"})
799+
ui.Emit(event.Event{Type: event.Quit})
800+
}()
801+
if err := editor.Run(); err != nil {
802+
t.Errorf("err should be nil but got: %v", err)
803+
}
804+
if err := editor.err; err == nil || err.Error() != dir {
805+
t.Errorf("err should end with %q but got: %v", dir, err)
806+
}
807+
if err := editor.Close(); err != nil {
808+
t.Errorf("err should be nil but got: %v", err)
809+
}
810+
}

event/event.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ const (
127127
MoveWindowBottom
128128
MoveWindowLeft
129129
MoveWindowRight
130+
131+
PrintDirectory
132+
ChangeDirectory
130133
Suspend
131134
Quit
132135
QuitAll

0 commit comments

Comments
 (0)