-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Autocompletion of file paths in editor. #3586
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,13 +101,28 @@ func (b *Buffer) GetArg() (string, int) { | |
return input, argstart | ||
} | ||
|
||
// Complete filenames or buffer words | ||
func Complete(b *Buffer) ([]string, []string) { | ||
completions, suggestions := FileComplete(b) | ||
if len(completions) != 0 || len(suggestions) != 0 { | ||
return completions, suggestions | ||
} | ||
return BufferComplete(b) | ||
} | ||
|
||
func IsQuoteOrBraceStart(r rune) bool { | ||
return r == '"' || r == '\'' || r == '(' || r == '[' || r == '{' | ||
} | ||
Comment on lines
+113
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Functions like this are placed in |
||
|
||
// FileComplete autocompletes filenames | ||
func FileComplete(b *Buffer) ([]string, []string) { | ||
c := b.GetActiveCursor() | ||
input, argstart := b.GetArg() | ||
// Allow completion of paths inside quotes and braces | ||
path_start := strings.LastIndexFunc(input, IsQuoteOrBraceStart) + 1 | ||
Comment on lines
+121
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think supporting this is good, but it is unrelated with just adding a way to complete paths in editing. It would probably be better to do this change in another commit or mention in the commit description. |
||
|
||
sep := string(os.PathSeparator) | ||
dirs := strings.Split(input, sep) | ||
dirs := strings.Split(input[path_start:], sep) | ||
|
||
var files []os.FileInfo | ||
var err error | ||
|
@@ -144,7 +159,7 @@ func FileComplete(b *Buffer) ([]string, []string) { | |
} else { | ||
complete = suggestions[i] | ||
} | ||
completions[i] = util.SliceEndStr(complete, c.X-argstart) | ||
completions[i] = util.SliceEndStr(complete, c.X-argstart-path_start) | ||
} | ||
|
||
return completions, suggestions | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package buffer | ||
|
||
import ( | ||
"io/ioutil" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"os" | ||
"testing" | ||
|
||
"github.com/zyedidia/micro/v2/internal/config" | ||
) | ||
|
||
func setupTestDir() (string, error) { | ||
dir, err := ioutil.TempDir("", "testdir") | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
subdirs := []string{"subdir1", "subdir2"} | ||
files := []string{"file1.txt", "file2.txt"} | ||
|
||
for _, subdir := range subdirs { | ||
if err := os.Mkdir(dir+string(os.PathSeparator)+subdir, 0755); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
return "", err | ||
} | ||
} | ||
|
||
for _, file := range files { | ||
if _, err := os.Create(dir + string(os.PathSeparator) + file); err != nil { | ||
return "", err | ||
} | ||
} | ||
return dir, nil | ||
} | ||
|
||
func init() { | ||
config.InitRuntimeFiles(false) | ||
config.InitGlobalSettings() | ||
config.GlobalSettings["backup"] = false | ||
config.GlobalSettings["fastdirty"] = true | ||
} | ||
|
||
func teardownTestDir(dir string) { | ||
os.RemoveAll(dir) | ||
} | ||
|
||
func TestFileComplete(t *testing.T) { | ||
dir, err := setupTestDir() | ||
if err != nil { | ||
t.Fatalf("Failed to set up test directory: %v", err) | ||
} | ||
defer teardownTestDir(dir) | ||
|
||
tests := []struct { | ||
name string | ||
input string | ||
expectedComp []string | ||
expectedSugg []string | ||
expectedErr bool | ||
}{ | ||
{ | ||
name: "Complete subdir", | ||
input: dir + string(os.PathSeparator) + "sub", | ||
expectedComp: []string{"dir1" + string(os.PathSeparator), "dir2" + string(os.PathSeparator)}, | ||
expectedSugg: []string{"subdir1" + string(os.PathSeparator), "subdir2" + string(os.PathSeparator)}, | ||
}, | ||
{ | ||
name: "Complete file", | ||
input: dir + string(os.PathSeparator) + "f", | ||
expectedComp: []string{"ile1.txt", "ile2.txt"}, | ||
expectedSugg: []string{"file1.txt", "file2.txt"}, | ||
}, | ||
{ | ||
name: "No match", | ||
input: dir + string(os.PathSeparator) + "nomatch", | ||
expectedComp: []string{}, | ||
expectedSugg: []string{}, | ||
}, | ||
{ | ||
name: "Complete subdir inside quotes", | ||
input: "\"" + dir + string(os.PathSeparator) + "sub", | ||
expectedComp: []string{"dir1" + string(os.PathSeparator), "dir2" + string(os.PathSeparator)}, | ||
expectedSugg: []string{"subdir1" + string(os.PathSeparator), "subdir2" + string(os.PathSeparator)}, | ||
}, | ||
{ | ||
name: "Complete inside function and quotes", | ||
input: "funtion(\"" + dir + string(os.PathSeparator) + "sub", | ||
expectedComp: []string{"dir1" + string(os.PathSeparator), "dir2" + string(os.PathSeparator)}, | ||
expectedSugg: []string{"subdir1" + string(os.PathSeparator), "subdir2" + string(os.PathSeparator)}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
b := NewBufferFromString(tt.input, "", BTDefault) | ||
c := b.GetActiveCursor() | ||
c.End() | ||
|
||
completions, suggestions := FileComplete(b) | ||
if len(completions) != len(tt.expectedComp) { | ||
t.Errorf("expected %d completions, got %d", len(tt.expectedComp), len(completions)) | ||
} | ||
for i, exp := range tt.expectedComp { | ||
if completions[i] != exp { | ||
t.Errorf("expected completion %s, got %s", exp, completions[i]) | ||
} | ||
} | ||
if len(suggestions) != len(tt.expectedSugg) { | ||
t.Errorf("expected %d suggestions, got %d", len(tt.expectedSugg), len(suggestions)) | ||
} | ||
for i, exp := range tt.expectedSugg { | ||
if suggestions[i] != exp { | ||
t.Errorf("expected suggestion %s, got %s", exp, suggestions[i]) | ||
} | ||
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be better to support completing paths using a different action (like
CommandComplete
?), then change the default binding ofTab
toAutoComplete|FileComplete
(Always trying to complete paths first somehow does not seem nice). It would be possible to change the order or remove one action if someone wanted to.