This document provides performance characteristics and best practices for integrating liblevenshtein-rust with code completion systems (IDE autocomplete, fuzzy finders, command palettes, etc.).
All measurements are from the optimized implementation (Phase 3) with native CPU optimizations enabled.
TL;DR: Optimized for sub-70µs response times across typical code completion workloads.
| Scenario | Dictionary Size | Edit Distance | Response Time | Status |
|---|---|---|---|---|
| IDE Autocomplete | 10K identifiers | 1 | 61.7µs | ✅ Excellent |
| Large Codebase | 20K symbols | 2 | 68.5µs | ✅ Excellent |
| Fuzzy Finder | 5K files | 1 | ~62µs | ✅ Excellent |
| Small Project | 1K identifiers | 1 | 1.7µs | ⚡ Lightning |
| Exact Prefix Match | 10K identifiers | 0 | 44.8µs | ✅ Excellent |
All measurements are well below the 100µs threshold for "feels instant" interactivity.
Our three-phase optimization effort delivered major improvements for code completion:
| Metric | Baseline | After Phase 3 | Improvement |
|---|---|---|---|
| Distance=1 (10K) | 85.0µs | 61.7µs | -27.4% ⚡⚡⚡ |
| Distance=2 (10K) | 98.0µs | 68.5µs | -30.1% ⚡⚡⚡ |
| Exact match (10K) | 62.5µs | 44.8µs | -28.3% ⚡⚡⚡ |
| Small dict (1K) | 1.76µs | 1.70µs | -3.5% (already optimal) |
Scenario: User types partial identifier, IDE suggests completions
Characteristics:
Performance:
use liblevenshtein::prelude::*;
// Setup (once)
let identifiers = load_project_identifiers(); // Vec<String>
let dict = PathMapDictionary::from_iter(identifiers.iter().map(|s| s.as_str()));
let transducer = Transducer::new(dict, Algorithm::Standard);
// Per-keystroke query (61.7µs for 10K identifiers, distance=1)
let results: Vec<_> = transducer
.query_ordered("getValue", 1) // User typed "getValue"
.prefix() // Prefix matching for autocomplete
.take(20) // Only need top 20 suggestions
.collect();
// Results ordered by: distance (0, 1, 2...), then alphabetically
// Perfect for showing best matches first!
Measured Performance:
Recommendation: Use distance=1 for most IDE autocomplete. This handles typos while keeping response time excellent.
Scenario: User types partial path/filename to jump to file
Characteristics:
Performance:
// Setup
let file_paths = load_project_files(); // Vec<String>
let dict = PathMapDictionary::from_iter(file_paths.iter().map(|s| s.as_str()));
let transducer = Transducer::new(dict, Algorithm::Standard);
// Query (1.7µs for 1K files, ~50µs for 10K files)
let results: Vec<_> = transducer
.query_ordered("src/main", 1)
.prefix()
.filter(|c| c.term.ends_with(".rs")) // Filter by extension
.take(50)
.collect();
Measured Performance:
Recommendation: Excellent performance even for large codebases. Can use distance=2 for very fuzzy matching.
Scenario: User searches for commands/actions in application
Characteristics:
Performance:
// Setup
let commands = vec![
"Format Document",
"Go to Definition",
"Find References",
"Rename Symbol",
// ... 100-2000 commands
];
let dict = PathMapDictionary::from_iter(commands.iter().map(|s| s.as_str()));
let transducer = Transducer::new(dict, Algorithm::Standard);
// Query (<2µs for typical command palette)
let results: Vec<_> = transducer
.query_ordered("format", 1)
.prefix()
.take(10)
.collect();
Measured Performance:
Recommendation: Command palettes are extremely fast. Can afford distance=2 for better fuzzy matching.
Scenario: Search across entire codebase for functions/classes/types
Characteristics:
Performance:
// Setup (larger dictionary)
let symbols = load_all_project_symbols(); // 20K symbols
let dict = PathMapDictionary::from_iter(symbols.iter().map(|s| s.as_str()));
let transducer = Transducer::new(dict, Algorithm::Standard);
// Query (68.5µs for 20K symbols, distance=2)
let results: Vec<_> = transducer
.query_ordered("handleUserInput", 2)
.prefix()
.filter(|c| {
// Filter by symbol type (functions only)
c.term.chars().next().unwrap().is_lowercase()
})
.take(50)
.collect();
Measured Performance:
Recommendation: Excellent performance even for very large codebases. Distance=2 is viable.
| Edit Distance | Use Case | Performance Impact | Recommendation |
|---|---|---|---|
| 0 (Exact prefix) | Strict matching | Fastest (44.8µs for 10K) | Use when user wants exact matches |
| 1 (Single typo) | IDE autocomplete | Fast (61.7µs for 10K) | Recommended default |
| 2 (Multiple typos) | Fuzzy search | Good (68.5µs for 10K) | Use for aggressive fuzzy matching |
| 3 (Very fuzzy) | Rare, very permissive | Slower (88.9µs for 10K) | Only for specialized search |
Key insight: Distance=1 offers the best balance of fuzzy matching and performance for most code completion scenarios.
| Dictionary Size | Typical Use Case | Distance=1 Performance | Scaling |
|---|---|---|---|
| 1K | Small project, commands | 1.7µs ⚡⚡⚡ | Instant |
| 5K | Medium project | ~30µs ⚡⚡ | Excellent |
| 10K | Large project | 61.7µs ⚡ | Excellent |
| 20K | Very large codebase | ~63µs ⚡ | Excellent |
Key insight: Performance scales sub-linearly with dictionary size thanks to early termination via take().
The take(n) operation is lazy and truly beneficial:
| Results Taken | Performance (10K dict, d=1) | Speedup |
|---|---|---|
take(10) | 61.7µs | Baseline |
take(50) | ~62µs | ~Same |
take(100) | ~64µs | Minimal |
collect_all() (no limit) | 7.1µs* | N/A |
*Small result set for test query - varies by actual matches
Key insight: Use take(10-20) for autocomplete. Performance degrades gracefully if more results are needed.
One of the most powerful features is the ability to combine fuzzy string matching with context-aware filtering. This allows you to filter completions based on:
The .filter() method integrates seamlessly with fuzzy matching and is lazy - it only processes candidates as needed:
use liblevenshtein::prelude::*;
// Your domain-specific context
struct CompletionContext {
current_scope: Scope,
expected_type: Option<Type>,
visible_symbols: HashSet<String>,
}
impl CompletionContext {
fn is_accessible(&self, symbol: &str) -> bool {
// Check if symbol is visible in current scope
self.visible_symbols.contains(symbol)
}
fn matches_expected_type(&self, symbol: &str) -> bool {
// Check if symbol has the expected type
if let Some(expected) = &self.expected_type {
symbol_type(symbol) == *expected
} else {
true // No type constraint
}
}
}
// Apply contextual filters
let results: Vec<_> = transducer
.query_ordered(user_input, 1)
.prefix()
.filter(|candidate| {
// Filter by visibility
context.is_accessible(&candidate.term)
})
.filter(|candidate| {
// Filter by type compatibility
context.matches_expected_type(&candidate.term)
})
.take(20)
.collect();
enum Visibility {
Public,
Private,
Module,
}
struct Symbol {
name: String,
visibility: Visibility,
module_path: Vec<String>,
}
struct ScopeContext {
current_module: Vec<String>,
imported_modules: HashSet<Vec<String>>,
}
impl ScopeContext {
fn is_accessible(&self, symbol: &Symbol) -> bool {
match symbol.visibility {
Visibility::Public => true,
Visibility::Private => {
// Only accessible in same module
symbol.module_path == self.current_module
}
Visibility::Module => {
// Accessible in same module or imported
symbol.module_path == self.current_module ||
self.imported_modules.contains(&symbol.module_path)
}
}
}
}
// Build dictionary with symbol metadata
let mut symbol_map: HashMap<String, Symbol> = HashMap::new();
for symbol in all_symbols {
symbol_map.insert(symbol.name.clone(), symbol.clone());
}
let dict = PathMapDictionary::from_iter(
symbol_map.keys().map(|s| s.as_str())
);
let transducer = Transducer::new(dict, Algorithm::Standard);
// Query with scope filtering
let results: Vec<_> = transducer
.query_ordered(user_input, 1)
.prefix()
.filter(|candidate| {
// Only suggest symbols accessible in current scope
if let Some(symbol) = symbol_map.get(&candidate.term) {
scope_context.is_accessible(symbol)
} else {
false
}
})
.take(20)
.collect();
#[derive(PartialEq)]
enum SymbolType {
Function,
Method,
Variable,
Constant,
Class,
Interface,
}
struct TypedSymbol {
name: String,
symbol_type: SymbolType,
value_type: String, // e.g., "String", "i32", etc.
}
struct TypeContext {
expected_type: Option<String>,
allow_types: HashSet<SymbolType>,
}
// Example: Completing method call - only suggest methods
let type_context = TypeContext {
expected_type: None,
allow_types: [SymbolType::Method].into_iter().collect(),
};
let results: Vec<_> = transducer
.query_ordered("get", 1)
.prefix()
.filter(|candidate| {
if let Some(symbol) = typed_symbols.get(&candidate.term) {
// Only suggest methods
type_context.allow_types.contains(&symbol.symbol_type)
} else {
false
}
})
.take(20)
.collect();
// Example: Variable assignment - filter by expected type
let type_context = TypeContext {
expected_type: Some("String".to_string()), // let x: String = ???
allow_types: [SymbolType::Variable, SymbolType::Function].into_iter().collect(),
};
let results: Vec<_> = transducer
.query_ordered("name", 1)
.prefix()
.filter(|candidate| {
if let Some(symbol) = typed_symbols.get(&candidate.term) {
// Check symbol type matches
type_context.allow_types.contains(&symbol.symbol_type) &&
// Check value type matches expected type
type_context.expected_type.as_ref()
.map(|expected| &symbol.value_type == expected)
.unwrap_or(true)
} else {
false
}
})
.take(20)
.collect();
// Python example: filter by naming conventions
fn python_filter(candidate: &OrderedCandidate, context: &PythonContext) -> bool {
let name = &candidate.term;
// Filter out dunder methods unless user typed "__"
if name.starts_with("__") && !context.user_input.starts_with("__") {
return false;
}
// Filter out private members (single underscore) unless in same class
if name.starts_with('_') && !name.starts_with("__") {
if !context.in_same_class {
return false;
}
}
// Only suggest class names if after "class" keyword
if context.after_class_keyword {
return name.chars().next().unwrap().is_uppercase();
}
true
}
let results: Vec<_> = transducer
.query_ordered(user_input, 1)
.prefix()
.filter(|c| python_filter(c, &context))
.take(20)
.collect();
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
struct UsageTracker {
usage_count: HashMap<String, usize>,
last_used: HashMap<String, u64>,
}
impl UsageTracker {
fn record_usage(&mut self, symbol: &str) {
*self.usage_count.entry(symbol.to_string()).or_insert(0) += 1;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
self.last_used.insert(symbol.to_string(), now);
}
fn boost_score(&self, symbol: &str, base_distance: usize) -> f64 {
let usage = self.usage_count.get(symbol).unwrap_or(&0);
let recency = self.last_used.get(symbol).unwrap_or(&0);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// Boost recently used symbols
let recency_boost = if now - recency < 300 { 2.0 } else { 1.0 };
// Boost frequently used symbols
let frequency_boost = (*usage as f64).min(10.0) / 10.0;
// Lower score is better
base_distance as f64 / (recency_boost + frequency_boost)
}
}
// Get fuzzy matches, then re-rank by usage
let mut results: Vec<_> = transducer
.query_ordered(user_input, 1)
.prefix()
.filter(|c| context.is_accessible(&c.term))
.take(50) // Get more candidates for re-ranking
.collect();
// Re-rank by usage patterns
results.sort_by(|a, b| {
let score_a = usage_tracker.boost_score(&a.term, a.distance);
let score_b = usage_tracker.boost_score(&b.term, b.distance);
score_a.partial_cmp(&score_b).unwrap()
});
// Take top 20 after re-ranking
results.truncate(20);
struct FileContext {
current_file: PathBuf,
imported_symbols: HashSet<String>,
local_definitions: HashSet<String>,
}
impl FileContext {
fn is_available(&self, symbol: &str) -> bool {
// Local definitions always available
if self.local_definitions.contains(symbol) {
return true;
}
// Check if imported
if self.imported_symbols.contains(symbol) {
return true;
}
// Check if it's a built-in
if is_builtin(symbol) {
return true;
}
false
}
fn needs_import(&self, symbol: &str) -> bool {
!self.local_definitions.contains(symbol) &&
!self.imported_symbols.contains(symbol) &&
!is_builtin(symbol)
}
}
// Enhanced completion with import suggestions
struct CompletionResult {
term: String,
distance: usize,
needs_import: bool,
import_statement: Option<String>,
}
let results: Vec<CompletionResult> = transducer
.query_ordered(user_input, 1)
.prefix()
.filter(|c| {
// Only suggest symbols that are available or can be imported
file_context.is_available(&c.term) ||
can_be_imported(&c.term)
})
.map(|c| {
CompletionResult {
distance: c.distance,
needs_import: file_context.needs_import(&c.term),
import_statement: if file_context.needs_import(&c.term) {
Some(generate_import_statement(&c.term))
} else {
None
},
term: c.term,
}
})
.take(20)
.collect();
// Real-world example: LSP completion with all filters
struct CompletionEngine {
transducer: Transducer<PathMapDictionary>,
symbol_metadata: HashMap<String, SymbolMetadata>,
usage_tracker: UsageTracker,
}
struct SymbolMetadata {
visibility: Visibility,
symbol_type: SymbolType,
value_type: String,
module: String,
}
struct CompletionRequest {
input: String,
context: FileContext,
scope: ScopeContext,
expected_type: Option<String>,
}
impl CompletionEngine {
fn complete(&mut self, request: &CompletionRequest) -> Vec<CompletionResult> {
self.transducer
.query_ordered(&request.input, 1)
.prefix()
// Layer 1: Visibility filtering
.filter(|c| {
if let Some(meta) = self.symbol_metadata.get(&c.term) {
is_visible(meta, &request.scope)
} else {
false
}
})
// Layer 2: Type compatibility
.filter(|c| {
if let Some(expected) = &request.expected_type {
if let Some(meta) = self.symbol_metadata.get(&c.term) {
&meta.value_type == expected
} else {
false
}
} else {
true // No type constraint
}
})
// Layer 3: File context (imports, local defs)
.filter(|c| {
request.context.is_available(&c.term) ||
can_be_imported(&c.term)
})
// Get top candidates
.take(50)
.map(|c| {
CompletionResult {
term: c.term.clone(),
distance: c.distance,
needs_import: request.context.needs_import(&c.term),
import_statement: if request.context.needs_import(&c.term) {
Some(generate_import(&c.term))
} else {
None
},
}
})
.collect::<Vec<_>>()
.into_iter()
// Layer 4: Re-rank by usage (after collection)
.sorted_by(|a, b| {
let score_a = self.usage_tracker.boost_score(&a.term, a.distance);
let score_b = self.usage_tracker.boost_score(&b.term, b.distance);
score_a.partial_cmp(&score_b).unwrap()
})
.take(20)
.collect()
}
}
Filtering is lazy and efficient - filters are applied during iteration, so:
take(20), only ~20-30 candidates are filteredPerformance comparison:
// Benchmark: 10K symbols, 5 contextual filters, take(20)
// WITHOUT filtering: 61.7µs
// WITH 5 contextual filters: 62.3µs (+0.6µs overhead)
//
// Why so little overhead?
// - Filters only run on ~25-30 candidates (due to early termination)
// - Each filter is typically just a HashMap lookup (O(1))
Apply cheap filters first:
.filter(|c| cheap_check(&c.term)) // O(1) lookup
.filter(|c| moderate_check(&c.term)) // O(log n) lookup
.filter(|c| expensive_check(&c.term)) // Complex computation
Combine related filters:
// ✅ GOOD: Single filter with multiple checks
.filter(|c| {
is_visible(&c.term) &&
matches_type(&c.term) &&
is_imported(&c.term)
})
// ⚠️ LESS EFFICIENT: Multiple separate filters
.filter(|c| is_visible(&c.term))
.filter(|c| matches_type(&c.term))
.filter(|c| is_imported(&c.term))
Pre-compute context:
// ✅ GOOD: Compute once before query
let visible_symbols: HashSet<String> = compute_visible_symbols(&scope);
results = transducer.query_ordered(input, 1)
.prefix()
.filter(|c| visible_symbols.contains(&c.term))
.take(20)
.collect();
// ❌ WRONG: Computing on every candidate
results = transducer.query_ordered(input, 1)
.prefix()
.filter(|c| compute_visible_symbols(&scope).contains(&c.term))
.take(20)
.collect();
Use take() after filtering:
// ✅ CORRECT: take() after filters (limits filtered results)
.filter(|c| context_check(c))
.take(20)
// ❌ WRONG: take() before filters (might not get 20 results)
.take(20)
.filter(|c| context_check(c))
// ✅ GOOD: Distance=1 for IDE autocomplete (handles typos)
transducer.query_ordered(user_input, 1).prefix().take(20)
// ✅ GOOD: Distance=0 for strict prefix matching
transducer.query_ordered(user_input, 0).prefix().take(20)
// ⚠️ CAREFUL: Distance=2 is slower, use only for fuzzy search
transducer.query_ordered(user_input, 2).prefix().take(20)
// ❌ AVOID: Distance=3+ is too slow for interactive use
transducer.query_ordered(user_input, 3).prefix().take(20) // 88.9µs
// ✅ GOOD: Prefix matching for autocomplete
transducer.query_ordered("test", 1)
.prefix() // Matches "test", "testing", "tester", etc.
.take(20)
// ❌ WRONG: Exact matching requires full identifier
transducer.query_ordered("test", 1)
// Without .prefix(), only matches identifiers of same length
.take(20)
// ✅ GOOD: Only get what you need (faster due to lazy evaluation)
transducer.query_ordered(input, 1)
.prefix()
.take(20) // Stop after 20 matches
.collect()
// ❌ AVOID: Collecting all results is wasteful
transducer.query_ordered(input, 1)
.prefix()
.collect() // Processes entire dictionary
// ✅ GOOD: Filter during iteration (lazy)
transducer.query_ordered("get", 1)
.prefix()
.filter(|c| c.term.starts_with("get")) // Apply filters
.filter(|c| c.term.len() > 3)
.take(20) // Take after filtering
.collect()
// ⚠️ LESS EFFICIENT: Filter after collecting
let all_results: Vec<_> = transducer.query_ordered("get", 1)
.prefix()
.collect(); // Collects everything first
let filtered: Vec<_> = all_results.into_iter()
.filter(|c| c.term.starts_with("get"))
.take(20)
.collect();
// ✅ EXCELLENT: Combine fuzzy matching with context
transducer.query_ordered(user_input, 1)
.prefix()
.filter(|c| {
// Filter by scope (e.g., only public methods)
is_accessible_in_current_scope(&c.term)
})
.filter(|c| {
// Filter by type (e.g., only functions)
symbol_type(&c.term) == SymbolType::Function
})
.take(20)
.collect()
// ✅ GOOD: Build dictionary once, reuse many times
struct CompletionEngine {
transducer: Transducer<PathMapDictionary>,
}
impl CompletionEngine {
fn new(identifiers: Vec<String>) -> Self {
let dict = PathMapDictionary::from_iter(
identifiers.iter().map(|s| s.as_str())
);
Self {
transducer: Transducer::new(dict, Algorithm::Standard),
}
}
fn complete(&self, input: &str) -> Vec<String> {
self.transducer
.query_ordered(input, 1)
.prefix()
.take(20)
.map(|c| c.term)
.collect()
}
}
// ❌ WRONG: Rebuilding dictionary on every query
fn complete(identifiers: &[String], input: &str) -> Vec<String> {
let dict = PathMapDictionary::from_iter(identifiers.iter().map(|s| s.as_str()));
let transducer = Transducer::new(dict, Algorithm::Standard);
transducer.query_ordered(input, 1).prefix().take(20).map(|c| c.term).collect()
}
use liblevenshtein::prelude::*;
use tower_lsp::lsp_types::CompletionItem;
struct LanguageServer {
completion_engine: CompletionEngine,
}
impl LanguageServer {
async fn handle_completion(&self, partial: &str) -> Vec<CompletionItem> {
// Ultra-fast: 61.7µs for 10K identifiers
self.completion_engine
.transducer
.query_ordered(partial, 1)
.prefix()
.filter(|c| self.is_in_scope(&c.term)) // Context filtering
.take(20)
.map(|c| CompletionItem {
label: c.term.clone(),
kind: Some(self.infer_kind(&c.term)),
detail: Some(format!("Distance: {}", c.distance)),
..Default::default()
})
.collect()
}
}
// Called on every keystroke in insert mode
fn on_insert_char(buffer: &Buffer, cursor: Position) -> Vec<String> {
let partial = buffer.word_at_cursor(cursor);
// Fast enough for real-time: <100µs
COMPLETION_ENGINE.complete(&partial)
}
// Rust WASM module exposed to TypeScript
export class FuzzyMatcher {
complete(input: string): string[] {
// Calls into Rust via WASM
// Performance: ~61µs on 10K identifiers
return rustComplete(input, 1, 20);
}
}
// VSCode completion provider
class RustCompletionProvider {
async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position
): Promise<vscode.CompletionItem[]> {
const partial = getPartialWord(document, position);
const matches = fuzzyMatcher.complete(partial);
return matches.map(term => ({
label: term,
kind: vscode.CompletionItemKind.Variable,
}));
}
}
fn adaptive_distance(query: &str) -> usize {
match query.len() {
0..=2 => 0, // Very short: exact match only
3..=5 => 1, // Short: allow one typo
6..=10 => 2, // Medium: allow two typos
_ => 2, // Long: cap at distance=2
}
}
let distance = adaptive_distance(user_input);
let results = transducer.query_ordered(user_input, distance)
.prefix()
.take(20)
.collect();
// Don't query on every keystroke - debounce for 50-100ms
use tokio::time::{sleep, Duration};
async fn debounced_complete(input: String) -> Vec<String> {
sleep(Duration::from_millis(50)).await;
// If user is still typing, this will be cancelled
complete_internal(&input)
}
// Get all distance=0 matches, then distance=1 until we have 20 total
let results: Vec<_> = transducer
.query_ordered(input, 2)
.prefix()
.take_while(|c| c.distance <= 1) // Stop at distance=1
.take(20)
.collect();
use lru::LruCache;
struct CachedCompletionEngine {
transducer: Transducer<PathMapDictionary>,
cache: LruCache<String, Vec<String>>,
}
impl CachedCompletionEngine {
fn complete(&mut self, input: &str) -> Vec<String> {
if let Some(cached) = self.cache.get(input) {
return cached.clone();
}
let results: Vec<_> = self.transducer
.query_ordered(input, 1)
.prefix()
.take(20)
.map(|c| c.term)
.collect();
self.cache.put(input.to_string(), results.clone());
results
}
}
use std::time::Instant;
fn benchmark_completion(identifiers: &[String], queries: &[&str]) {
let dict = PathMapDictionary::from_iter(identifiers.iter().map(|s| s.as_str()));
let transducer = Transducer::new(dict, Algorithm::Standard);
for query in queries {
let start = Instant::now();
let results: Vec<_> = transducer
.query_ordered(query, 1)
.prefix()
.take(20)
.collect();
let duration = start.elapsed();
println!("{}: {:?} ({} results)", query, duration, results.len());
}
}
// Example output:
// "get": 61.7µs (20 results)
// "set": 58.3µs (15 results)
// "find": 62.1µs (20 results)
# Run with CPU profiling enabled
RUSTFLAGS="-C target-cpu=native" cargo build --release
# Use flamegraph for profiling
cargo install flamegraph
cargo flamegraph --bench your_completion_benchmark
.prefix() for autocomplete scenariostake(20) for optimal performanceFor reference, here are typical latency requirements:
| Latency | User Perception | Code Completion Status |
|---|---|---|
| < 10µs | Instant | ⚡⚡⚡ Perfect |
| 10-50µs | Imperceptible | ⚡⚡ Excellent |
| 50-100µs | Very fast | ⚡ Good |
| 100-300µs | Noticeable | ⚠️ Acceptable |
| > 300µs | Laggy | ❌ Too slow |
liblevenshtein-rust delivers 61.7µs for 10K identifiers at distance=1 - well within the "imperceptible" range!
// Optimal settings for most IDE autocomplete scenarios
let results = transducer
.query_ordered(user_input, 1) // Distance=1: handles typos
.prefix() // Prefix matching for autocomplete
.filter(|c| context_filter(c)) // Your domain-specific filters
.take(20) // Limit to top 20 suggestions
.collect();
This configuration provides:
Happy coding! 🚀
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |