use super::error::{ContextError, Result};
use super::{CheckpointStack, Completion, ContextId, ContextTree, DraftBuffer};
use crate::transducer::{Algorithm, Transducer};
use libdictenstein::dynamic_dawg::char::DynamicDawgChar;
use libdictenstein::dynamic_dawg::DynamicDawg;
use libdictenstein::pathmap::PathMapDictionary;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
pub struct DynamicContextualCompletionEngine<D = PathMapDictionary<Vec<ContextId>>>
where
D: crate::dictionary::MutableMappedDictionary<Value = Vec<ContextId>> + Clone,
{
drafts: Arc<Mutex<HashMap<ContextId, DraftBuffer>>>,
checkpoints: Arc<Mutex<HashMap<ContextId, CheckpointStack>>>,
context_tree: Arc<RwLock<ContextTree>>,
transducer: Arc<RwLock<Transducer<D>>>,
}
impl DynamicContextualCompletionEngine<PathMapDictionary<Vec<ContextId>>> {
pub fn new() -> Self {
Self::with_algorithm(Algorithm::Standard)
}
pub fn with_algorithm(algorithm: Algorithm) -> Self {
let dictionary = PathMapDictionary::<Vec<ContextId>>::new();
Self::with_dictionary(dictionary, algorithm)
}
}
impl DynamicContextualCompletionEngine<DynamicDawg<Vec<ContextId>>> {
pub fn with_dynamic_dawg(algorithm: Algorithm) -> Self {
let dictionary = DynamicDawg::<Vec<ContextId>>::new();
Self::with_dictionary(dictionary, algorithm)
}
}
impl DynamicContextualCompletionEngine<DynamicDawgChar<Vec<ContextId>>> {
pub fn with_dynamic_dawg_char(algorithm: Algorithm) -> Self {
let dictionary = DynamicDawgChar::<Vec<ContextId>>::new();
Self::with_dictionary(dictionary, algorithm)
}
}
impl<D> DynamicContextualCompletionEngine<D>
where
D: crate::dictionary::MutableMappedDictionary<Value = Vec<ContextId>> + Clone,
{
pub fn with_dictionary(dictionary: D, algorithm: Algorithm) -> Self {
let transducer = Transducer::new(dictionary, algorithm);
Self {
drafts: Arc::new(Mutex::new(HashMap::new())),
checkpoints: Arc::new(Mutex::new(HashMap::new())),
context_tree: Arc::new(RwLock::new(ContextTree::new())),
transducer: Arc::new(RwLock::new(transducer)),
}
}
#[inline]
pub fn transducer(&self) -> &Arc<RwLock<Transducer<D>>> {
&self.transducer
}
pub fn create_root_context(&self, id: ContextId) -> ContextId {
let mut tree = self
.context_tree
.write()
.expect("contextual engine: context_tree RwLock poisoned");
tree.create_root(id);
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
drafts.insert(id, DraftBuffer::new());
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
checkpoints.insert(id, CheckpointStack::new());
id
}
pub fn create_child_context(&self, id: ContextId, parent_id: ContextId) -> Result<ContextId> {
let mut tree = self
.context_tree
.write()
.expect("contextual engine: context_tree RwLock poisoned");
tree.create_child(id, parent_id)?;
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
drafts.insert(id, DraftBuffer::new());
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
checkpoints.insert(id, CheckpointStack::new());
Ok(id)
}
pub fn remove_context(&self, id: ContextId) -> bool {
let mut tree = self
.context_tree
.write()
.expect("contextual engine: context_tree RwLock poisoned");
let removed = tree.remove(id);
if removed {
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
drafts.retain(|ctx_id, _| tree.depth(*ctx_id).is_some());
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
checkpoints.retain(|ctx_id, _| tree.depth(*ctx_id).is_some());
}
removed
}
pub fn get_visible_contexts(&self, id: ContextId) -> Vec<ContextId> {
let tree = self
.context_tree
.read()
.expect("contextual engine: context_tree RwLock poisoned");
tree.visible_contexts(id)
}
pub fn context_exists(&self, id: ContextId) -> bool {
let tree = self
.context_tree
.read()
.expect("contextual engine: context_tree RwLock poisoned");
tree.depth(id).is_some()
}
pub fn get_draft(&self, context: ContextId) -> Option<String> {
let drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
drafts.get(&context).map(|buf| buf.as_str())
}
pub fn has_draft(&self, context: ContextId) -> bool {
let drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
drafts
.get(&context)
.map(|buf| !buf.is_empty())
.unwrap_or(false)
}
pub fn insert_char(&self, context: ContextId, ch: char) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
if let Some(buffer) = drafts.get_mut(&context) {
buffer.insert(ch);
Ok(())
} else {
Err(ContextError::NoDraftBuffer(context))
}
}
pub fn insert_str(&self, context: ContextId, s: &str) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
for ch in s.chars() {
self.insert_char(context, ch)?;
}
Ok(())
}
pub fn delete_char(&self, context: ContextId) -> Option<char> {
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
drafts.get_mut(&context).and_then(|buf| buf.delete())
}
pub fn clear_draft(&self, context: ContextId) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
if let Some(buffer) = drafts.get_mut(&context) {
buffer.clear();
Ok(())
} else {
Err(ContextError::NoDraftBuffer(context))
}
}
pub fn checkpoint(&self, context: ContextId) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
let drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
let buffer = drafts
.get(&context)
.ok_or(ContextError::NoDraftBuffer(context))?;
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
let stack = checkpoints
.get_mut(&context)
.ok_or(ContextError::NoCheckpointStack(context))?;
stack.push_from_buffer(buffer);
Ok(())
}
pub fn undo(&self, context: ContextId) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
let stack = checkpoints
.get_mut(&context)
.ok_or(ContextError::NoCheckpointStack(context))?;
if stack.is_empty() {
return Err(ContextError::NoCheckpoints(context));
}
let checkpoint = stack.peek().ok_or(ContextError::NoCheckpoints(context))?;
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
let buffer = drafts
.get_mut(&context)
.ok_or(ContextError::NoDraftBuffer(context))?;
checkpoint.restore(buffer);
drop(drafts); stack.pop();
Ok(())
}
pub fn checkpoint_count(&self, context: ContextId) -> usize {
let checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
checkpoints.get(&context).map(|s| s.len()).unwrap_or(0)
}
pub fn clear_checkpoints(&self, context: ContextId) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
if let Some(stack) = checkpoints.get_mut(&context) {
stack.clear();
Ok(())
} else {
Err(ContextError::NoCheckpointStack(context))
}
}
pub fn finalize(&self, context: ContextId) -> Result<String> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
let mut drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
let buffer = drafts
.get_mut(&context)
.ok_or(ContextError::NoDraftBuffer(context))?;
let term = buffer.as_str();
if term.is_empty() {
return Err(ContextError::EmptyDraft(context));
}
let term_owned = term.clone();
buffer.clear();
drop(drafts);
let transducer = self
.transducer
.read()
.expect("contextual engine: transducer RwLock poisoned");
let dictionary = transducer.dictionary();
let mut contexts = dictionary.get_value(&term_owned).unwrap_or_default();
if !contexts.contains(&context) {
contexts.push(context);
}
dictionary.insert_with_value(&term_owned, contexts);
drop(transducer);
let mut checkpoints = self
.checkpoints
.lock()
.expect("contextual engine: checkpoints Mutex poisoned");
if let Some(stack) = checkpoints.get_mut(&context) {
stack.clear();
}
Ok(term_owned)
}
pub fn finalize_direct(&self, context: ContextId, term: &str) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
if term.is_empty() {
return Err(ContextError::EmptyTerm);
}
let transducer = self
.transducer
.read()
.expect("contextual engine: transducer RwLock poisoned");
let dictionary = transducer.dictionary();
let mut contexts = dictionary.get_value(term).unwrap_or_default();
if !contexts.contains(&context) {
contexts.push(context);
}
dictionary.insert_with_value(term, contexts);
Ok(())
}
pub fn discard(&self, context: ContextId) -> Result<()> {
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context));
}
self.clear_draft(context)?;
self.clear_checkpoints(context)?;
Ok(())
}
pub fn has_term(&self, term: &str) -> bool {
let transducer = self
.transducer
.read()
.expect("contextual engine: transducer RwLock poisoned");
transducer.dictionary().contains(term)
}
pub fn term_contexts(&self, term: &str) -> Vec<ContextId> {
let transducer = self
.transducer
.read()
.expect("contextual engine: transducer RwLock poisoned");
transducer.dictionary().get_value(term).unwrap_or_default()
}
pub fn complete(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Vec<Completion> {
let mut results = Vec::new();
results.extend(self.complete_drafts(context, query, max_distance));
results.extend(self.complete_finalized(context, query, max_distance));
let mut seen = std::collections::HashSet::new();
results.retain(|c| {
if seen.contains(&c.term) {
false
} else {
seen.insert(c.term.clone());
true
}
});
results.sort();
results
}
pub fn complete_drafts(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Vec<Completion> {
let mut results = Vec::new();
let visible = self.get_visible_contexts(context);
let drafts = self
.drafts
.lock()
.expect("contextual engine: drafts Mutex poisoned");
for ctx_id in visible {
if let Some(buffer) = drafts.get(&ctx_id) {
let term = buffer.as_str();
if !term.is_empty() {
let distance = Self::levenshtein_distance(query, &term);
if distance <= max_distance {
results.push(Completion::draft(term, distance, ctx_id));
}
}
}
}
results
}
pub fn complete_finalized(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Vec<Completion> {
let mut results = Vec::new();
let visible = self.get_visible_contexts(context);
let transducer = self
.transducer
.read()
.expect("contextual engine: transducer RwLock poisoned");
for candidate in transducer.query_with_distance(query, max_distance) {
if let Some(contexts) = transducer.dictionary().get_value(&candidate.term) {
let visible_contexts: Vec<ContextId> = contexts
.iter()
.filter(|ctx_id| visible.contains(ctx_id))
.copied()
.collect();
if !visible_contexts.is_empty() {
results.push(Completion::finalized(
candidate.term,
candidate.distance,
visible_contexts,
));
}
}
}
results
}
fn levenshtein_distance(s1: &str, s2: &str) -> usize {
let len1 = s1.chars().count();
let len2 = s2.chars().count();
if len1 == 0 {
return len2;
}
if len2 == 0 {
return len1;
}
let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
for (i, row) in matrix.iter_mut().enumerate() {
row[0] = i;
}
for (j, cell) in matrix[0].iter_mut().enumerate() {
*cell = j;
}
let s1_chars: Vec<char> = s1.chars().collect();
let s2_chars: Vec<char> = s2.chars().collect();
for i in 1..=len1 {
for j in 1..=len2 {
let cost = if s1_chars[i - 1] == s2_chars[j - 1] {
0
} else {
1
};
matrix[i][j] = std::cmp::min(
std::cmp::min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1),
matrix[i - 1][j - 1] + cost,
);
}
}
matrix[len1][len2]
}
}
impl Default for DynamicContextualCompletionEngine<PathMapDictionary<Vec<ContextId>>> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let engine = DynamicContextualCompletionEngine::new();
assert!(!engine.context_exists(0));
}
#[test]
fn test_with_algorithm() {
let engine = DynamicContextualCompletionEngine::with_algorithm(Algorithm::Transposition);
assert!(!engine.context_exists(0));
}
#[test]
fn test_create_root_context() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
assert_eq!(ctx, 0);
assert!(engine.context_exists(0));
}
#[test]
fn test_create_child_context() {
let engine = DynamicContextualCompletionEngine::new();
let root = engine.create_root_context(0);
let child = engine
.create_child_context(1, root)
.expect("test fixture: create_child_context with valid parent");
assert_eq!(child, 1);
assert!(engine.context_exists(1));
}
#[test]
fn test_create_child_invalid_parent() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.create_child_context(1, 999);
assert!(result.is_err());
assert!(!engine.context_exists(1));
}
#[test]
fn test_remove_context() {
let engine = DynamicContextualCompletionEngine::new();
let root = engine.create_root_context(0);
let child = engine
.create_child_context(1, root)
.expect("test fixture: create_child_context with valid parent");
assert!(engine.remove_context(child));
assert!(!engine.context_exists(child));
assert!(engine.context_exists(root));
assert!(!engine.remove_context(child));
}
#[test]
fn test_remove_context_with_descendants() {
let engine = DynamicContextualCompletionEngine::new();
let root = engine.create_root_context(0);
let child1 = engine
.create_child_context(1, root)
.expect("test fixture: create_child_context with valid parent");
let child2 = engine
.create_child_context(2, child1)
.expect("test fixture: create_child_context with valid parent");
assert!(engine.remove_context(child1));
assert!(!engine.context_exists(child1));
assert!(!engine.context_exists(child2));
assert!(engine.context_exists(root));
}
#[test]
fn test_get_visible_contexts() {
let engine = DynamicContextualCompletionEngine::new();
let global = engine.create_root_context(0);
let module = engine
.create_child_context(1, global)
.expect("test fixture: create_child_context with valid parent");
let func = engine
.create_child_context(2, module)
.expect("test fixture: create_child_context with valid parent");
let visible = engine.get_visible_contexts(func);
assert_eq!(visible, vec![func, module, global]);
let visible_module = engine.get_visible_contexts(module);
assert_eq!(visible_module, vec![module, global]);
let visible_global = engine.get_visible_contexts(global);
assert_eq!(visible_global, vec![global]);
}
#[test]
fn test_get_draft_empty() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
assert_eq!(engine.get_draft(ctx), Some(String::new()));
assert!(!engine.has_draft(ctx));
}
#[test]
fn test_get_draft_nonexistent() {
let engine = DynamicContextualCompletionEngine::new();
assert_eq!(engine.get_draft(999), None);
assert!(!engine.has_draft(999));
}
#[test]
fn test_insert_char() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_char(ctx, 'h')
.expect("test fixture: insert_char on existing context");
engine
.insert_char(ctx, 'i')
.expect("test fixture: insert_char on existing context");
assert_eq!(engine.get_draft(ctx), Some("hi".to_string()));
assert!(engine.has_draft(ctx));
}
#[test]
fn test_insert_char_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.insert_char(999, 'x');
assert!(result.is_err());
}
#[test]
fn test_insert_str() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "hello")
.expect("test fixture: insert_str on existing context");
assert_eq!(engine.get_draft(ctx), Some("hello".to_string()));
engine
.insert_str(ctx, " world")
.expect("test fixture: insert_str on existing context");
assert_eq!(engine.get_draft(ctx), Some("hello world".to_string()));
}
#[test]
fn test_insert_str_unicode() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "Hello 世界")
.expect("test fixture: insert_str on existing context");
assert_eq!(engine.get_draft(ctx), Some("Hello 世界".to_string()));
engine
.insert_str(ctx, " 🌍")
.expect("test fixture: insert_str on existing context");
assert_eq!(engine.get_draft(ctx), Some("Hello 世界 🌍".to_string()));
}
#[test]
fn test_delete_char() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "hello")
.expect("test fixture: insert_str on existing context");
assert_eq!(engine.delete_char(ctx), Some('o'));
assert_eq!(engine.get_draft(ctx), Some("hell".to_string()));
assert_eq!(engine.delete_char(ctx), Some('l'));
assert_eq!(engine.get_draft(ctx), Some("hel".to_string()));
}
#[test]
fn test_delete_char_empty() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
assert_eq!(engine.delete_char(ctx), None);
}
#[test]
fn test_delete_char_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
assert_eq!(engine.delete_char(999), None);
}
#[test]
fn test_clear_draft() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "hello")
.expect("test fixture: insert_str on existing context");
assert!(engine.has_draft(ctx));
engine
.clear_draft(ctx)
.expect("test fixture: clear_draft on existing context");
assert!(!engine.has_draft(ctx));
assert_eq!(engine.get_draft(ctx), Some(String::new()));
}
#[test]
fn test_clear_draft_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.clear_draft(999);
assert!(result.is_err());
}
#[test]
fn test_checkpoint_and_undo() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert_eq!(engine.checkpoint_count(ctx), 1);
engine
.insert_str(ctx, "hello")
.expect("test fixture: insert_str on existing context");
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert_eq!(engine.checkpoint_count(ctx), 2);
engine
.insert_str(ctx, " world")
.expect("test fixture: insert_str on existing context");
assert_eq!(engine.get_draft(ctx), Some("hello world".to_string()));
engine
.undo(ctx)
.expect("test fixture: undo with available checkpoint");
assert_eq!(engine.get_draft(ctx), Some("hello".to_string()));
assert_eq!(engine.checkpoint_count(ctx), 1);
engine
.undo(ctx)
.expect("test fixture: undo with available checkpoint");
assert_eq!(engine.get_draft(ctx), Some(String::new()));
assert_eq!(engine.checkpoint_count(ctx), 0); }
#[test]
fn test_undo_no_checkpoints() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
let result = engine.undo(ctx);
assert!(result.is_err());
}
#[test]
fn test_checkpoint_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.checkpoint(999);
assert!(result.is_err());
}
#[test]
fn test_undo_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.undo(999);
assert!(result.is_err());
}
#[test]
fn test_checkpoint_count() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
assert_eq!(engine.checkpoint_count(ctx), 0);
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert_eq!(engine.checkpoint_count(ctx), 1);
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert_eq!(engine.checkpoint_count(ctx), 2);
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert_eq!(engine.checkpoint_count(ctx), 3);
}
#[test]
fn test_checkpoint_count_nonexistent() {
let engine = DynamicContextualCompletionEngine::new();
assert_eq!(engine.checkpoint_count(999), 0);
}
#[test]
fn test_clear_checkpoints() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert_eq!(engine.checkpoint_count(ctx), 2);
engine
.clear_checkpoints(ctx)
.expect("test fixture: clear_checkpoints on existing context");
assert_eq!(engine.checkpoint_count(ctx), 0);
}
#[test]
fn test_clear_checkpoints_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.clear_checkpoints(999);
assert!(result.is_err());
}
#[test]
fn test_multiple_undo_steps() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context"); engine
.insert_char(ctx, 'a')
.expect("test fixture: insert_char on existing context");
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context"); engine
.insert_char(ctx, 'b')
.expect("test fixture: insert_char on existing context");
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context"); engine
.insert_char(ctx, 'c')
.expect("test fixture: insert_char on existing context");
assert_eq!(engine.get_draft(ctx), Some("abc".to_string()));
assert_eq!(engine.checkpoint_count(ctx), 3);
engine
.undo(ctx)
.expect("test fixture: undo with available checkpoint"); assert_eq!(engine.get_draft(ctx), Some("ab".to_string()));
assert_eq!(engine.checkpoint_count(ctx), 2);
engine
.undo(ctx)
.expect("test fixture: undo with available checkpoint"); assert_eq!(engine.get_draft(ctx), Some("a".to_string()));
assert_eq!(engine.checkpoint_count(ctx), 1);
engine
.undo(ctx)
.expect("test fixture: undo with available checkpoint"); assert_eq!(engine.get_draft(ctx), Some(String::new()));
assert_eq!(engine.checkpoint_count(ctx), 0);
}
#[test]
fn test_finalize() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "hello")
.expect("test fixture: insert_str on existing context");
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
let term = engine
.finalize(ctx)
.expect("test fixture: finalize with non-empty draft");
assert_eq!(term, "hello");
assert!(!engine.has_draft(ctx));
assert_eq!(engine.checkpoint_count(ctx), 0);
assert!(engine.has_term("hello"));
assert_eq!(engine.term_contexts("hello"), vec![ctx]);
}
#[test]
fn test_finalize_empty_draft() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
let result = engine.finalize(ctx);
assert!(result.is_err());
}
#[test]
fn test_finalize_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.finalize(999);
assert!(result.is_err());
}
#[test]
fn test_finalize_direct() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "function")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(ctx, "variable")
.expect("test fixture: finalize_direct with non-empty term");
assert!(engine.has_term("function"));
assert!(engine.has_term("variable"));
assert_eq!(engine.term_contexts("function"), vec![ctx]);
}
#[test]
fn test_finalize_direct_empty_term() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
let result = engine.finalize_direct(ctx, "");
assert!(result.is_err());
}
#[test]
fn test_finalize_direct_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.finalize_direct(999, "test");
assert!(result.is_err());
}
#[test]
fn test_discard() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "mistake")
.expect("test fixture: insert_str on existing context");
engine
.checkpoint(ctx)
.expect("test fixture: checkpoint on existing context");
assert!(engine.has_draft(ctx));
assert_eq!(engine.checkpoint_count(ctx), 1);
engine
.discard(ctx)
.expect("test fixture: discard on existing context");
assert!(!engine.has_draft(ctx));
assert_eq!(engine.checkpoint_count(ctx), 0);
assert!(!engine.has_term("mistake"));
}
#[test]
fn test_discard_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
let result = engine.discard(999);
assert!(result.is_err());
}
#[test]
fn test_has_term() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
assert!(!engine.has_term("test"));
engine
.finalize_direct(ctx, "test")
.expect("test fixture: finalize_direct with non-empty term");
assert!(engine.has_term("test"));
}
#[test]
fn test_term_contexts() {
let engine = DynamicContextualCompletionEngine::new();
let global = engine.create_root_context(0);
let func = engine
.create_child_context(1, global)
.expect("test fixture: create_child_context with valid parent");
engine
.finalize_direct(global, "global_var")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(func, "local_var")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(func, "shared")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(global, "shared")
.expect("test fixture: finalize_direct with non-empty term");
assert_eq!(engine.term_contexts("global_var"), vec![global]);
assert_eq!(engine.term_contexts("local_var"), vec![func]);
assert_eq!(engine.term_contexts("shared"), vec![func, global]);
assert!(engine.term_contexts("unknown").is_empty());
}
#[test]
fn test_term_contexts_unknown() {
let engine = DynamicContextualCompletionEngine::new();
assert!(engine.term_contexts("unknown").is_empty());
}
#[test]
fn test_complete_drafts() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.insert_str(ctx, "hello")
.expect("test fixture: insert_str on existing context");
let results = engine.complete_drafts(ctx, "hel", 2);
assert_eq!(results.len(), 1);
assert_eq!(results[0].term, "hello");
assert!(results[0].is_draft);
assert_eq!(results[0].distance, 2); }
#[test]
fn test_complete_finalized() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "hello")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(ctx, "help")
.expect("test fixture: finalize_direct with non-empty term");
let results = engine.complete_finalized(ctx, "hel", 2);
assert_eq!(results.len(), 2);
assert!(results.iter().any(|c| c.term == "hello"));
assert!(results.iter().any(|c| c.term == "help"));
assert!(results.iter().all(|c| !c.is_draft));
}
#[test]
fn test_complete_fusion() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "hello")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(ctx, "help")
.expect("test fixture: finalize_direct with non-empty term");
engine
.insert_str(ctx, "hero")
.expect("test fixture: insert_str on existing context");
let results = engine.complete(ctx, "hel", 2);
assert!(results.len() >= 3);
assert!(results.iter().any(|c| c.term == "hello"));
assert!(results.iter().any(|c| c.term == "help"));
assert!(results.iter().any(|c| c.term == "hero"));
}
#[test]
fn test_complete_deduplication() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "test")
.expect("test fixture: finalize_direct with non-empty term");
engine
.insert_str(ctx, "test")
.expect("test fixture: insert_str on existing context");
let results = engine.complete(ctx, "test", 0);
let test_results: Vec<_> = results.iter().filter(|c| c.term == "test").collect();
assert_eq!(test_results.len(), 1);
assert!(test_results[0].is_draft);
}
#[test]
fn test_complete_hierarchical_visibility() {
let engine = DynamicContextualCompletionEngine::new();
let global = engine.create_root_context(0);
let func = engine
.create_child_context(1, global)
.expect("test fixture: create_child_context with valid parent");
engine
.finalize_direct(global, "global_var")
.expect("test fixture: finalize_direct with non-empty term");
engine
.finalize_direct(func, "local_var")
.expect("test fixture: finalize_direct with non-empty term");
let results = engine.complete_finalized(func, "var", 10);
assert!(results.iter().any(|c| c.term == "global_var"));
assert!(results.iter().any(|c| c.term == "local_var"));
let results = engine.complete_finalized(global, "var", 10);
assert!(results.iter().any(|c| c.term == "global_var"));
assert!(!results.iter().any(|c| c.term == "local_var"));
}
#[test]
fn test_complete_sorting() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "test")
.expect("test fixture: finalize_direct with non-empty term"); engine
.finalize_direct(ctx, "text")
.expect("test fixture: finalize_direct with non-empty term"); engine
.finalize_direct(ctx, "best")
.expect("test fixture: finalize_direct with non-empty term");
let mut results = engine.complete_finalized(ctx, "test", 1);
results.sort();
assert!(results.len() >= 2);
assert!(results[0].distance <= results[1].distance);
if results.len() >= 2 && results[0].distance == results[1].distance {
assert!(results[0].term <= results[1].term);
}
}
#[test]
fn test_complete_empty_query() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "test")
.expect("test fixture: finalize_direct with non-empty term");
let results = engine.complete_finalized(ctx, "", 10);
assert!(!results.is_empty());
}
#[test]
fn test_complete_no_matches() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine
.finalize_direct(ctx, "hello")
.expect("test fixture: finalize_direct with non-empty term");
let results = engine.complete_finalized(ctx, "xyz", 1);
assert!(results.is_empty());
}
#[test]
fn test_levenshtein_distance() {
type Engine = DynamicContextualCompletionEngine<PathMapDictionary<Vec<ContextId>>>;
assert_eq!(Engine::levenshtein_distance("", ""), 0);
assert_eq!(Engine::levenshtein_distance("abc", ""), 3);
assert_eq!(Engine::levenshtein_distance("", "abc"), 3);
assert_eq!(Engine::levenshtein_distance("abc", "abc"), 0);
assert_eq!(Engine::levenshtein_distance("abc", "abd"), 1);
assert_eq!(Engine::levenshtein_distance("abc", "abcd"), 1);
assert_eq!(Engine::levenshtein_distance("kitten", "sitting"), 3);
}
}