Unverified Commit 9a4e77dd authored by Xavier L'Heureux's avatar Xavier L'Heureux

Merge branch 'master' of gitlab.redox-os.org:redox-os/liner

parents d14f1e6a de63c6f6
Pipeline #6892 passed with stage
in 1 minute and 56 seconds
[package]
name = "liner"
version = "0.5.0" # remember to update README
authors = ["MovingtoMars <definitelynotliam@gmail.com>"]
name = "redox_liner"
version = "0.5.1" # remember to update README
authors = [
"MovingtoMars <definitelynotliam@gmail.com>",
"Michael Aaron Murphy <mmstickman@gmail.com>",
"AdminXVII <dev.xlheureux@gmail.com>",
"Jeremy Soller <jackpot51@gmail.com>",
]
description = "A library offering readline-like functionality."
repository = "https://gitlab.redox-os.org/redox-os/liner"
homepage = "https://gitlab.redox-os.org/redox-os/liner"
......@@ -17,10 +22,11 @@ edition = "2018"
name = "liner"
[dependencies]
bytecount = "0.3.1"
termion = { git = "https://gitlab.redox-os.org/redox-os/termion" }
unicode-width = "0.1.*"
itertools = "*"
bytecount = "0.6.0"
termion = "1.5.4"
unicode-width = "0.1.6"
itertools = "0.8.2"
strip-ansi-escapes = "0.1.0"
[dev-dependencies]
regex = "1.0.0"
regex = "1.3.1"
......@@ -20,7 +20,7 @@ A Rust library offering readline-like functionality.
In `Cargo.toml`:
```toml
[dependencies]
liner = "0.5.0"
redox_liner = "0.5.1"
...
```
......
......@@ -6,7 +6,7 @@ use std::env::{args, current_dir};
use std::io;
use std::mem::replace;
use liner::{Completer, Context, CursorPosition, Event, EventKind, FilenameCompleter};
use liner::{Completer, Context, CursorPosition, Event, EventKind, FilenameCompleter, Prompt};
use regex::Regex;
use termion::color;
......@@ -90,7 +90,7 @@ fn main() {
// Reads the line, the first arg is the prompt, the second arg is a function called on every bit of text leaving liner, and the third is called on every key press
// Basically highlight_dodo(read_line()), where on every keypress, the lambda is called
let res = con.read_line(
"[prompt]\n% ",
Prompt::from("[prompt]\n% "),
Some(Box::new(highlight_dodo)),
&mut completer,
);
......
......@@ -6,7 +6,7 @@ use std::env::{args, current_dir};
use std::io;
use std::mem::replace;
use liner::{Completer, Context, CursorPosition, Event, EventKind, FilenameCompleter};
use liner::{Completer, Context, CursorPosition, Event, EventKind, FilenameCompleter, Prompt};
use regex::Regex;
use termion::color;
......@@ -72,7 +72,11 @@ fn main() {
.unwrap();
loop {
let res = con.read_line("[prompt]$ ", Some(Box::new(highlight_dodo)), &mut completer);
let res = con.read_line(
Prompt::from("[prompt]$ "),
Some(Box::new(highlight_dodo)),
&mut completer,
);
match res {
Ok(res) => {
......
......@@ -3,6 +3,7 @@ use termion::input::TermRead;
use termion::raw::IntoRawMode;
use super::*;
use crate::editor::Prompt;
use keymap;
pub type ColorClosure = Box<dyn Fn(&str) -> String>;
......@@ -73,9 +74,9 @@ impl Context {
/// The output is stdout.
/// The returned line has the newline removed.
/// Before returning, will revert all changes to the history buffers.
pub fn read_line<P: Into<String>, C: Completer>(
pub fn read_line<C: Completer>(
&mut self,
prompt: P,
prompt: Prompt,
f: Option<ColorClosure>,
handler: &mut C,
) -> io::Result<String> {
......@@ -85,7 +86,7 @@ impl Context {
/// Same as `Context.read_line()`, but passes the provided initial buffer to the editor.
///
/// ```no_run
/// use liner::{Context, Completer};
/// use liner::{Context, Completer, Prompt};
///
/// struct EmptyCompleter;
///
......@@ -97,14 +98,14 @@ impl Context {
///
/// let mut context = Context::new();
/// let line =
/// context.read_line_with_init_buffer("[prompt]$ ",
/// context.read_line_with_init_buffer(Prompt::from("[prompt]$ "),
/// &mut EmptyCompleter,
/// Some(Box::new(|s| String::from(s))),
/// "some initial buffer");
/// ```
pub fn read_line_with_init_buffer<P: Into<String>, B: Into<Buffer>, C: Completer>(
pub fn read_line_with_init_buffer<B: Into<Buffer>, C: Completer>(
&mut self,
prompt: P,
prompt: Prompt,
handler: &mut C,
f: Option<ColorClosure>,
buffer: B,
......
use std::cmp;
use std::fmt::Write;
use std::fmt::{self, Write};
use std::io;
use strip_ansi_escapes::strip;
use termion::{self, clear, color, cursor};
use super::complete::Completer;
......@@ -11,6 +12,114 @@ use crate::Buffer;
use crate::Context;
use itertools::Itertools;
/// Indicates the mode that should be currently displayed in the propmpt.
#[derive(Clone, Copy, Debug)]
pub enum ViPromptMode {
Normal,
Insert,
}
/// Holds the current mode and the indicators for all modes.
#[derive(Debug)]
pub struct ViStatus {
pub mode: ViPromptMode,
normal: String,
insert: String,
}
impl ViStatus {
pub fn new<N, I>(mode: ViPromptMode, normal: N, insert: I) -> Self
where
N: Into<String>,
I: Into<String>,
{
Self {
mode,
normal: normal.into(),
insert: insert.into(),
}
}
pub fn as_str(&self) -> &str {
use ViPromptMode::*;
match self.mode {
Normal => &self.normal,
Insert => &self.insert,
}
}
}
impl Default for ViStatus {
fn default() -> Self {
ViStatus {
mode: ViPromptMode::Insert,
normal: String::from("[N] "),
insert: String::from("[I] "),
}
}
}
impl fmt::Display for ViStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ViPromptMode::*;
match self.mode {
Normal => write!(f, "{}", self.normal),
Insert => write!(f, "{}", self.insert),
}
}
}
/// User-defined prompt.
///
/// # Examples
///
/// For Emacs mode, you simply define a static prompt that holds a string.
/// ```
/// # use liner::Prompt;
/// let prompt = Prompt::from("prompt$ ");
/// assert_eq!(&prompt.to_string(), "prompt$ ");
/// ```
///
/// You can also display Vi mode indicator in your prompt.
/// ```
/// # use liner::{Prompt, ViStatus, ViPromptMode};
/// let prompt = Prompt {
/// prompt: "prompt$ ".into(),
/// vi_status: Some(ViStatus::default()),
/// };
/// assert_eq!(&prompt.to_string(), "[I] prompt$ ");
/// ```
pub struct Prompt {
pub prompt: String,
pub vi_status: Option<ViStatus>,
}
impl Prompt {
/// Constructs a static prompt.
pub fn from<P: Into<String>>(prompt: P) -> Self {
Prompt {
prompt: prompt.into(),
vi_status: None,
}
}
pub fn prefix(&self) -> &str {
match &self.vi_status {
Some(status) => status.as_str(),
None => "",
}
}
}
impl fmt::Display for Prompt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(status) = &self.vi_status {
write!(f, "{}", status)?
}
write!(f, "{}", self.prompt)
}
}
/// Represents the position of the cursor relative to words in the buffer.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorPosition {
......@@ -60,7 +169,7 @@ impl CursorPosition {
/// The core line editor. Displays and provides editing for history and the new buffer.
pub struct Editor<'a, W: io::Write> {
prompt: String,
prompt: Prompt,
out: W,
context: &'a mut Context,
......@@ -137,18 +246,18 @@ macro_rules! cur_buf {
}
impl<'a, W: io::Write> Editor<'a, W> {
pub fn new<P: Into<String>>(
pub fn new(
out: W,
prompt: P,
prompt: Prompt,
f: Option<ColorClosure>,
context: &'a mut Context,
) -> io::Result<Self> {
Editor::new_with_init_buffer(out, prompt, f, context, Buffer::new())
}
pub fn new_with_init_buffer<P: Into<String>, B: Into<Buffer>>(
pub fn new_with_init_buffer<B: Into<Buffer>>(
mut out: W,
prompt: P,
prompt: Prompt,
f: Option<ColorClosure>,
context: &'a mut Context,
buffer: B,
......@@ -158,12 +267,15 @@ impl<'a, W: io::Write> Editor<'a, W> {
out.write_all(b" ")?; // if the line is not empty, overflow on next line
}
out.write_all("\r".as_bytes())?;
let mut prompt = prompt.into();
let Prompt {
mut prompt,
vi_status,
} = prompt;
out.write_all(prompt.split('\n').join("\r\n").as_bytes())?;
if let Some(index) = prompt.rfind('\n') {
prompt = prompt.split_at(index + 1).1.into()
}
let prompt = Prompt { prompt, vi_status };
let mut ed = Editor {
prompt,
cursor: 0,
......@@ -217,7 +329,12 @@ impl<'a, W: io::Write> Editor<'a, W> {
(words, pos)
}
pub fn set_prompt(&mut self, prompt: String) {
pub fn set_prompt(&mut self, mut prompt: Prompt) {
if let Some(passed_status) = &mut prompt.vi_status {
if let Some(old_status) = &self.prompt.vi_status {
passed_status.mode = old_status.mode;
}
}
self.prompt = prompt;
}
......@@ -854,19 +971,21 @@ impl<'a, W: io::Write> Editor<'a, W> {
color::Green.fg_str(),
)
};
let prefix = self.prompt.prefix();
(
format!(
"(search)'{}{}{}` ({}/{}): ",
"{}(search)'{}{}{}` ({}/{}): ",
&prefix,
color,
self.current_buffer(),
color::Reset.fg_str(),
hplace,
self.history_subset_index.len()
),
9,
strip(&prefix).unwrap().len() + 9,
)
} else {
(self.prompt.clone(), 0)
(self.prompt.to_string(), 0)
}
}
......@@ -1069,6 +1188,16 @@ impl<'a, W: io::Write> Editor<'a, W> {
self._display(true)
}
/// Modifies the prompt to reflect the Vi mode.
///
/// This operation will be ignored if a static prompt is used, as mode changes will have no
/// side effect.
pub fn set_vi_mode(&mut self, mode: ViPromptMode) {
if let Some(status) = &mut self.prompt.vi_status {
status.mode = mode;
}
}
}
impl<'a, W: io::Write> From<Editor<'a, W>> for String {
......@@ -1097,7 +1226,7 @@ mod tests {
fn delete_all_after_cursor_undo() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("delete all of this").unwrap();
ed.move_cursor_to_start_of_line().unwrap();
ed.delete_all_after_cursor().unwrap();
......@@ -1109,7 +1238,7 @@ mod tests {
fn move_cursor_left() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("let").unwrap();
assert_eq!(ed.cursor, 3);
......@@ -1125,7 +1254,7 @@ mod tests {
fn cursor_movement() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
assert_eq!(ed.cursor, 5);
......@@ -1138,7 +1267,7 @@ mod tests {
fn delete_until_backwards() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
assert_eq!(ed.cursor, 5);
......@@ -1151,7 +1280,7 @@ mod tests {
fn delete_until_forwards() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
ed.cursor = 0;
......@@ -1164,7 +1293,7 @@ mod tests {
fn delete_until() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
ed.cursor = 4;
......@@ -1177,7 +1306,7 @@ mod tests {
fn delete_until_inclusive() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
ed.cursor = 4;
......
......@@ -171,6 +171,7 @@ fn emacs_move_word<W: Write>(ed: &mut Editor<W>, direction: EmacsMoveDir) -> io:
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::Prompt;
use crate::{Completer, Context, Editor, KeyMap};
use std::io::Write;
use termion::event::Key;
......@@ -204,7 +205,7 @@ mod tests {
fn enter_is_done() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = Emacs::new();
ed.insert_str_after_cursor("done").unwrap();
assert_eq!(ed.cursor(), 4);
......@@ -219,7 +220,7 @@ mod tests {
fn move_cursor_left() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = Emacs::new();
ed.insert_str_after_cursor("let").unwrap();
assert_eq!(ed.cursor(), 3);
......@@ -235,7 +236,7 @@ mod tests {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = Emacs::new();
ed.insert_str_after_cursor("abc def ghi").unwrap();
assert_eq!(ed.cursor(), 11);
......@@ -255,7 +256,7 @@ mod tests {
fn cursor_movement() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = Emacs::new();
ed.insert_str_after_cursor("right").unwrap();
assert_eq!(ed.cursor(), 5);
......@@ -270,7 +271,7 @@ mod tests {
fn ctrl_h() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = Emacs::new();
ed.insert_str_after_cursor("not empty").unwrap();
......
......@@ -82,6 +82,7 @@ pub use emacs::Emacs;
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::Prompt;
use crate::Context;
use std::io::ErrorKind;
use termion::event::Key::*;
......@@ -112,7 +113,7 @@ mod tests {
fn ctrl_d_empty() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = TestKeyMap;
let res = map.handle_key(Ctrl('d'), &mut ed, &mut EmptyCompleter);
......@@ -125,7 +126,7 @@ mod tests {
fn ctrl_d_non_empty() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = TestKeyMap;
ed.insert_str_after_cursor("not empty").unwrap();
......@@ -138,7 +139,7 @@ mod tests {
fn ctrl_c() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
let mut map = TestKeyMap;
let res = map.handle_key(Ctrl('c'), &mut ed, &mut EmptyCompleter);
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment