Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions noline/src/complete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::line_buffer::{Buffer, LineBuffer};

/// A type that provides possible line completions
///
/// ```
/// use noline::complete::Completer;
///
/// static FRUIT_LIST: [&str; 14] = [
/// "Apple",
/// "Banana",
/// "Grape",
/// "Kiwi",
/// "Lemon",
/// "Lime",
/// "Mango",
/// "Melon",
/// "Nectarine",
/// "Orange",
/// "Peach",
/// "Pear",
/// "Pineapple",
/// "Plum",
/// ];
///
/// struct Fruit;
///
/// impl Completer for Fruit {
/// fn complete(&self, line: &str, n: usize) -> Option<&str> {
/// FRUIT_LIST.iter()
/// .filter(|candidate| candidate.starts_with(line))
/// .skip(n)
/// .next()
/// .map(|candidate| &candidate[line.len()..])
/// }
/// }
///
/// assert_eq!(Fruit.complete("Pe", 1), Some("ar"))
/// ```
pub trait Completer {
/// Given `line` return the `n`'th possible continuation
fn complete(&self, line: &str, n: usize) -> Option<&str>;
}

impl Completer for () {
fn complete(&self, _: &str, _: usize) -> Option<&str> {
None
}
}

impl<T: Completer> Completer for &T {
fn complete(&self, line: &str, n: usize) -> Option<&str> {
T::complete(self, line, n)
}
}

impl<T: Completer> Completer for &mut T {
fn complete(&self, line: &str, n: usize) -> Option<&str> {
T::complete(self, line, n)
}
}

pub(crate) struct CompletionCycler<C> {
completer: C,
pre_completion_len: Option<usize>,
n: usize,
}

impl<C: Completer> CompletionCycler<C> {
pub(crate) fn new(completer: C) -> Self {
CompletionCycler {
completer,
pre_completion_len: None,
n: 0,
}
}

pub(crate) fn complete<B: Buffer>(&mut self, line: &mut LineBuffer<B>) -> Result<(), ()> {
let pre_completion_len = *self.pre_completion_len.get_or_insert(line.len());
line.delete_after_char(pre_completion_len);
if let Some(completion) = self.completer.complete(line.as_str(), self.n) {
self.n += 1;
line.insert_str(pre_completion_len, completion)
} else {
self.n = 0;
Ok(())
}
}

pub(crate) fn reset(&mut self) {
self.pre_completion_len = None;
self.n = 0;
}
}
35 changes: 20 additions & 15 deletions noline/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Use [`Initializer`] to get [`crate::terminal::Terminal`] and then
//! use [`Line`] to read a single line.

use crate::complete::{Completer, CompletionCycler};
use crate::history::{History, HistoryNavigator};
use crate::input::{Action, ControlCharacter::*, Parser, CSI};
use crate::line_buffer::Buffer;
Expand Down Expand Up @@ -88,27 +89,30 @@ impl Initializer {
// line, get cursor position and print prompt. Call [`Line::advance`]
// for each byte read from input and print bytes from
// [`crate::output::Output`] to output.
pub struct Line<'a, B: Buffer, H: History> {
pub struct Line<'a, B: Buffer, H: History, C: Completer> {
buffer: &'a mut LineBuffer<B>,
terminal: &'a mut Terminal,
parser: Parser,
prompt: &'a str,
nav: HistoryNavigator<'a, H>,
completer: CompletionCycler<C>,
}

