From 97736960959ae9b2c773facedbc850423b69af4d Mon Sep 17 00:00:00 2001 From: Em Zhan Date: Sat, 2 Sep 2023 23:02:18 -0500 Subject: [PATCH] Add `insert-final-newline` config option This resolves #4274 with the implementation largely based off of #5435 and also addresses the review from @the-mikedavis on that PR. The option name is from EditorConfig's `insert_final_newline`, which is also used by VS Code as `files.insertFinalNewline`. We match Vim's behavior in that :w will add the newline to unmodified files but :wa will not; see #1760. Co-authored-by: Xalfer <64538944+Xalfer@users.noreply.github.com> --- book/src/configuration.md | 1 + helix-term/src/commands/typed.rs | 32 ++++++++++++++++++++++++-------- helix-view/src/editor.rs | 3 +++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index eb2cf473cffd8..770993cae729a 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -64,6 +64,7 @@ Its settings will be merged with the configuration directory `config.toml` and t | `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` | | `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` | | `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` | +| `insert-final-newline` | Whether to automatically insert a final newline on write if missing | `false` | ### `[editor.statusline]` Section diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 0e1d943177084..419be5db54018 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -6,7 +6,7 @@ use crate::job::Job; use super::*; use helix_core::fuzzy::fuzzy_match; -use helix_core::{encoding, shellwords::Shellwords}; +use helix_core::{encoding, line_ending, shellwords::Shellwords}; use helix_view::document::DEFAULT_LANGUAGE_NAME; use helix_view::editor::{Action, CloseError, ConfigEvent}; use serde_json::Value; @@ -330,12 +330,12 @@ fn write_impl( path: Option<&Cow>, force: bool, ) -> anyhow::Result<()> { - let editor_auto_fmt = cx.editor.config().auto_format; + let config = cx.editor.config(); let jobs = &mut cx.jobs; let (view, doc) = current!(cx.editor); let path = path.map(AsRef::as_ref); - let fmt = if editor_auto_fmt { + let fmt = if config.auto_format { doc.auto_format().map(|fmt| { let callback = make_format_callback( doc.id(), @@ -352,6 +352,9 @@ fn write_impl( }; if fmt.is_none() { + if config.insert_final_newline { + insert_final_newline(doc, view); + } let id = doc.id(); cx.editor.save(id, path, force)?; } @@ -359,6 +362,16 @@ fn write_impl( Ok(()) } +fn insert_final_newline(doc: &mut Document, view: &mut View) { + let text = doc.text(); + if line_ending::get_line_ending(&text.slice(..)).is_none() { + let eof = Selection::point(text.len_chars()); + let insert = Transaction::insert(text, &eof, doc.line_ending.as_str().into()); + doc.apply(&insert, view.id); + doc.append_changes_to_history(view); + } +} + fn write( cx: &mut compositor::Context, args: &[Cow], @@ -658,7 +671,7 @@ pub fn write_all_impl( write_scratch: bool, ) -> anyhow::Result<()> { let mut errors: Vec<&'static str> = Vec::new(); - let auto_format = cx.editor.config().auto_format; + let config = cx.editor.config(); let jobs = &mut cx.jobs; let current_view = view!(cx.editor); @@ -693,7 +706,7 @@ pub fn write_all_impl( current_view.id }; - let fmt = if auto_format { + let fmt = if config.auto_format { doc.auto_format().map(|fmt| { let callback = make_format_callback( doc.id(), @@ -709,7 +722,7 @@ pub fn write_all_impl( }; if fmt.is_none() { - return Some(doc.id()); + return Some((doc.id(), target_view)); } None @@ -717,8 +730,11 @@ pub fn write_all_impl( .collect(); // manually call save for the rest of docs that don't have a formatter - for id in saves { - cx.editor.save::(id, None, force)?; + for (doc_id, view_id) in saves { + if config.insert_final_newline { + insert_final_newline(doc_mut!(cx.editor, &doc_id), view_mut!(cx.editor, view_id)); + } + cx.editor.save::(doc_id, None, force)?; } if !errors.is_empty() && !force { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 86f35e0db7bd3..b46e3fda583db 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -287,6 +287,8 @@ pub struct Config { pub workspace_lsp_roots: Vec, /// Which line ending to choose for new documents. Defaults to `native`. i.e. `crlf` on Windows, otherwise `lf`. pub default_line_ending: LineEndingConfig, + /// Whether to automatically insert a final newline on write if missing. Defaults to `false`. + pub insert_final_newline: bool, /// Enables smart tab pub smart_tab: Option, } @@ -842,6 +844,7 @@ impl Default for Config { completion_replace: false, workspace_lsp_roots: Vec::new(), default_line_ending: LineEndingConfig::default(), + insert_final_newline: false, smart_tab: Some(SmartTabConfig::default()), } }