Use a regular expression for exclusion

This commit is contained in:
Kovid Goyal
2025-05-22 23:06:28 +05:30
parent bb79a27569
commit aec8a6b0b4
3 changed files with 38 additions and 25 deletions

View File

@@ -3,6 +3,7 @@ package choose_files
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
"github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/cli"
@@ -21,19 +22,17 @@ type State struct {
select_dirs bool select_dirs bool
multiselect bool multiselect bool
max_depth int max_depth int
exclude_patterns *utils.Set[string] exclude_patterns []*regexp.Regexp
search_text string search_text string
} }
func (s State) BaseDir() string { return utils.IfElse(s.base_dir == "", default_cwd, s.base_dir) } func (s State) BaseDir() string { return utils.IfElse(s.base_dir == "", default_cwd, s.base_dir) }
func (s State) SelectDirs() bool { return s.select_dirs } func (s State) SelectDirs() bool { return s.select_dirs }
func (s State) Multiselect() bool { return s.multiselect } func (s State) Multiselect() bool { return s.multiselect }
func (s State) MaxDepth() int { return utils.IfElse(s.max_depth < 1, 4, s.max_depth) } func (s State) MaxDepth() int { return utils.IfElse(s.max_depth < 1, 4, s.max_depth) }
func (s State) String() string { return utils.Repr(s) } func (s State) String() string { return utils.Repr(s) }
func (s State) SearchText() string { return s.search_text } func (s State) SearchText() string { return s.search_text }
func (s State) ExcludePatterns() *utils.Set[string] { func (s State) ExcludePatterns() []*regexp.Regexp { return s.exclude_patterns }
return utils.IfElse(s.exclude_patterns == nil, utils.NewSet[string](2), s.exclude_patterns)
}
func (s State) CurrentDir() string { func (s State) CurrentDir() string {
return utils.IfElse(s.current_dir == "", s.BaseDir(), s.current_dir) return utils.IfElse(s.current_dir == "", s.BaseDir(), s.current_dir)
} }
@@ -112,14 +111,20 @@ func (h *Handler) OnText(text string, from_key_event, in_bracketed_paste bool) (
func (h *Handler) set_state_from_config(conf *Config) (err error) { func (h *Handler) set_state_from_config(conf *Config) (err error) {
h.state.max_depth = int(conf.Max_depth) h.state.max_depth = int(conf.Max_depth)
h.state.exclude_patterns = utils.NewSet[string](len(conf.Exclude_directory)) h.state.exclude_patterns = make([]*regexp.Regexp, 0, len(conf.Exclude_directory))
seen := map[string]*regexp.Regexp{}
for _, x := range conf.Exclude_directory { for _, x := range conf.Exclude_directory {
if strings.HasPrefix(x, "!") { if strings.HasPrefix(x, "!") {
h.state.exclude_patterns.Discard(x[1:]) delete(seen, x[1:])
} else { } else if seen[x] == nil {
h.state.exclude_patterns.Add(x) if pat, err := regexp.Compile(x); err == nil {
seen[x] = pat
} else {
return fmt.Errorf("The exclude directory pattern %#v is invalid: %w", x, err)
}
} }
} }
h.state.exclude_patterns = utils.Values(seen)
return return
} }

View File

@@ -18,13 +18,20 @@ map = definition.add_map
mma = definition.add_mouse_map mma = definition.add_mouse_map
agr('scan', 'Scanning the filesystem') agr('scan', 'Scanning the filesystem')
opt('+exclude_directory', '/proc', add_to_default=True, long_text=''' opt(
Pattern to exclude directories. Can be specified multiple times. Matches against the absolute path to the directory. '+exclude_directory',
If the pattern starts with :code:`!`, the :code:`!` is removed and the remaning pattern is removed from the list of patterns. This '^/proc$',
add_to_default=True,
long_text='''
Regular expression to exclude directories. Matching directories will not be recursed into, but
you can still or change into them to inspect their contents. Can be specified multiple times. Matches against the absolute path to the directory.
If the pattern starts with :code:`!`, the :code:`!` is removed and the remaining pattern is removed from the list of patterns. This
can be used to remove the default excluded directory patterns. can be used to remove the default excluded directory patterns.
''') ''',
opt('+exclude_directory', '/dev', add_to_default=True) )
opt('+exclude_directory', '/sys', add_to_default=True) opt('+exclude_directory', '^/dev$', add_to_default=True)
opt('+exclude_directory', '^/sys$', add_to_default=True)
opt('+exclude_directory', '/__pycache__$', add_to_default=True)
opt('max_depth', '4', option_type='positive_int', long_text=''' opt('max_depth', '4', option_type='positive_int', long_text='''
The maximum depth to which to scan the filesystem for matches. Using large values will slow things down considerably. The better The maximum depth to which to scan the filesystem for matches. Using large values will slow things down considerably. The better

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"slices" "slices"
"strings" "strings"
"sync" "sync"
@@ -64,16 +65,16 @@ func scan_dir(path, root_dir string) []ResultItem {
return []ResultItem{} return []ResultItem{}
} }
func is_excluded(path string, exclude_patterns *utils.Set[string]) bool { func is_excluded(path string, exclude_patterns []*regexp.Regexp) bool {
for pattern := range exclude_patterns.Iterable() { for _, pattern := range exclude_patterns {
if matched, _ := filepath.Match(pattern, path); matched { if pattern.MatchString(path) {
return true return true
} }
} }
return false return false
} }
func (sc *ScanCache) fs_scan(root_dir, current_dir string, max_depth int, exclude_patterns *utils.Set[string], seen map[string]bool) (ans []ResultItem) { func (sc *ScanCache) fs_scan(root_dir, current_dir string, max_depth int, exclude_patterns []*regexp.Regexp, seen map[string]bool) (ans []ResultItem) {
var found bool var found bool
if ans, found = sc.get_cached_entries(current_dir); !found { if ans, found = sc.get_cached_entries(current_dir); !found {
ans = scan_dir(current_dir, root_dir) ans = scan_dir(current_dir, root_dir)
@@ -92,7 +93,7 @@ func (sc *ScanCache) fs_scan(root_dir, current_dir string, max_depth int, exclud
return return
} }
func (sc *ScanCache) scan(root_dir, search_text string, max_depth int, exclude_patterns *utils.Set[string]) (ans []ResultItem) { func (sc *ScanCache) scan(root_dir, search_text string, max_depth int, exclude_patterns []*regexp.Regexp) (ans []ResultItem) {
seen := make(map[string]bool, 1024) seen := make(map[string]bool, 1024)
ans = sc.fs_scan(root_dir, root_dir, max_depth, exclude_patterns, seen) ans = sc.fs_scan(root_dir, root_dir, max_depth, exclude_patterns, seen)
if search_text == "" { if search_text == "" {