diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index dbb8b5f380d2f..64bb45f70697c 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -3,6 +3,7 @@ | `:quit`, `:q` | Close the current view. | | `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. | | `:open`, `:o` | Open a file from disk into the current view. | +| `:read`, `:r` | Load a file into buffer | | `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. | | `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. | | `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. | diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 5d7057da6b39b..746ad51f1dad6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,4 +1,5 @@ use std::fmt::Write; +use std::io::BufReader; use std::ops::Deref; use crate::job::Job; @@ -8,7 +9,7 @@ use super::*; use helix_core::fuzzy::fuzzy_match; use helix_core::indent::MAX_INDENT; use helix_core::{line_ending, shellwords::Shellwords}; -use helix_view::document::DEFAULT_LANGUAGE_NAME; +use helix_view::document::{read_to_string, DEFAULT_LANGUAGE_NAME}; use helix_view::editor::{CloseError, ConfigEvent}; use serde_json::Value; use ui::completers::{self, Completer}; @@ -2454,6 +2455,37 @@ fn yank_diagnostic( 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() { + 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, _, _) = read_to_string(&mut reader, Some(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(()) +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -3068,6 +3100,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: yank_diagnostic, signature: CommandSignature::all(completers::register), }, + TypableCommand { + name: "read", + aliases: &["r"], + doc: "Load a file into buffer", + fun: read_file_info_buffer, + signature: CommandSignature::positional(&[completers::filename]), + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy> = diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 351f07fe912db..c015416568a79 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -640,3 +640,29 @@ async fn test_join_selections_space() -> anyhow::Result<()> { Ok(()) } + +#[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(()) +}