Skip to content

Commit

Permalink
Merge pull request #7 from rustne-kretser/line-history
Browse files Browse the repository at this point in the history
Implement basic line history
  • Loading branch information
eivindbergem authored Mar 22, 2022
2 parents 7e55d5f + 0da6bcf commit 22a6e39
Show file tree
Hide file tree
Showing 16 changed files with 1,161 additions and 140 deletions.
42 changes: 32 additions & 10 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ jobs:

runs-on: ubuntu-latest

defaults:
run:
working-directory: ./noline

steps:
- uses: actions/checkout@v2
- name: Format
run: cargo fmt --all -- --check
- name: Doc
run: cargo doc --verbose --all-features
- name: Build
run: cargo build --verbose --all-features
- name: Run tests
run: cargo test --verbose --all-features

readme:

runs-on: ubuntu-latest

defaults:
run:
working-directory: ./noline
Expand All @@ -25,23 +44,26 @@ jobs:
crate: cargo-readme
version: latest
use-tool-cache: true
- name: Readme
run: cargo readme > ../README.md && git diff --exit-code

cargo-outdated:

runs-on: ubuntu-latest

defaults:
run:
working-directory: ./noline

steps:
- uses: actions/checkout@v2
- uses: actions-rs/[email protected]
with:
crate: cargo-outdated
version: latest
use-tool-cache: true
- name: Readme
run: cargo readme > ../README.md && git diff --exit-code
- name: Outdated dependencies
run: cargo outdated --exit-code 1
- name: Format
run: cargo fmt --all -- --check
- name: Doc
run: cargo doc --verbose --all-features
- name: Build
run: cargo build --verbose --all-features
- name: Run tests
run: cargo test --verbose --all-features

examples-std:

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Added basic line history

- Added EditorBuilder for more ergonomic construction of editors

## [0.1.0] - 2022-03-14

- Initial release
Expand Down
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,44 @@ Features:
- No allocation needed - Both heap-based and static buffers are provided
- UTF-8 support
- Emacs keybindings
- Line history

Possible future features:
- Auto-completion and hints
- Line history

The API should be considered experimental and will change in the
future.

The core implementation consists of a state machie taking bytes as
input and yielding iterators over byte slices. Because this is
done without any IO, Noline can be adapted to work on any platform.

Noline comes with multiple implemenations:
- [`sync::Editor`] – Editor for asynchronous IO with two separate IO wrappers:
- [`sync::std::IO`] – IO wrapper for [`std::io::Read`] and [`std::io::Write`] traits
- [`sync::embedded::IO`] – IO wrapper for [`embedded_hal::serial::Read`] and [`embedded_hal::serial::Write`]
- [`no_sync::tokio::Editor`] - Editor for [`tokio::io::AsyncRead`] and [`tokio::io::AsyncWrite`]

The core consists of a massive state machine taking bytes as input
and returning an iterator over byte slices. There are, however,
some convenience wrappers:
- [`sync::Editor`]
- [`sync::std::IO`]
- [`sync::embedded::IO`]
- [`no_sync::tokio::Editor`]
Editors can be built using [`builder::EditorBuilder`].

