Skip to content

Commit

Permalink
Fix issues introduced by Reader::read_line_step
Browse files Browse the repository at this point in the history
* Moves `PromptData` methods `set_buffer` and `set_cursor` to `Reader`
* Removes `PromptData`
* Changes signature of `set_buffer`, `set_cursor`, and `set_prompt`
  to return `io::Result<()>`, as these methods may be called while
  the prompt is drawn
  • Loading branch information
murarth committed May 28, 2018
1 parent b0032cb commit b01cbbe
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 110 deletions.
2 changes: 1 addition & 1 deletion examples/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const TIME_LIMIT: Duration = Duration::from_secs(5);
fn main() -> io::Result<()> {
let interface = Interface::new("async-demo")?;

interface.set_prompt("async-demo> ");
interface.set_prompt("async-demo> ")?;

println!("This is a demo of linefeed's asynchronous operation.");
println!("This demo will terminate in {} seconds.", TIME_LIMIT.as_secs());
Expand Down
2 changes: 1 addition & 1 deletion examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn main() -> io::Result<()> {
println!("");

interface.set_completer(Arc::new(DemoCompleter));
interface.set_prompt("demo> ");
interface.set_prompt("demo> ")?;

if let Err(e) = interface.load_history(HISTORY_FILE) {
if e.kind() == io::ErrorKind::NotFound {
Expand Down
2 changes: 1 addition & 1 deletion examples/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const DEMO_FN_SEQ: &str = "\x18d"; // Ctrl-X, d
fn main() -> io::Result<()> {
let interface = Interface::new("function-demo")?;

interface.set_prompt("fn-demo> ");
interface.set_prompt("fn-demo> ")?;

interface.define_function("demo-function", Arc::new(DemoFunction));

Expand Down
2 changes: 1 addition & 1 deletion examples/path_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn main() -> io::Result<()> {
let interface = Interface::new("path-completion-demo")?;

interface.set_completer(Arc::new(PathCompleter));
interface.set_prompt("path> ");
interface.set_prompt("path> ")?;

while let ReadResult::Input(line) = interface.read_line()? {
println!("read input: {:?}", line);
Expand Down
2 changes: 1 addition & 1 deletion examples/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use linefeed::{Interface, ReadResult, Signal};
fn main() -> io::Result<()> {
let interface = Interface::new("signal-demo")?;

interface.set_prompt("signals> ");
interface.set_prompt("signals> ")?;

// Report all signals to application
interface.set_report_signal(Signal::Break, true);
Expand Down
2 changes: 1 addition & 1 deletion src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl<Term: Terminal> Interface<Term> {
/// If `prompt` contains any terminal escape sequences (e.g. color codes),
/// such escape sequences should be immediately preceded by the character
/// `'\x01'` and immediately followed by the character `'\x02'`.
pub fn set_prompt(&self, prompt: &str) {
pub fn set_prompt(&self, prompt: &str) -> io::Result<()> {
self.lock_reader().set_prompt(prompt)
}

Expand Down
18 changes: 17 additions & 1 deletion src/prompter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ impl<'a, 'b: 'a, Term: 'b + Terminal> Prompter<'a, 'b, Term> {
}

/// Sets the buffer to the given value.
///
/// The cursor is moved to the end of the buffer.
pub fn set_buffer(&mut self, buf: &str) -> io::Result<()> {
self.write.set_buffer(buf)
Expand All @@ -190,7 +191,18 @@ impl<'a, 'b: 'a, Term: 'b + Terminal> Prompter<'a, 'b, Term> {
///
/// If the given position is out of bounds or is not aligned to `char` boundaries.
pub fn set_cursor(&mut self, pos: usize) -> io::Result<()> {
self.write.move_to(pos)
self.write.set_cursor(pos)
}

/// Sets the prompt that will be displayed when `read_line` is called.
///
/// # Notes
///
/// If `prompt` contains any terminal escape sequences (e.g. color codes),
/// such escape sequences should be immediately preceded by the character
/// `'\x01'` and immediately followed by the character `'\x02'`.
pub fn set_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.write.set_prompt(prompt)
}

/// Returns the size of the terminal at the last draw operation.
Expand Down Expand Up @@ -246,6 +258,10 @@ impl<'a, 'b: 'a, Term: 'b + Terminal> Prompter<'a, 'b, Term> {
/// Selects the history entry currently being edited by the user.
///
/// Setting the entry to `None` will result in editing the input buffer.
///
/// # Panics
///
/// If the index is out of bounds.
pub fn select_history_entry(&mut self, new: Option<usize>) -> io::Result<()> {
self.write.select_history_entry(new)
}
Expand Down
41 changes: 29 additions & 12 deletions src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use terminal::{
};
use util::{first_char, match_name};
use variables::{Variable, Variables, VariableIter};
use writer::PromptData;

/// Default set of string characters
pub const STRING_CHARS: &str = "\"'";
Expand Down Expand Up @@ -255,7 +254,7 @@ impl<'a, Term: 'a + Terminal> Reader<'a, Term> {

let new_macro_len = self.lock.data.macro_buffer.len();

if new_macro_len >= macro_len {
if new_macro_len != 0 && new_macro_len >= macro_len {
break;
}

Expand Down Expand Up @@ -291,15 +290,33 @@ impl<'a, Term: 'a + Terminal> Reader<'a, Term> {
}
}

/// Acquires the `Interface` write lock and returns a `PromptData` instance.
/// Sets the input buffer to the given string.
///
/// The `PromptData` structure enables modification of prompt input data
/// before a call to `read_line`. Prompt data is reset when a `read_line`
/// call completes.
pub fn lock_prompt_data<'b>(&'b mut self) -> PromptData<'b, 'a> {
// Borrows from &mut Reader lifetime to prevent prompt data from being
// modified while a read_line call is in progress.
PromptData::new(self.iface.lock_write_data())
/// # Notes
///
/// To prevent invalidating the cursor, this method sets the cursor
/// position to the end of the new buffer.
pub fn set_buffer(&mut self, buf: &str) -> io::Result<()> {
if self.lock.read_line_running {
self.prompter().set_buffer(buf)
} else {
self.iface.lock_write_data().set_buffer(buf);
Ok(())
}
}

/// Sets the cursor position in the input buffer.
///
/// # Panics
///
/// If the given position is out of bounds or not on a `char` boundary.
pub fn set_cursor(&mut self, pos: usize) -> io::Result<()> {
if self.lock.read_line_running {
self.prompter().set_cursor(pos)
} else {
self.iface.lock_write_data().set_cursor(pos);
Ok(())
}
}

/// Sets the prompt that will be displayed when `read_line` is called.
Expand All @@ -311,8 +328,8 @@ impl<'a, Term: 'a + Terminal> Reader<'a, Term> {
/// If `prompt` contains any terminal escape sequences (e.g. color codes),
/// such escape sequences should be immediately preceded by the character
/// `'\x01'` and immediately followed by the character `'\x02'`.
pub fn set_prompt(&mut self, prompt: &str) {
self.lock_prompt_data().set_prompt(prompt)
pub fn set_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.prompter().set_prompt(prompt)
}

/// Returns the application name
Expand Down
150 changes: 60 additions & 90 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::collections::{vec_deque, VecDeque};
use std::fmt;
use std::io;
use std::iter::repeat;
use std::marker::PhantomData;
use std::mem::swap;
use std::ops::{Deref, DerefMut, Range};
use std::sync::MutexGuard;
Expand Down Expand Up @@ -52,21 +51,6 @@ pub struct Writer<'a, Term: 'a + Terminal> {
write: WriteLock<'a, Term>,
}

/// Enables modification of prompt input data before a call to `read_line`.
///
/// Prompt data is reset when a `read_line` call completes.
///
/// An instance of this type can be constructed using the
/// [`Reader::lock_prompt_data`] method.
///
/// [`Reader::lock_prompt_data`]: ../reader/struct.Reader.html#method.lock_prompt_data
pub struct PromptData<'a, 'b: 'a> {
data: MutexGuard<'b, Write>,
// Borrows a lifetime from Reader to prevent prompt data from being modified
// while a read_line call is in progress.
_marker: PhantomData<&'a ()>,
}

pub(crate) struct Write {
/// Input buffer
pub buffer: String,
Expand Down Expand Up @@ -137,6 +121,22 @@ impl<'a, Term: Terminal> WriteLock<'a, Term> {
Ok(())
}

pub fn set_prompt(&mut self, prompt: &str) -> io::Result<()> {
let redraw = self.is_prompt_drawn && self.prompt_type.is_normal();

if redraw {
self.clear_full_prompt()?;
}

self.data.set_prompt(prompt);

if redraw {
self.draw_prompt()?;
}

Ok(())
}

/// Draws the prompt and current input, assuming the cursor is at column 0
pub fn draw_prompt(&mut self) -> io::Result<()> {
let pfx = self.prompt_prefix.clone();
Expand Down Expand Up @@ -292,6 +292,15 @@ impl<'a, Term: Terminal> WriteLock<'a, Term> {
self.new_buffer()
}

pub fn set_cursor(&mut self, pos: usize) -> io::Result<()> {
if !self.buffer.is_char_boundary(pos) {
panic!("invalid cursor position {} in buffer {:?}",
pos, self.buffer);
}

self.move_to(pos)
}

pub fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> {
self.term.set_cursor_mode(mode)
}
Expand Down Expand Up @@ -964,79 +973,6 @@ impl<'a, Term: Terminal> Drop for Writer<'a, Term> {
}
}

impl<'a, 'b: 'a> PromptData<'a, 'b> {
pub(crate) fn new(data: MutexGuard<'b, Write>) -> Self {
PromptData{data, _marker: PhantomData}
}

/// Returns the current contents of the input buffer.
pub fn buffer(&self) -> &str {
&self.data.buffer
}

/// Returns a mutable reference to the current input buffer.
///
/// # Notes
///
/// To prevent invalidating the cursor, this method sets the cursor
/// position to `0`.
pub fn buffer_mut(&mut self) -> &mut String {
self.data.cursor = 0;
&mut self.data.buffer
}

/// Sets the input buffer to the given string.
///
/// # Notes
///
/// To prevent invalidating the cursor, this method sets the cursor
/// position to `0`.
pub fn set_buffer(&mut self, s: &str) {
self.data.cursor = 0;
self.data.buffer.clear();
self.data.buffer.push_str(s);
}

/// Returns the current cursor position.
pub fn cursor(&self) -> usize {
self.data.cursor
}

/// Sets the cursor position in the input buffer.
///
/// # Panics
///
/// If the given position is out of bounds or not on a `char` boundary.
pub fn set_cursor(&mut self, pos: usize) {
if !self.data.buffer.is_char_boundary(pos) {
panic!("invalid cursor position {} in buffer {:?}",
pos, self.data.buffer);
}

self.data.cursor = pos;
}

/// Sets the prompt that will be displayed when `read_line` is called.
///
/// # Notes
///
/// If `prompt` contains any terminal escape sequences (e.g. color codes),
/// such escape sequences should be immediately preceded by the character
/// `'\x01'` and immediately followed by the character `'\x02'`.
pub fn set_prompt(&mut self, prompt: &str) {
match prompt.rfind('\n') {
Some(pos) => {
self.data.prompt_prefix = prompt[..pos + 1].to_owned();
self.data.prompt_suffix = prompt[pos + 1..].to_owned();
}
None => {
self.data.prompt_prefix.clear();
self.data.prompt_suffix = prompt.to_owned();
}
}
}
}

impl<'a, Term: 'a + Terminal> Deref for WriteLock<'a, Term> {
type Target = Write;

Expand Down Expand Up @@ -1098,6 +1034,34 @@ impl Write {
self.input_arg = Digit::None;
self.explicit_arg = false;
}

pub fn set_buffer(&mut self, buf: &str) {
self.buffer.clear();
self.buffer.push_str(buf);
self.cursor = buf.len();
}

pub fn set_cursor(&mut self, pos: usize) {
if !self.buffer.is_char_boundary(pos) {
panic!("invalid cursor position {} in buffer {:?}",
pos, self.buffer);
}

self.cursor = pos;
}

pub fn set_prompt(&mut self, prompt: &str) {
match prompt.rfind('\n') {
Some(pos) => {
self.prompt_prefix = prompt[..pos + 1].to_owned();
self.prompt_suffix = prompt[pos + 1..].to_owned();
}
None => {
self.prompt_prefix.clear();
self.prompt_suffix = prompt.to_owned();
}
}
}
}

/// Maximum value of digit input
Expand Down Expand Up @@ -1150,13 +1114,19 @@ impl From<char> for Digit {
}
}

#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum PromptType {
Normal,
Number,
Search,
}

impl PromptType {
pub(crate) fn is_normal(&self) -> bool {
*self == PromptType::Normal
}
}

/// Iterator over `Interface` history entries
pub struct HistoryIter<'a>(vec_deque::Iter<'a, String>);

Expand Down
2 changes: 1 addition & 1 deletion tests/linefeed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn test(input: &str) -> (MemoryTerminal, Interface<MemoryTerminal>) {

let interface = Interface::with_term("test", term.clone()).unwrap();

interface.set_prompt("$ ");
interface.set_prompt("$ ").unwrap();

(term, interface)
}
Expand Down

0 comments on commit b01cbbe

Please sign in to comment.