use std::borrow::Cow;
use std::sync::Arc;
use crate::editing::{
application::ApplicationInfo,
completion::{complete_path, CompletionList},
cursor::Adjustable,
store::Store,
};
use crate::errors::EditResult;
use crate::prelude::*;
use super::{CursorGroupIdContext, EditBuffer};
pub trait CompletionActions<C, I>
where
I: ApplicationInfo,
{
fn complete_auto(
&mut self,
style: &CompletionStyle,
display: &CompletionDisplay,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<EditInfo, I>;
fn complete_file(
&mut self,
style: &CompletionStyle,
display: &CompletionDisplay,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<EditInfo, I>;
fn complete_line(
&mut self,
style: &CompletionStyle,
scope: &CompletionScope,
display: &CompletionDisplay,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<EditInfo, I>;
fn complete_word(
&mut self,
style: &CompletionStyle,
scope: &CompletionScope,
display: &CompletionDisplay,
ctx: &C,
store: &mut Store<I>,
) -> EditResult<EditInfo, I>;
}
impl<'a, I> CompletionActions<CursorGroupIdContext<'a>, I> for EditBuffer<I>
where
I: ApplicationInfo,
{
fn complete_auto(
&mut self,
style: &CompletionStyle,
display: &CompletionDisplay,
ctx: &CursorGroupIdContext<'a>,
store: &mut Store<I>,
) -> EditResult<EditInfo, I> {
let gid = ctx.0;
if !matches!(style, CompletionStyle::None) {
if let Some(list) = self.completions.get_mut(&gid) {
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
self._adjust_all(adjs, store);
}
return Ok(None);
}
}
let cursor = self.get_leader(gid);
let mut start = cursor.clone();
let list =
store
.completer
.complete(&self.text, &mut start, &self.id, &mut store.application);
if list.is_empty() {
let scope = CompletionScope::Global;
return self.complete_word(style, &scope, display, ctx, store);
}
let so = self.text.cursor_to_offset(&start);
let eo = self.text.cursor_to_offset(&cursor);
let prefix = self.text.slice(so..eo).to_string();
let mut list = CompletionList {
prefix,
candidates: Arc::new(list),
selected: None,
display: display.clone(),
cursor,
start,
};
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
list.cursor.adjust(&adjs);
self._adjust_all(adjs, store);
}
self.completions.insert(gid, list);
Ok(None)
}
fn complete_file(
&mut self,
style: &CompletionStyle,
display: &CompletionDisplay,
ctx: &CursorGroupIdContext<'a>,
store: &mut Store<I>,
) -> EditResult<EditInfo, I> {
let gid = ctx.0;
if !matches!(style, CompletionStyle::None) {
if let Some(list) = self.completions.get_mut(&gid) {
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
self._adjust_all(adjs, store);
}
return Ok(None);
}
}
let cursor = self.get_leader(gid);
let mut start = cursor.clone();
let list = complete_path(&self.text, &mut start);
if list.is_empty() {
return Ok(None);
}
let so = self.text.cursor_to_offset(&start);
let eo = self.text.cursor_to_offset(&cursor);
let prefix = self.text.slice(so..eo).to_string();
let mut list = CompletionList {
prefix,
candidates: Arc::new(list),
selected: None,
display: display.clone(),
cursor,
start,
};
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
list.cursor.adjust(&adjs);
self._adjust_all(adjs, store);
}
self.completions.insert(gid, list);
Ok(None)
}
fn complete_line(
&mut self,
style: &CompletionStyle,
scope: &CompletionScope,
display: &CompletionDisplay,
ctx: &CursorGroupIdContext<'a>,
store: &mut Store<I>,
) -> EditResult<EditInfo, I> {
let gid = ctx.0;
if !matches!(style, CompletionStyle::None) {
if let Some(list) = self.completions.get_mut(&gid) {
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
self._adjust_all(adjs, store);
}
return Ok(None);
}
}
let cursor = self.get_leader(gid);
let mut start = cursor.clone();
start.x = 0;
let begc = self.text.cursor_to_offset(&start);
let endc = self.text.cursor_to_offset(&cursor);
let prefix = self.text.slice(begc..endc);
let source = match scope {
CompletionScope::Buffer => &self.lines,
CompletionScope::Global => &store.completions.lines,
};
let prefix = Cow::from(&prefix);
let list = source.complete_line(prefix.as_ref());
if list.is_empty() {
return Ok(None);
}
let mut list = CompletionList {
prefix: prefix.into_owned(),
candidates: Arc::new(list),
selected: None,
display: display.clone(),
cursor,
start,
};
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
list.cursor.adjust(&adjs);
self._adjust_all(adjs, store);
}
self.completions.insert(gid, list);
Ok(None)
}
fn complete_word(
&mut self,
style: &CompletionStyle,
scope: &CompletionScope,
display: &CompletionDisplay,
ctx: &CursorGroupIdContext<'a>,
store: &mut Store<I>,
) -> EditResult<EditInfo, I> {
let gid = ctx.0;
if !matches!(style, CompletionStyle::None) {
if let Some(list) = self.completions.get_mut(&gid) {
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
self._adjust_all(adjs, store);
}
return Ok(None);
}
}
let cursor = self.get_leader(gid);
let mut start = cursor.clone();
let prefix = match self.text.get_prefix_word_mut(&mut start, &WordStyle::Big) {
None => return Ok(None),
Some(p) => p,
};
let source = match scope {
CompletionScope::Buffer => &self.lines,
CompletionScope::Global => &store.completions.lines,
};
let prefix = Cow::from(&prefix);
let list = source.complete_word(prefix.as_ref());
if list.is_empty() {
return Ok(None);
}
let mut list = CompletionList {
prefix: prefix.into_owned(),
candidates: Arc::new(list),
selected: None,
display: display.clone(),
cursor,
start,
};
if let Some(val) = list.select(style) {
let adjs = list.replace(&mut self.text, val);
list.cursor.adjust(&adjs);
self._adjust_all(adjs, store);
}
self.completions.insert(gid, list);
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::super::tests::*;
use super::*;
use std::fs::File;
use std::path::{Path, MAIN_SEPARATOR};
use temp_dir::TempDir;
use crate::editing::{
application::{ApplicationInfo, ApplicationStore},
completion::{Completer, CompletionMap},
};
struct TestStore {
completions: CompletionMap<String, ()>,
}
impl Default for TestStore {
fn default() -> Self {
let mut completions = CompletionMap::default();
completions.insert("pressed".into(), ());
completions.insert("dressed".into(), ());
TestStore { completions }
}
}
impl ApplicationStore for TestStore {}
#[derive(Clone, Debug, Eq, PartialEq)]
enum TestInfo {}
impl ApplicationInfo for TestInfo {
type Error = String;
type Action = ();
type Store = TestStore;
type WindowId = String;
type ContentId = String;
fn content_of_command(ct: CommandType) -> String {
EmptyInfo::content_of_command(ct)
}
}
pub struct TestCompleter;
impl Completer<TestInfo> for TestCompleter {
fn complete(
&mut self,
text: &EditRope,
cursor: &mut Cursor,
_: &String,
store: &mut TestStore,
) -> Vec<String> {
let word = text
.get_prefix_word_mut(cursor, &WordStyle::Little)
.unwrap_or_else(EditRope::empty);
let word = Cow::from(&word);
store.completions.complete(word.as_ref())
}
}
#[test]
fn test_complete_auto() {
let mut ebuf = EditBuffer::new("".to_string());
let gid = ebuf.create_group();
let vwctx = ViewportContext::default();
let vctx = EditContext::default();
let mut store = Store::<TestInfo>::default();
store.completer = Box::new(TestCompleter);
let prev = MoveDir1D::Previous;
let next = MoveDir1D::Next;
ebuf.complete_auto(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text(), "dressed\n");
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 7));
ebuf.complete_auto(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text(), "pressed\n");
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 7));
ebuf.complete_auto(
&CompletionStyle::List(prev),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text(), "dressed\n");
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 7));
ebuf.complete_auto(
&CompletionStyle::List(prev),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text(), "\n");
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 0));
ebuf.completions.remove(&gid);
type_char!(ebuf, 'd', gid, vwctx, vctx, store);
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 1));
ebuf.complete_auto(
&CompletionStyle::List(prev),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text(), "dressed\n");
assert_eq!(ebuf.get_leader(gid), Cursor::new(0, 7));
}
#[test]
fn test_complete_file() {
let tmp = TempDir::new().unwrap();
let file1 = tmp.child("file1");
let file2 = tmp.child("file2");
let hidden = tmp.child(".hidden");
let _ = File::create(file1.as_path()).unwrap();
let _ = File::create(file2.as_path()).unwrap();
let _ = File::create(hidden.as_path()).unwrap();
let file1 = file1.as_os_str().to_string_lossy();
let file2 = file2.as_os_str().to_string_lossy();
let hidden = hidden.as_os_str().to_string_lossy();
let next = MoveDir1D::Next;
let path = tmp.path().as_os_str().to_string_lossy();
let (mut ebuf, gid, vwctx, mut vctx, mut store) = mkfivestr(path.as_ref());
vctx.persist.insert = Some(InsertStyle::Insert);
ebuf.set_leader(gid, Cursor::new(0, 0));
let mv = mv!(MoveType::LinePos(MovePosition::End), 0);
edit!(ebuf, EditAction::Motion, mv, ctx!(gid, vwctx, vctx), store);
type_char!(ebuf, MAIN_SEPARATOR, gid, vwctx, vctx, store);
ebuf.complete_file(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text().trim_end(), file1);
ebuf.complete_file(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text().trim_end(), file2);
ebuf.complete_file(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
let result = ebuf.get_text();
let result = Path::new(result.trim_end());
assert_eq!(result, tmp.path());
ebuf.completions.remove(&gid);
type_char!(ebuf, '.', gid, vwctx, vctx, store);
ebuf.complete_file(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text().trim_end(), hidden);
ebuf.complete_file(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
let result = ebuf.get_text();
let result = Path::new(result.trim_end());
assert_eq!(result, tmp.path());
ebuf.completions.remove(&gid);
type_char!(ebuf, 'h', gid, vwctx, vctx, store);
ebuf.complete_file(
&CompletionStyle::List(next),
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf.get_text().trim_end(), hidden);
}
#[test]
fn test_complete_line() {
let (mut ebuf1, gid, vwctx, vctx, mut store) = mkfivestr("foo\n1 2 3\n");
let mut ebuf2 = mkbuf();
ebuf2.set_text("foo bar baz\nfoo bar quux\n\n");
ebuf2.checkpoint(ctx!(gid, vwctx, vctx), &mut store).unwrap();
let prev = MoveDir1D::Previous;
let next = MoveDir1D::Next;
ebuf1.set_leader(gid, Cursor::new(0, 3));
type_char!(ebuf1, ' ', gid, vwctx, vctx, store);
type_char!(ebuf1, 'b', gid, vwctx, vctx, store);
ebuf1
.complete_line(
&CompletionStyle::List(next),
&CompletionScope::Buffer,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo b\n1 2 3\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 5));
ebuf1
.complete_line(
&CompletionStyle::List(next),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo bar baz\n1 2 3\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 11));
ebuf1
.complete_line(
&CompletionStyle::List(next),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo bar quux\n1 2 3\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 12));
ebuf1
.complete_line(
&CompletionStyle::List(next),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo b\n1 2 3\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 5));
ebuf1
.complete_line(
&CompletionStyle::List(prev),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo bar quux\n1 2 3\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 12));
}
#[test]
fn test_complete_word() {
let (mut ebuf1, gid, vwctx, vctx, mut store) = mkfivestr("foo\nbar\n");
let mut ebuf2 = mkbuf();
ebuf2.set_text("foo baz\n");
ebuf2.checkpoint(ctx!(gid, vwctx, vctx), &mut store).unwrap();
let prev = MoveDir1D::Previous;
let next = MoveDir1D::Next;
ebuf1.set_leader(gid, Cursor::new(0, 3));
type_char!(ebuf1, ' ', gid, vwctx, vctx, store);
type_char!(ebuf1, 'b', gid, vwctx, vctx, store);
ebuf1
.complete_word(
&CompletionStyle::List(next),
&CompletionScope::Buffer,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo bar\nbar\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 7));
ebuf1
.complete_word(
&CompletionStyle::List(next),
&CompletionScope::Buffer,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo b\nbar\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 5));
ebuf1.completions.remove(&gid);
ebuf1
.complete_word(
&CompletionStyle::List(next),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo bar\nbar\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 7));
ebuf1
.complete_word(
&CompletionStyle::List(next),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo baz\nbar\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 7));
ebuf1
.complete_word(
&CompletionStyle::List(next),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo b\nbar\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 5));
ebuf1
.complete_word(
&CompletionStyle::List(prev),
&CompletionScope::Global,
&CompletionDisplay::None,
ctx!(gid, vwctx, vctx),
&mut store,
)
.unwrap();
assert_eq!(ebuf1.get_text(), "foo baz\nbar\n");
assert_eq!(ebuf1.get_leader(gid), Cursor::new(0, 7));
}
}