## Example
```rust
use noline::sync::{std::IO, Editor};
use std::io;
use noline::{sync::std::IO, builder::EditorBuilder};
use std::fmt::Write;
use std::io;
use termion::raw::IntoRawMode;

fn main() {
let mut stdin = io::stdin();
let mut stdout = io::stdout().into_raw_mode().unwrap();
let stdin = io::stdin();
let stdout = io::stdout().into_raw_mode().unwrap();
let prompt = "> ";

let mut io = IO::new(stdin, stdout);
let mut editor = Editor::<Vec<u8>, _>::new(&mut io).unwrap();

let mut editor = EditorBuilder::new_unbounded()
.with_unbounded_history()
.build_sync(&mut io)
.unwrap();

loop {
if let Ok(line) = editor.readline(prompt, &mut io) {
Expand Down
13 changes: 6 additions & 7 deletions examples/no_std/stm32f103/src/bin/uart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
#![no_std]

use heapless::spsc::{Consumer, Producer, Queue};
use noline::{
error::Error,
line_buffer::StaticBuffer,
sync::{embedded::IO, Editor},
};
use noline::{builder::EditorBuilder, error::Error, sync::embedded::IO};
use panic_halt as _;

use cortex_m::asm;
Expand Down Expand Up @@ -130,8 +126,11 @@ fn main() -> ! {
let mut io = IO::new(SerialWrapper::new(tx, rx_consumer));

let prompt = "> ";
let mut editor: Editor<StaticBuffer<128>, _> = loop {
match Editor::new(&mut io) {
let mut editor = loop {
match EditorBuilder::new_static::<128>()
.with_static_history::<128>()
.build_sync(&mut io)
{
Ok(editor) => break editor,
Err(err) => {
let error = match err {
Expand Down
24 changes: 13 additions & 11 deletions examples/no_std/stm32f103/src/bin/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ use core::fmt::Write as FmtWrite;

use embedded_hal::serial::{Read, Write};
use nb::block;
use noline::builder::EditorBuilder;
use noline::error::Error;
use noline::line_buffer::StaticBuffer;
use noline::sync::embedded::IO;
use noline::sync::Editor;
use panic_halt as _;

use cortex_m::asm::delay;
Expand Down Expand Up @@ -158,10 +157,10 @@ fn main() -> ! {

let prompt = "> ";

let mut wrapper = IO::new(SerialWrapper::new(&mut usb_dev, &mut serial));
let mut io = IO::new(SerialWrapper::new(&mut usb_dev, &mut serial));

let mut editor: Editor<StaticBuffer<128>, _> = loop {
if !wrapper.poll() || !wrapper.serial.dtr() || !wrapper.serial.rts() {
let mut editor = loop {
if !io.inner().poll() || !io.inner().serial.dtr() || !io.inner().serial.rts() {
continue;
}

Expand All @@ -171,18 +170,21 @@ fn main() -> ! {
// usbd-serial. Becase noline needs to write during
// initialization, I've added this blocking read here to wait
// for user input before proceeding.
block!(wrapper.read()).unwrap();
break Editor::new(&mut wrapper).unwrap();
block!(io.inner().read()).unwrap();
break EditorBuilder::new_static::<128>()
.with_static_history::<128>()
.build_sync(&mut io)
.unwrap();
};

loop {
match editor.readline(prompt, &mut wrapper) {
match editor.readline(prompt, &mut io) {
Ok(s) => {
if s.len() > 0 {
writeln!(wrapper, "Echo: {}\r", s).unwrap();
writeln!(io, "Echo: {}\r", s).unwrap();
} else {
// Writing emtpy slice causes panic
writeln!(wrapper, "Echo: \r").unwrap();
writeln!(io, "Echo: \r").unwrap();
}
}
Err(err) => {
Expand All @@ -201,7 +203,7 @@ fn main() -> ! {
Error::Aborted => "Aborted",
};

writeln!(wrapper, "Error: {}\r", error).unwrap();
writeln!(io, "Error: {}\r", error).unwrap();
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions examples/std/src/bin/std-async-tokio.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use noline::no_sync::tokio::Editor;
use noline::builder::EditorBuilder;
use termion::raw::IntoRawMode;
use tokio::io::{self, AsyncWriteExt};

Expand All @@ -10,7 +10,9 @@ async fn main() {

let prompt = "> ";

let mut editor = Editor::<Vec<u8>>::new(&mut stdin, &mut stdout)
let mut editor = EditorBuilder::new_unbounded()
.with_unbounded_history()
.build_async_tokio(&mut stdin, &mut stdout)
.await
.unwrap();

Expand Down
7 changes: 5 additions & 2 deletions examples/std/src/bin/std-sync.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use noline::sync::{std::IO, Editor};
use noline::{builder::EditorBuilder, sync::std::IO};
use std::fmt::Write;
use std::io;
use termion::raw::IntoRawMode;
Expand All @@ -10,7 +10,10 @@ fn main() {

let mut io = IO::new(stdin, stdout);

let mut editor = Editor::<Vec<u8>, _>::new(&mut io).unwrap();
let mut editor = EditorBuilder::new_unbounded()
.with_unbounded_history()
.build_sync(&mut io)
.unwrap();

loop {
if let Ok(line) = editor.readline(prompt, &mut io) {
Expand Down
118 changes: 118 additions & 0 deletions noline/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Builder for editors
use core::marker::PhantomData;

use crate::{
error::Error,
history::{History, NoHistory, StaticHistory},
line_buffer::{Buffer, NoBuffer, StaticBuffer},
sync::{self, Read, Write},
};

#[cfg(any(test, feature = "alloc", feature = "std"))]
use crate::line_buffer::UnboundedBuffer;

#[cfg(any(test, feature = "alloc", feature = "std"))]
use crate::history::UnboundedHistory;

#[cfg(any(test, doc, feature = "tokio"))]
use ::tokio::io::{AsyncReadExt, AsyncWriteExt};

#[cfg(any(test, doc, feature = "tokio"))]
use crate::no_sync;

/// Builder for [`sync::Editor`] and [`no_sync::tokio::Editor`].
///
/// # Example
/// ```no_run
/// # use noline::sync::{Read, Write};
/// # struct IO {}
/// # impl Write for IO {
/// # type Error = ();
/// # fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { unimplemented!() }
/// # fn flush(&mut self) -> Result<(), Self::Error> { unimplemented!() }
/// # }
/// # impl Read for IO {
/// # type Error = ();
/// # fn read(&mut self) -> Result<u8, Self::Error> { unimplemented!() }
/// # }
/// # let mut io = IO {};
/// use noline::builder::EditorBuilder;
///
/// let mut editor = EditorBuilder::new_static::<100>()
/// .with_static_history::<200>()
/// .build_sync(&mut io)
/// .unwrap();
/// ```
pub struct EditorBuilder<B: Buffer, H: History> {
_marker: PhantomData<(B, H)>,
}

