use core::ops::Range;
use crate::{DocId, Point};
pub const LOOKBACK_LINES: u32 = 40;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CompletionTrigger {
Typed(char),
TriggerChar(char),
Manual,
}
#[derive(Clone, Debug)]
pub struct CompletionCx {
pub doc: DocId,
pub revision: u64,
pub position: Point,
pub word: Range<u32>,
pub lookback: String,
pub trigger: CompletionTrigger,
}
pub trait Completions {
fn complete(&mut self, cx: &CompletionCx) -> Vec<CompletionItem>;
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CompletionKind {
Keyword,
Construct,
Param,
Value,
Type,
Event,
Field,
Symbol,
Method,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InsertText {
Plain(String),
Snippet(String),
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct CompletionItem {
pub label: String,
pub kind: CompletionKind,
pub detail: Option<String>,
pub doc: Option<String>,
pub insert: InsertText,
pub replace: Option<Range<u32>>,
pub sort_key: String,
pub retrigger: bool,
}
impl CompletionItem {
#[must_use]
pub fn new(label: impl Into<String>, kind: CompletionKind, insert: InsertText) -> Self {
let label = label.into();
Self {
sort_key: label.clone(),
label,
kind,
detail: None,
doc: None,
insert,
replace: None,
retrigger: false,
}
}
#[must_use]
pub fn plain(label: impl Into<String>, kind: CompletionKind) -> Self {
let label = label.into();
Self::new(label.clone(), kind, InsertText::Plain(label))
}
#[must_use]
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
#[must_use]
pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
self.doc = Some(doc.into());
self
}
#[must_use]
pub fn with_replace(mut self, replace: Range<u32>) -> Self {
self.replace = Some(replace);
self
}
#[must_use]
pub fn with_sort_key(mut self, sort_key: impl Into<String>) -> Self {
self.sort_key = sort_key.into();
self
}
#[must_use]
pub fn with_retrigger(mut self, retrigger: bool) -> Self {
self.retrigger = retrigger;
self
}
}
#[must_use]
pub fn is_completion_word_char(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '-'
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn completion_word_char_covers_kebab_identifiers() {
assert!(is_completion_word_char('a'));
assert!(is_completion_word_char('Z'));
assert!(is_completion_word_char('9'));
assert!(is_completion_word_char('_'));
assert!(is_completion_word_char('-'), "kebab keywords filter as one word");
assert!(!is_completion_word_char(' '));
assert!(!is_completion_word_char('('));
assert!(!is_completion_word_char('.'));
assert!(!is_completion_word_char('='));
}
#[test]
fn completion_item_builder_defaults_and_overrides() {
let item = CompletionItem::plain("if", CompletionKind::Keyword);
assert_eq!(item.label, "if");
assert_eq!(item.insert, InsertText::Plain("if".into()));
assert_eq!(item.sort_key, "if", "sort_key defaults to the label");
assert!(item.detail.is_none() && item.doc.is_none() && item.replace.is_none());
assert!(!item.retrigger);
let built = CompletionItem::new("size", CompletionKind::Param, InsertText::Snippet("size=${1:8}".into()))
.with_detail("u8")
.with_doc("the block size")
.with_sort_key("0_size")
.with_replace(4..9)
.with_retrigger(true);
assert_eq!(built.detail.as_deref(), Some("u8"));
assert_eq!(built.doc.as_deref(), Some("the block size"));
assert_eq!(built.sort_key, "0_size");
assert_eq!(built.replace, Some(4..9));
assert!(built.retrigger);
assert!(matches!(built.insert, InsertText::Snippet(_)));
}
}