impl<'a, B: Buffer, H: History> Line<'a, B, H> {
impl<'a, B: Buffer, H: History, C: Completer> Line<'a, B, H, C> {
pub fn new(
prompt: &'a str,
buffer: &'a mut LineBuffer<B>,
terminal: &'a mut Terminal,
history: &'a mut H,
completer: C,
) -> Self {
Self {
buffer,
terminal,
parser: Parser::new(),
prompt,
nav: HistoryNavigator::new(history),
completer: CompletionCycler::new(completer),
}
}

Expand Down Expand Up @@ -183,10 +187,19 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
#[cfg(test)]
dbg!(action);

let pos = self.current_position();

if self.buffer.len() == pos {
if let Action::ControlCharacter(Tab) = action {
if self.completer.complete(self.buffer).is_ok() {
return self.generate_output(ClearAndPrintBuffer);
}
}
}
self.completer.reset();

match action {
Action::Print(c) => {
let pos = self.current_position();

if self.buffer.insert_utf8_char(pos, c).is_ok() {
self.generate_output(PrintBufferAndMoveCursorForward)
} else {
Expand All @@ -201,8 +214,6 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
let len = self.buffer.len();

if len > 0 {
let pos = self.current_position();

if pos < len {
self.buffer.delete(pos);

Expand All @@ -217,8 +228,6 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
CtrlE => self.generate_output(MoveCursor(CursorMove::End)),
CtrlF => self.generate_output(MoveCursor(CursorMove::Forward)),
CtrlK => {
let pos = self.current_position();

self.buffer.delete_after_char(pos);

self.generate_output(EraseAfterCursor)
Expand All @@ -230,8 +239,6 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
CtrlN => self.history_move_down(),
CtrlP => self.history_move_up(),
CtrlT => {
let pos = self.current_position();

if pos > 0 && pos < self.buffer.as_str().chars().count() {
self.buffer.swap_chars(pos);
self.generate_output(MoveCursorBackAndPrintBufferAndMoveForward)
Expand All @@ -244,7 +251,6 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
self.generate_output(ClearLine)
}
CtrlW => {
let pos = self.current_position();
let move_cursor = -(self.buffer.delete_previous_word(pos) as isize);
self.generate_output(MoveCursorAndEraseAndPrintBuffer(move_cursor))
}
Expand All @@ -256,7 +262,6 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
self.generate_output(Done)
}
CtrlH | Backspace => {
let pos = self.current_position();
if pos > 0 {
self.buffer.delete(pos - 1);
self.generate_output(MoveCursorAndEraseAndPrintBuffer(-1))
Expand All @@ -272,7 +277,6 @@ impl<'a, B: Buffer, H: History> Line<'a, B, H> {
CSI::Home => self.generate_output(MoveCursor(CursorMove::Start)),
CSI::Delete => {
let len = self.buffer.len();
let pos = self.current_position();

if pos < len {
self.buffer.delete(pos);
Expand Down Expand Up @@ -352,13 +356,14 @@ pub(crate) mod tests {
&'b mut self,
prompt: &'b str,
mockterm: &mut MockTerminal,
) -> Line<'b, B, H> {
) -> Line<'b, B, H, ()> {
let cursor = mockterm.get_cursor();
let mut line = Line::new(
prompt,
&mut self.buffer,
&mut self.terminal,
&mut self.history,
(),
);

let output: Vec<u8> = line
Expand Down Expand Up @@ -388,7 +393,7 @@ pub(crate) mod tests {

fn advance<'a, B: Buffer, H: History>(
terminal: &mut MockTerminal,
noline: &mut Line<'a, B, H>,
noline: &mut Line<'a, B, H, ()>,
input: impl AsByteVec,
) -> core::result::Result<(), ()> {
terminal.bell = false;
Expand Down
3 changes: 2 additions & 1 deletion noline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
//! .unwrap();
//!
//! loop {
//! if let Ok(line) = editor.readline(prompt, &mut io) {
//! if let Ok(line) = editor.readline(prompt, &mut io, ()) {
//! write!(io, "Read: '{}'\n\r", line).unwrap();
//! } else {
//! break;
Expand All @@ -64,6 +64,7 @@
#[macro_use]
extern crate std;
pub mod builder;
pub mod complete;
mod core;
pub mod error;
pub mod history;
Expand Down
3 changes: 3 additions & 0 deletions noline/src/no_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod tokio {
//! Implementation for tokio

use crate::{
complete::Completer,
core::{Initializer, InitializerResult, Line},
error::Error,
history::{get_history_entries, CircularSlice, History},
Expand Down Expand Up @@ -92,12 +93,14 @@ pub mod tokio {
prompt: &str,
stdin: &mut R,
stdout: &mut W,
completer: impl Completer,
) -> Result<&'b str, Error<std::io::Error, std::io::Error>> {
let mut line = Line::new(
prompt,
&mut self.buffer,
&mut self.terminal,
&mut self.history,
completer,
);

for output in line.reset() {
Expand Down
9 changes: 6 additions & 3 deletions noline/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::error::Error;
use crate::history::{get_history_entries, CircularSlice, History};
use crate::line_buffer::{Buffer, LineBuffer};

use crate::complete::Completer;
use crate::core::{Initializer, InitializerResult, Line};
use crate::output::{Output, OutputItem};
use crate::terminal::Terminal;
Expand Down Expand Up @@ -98,12 +99,14 @@ where
&'b mut self,
prompt: &'b str,
io: &mut IO,
completer: impl Completer,
) -> Result<&'b str, Error<RE, WE>> {
let mut line = Line::new(
prompt,
&mut self.buffer,
&mut self.terminal,
&mut self.history,
completer,
);
Self::handle_output(line.reset(), io)?;

Expand Down Expand Up @@ -190,7 +193,7 @@ mod tests {
let handle = thread::spawn(move || {
let mut editor = EditorBuilder::new_unbounded().build_sync(&mut io).unwrap();

if let Ok(s) = editor.readline("> ", &mut io) {
if let Ok(s) = editor.readline("> ", &mut io, ()) {
Some(s.to_string())
} else {
None
Expand Down Expand Up @@ -423,7 +426,7 @@ pub mod std {
.build_sync(&mut io)
.unwrap();

while let Ok(s) = editor.readline(prompt, &mut io) {
while let Ok(s) = editor.readline(prompt, &mut io, ()) {
string_tx.send(s.to_string()).unwrap();
}
})
Expand Down Expand Up @@ -593,7 +596,7 @@ pub mod embedded {
.build_sync(&mut io)
.unwrap();

while let Ok(s) = editor.readline(prompt, &mut io) {
while let Ok(s) = editor.readline(prompt, &mut io, ()) {
string_tx.send(s.to_string()).unwrap();
}
})
Expand Down