From 9f332e6adc01f9f752b0bffcd9c4965037eb719c Mon Sep 17 00:00:00 2001 From: ibrahim dursun Date: Sat, 24 Sep 2022 21:30:34 +0000 Subject: [PATCH 1/5] feat: implement :read typed command --- helix-term/src/commands/typed.rs | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index b8f99ff369d2..096c6a834be3 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1783,6 +1783,42 @@ fn run_shell_command( Ok(()) } +fn read_file_info_buffer( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let (view, doc) = current!(cx.editor); + + ensure!(!args.is_empty(), "file name is expected"); + + let filename = args.get(0).unwrap(); + let path = PathBuf::from(filename.to_string()); + if path.exists() { + match std::fs::read_to_string(path) { + Ok(contents) => { + let contents = Tendril::from(contents); + let selection = doc.selection(view.id); + let transaction = Transaction::insert(doc.text(), selection, contents); + doc.apply(&transaction, view.id); + } + Err(error) => { + cx.editor + .set_error(format!("error reading file: {}", error)); + } + } + } else { + cx.editor + .set_error(format!("file doesn't exist: {}", filename)); + } + + Ok(()) +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -2291,6 +2327,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: run_shell_command, completer: Some(completers::directory), }, + TypableCommand { + name: "read", + aliases: &["r"], + doc: "Load a file into buffer", + fun: read_file_info_buffer, + completer: Some(completers::filename), + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy> = From 4795f380ffb40447781fa1ee0e45c1b8b2a4f862 Mon Sep 17 00:00:00 2001 From: Ibrahim Dursun Date: Sun, 25 Sep 2022 21:56:37 +0100 Subject: [PATCH 2/5] Use document::from_reader to read file contents --- helix-term/src/commands/typed.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 096c6a834be3..e4072e3587c2 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use std::{io::BufReader, ops::Deref}; use crate::job::Job; @@ -1799,17 +1799,20 @@ fn read_file_info_buffer( let filename = args.get(0).unwrap(); let path = PathBuf::from(filename.to_string()); if path.exists() { - match std::fs::read_to_string(path) { - Ok(contents) => { - let contents = Tendril::from(contents); + let file = std::fs::File::open(path)?; + let mut reader = BufReader::new(file); + + match helix_view::document::from_reader(&mut reader, Some(doc.encoding())) { + Ok((rope, _)) => { + let rope: String = rope.into(); + let contents = Tendril::from(rope); let selection = doc.selection(view.id); let transaction = Transaction::insert(doc.text(), selection, contents); doc.apply(&transaction, view.id); } - Err(error) => { - cx.editor - .set_error(format!("error reading file: {}", error)); - } + Err(error) => cx + .editor + .set_error(format!("error reading file: {}", error)), } } else { cx.editor From a20044a7dadd1014e8ebb82668291d2a90e29b47 Mon Sep 17 00:00:00 2001 From: Ibrahim Dursun Date: Tue, 4 Oct 2022 14:34:28 +0100 Subject: [PATCH 3/5] Use decoder::decode_to_string `read_file` method is a stripped down version of `helix_view::document::from_reader` --- helix-term/src/commands/typed.rs | 80 ++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index e4072e3587c2..2710f54b2d4c 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1798,30 +1798,72 @@ fn read_file_info_buffer( let filename = args.get(0).unwrap(); let path = PathBuf::from(filename.to_string()); - if path.exists() { - let file = std::fs::File::open(path)?; - let mut reader = BufReader::new(file); - - match helix_view::document::from_reader(&mut reader, Some(doc.encoding())) { - Ok((rope, _)) => { - let rope: String = rope.into(); - let contents = Tendril::from(rope); - let selection = doc.selection(view.id); - let transaction = Transaction::insert(doc.text(), selection, contents); - doc.apply(&transaction, view.id); - } - Err(error) => cx - .editor - .set_error(format!("error reading file: {}", error)), - } - } else { - cx.editor - .set_error(format!("file doesn't exist: {}", filename)); + if !path.exists() { + bail!("file doesn't exist: {}", filename); } + let file = std::fs::File::open(path).map_err(|err| anyhow!("error reading file {}", err))?; + let mut reader = BufReader::new(file); + let contents = from_reader(&mut reader, doc.encoding()) + .map_err(|err| anyhow!("error reading file: {}", err))?; + let contents = Tendril::from(contents); + let selection = doc.selection(view.id); + let transaction = Transaction::insert(doc.text(), selection, contents); + doc.apply(&transaction, view.id); + Ok(()) } +/// Stripped down version of [`helix_view::document::from_reader`] which is adapted to use encoding_rs::Decoder::read_to_string +fn from_reader( + reader: &mut R, + encoding: &'static helix_core::encoding::Encoding, +) -> anyhow::Result { + let mut buf = [0u8; 8192]; + + let (mut decoder, mut slice, read) = { + let read = reader.read(&mut buf)?; + let decoder = encoding.new_decoder(); + let slice = &buf[..read]; + (decoder, slice, read) + }; + + let mut is_empty = read == 0; + let mut buf_str = String::with_capacity(buf.len()); + + loop { + let mut total_read = 0usize; + + loop { + let (result, read, ..) = + decoder.decode_to_string(&slice[total_read..], &mut buf_str, is_empty); + + total_read += read; + + match result { + helix_core::encoding::CoderResult::InputEmpty => { + debug_assert_eq!(slice.len(), total_read); + break; + } + helix_core::encoding::CoderResult::OutputFull => { + debug_assert!(slice.len() > total_read); + buf_str.reserve(buf.len()) + } + } + } + + if is_empty { + debug_assert_eq!(reader.read(&mut buf)?, 0); + break; + } + + let read = reader.read(&mut buf)?; + slice = &buf[..read]; + is_empty = read == 0; + } + Ok(buf_str) +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", From 9cb459867d6699d77aa42df8e92ee58fd2c80a6f Mon Sep 17 00:00:00 2001 From: Ibrahim Dursun Date: Tue, 4 Oct 2022 15:30:58 +0100 Subject: [PATCH 4/5] Add integration test --- helix-term/tests/test/commands.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 114bf22211a5..4b3438691942 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -31,6 +31,33 @@ async fn test_write_quit_fail() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread")] +async fn test_read_file() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + let expected_content = String::from("some contents"); + let output_file = helpers::temp_file_with_contents(&expected_content)?; + let mut command = String::new(); + let cmd = format!(":r {:?}:w", output_file.path()); + command.push_str(&cmd); + + test_key_sequence( + &mut helpers::AppBuilder::new() + .with_file(file.path(), None) + .build()?, + Some(&command), + Some(&|app| { + assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status()); + }), + false, + ) + .await?; + + helpers::assert_file_has_content(file.as_file_mut(), expected_content.as_str())?; + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore] async fn test_buffer_close_concurrent() -> anyhow::Result<()> { test_key_sequences( &mut helpers::AppBuilder::new().build()?, From 8e7d9cc03b89fa6c267d1be401c5fce43c14d83e Mon Sep 17 00:00:00 2001 From: Ibrahim Dursun Date: Tue, 4 Oct 2022 17:55:55 +0100 Subject: [PATCH 5/5] Add command to docs --- book/src/generated/typable-cmd.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 6390ef858e7b..3d476300d22e 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -72,3 +72,4 @@ | `:append-output` | Run shell command, appending output after each selection. | | `:pipe` | Pipe each selection to the shell command. | | `:run-shell-command`, `:sh` | Run a shell command | +| `:read`, `:r` | Load a file into buffer |