use std::fmt;
#[derive(Debug, Clone)]
pub struct CompletionCandidate {
pub label: String,
pub insert_text: Option<String>,
pub detail: Option<String>,
pub icon: Option<String>,
pub score: i64,
pub source: Option<CompletionSourceId>,
pub is_snippet: bool,
pub provider_data: Option<String>,
}
impl CompletionCandidate {
pub fn word(label: String, score: i64) -> Self {
Self {
label,
insert_text: None,
detail: None,
icon: None,
score,
source: None,
is_snippet: false,
provider_data: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CompletionSourceId(pub String);
impl fmt::Display for CompletionSourceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone)]
pub struct OtherBufferSlice {
pub buffer_id: u64,
pub bytes: Vec<u8>,
pub label: String,
}
#[derive(Debug, Clone)]
pub struct CompletionContext {
pub prefix: String,
pub cursor_byte: usize,
pub word_start_byte: usize,
pub buffer_len: usize,
pub is_large_file: bool,
pub scan_range: std::ops::Range<usize>,
pub viewport_top_byte: usize,
pub viewport_bottom_byte: usize,
pub language_id: Option<String>,
pub word_chars_extra: String,
pub prefix_has_uppercase: bool,
pub other_buffers: Vec<OtherBufferSlice>,
}
pub const NORMAL_SCAN_RADIUS: usize = 512 * 1024;
pub const LARGE_FILE_SCAN_RADIUS: usize = 32 * 1024;
impl CompletionContext {
pub fn compute_scan_range(
cursor_byte: usize,
buffer_len: usize,
is_large_file: bool,
) -> std::ops::Range<usize> {
let radius = if is_large_file {
LARGE_FILE_SCAN_RADIUS
} else {
NORMAL_SCAN_RADIUS
};
let start = cursor_byte.saturating_sub(radius);
let end = (cursor_byte + radius).min(buffer_len);
start..end
}
}
pub enum ProviderResult {
Ready(Vec<CompletionCandidate>),
Pending(u64),
}
pub trait CompletionProvider: Send {
fn id(&self) -> CompletionSourceId;
fn display_name(&self) -> &str;
fn is_enabled(&self, ctx: &CompletionContext) -> bool;
fn provide(&self, ctx: &CompletionContext, buffer_window: &[u8]) -> ProviderResult;
fn priority(&self) -> u32 {
20
}
}
pub fn is_word_char_for_lang(c: char, extra: &str) -> bool {
c.is_alphanumeric() || c == '_' || extra.contains(c)
}
pub fn is_word_grapheme_for_lang(g: &str, extra: &str) -> bool {
g.chars().any(|c| is_word_char_for_lang(c, extra))
}
pub fn smart_case_matches(candidate: &str, prefix: &str, prefix_has_upper: bool) -> bool {
if prefix_has_upper {
candidate.starts_with(prefix)
} else {
candidate.to_lowercase().starts_with(&prefix.to_lowercase())
}
}
pub fn case_mismatch_penalty(candidate: &str, prefix: &str, prefix_has_upper: bool) -> i64 {
if prefix_has_upper {
0
} else {
if candidate.starts_with(prefix) {
0 } else {
-50_000 }
}
}