diff --git a/book/src/configuration.md b/book/src/configuration.md index 723e9601f..1b94ae856 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -63,6 +63,7 @@ Its settings will be merged with the configuration directory `config.toml` and t | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | | `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` | ### `[editor.statusline]` Section diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 14abf0162..03fbdedfa 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -66,5 +66,5 @@ pub use syntax::Syntax; pub use diagnostic::Diagnostic; -pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING}; +pub use line_ending::{LineEnding, NATIVE_LINE_ENDING}; pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction}; diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index 953d567d5..36c02a941 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -1,9 +1,9 @@ use crate::{Rope, RopeSlice}; #[cfg(target_os = "windows")] -pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf; +pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf; #[cfg(not(target_os = "windows"))] -pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::LF; +pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF; /// Represents one of the valid Unicode line endings. #[derive(PartialEq, Eq, Copy, Clone, Debug)] diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs index e10e0840b..ada488fc9 100644 --- a/helix-term/tests/test/auto_pairs.rs +++ b/helix-term/tests/test/auto_pairs.rs @@ -2,7 +2,7 @@ use helix_core::{auto_pairs::DEFAULT_PAIRS, hashmap}; use super::*; -const LINE_END: &str = helix_core::DEFAULT_LINE_ENDING.as_str(); +const LINE_END: &str = helix_core::NATIVE_LINE_ENDING.as_str(); fn differing_pairs() -> impl Iterator { DEFAULT_PAIRS.iter().filter(|(open, close)| open != close) diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 30fe7d0ed..6466bc764 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -244,7 +244,7 @@ pub fn test_editor_config() -> helix_view::editor::Config { /// character, and if one doesn't exist already, appends the system's /// appropriate line ending to the end of a string. pub fn platform_line(input: &str) -> String { - let line_end = helix_core::DEFAULT_LINE_ENDING.as_str(); + let line_end = helix_core::NATIVE_LINE_ENDING.as_str(); // we can assume that the source files in this code base will always // be LF, so indoc strings will always insert LF diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index bd3c465d4..6ec9ab973 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -8,7 +8,6 @@ use helix_core::doc_formatter::TextFormat; use helix_core::encoding::Encoding; use helix_core::syntax::{Highlight, LanguageServerFeature}; use helix_core::text_annotations::{InlineAnnotation, TextAnnotations}; -use helix_core::Range; use helix_vcs::{DiffHandle, DiffProviderRegistry}; use ::parking_lot::Mutex; @@ -31,8 +30,8 @@ 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, Range, Rope, RopeBuilder, Selection, Syntax, Transaction, + NATIVE_LINE_ENDING, }; use crate::editor::{Config, RedrawHandle}; @@ -590,6 +589,7 @@ impl Document { config: Arc>, ) -> Self { let (encoding, has_bom) = encoding_with_bom_info.unwrap_or((encoding::UTF_8, false)); + let line_ending = config.load().default_line_ending.into(); let changes = ChangeSet::new(&text); let old_state = None; @@ -603,7 +603,7 @@ impl Document { inlay_hints: HashMap::default(), inlay_hints_oudated: false, indent_style: DEFAULT_INDENT, - line_ending: DEFAULT_LINE_ENDING, + line_ending, restore_cursor: false, syntax: None, language: None, @@ -623,10 +623,12 @@ impl Document { focused_at: std::time::Instant::now(), } } + pub fn default(config: Arc>) -> Self { - let text = Rope::from(DEFAULT_LINE_ENDING.as_str()); + let text = Rope::from(NATIVE_LINE_ENDING.as_str()); Self::from(text, None, config) } + // TODO: async fn? /// Create a new document from `path`. Encoding is auto-detected, but it can be manually /// overwritten with the `encoding` parameter. @@ -643,7 +645,7 @@ impl Document { from_reader(&mut file, encoding)? } else { let encoding = encoding.unwrap_or(encoding::UTF_8); - (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding, false) + (Rope::from(NATIVE_LINE_ENDING.as_str()), encoding, false) }; let mut doc = Self::from(rope, Some((encoding, has_bom)), config); @@ -887,14 +889,16 @@ impl Document { /// Detect the indentation used in the file, or otherwise defaults to the language indentation /// configured in `languages.toml`, with a fallback to tabs if it isn't specified. Line ending - /// is likewise auto-detected, and will fallback to the default OS line ending. + /// is likewise auto-detected, and will remain unchanged if no line endings were detected. pub fn detect_indent_and_line_ending(&mut self) { self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| { self.language_config() .and_then(|config| config.indent.as_ref()) .map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit)) }); - self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING); + if let Some(line_ending) = auto_detect_line_ending(&self.text) { + self.line_ending = line_ending; + } } /// Reload the document from its path. @@ -1921,7 +1925,7 @@ mod test { Document::default(Arc::new(ArcSwap::new(Arc::new(Config::default())))) .text() .to_string(), - DEFAULT_LINE_ENDING.as_str() + NATIVE_LINE_ENDING.as_str() ); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b2e07c730..1a884c324 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -44,7 +44,7 @@ pub use helix_core::register::Registers; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig, SoftWrap}, - Change, + Change, LineEnding, NATIVE_LINE_ENDING, }; use helix_core::{Position, Selection}; use helix_dap as dap; @@ -273,7 +273,7 @@ pub struct Config { pub search: SearchConfig, pub lsp: LspConfig, pub terminal: Option, - /// Column numbers at which to draw the rulers. Default to `[]`, meaning no rulers. + /// Column numbers at which to draw the rulers. Defaults to `[]`, meaning no rulers. pub rulers: Vec, #[serde(default)] pub whitespace: WhitespaceConfig, @@ -286,6 +286,8 @@ pub struct Config { pub soft_wrap: SoftWrap, /// Workspace specific lsp ceiling dirs 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, } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -727,6 +729,51 @@ impl Default for IndentGuidesConfig { } } +/// Line ending configuration. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum LineEndingConfig { + /// The platform's native line ending. + /// + /// `crlf` on Windows, otherwise `lf`. + Native, + /// Line feed. + LF, + /// Carriage return followed by line feed. + Crlf, + /// Form feed. + #[cfg(feature = "unicode-lines")] + FF, + /// Carriage return. + #[cfg(feature = "unicode-lines")] + CR, + /// Next line. + #[cfg(feature = "unicode-lines")] + Nel, +} + +impl Default for LineEndingConfig { + fn default() -> Self { + LineEndingConfig::Native + } +} + +impl From for LineEnding { + fn from(line_ending: LineEndingConfig) -> Self { + match line_ending { + LineEndingConfig::Native => NATIVE_LINE_ENDING, + LineEndingConfig::LF => LineEnding::LF, + LineEndingConfig::Crlf => LineEnding::Crlf, + #[cfg(feature = "unicode-lines")] + LineEndingConfig::FF => LineEnding::FF, + #[cfg(feature = "unicode-lines")] + LineEndingConfig::CR => LineEnding::CR, + #[cfg(feature = "unicode-lines")] + LineEndingConfig::Nel => LineEnding::Nel, + } + } +} + impl Default for Config { fn default() -> Self { Self { @@ -771,6 +818,7 @@ impl Default for Config { text_width: 80, completion_replace: false, workspace_lsp_roots: Vec::new(), + default_line_ending: LineEndingConfig::default(), } } }