Class: SwarmCLI::V3::FileCompleter
- Inherits:
-
Object
- Object
- SwarmCLI::V3::FileCompleter
- Defined in:
- lib/swarm_cli/v3/file_completer.rb
Overview
File path completion logic for autocomplete.
FileCompleter provides methods to extract completion targets from text buffers and find matching file paths using ripgrep (rg). It has no knowledge of the UI, keyboard input, or display rendering - it only handles the file matching logic.
Class Method Summary collapse
-
.extract_completion_word(buffer, cursor) ⇒ Array<String>?
Extract the word at cursor position that should trigger completion.
-
.find_matches(target, max: 5) ⇒ Array<String>
Find matching files for a query (with @ prefix).
-
.score_match(path, query) ⇒ Integer
Score a match based on quality (higher = better).
Class Method Details
.extract_completion_word(buffer, cursor) ⇒ Array<String>?
Extract the word at cursor position that should trigger completion.
Searches backward from cursor to find the nearest @ symbol, then extracts from that @ to the cursor position. Returns nil if no @ is found before cursor.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/swarm_cli/v3/file_completer.rb', line 46 def extract_completion_word(buffer, cursor) # Find @ symbol before cursor before_cursor = buffer[0...cursor] at_index = before_cursor.rindex("@") return unless at_index # Extract target from @ to cursor target_start = at_index target_end = cursor # Find end of word after cursor (next space or end of buffer) target_end += 1 while target_end < buffer.length && buffer[target_end] !~ /\s/ pre = buffer[0...target_start] target = buffer[target_start...cursor] post = buffer[cursor...target_end] || "" [pre, target, post] end |
.find_matches(target, max: 5) ⇒ Array<String>
Requires ripgrep (rg) to be installed. Returns [] if not available.
Find matching files for a query (with @ prefix).
Uses ripgrep (rg) for fast file searching with fuzzy case-insensitive matching. Falls back to empty array if rg is not available or fails.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/swarm_cli/v3/file_completer.rb', line 85 def find_matches(target, max: 5) return [] unless target.start_with?("@") query = target[1..] # Strip @ # Empty query -> show current directory contents (non-hidden) if query.empty? matches = %x(rg --files --max-count=#{max} 2>/dev/null).split("\n") return matches.first(max).map { |p| "@#{p}" } end # Use rg --files with grep for fuzzy matching # Get more results than requested so we can sort by quality escaped_query = Regexp.escape(query) matches = %x(rg --files 2>/dev/null | rg -i '#{escaped_query}' --max-count=#{max * 3}).split("\n") # Sort by match quality (exact matches first, then by relevance) sorted = matches.sort_by { |path| -score_match(path, query) } sorted.first(max).map { |p| "@#{p}" } rescue StandardError => _e # Fallback to empty if rg fails [] end |
.score_match(path, query) ⇒ Integer
Score a match based on quality (higher = better). Prioritizes exact matches, then start-of-filename matches, then shorter/shallower paths.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/swarm_cli/v3/file_completer.rb', line 116 def score_match(path, query) score = 0 basename = File.basename(path) query_lower = query.downcase basename_lower = basename.downcase # Exact match gets highest priority score += 1000 if basename_lower == query_lower # Match at start of filename gets high priority score += 500 if basename_lower.start_with?(query_lower) # Prefer shorter filenames (more specific/complete matches) score += (100 - basename.length).clamp(0, 100) # Prefer shallower paths (current directory over subdirectories) depth = path.count("/") score -= depth * 20 # Small bonus for any match (already filtered by rg) score += 10 score end |