Skip to content

Commit

Permalink
make streaming io generic
Browse files Browse the repository at this point in the history
  • Loading branch information
kirawi committed Dec 3, 2022
1 parent 8dc48de commit 0f9a794
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 60 deletions.
2 changes: 1 addition & 1 deletion helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4785,7 +4785,7 @@ async fn shell_impl_async(
let output = if let Some(mut stdin) = process.stdin.take() {
let input_task = tokio::spawn(async move {
if let Some(input) = input {
helix_view::stream::to_writer(&mut stdin, encoding::UTF_8, &input).await?;
helix_view::stream::to_writer(&mut stdin, encoding::UTF_8, input.chunks()).await?;
}
Ok::<_, anyhow::Error>(())
});
Expand Down
30 changes: 19 additions & 11 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ use helix_core::{
indent::{auto_detect_indent_style, IndentStyle},
line_ending::auto_detect_line_ending,
syntax::{self, LanguageConfiguration},
ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, Syntax, Transaction,
DEFAULT_LINE_ENDING,
ChangeSet, Diagnostic, LineEnding, Rope, Selection, Syntax, Transaction, DEFAULT_LINE_ENDING,
};

use crate::editor::RedrawHandle;
use crate::stream::{from_reader, to_writer};
use crate::stream::{from_reader, to_writer, RopeWrite};
use crate::{apply_transaction, DocumentId, Editor, View, ViewId};

const DEFAULT_INDENT: IndentStyle = IndentStyle::Tabs;
Expand Down Expand Up @@ -215,7 +214,9 @@ impl Document {
let (rope, encoding) = if path.exists() {
let mut file =
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
from_reader::<_, RopeBuilder>(&mut file, encoding)?
let (builder, encoding) =
from_reader(&mut file, crate::stream::RopeWrite::default(), encoding)?;
(builder.finish(), encoding)
} else {
let encoding = encoding.unwrap_or(encoding::UTF_8);
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
Expand Down Expand Up @@ -272,7 +273,7 @@ impl Document {
})?;
{
let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
to_writer(&mut stdin, encoding::UTF_8, &text)
to_writer(&mut stdin, encoding::UTF_8, text.chunks())
.await
.map_err(|_| FormatterError::BrokenStdin)?;
}
Expand Down Expand Up @@ -401,7 +402,7 @@ impl Document {
}

let mut file = File::create(&path).await?;
to_writer(&mut file, encoding, &text).await?;
to_writer(&mut file, encoding, text.chunks()).await?;

let event = DocumentSavedEvent {
revision: current_rev,
Expand Down Expand Up @@ -467,7 +468,9 @@ impl Document {
.to_owned();

let mut file = std::fs::File::open(&path)?;
let (rope, ..) = from_reader::<_, RopeBuilder>(&mut file, Some(encoding))?;
let rope = from_reader(&mut file, RopeWrite::default(), Some(encoding))?
.0
.finish();

// Calculate the difference between the buffer and source text, and apply it.
// This is not considered a modification of the contents of the file regardless
Expand Down Expand Up @@ -894,8 +897,12 @@ impl Document {

/// Intialize/updates the differ for this document with a new base.
pub fn set_diff_base(&mut self, diff_base: Vec<u8>, redraw_handle: RedrawHandle) {
if let Ok((diff_base, _)) =
from_reader::<_, RopeBuilder>(&mut diff_base.as_slice(), Some(self.encoding))
if let Ok(diff_base) = from_reader(
&mut diff_base.as_slice(),
RopeWrite::default(),
Some(self.encoding),
)
.map(|(builder, _)| builder.finish())
{
if let Some(differ) = &self.diff_handle {
differ.update_diff_base(diff_base);
Expand Down Expand Up @@ -1234,9 +1241,10 @@ mod test {
assert!(ref_path.exists());

let mut file = std::fs::File::open(path).unwrap();
let text = from_reader::<_, RopeBuilder>(&mut file, Some(encoding))
let text = from_reader(&mut file, RopeWrite::default(), Some(encoding))
.unwrap()
.0
.finish()
.to_string();
let expectation = std::fs::read_to_string(ref_path).unwrap();
assert_eq!(text[..], expectation[..]);
Expand All @@ -1260,7 +1268,7 @@ mod test {

let text = Rope::from_str(&std::fs::read_to_string(path).unwrap());
let mut buf: Vec<u8> = Vec::new();
helix_lsp::block_on(to_writer(&mut buf, encoding, &text)).unwrap();
helix_lsp::block_on(to_writer(&mut buf, encoding, text.chunks())).unwrap();

let expectation = std::fs::read(ref_path).unwrap();
assert_eq!(buf, expectation);
Expand Down
6 changes: 3 additions & 3 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1114,9 +1114,9 @@ impl Editor {
}

pub fn new_file_from_stdin(&mut self, action: Action) -> Result<DocumentId, Error> {
let (rope, encoding) =
crate::stream::from_reader::<_, helix_core::RopeBuilder>(&mut stdin(), None)?;
Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding))))
let (builder, encoding) =
crate::stream::from_reader(&mut stdin(), crate::stream::RopeWrite::default(), None)?;
Ok(self.new_file_from_document(action, Document::from(builder.finish(), Some(encoding))))
}

// ??? possible use for integration tests
Expand Down
70 changes: 25 additions & 45 deletions helix-view/src/stream.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
use std::io::Write;

use anyhow::Error;
use helix_core::{encoding, ropey::iter::Chunks, Rope, RopeBuilder};
use helix_core::{encoding, Rope, RopeBuilder};

/// 8kB of buffer space for encoding and decoding `Rope`s.
const BUF_SIZE: usize = 8192;

pub trait TextBuilder {
type Output;

fn new() -> Self;
fn append(&mut self, chunk: &str);
fn finish(self) -> Self::Output;
}

pub trait ChunksIterator {
type IntoIter<'a>: Iterator<Item = &'a str>
where
Self: 'a;

fn chunks<'a>(&'a self) -> Self::IntoIter<'a>;
}

impl TextBuilder for RopeBuilder {
type Output = Rope;

fn new() -> Self {
RopeBuilder::new()
}

fn append(&mut self, chunk: &str) {
RopeBuilder::append(self, chunk);
}

fn finish(self) -> Self::Output {
RopeBuilder::finish(self)
#[derive(Default)]
pub struct RopeWrite(RopeBuilder);
impl RopeWrite {
pub fn finish(self) -> Rope {
self.0.finish()
}
}

impl ChunksIterator for Rope {
type IntoIter<'a> = Chunks<'a>;
impl Write for RopeWrite {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.append(std::str::from_utf8(buf).unwrap());
Ok(buf.len())
}

fn chunks<'a>(&'a self) -> Self::IntoIter<'a> {
Rope::chunks(self)
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

Expand All @@ -50,13 +31,14 @@ impl ChunksIterator for Rope {
/// Decodes a stream of bytes into UTF-8, returning a `Rope` and the
/// encoding it was decoded as. The optional `encoding` parameter can
/// be used to override encoding auto-detection.
pub fn from_reader<R, T>(
pub fn from_reader<R, B>(
reader: &mut R,
mut builder: B,
encoding: Option<&'static encoding::Encoding>,
) -> Result<(T::Output, &'static encoding::Encoding), Error>
) -> Result<(B, &'static encoding::Encoding), Error>
where
R: std::io::Read + ?Sized,
T: TextBuilder,
B: std::io::Write,
{
// These two buffers are 8192 bytes in size each and are used as
// intermediaries during the decoding process. Text read into `buf`
Expand All @@ -65,7 +47,6 @@ where
// contents are appended to `builder`.
let mut buf = [0u8; BUF_SIZE];
let mut buf_out = [0u8; BUF_SIZE];
let mut builder = T::new();

// By default, the encoding of the text is auto-detected via the
// `chardetng` crate which requires sample data from the reader.
Expand Down Expand Up @@ -131,7 +112,7 @@ where
}
encoding::CoderResult::OutputFull => {
debug_assert!(slice.len() > total_read);
builder.append(&buf_str[..total_written]);
let _ = builder.write(&buf_str[..total_written].as_bytes())?;
total_written = 0;
}
}
Expand All @@ -140,7 +121,7 @@ where
// flushed and the loop terminates.
if is_empty {
debug_assert_eq!(reader.read(&mut buf)?, 0);
builder.append(&buf_str[..total_written]);
let _ = builder.write(&buf_str[..total_written].as_bytes())?;
break;
}

Expand All @@ -152,8 +133,7 @@ where
slice = &buf[..read];
is_empty = read == 0;
}
let rope = builder.finish();
Ok((rope, encoding))
Ok((builder, encoding))
}

// The documentation and implementation of this function should be up-to-date with
Expand All @@ -165,11 +145,11 @@ where
pub async fn to_writer<'a, W, T>(
writer: &'a mut W,
encoding: &'static encoding::Encoding,
text: &T,
text: T,
) -> Result<(), Error>
where
W: tokio::io::AsyncWriteExt + Unpin + ?Sized,
T: ChunksIterator,
T: IntoIterator<Item = &'a str>,
{
// Text inside a `Rope` is stored as non-contiguous blocks of data called
// chunks. The absolute size of each chunk is unknown, thus it is impossible
Expand All @@ -178,7 +158,7 @@ where
// appending an empty chunk to it. This is valuable for detecting when all
// chunks in the `Rope` have been iterated over in the subsequent loop.
let iter = text
.chunks()
.into_iter()
.filter(|c| !c.is_empty())
.chain(std::iter::once(""));
let mut buf = [0u8; BUF_SIZE];
Expand Down

0 comments on commit 0f9a794

Please sign in to comment.