mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-02 11:58:28 +00:00
cmd/syncthing: Make directory auto-complete case insensitive (fixes #1347)
This commit is contained in:
parent
aec91d8f32
commit
add12b43aa
@ -1542,6 +1542,24 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
|
|||||||
sendJSON(w, browseFiles(current, fsType))
|
sendJSON(w, browseFiles(current, fsType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
matchExact int = iota
|
||||||
|
matchCaseIns
|
||||||
|
noMatch
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkPrefixMatch(s, prefix string) int {
|
||||||
|
if strings.HasPrefix(s, prefix) {
|
||||||
|
return matchExact
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix)) {
|
||||||
|
return matchCaseIns
|
||||||
|
}
|
||||||
|
|
||||||
|
return noMatch
|
||||||
|
}
|
||||||
|
|
||||||
func browseFiles(current string, fsType fs.FilesystemType) []string {
|
func browseFiles(current string, fsType fs.FilesystemType) []string {
|
||||||
if current == "" {
|
if current == "" {
|
||||||
filesystem := fs.NewFilesystem(fsType, "")
|
filesystem := fs.NewFilesystem(fsType, "")
|
||||||
@ -1567,16 +1585,29 @@ func browseFiles(current string, fsType fs.FilesystemType) []string {
|
|||||||
|
|
||||||
fs := fs.NewFilesystem(fsType, searchDir)
|
fs := fs.NewFilesystem(fsType, searchDir)
|
||||||
|
|
||||||
subdirectories, _ := fs.Glob(searchFile + "*")
|
subdirectories, _ := fs.DirNames(".")
|
||||||
|
|
||||||
|
exactMatches := make([]string, 0, len(subdirectories))
|
||||||
|
caseInsMatches := make([]string, 0, len(subdirectories))
|
||||||
|
|
||||||
ret := make([]string, 0, len(subdirectories))
|
|
||||||
for _, subdirectory := range subdirectories {
|
for _, subdirectory := range subdirectories {
|
||||||
info, err := fs.Stat(subdirectory)
|
info, err := fs.Stat(subdirectory)
|
||||||
if err == nil && info.IsDir() {
|
if err != nil || !info.IsDir() {
|
||||||
ret = append(ret, filepath.Join(searchDir, subdirectory)+pathSeparator)
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch checkPrefixMatch(subdirectory, searchFile) {
|
||||||
|
case matchExact:
|
||||||
|
exactMatches = append(exactMatches, filepath.Join(searchDir, subdirectory)+pathSeparator)
|
||||||
|
case matchCaseIns:
|
||||||
|
caseInsMatches = append(caseInsMatches, filepath.Join(searchDir, subdirectory)+pathSeparator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret
|
|
||||||
|
// sort to return matches in deterministic order (don't depend on file system order)
|
||||||
|
sort.Strings(exactMatches)
|
||||||
|
sort.Strings(caseInsMatches)
|
||||||
|
return append(exactMatches, caseInsMatches...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
|
func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -988,10 +988,14 @@ func TestBrowse(t *testing.T) {
|
|||||||
if err := ioutil.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
|
if err := ioutil.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err := os.Mkdir(filepath.Join(tmpDir, "MiXEDCase"), 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// We expect completion to return the full path to the completed
|
// We expect completion to return the full path to the completed
|
||||||
// directory, with an ending slash.
|
// directory, with an ending slash.
|
||||||
dirPath := filepath.Join(tmpDir, "dir") + pathSep
|
dirPath := filepath.Join(tmpDir, "dir") + pathSep
|
||||||
|
mixedCaseDirPath := filepath.Join(tmpDir, "MiXEDCase") + pathSep
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
current string
|
current string
|
||||||
@ -1002,13 +1006,15 @@ func TestBrowse(t *testing.T) {
|
|||||||
// With slash it's completed to its contents.
|
// With slash it's completed to its contents.
|
||||||
// Dirs are given pathSeps.
|
// Dirs are given pathSeps.
|
||||||
// Files are not returned.
|
// Files are not returned.
|
||||||
{tmpDir + pathSep, []string{dirPath}},
|
{tmpDir + pathSep, []string{mixedCaseDirPath, dirPath}},
|
||||||
// Globbing is automatic based on prefix.
|
// Globbing is automatic based on prefix.
|
||||||
{tmpDir + pathSep + "d", []string{dirPath}},
|
{tmpDir + pathSep + "d", []string{dirPath}},
|
||||||
{tmpDir + pathSep + "di", []string{dirPath}},
|
{tmpDir + pathSep + "di", []string{dirPath}},
|
||||||
{tmpDir + pathSep + "dir", []string{dirPath}},
|
{tmpDir + pathSep + "dir", []string{dirPath}},
|
||||||
{tmpDir + pathSep + "f", nil},
|
{tmpDir + pathSep + "f", nil},
|
||||||
{tmpDir + pathSep + "q", nil},
|
{tmpDir + pathSep + "q", nil},
|
||||||
|
// Globbing is case-insensitve
|
||||||
|
{tmpDir + pathSep + "mixed", []string{mixedCaseDirPath}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@ -1019,6 +1025,26 @@ func TestBrowse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrefixMatch(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
s string
|
||||||
|
prefix string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"aaaA", "aaa", matchExact},
|
||||||
|
{"AAAX", "BBB", noMatch},
|
||||||
|
{"AAAX", "aAa", matchCaseIns},
|
||||||
|
{"äÜX", "äü", matchCaseIns},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
ret := checkPrefixMatch(tc.s, tc.prefix)
|
||||||
|
if ret != tc.expected {
|
||||||
|
t.Errorf("checkPrefixMatch(%q, %q) => %v, expected %v", tc.s, tc.prefix, ret, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func equalStrings(a, b []string) bool {
|
func equalStrings(a, b []string) bool {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
return false
|
return false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user