impl EditorBuilder<NoBuffer, NoHistory> {
/// Create builder for editor with static buffer
///
/// # Example
/// ```
/// use noline::builder::EditorBuilder;
///
/// let builder = EditorBuilder::new_static::<100>();
/// ```
pub fn new_static<const N: usize>() -> EditorBuilder<StaticBuffer<N>, NoHistory> {
EditorBuilder {
_marker: PhantomData,
}
}

#[cfg(any(test, feature = "alloc", feature = "std"))]
/// Create builder for editor with unbounded buffer
///
/// # Example
/// ```
/// use noline::builder::EditorBuilder;
///
/// let builder = EditorBuilder::new_unbounded();
/// ```
pub fn new_unbounded() -> EditorBuilder<UnboundedBuffer, NoHistory> {
EditorBuilder {
_marker: PhantomData,
}
}
}

impl<B: Buffer, H: History> EditorBuilder<B, H> {
/// Add static history
pub fn with_static_history<const N: usize>(self) -> EditorBuilder<B, StaticHistory<N>> {
EditorBuilder {
_marker: PhantomData,
}
}

#[cfg(any(test, feature = "alloc", feature = "std"))]
/// Add unbounded history
pub fn with_unbounded_history(self) -> EditorBuilder<B, UnboundedHistory> {
EditorBuilder {
_marker: PhantomData,
}
}

/// Build [`sync::Editor`]. Is equivalent of calling [`sync::Editor::new()`].
pub fn build_sync<IO, RE, WE>(
self,
io: &mut IO,
) -> Result<sync::Editor<B, H, IO>, Error<RE, WE>>
where
IO: Read<Error = RE> + Write<Error = WE>,
{
sync::Editor::new(io)
}

#[cfg(any(test, doc, feature = "tokio"))]
/// Build [`no_sync::tokio::Editor`]. Is equivalent of calling [`no_sync::tokio::Editor::new()`].
pub async fn build_async_tokio<W: AsyncWriteExt + Unpin, R: AsyncReadExt + Unpin>(
self,
stdin: &mut R,
stdout: &mut W,
) -> Result<no_sync::tokio::Editor<B, H>, Error<std::io::Error, std::io::Error>> {
no_sync::tokio::Editor::new(stdin, stdout).await
}
}
Loading

0 comments on commit 22a6e39

Please sign in to comment.