use crate::completion::context::CompletionContext;
use crate::completion::types::{CompletionSuggestion, SuggestionSource, SymbolKind};
pub struct SuggestionRanker {
weights: RankingWeights,
}
#[derive(Debug, Clone)]
pub struct RankingWeights {
pub frequency: f32,
pub proximity: f32,
pub kind_match: f32,
pub text_match: f32,
pub import_proximity: f32,
}
impl Default for SuggestionRanker {
fn default() -> Self {
Self {
weights: RankingWeights {
frequency: 0.25,
proximity: 0.25,
kind_match: 0.15,
text_match: 0.20,
import_proximity: 0.15,
},
}
}
}
impl SuggestionRanker {
pub fn new() -> Self {
Self::default()
}
pub fn rank_suggestions(
&self,
suggestions: Vec<CompletionSuggestion>,
context: &CompletionContext,
) -> Vec<CompletionSuggestion> {
let mut filtered = if let Some(ref token) = context.current_token {
let token_lower = token.to_lowercase();
suggestions
.into_iter()
.filter(|s| {
s.label.to_lowercase().starts_with(&token_lower)
|| s.label.to_lowercase().contains(&token_lower)
})
.collect()
} else {
suggestions
};
for suggestion in &mut filtered {
suggestion.score = self.calculate_score(suggestion, context);
}
let mut ranked: Vec<CompletionSuggestion> = filtered;
ranked.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
ranked
}
fn calculate_score(
&self,
suggestion: &CompletionSuggestion,
context: &CompletionContext,
) -> f32 {
let frequency_score = self.normalize_frequency(suggestion.usage_count);
let proximity_score = self.calculate_proximity(suggestion, context);
let kind_score = self.calculate_kind_match(suggestion, context);
let text_match_score = self.calculate_text_match(suggestion, context);
let import_score = self.calculate_import_proximity(suggestion, context);
self.weights.frequency * frequency_score
+ self.weights.proximity * proximity_score
+ self.weights.kind_match * kind_score
+ self.weights.text_match * text_match_score
+ self.weights.import_proximity * import_score
}
fn normalize_frequency(&self, count: usize) -> f32 {
1.0 - (1.0 / (1.0 + count as f32))
}
fn calculate_proximity(
&self,
suggestion: &CompletionSuggestion,
context: &CompletionContext,
) -> f32 {
if let Some(ref func) = context.enclosing_function {
if suggestion.grounded_in.contains(&func.id) {
return 1.0;
}
}
if matches!(suggestion.source, SuggestionSource::Imported) {
return 0.85; }
if matches!(suggestion.source, SuggestionSource::Database) {
return 0.6; }
0.3 }
fn calculate_kind_match(
&self,
suggestion: &CompletionSuggestion,
_context: &CompletionContext,
) -> f32 {
match suggestion.kind {
SymbolKind::Function => 0.8,
SymbolKind::Struct => 0.7,
SymbolKind::Enum => 0.7,
SymbolKind::Trait => 0.6,
SymbolKind::Module => 0.5,
_ => 0.4,
}
}
fn calculate_text_match(
&self,
suggestion: &CompletionSuggestion,
context: &CompletionContext,
) -> f32 {
if let Some(ref token) = context.current_token {
let label_lower = suggestion.label.to_lowercase();
let token_lower = token.to_lowercase();
if label_lower.starts_with(&token_lower) {
return 1.0;
}
if label_lower.contains(&token_lower) {
return 0.7;
}
0.3
} else {
0.5
}
}
fn calculate_import_proximity(
&self,
suggestion: &CompletionSuggestion,
_context: &CompletionContext,
) -> f32 {
if matches!(suggestion.source, SuggestionSource::Imported) {
if suggestion.via_import.is_some() {
return 1.0;
}
return 0.7;
}
0.0 }
}