From aa65111146f91f07b3c76e508609c9425a6b59d0 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Tue, 8 Mar 2022 13:34:27 -0800 Subject: [PATCH 01/17] Add initial editorconfig parsing on document open Currently disabled, see the use of Document::open_with_editorconfig in editor.rs. More fields in Document will be needed for some of the properties. Currently, in theory, only line ending, indent style, and encoding are supported. --- Cargo.lock | 7 ++++ helix-view/Cargo.toml | 2 + helix-view/src/document.rs | 79 +++++++++++++++++++++++++++++++++++--- helix-view/src/editor.rs | 8 +++- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 346fd7fdfe15..dd4492f10bae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,6 +176,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "ec4rs" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0255cdb161744c81c8a656dbdd2f6f0eec392e64bcf80770c638f1882fed1da5" + [[package]] name = "either" version = "1.6.1" @@ -481,6 +487,7 @@ dependencies = [ "chardetng", "clipboard-win", "crossterm", + "ec4rs", "futures-util", "helix-core", "helix-dap", diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 932c33216920..fa6ee92cd342 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -39,6 +39,8 @@ log = "~0.4" which = "4.2" +ec4rs = "1.0.0-rc.1" + [target.'cfg(windows)'.dependencies] clipboard-win = { version = "4.4", features = ["std"] } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 671ceb75f84f..5bed97c35053 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -371,14 +371,54 @@ impl Document { encoding: Option<&'static encoding::Encoding>, config_loader: Option>, ) -> Result { + Self::open_with_editorconfig(path, encoding, config_loader, false) + } + + // TODO: async fn? + /// Create a new document from `path`, + /// optionally using settings from EditorConfig. + /// + /// If the `encoding` parameter is specified, + /// it overrides the encoding obtained from EditorConfig + /// and auto-detection. + pub fn open_with_editorconfig( + path: &Path, + encoding: Option<&'static encoding::Encoding>, + config_loader: Option>, + use_editorconfig: bool, + ) -> Result { + // Closure to read the editorconfig files that apply to `path`. + // Ideally this should be done after an existing file is already opened. + let get_editorconfig = || { + if use_editorconfig { + let mut ecfg = ec4rs::config_for(path).unwrap_or_default(); + ecfg.use_fallbacks(); + ecfg + } else { + Default::default() + } + }; + // Adds editorconfig values to encoding as a fallback. + let encoding_with_ecfg = move |ecfg: &ec4rs::Properties| { + encoding.or_else(|| { + ecfg.get_raw::() + .filter_unset() + .into_result() + .ok() + .map(|string| string.as_bytes()) + .and_then(encoding::Encoding::for_label) + }) + }; // Open the file if it exists, otherwise assume it is a new file (and thus empty). - let (rope, encoding) = if path.exists() { + let ((rope, encoding), ecfg) = if path.exists() { let mut file = std::fs::File::open(path).context(format!("unable to open {:?}", path))?; - from_reader(&mut file, encoding)? + let ecfg = get_editorconfig(); + (from_reader(&mut file, encoding_with_ecfg(&ecfg))?, ecfg) } else { - let encoding = encoding.unwrap_or(encoding::UTF_8); - (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding) + let ecfg = get_editorconfig(); + let encoding = encoding_with_ecfg(&ecfg).unwrap_or(encoding::UTF_8); + ((Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding), get_editorconfig()) }; let mut doc = Self::from(rope, Some(encoding)); @@ -389,7 +429,28 @@ impl Document { doc.detect_language(loader); } - doc.detect_indent_and_line_ending(); + // Set the indent style. + use ec4rs::property::{IndentSize, IndentStyle as EcIndentStyle}; + match (ecfg.get::(), ecfg.get::()) { + (Ok(EcIndentStyle::Tabs), _) => { + doc.indent_style = IndentStyle::Tabs; + } + (Ok(EcIndentStyle::Spaces), Ok(IndentSize::Value(n))) => { + doc.indent_style = IndentStyle::Spaces( + n.try_into().unwrap_or(u8::MAX) + ); + } + _ => doc.detect_indent_impl() + } + + // Set the line ending. + use ec4rs::property::EndOfLine; + match ecfg.get::() { + Ok(EndOfLine::Cr) => {doc.line_ending = LineEnding::CR}, + Ok(EndOfLine::Lf) => {doc.line_ending = LineEnding::LF}, + Ok(EndOfLine::CrLf) => {doc.line_ending = LineEnding::Crlf}, + Err(_) => doc.detect_line_ending_impl() + } Ok(doc) } @@ -517,11 +578,19 @@ impl Document { /// specified. Line ending is likewise auto-detected, and will fallback to the default OS /// line ending. pub fn detect_indent_and_line_ending(&mut self) { + self.detect_indent_impl(); + self.detect_line_ending_impl(); + } + + fn detect_indent_impl(&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)) }); + } + + fn detect_line_ending_impl(&mut self) { self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0eb613087d80..0db719ef0d52 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -587,7 +587,13 @@ impl Editor { let id = if let Some(id) = id { id } else { - let mut doc = Document::open(&path, None, Some(self.syn_loader.clone()))?; + // TODO: Config variable instead of `false`. + let mut doc = Document::open_with_editorconfig( + &path, + None, + Some(self.syn_loader.clone()), + false + )?; let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); From 0d433fd1016f19f0f44c449a666ff52850c56056 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Thu, 10 Mar 2022 10:54:31 -0800 Subject: [PATCH 02/17] Add document::Config and parse editorconfig into it --- helix-view/src/document.rs | 147 +++++++++++++++++++++++-------------- helix-view/src/editor.rs | 9 +-- 2 files changed, 93 insertions(+), 63 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 5bed97c35053..63a0acaf6889 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -371,85 +371,56 @@ impl Document { encoding: Option<&'static encoding::Encoding>, config_loader: Option>, ) -> Result { - Self::open_with_editorconfig(path, encoding, config_loader, false) + Self::open_with_config(path, config_loader, move |_| { + Config::from_encoding(encoding) + }) } // TODO: async fn? /// Create a new document from `path`, - /// optionally using settings from EditorConfig. - /// - /// If the `encoding` parameter is specified, - /// it overrides the encoding obtained from EditorConfig - /// and auto-detection. - pub fn open_with_editorconfig( + /// using document settings from a [Config]. + pub fn open_with_config( path: &Path, - encoding: Option<&'static encoding::Encoding>, - config_loader: Option>, - use_editorconfig: bool, + lang_config_loader: Option>, + doc_config_fn: impl FnOnce(&Path) -> Config, ) -> Result { // Closure to read the editorconfig files that apply to `path`. // Ideally this should be done after an existing file is already opened. - let get_editorconfig = || { - if use_editorconfig { - let mut ecfg = ec4rs::config_for(path).unwrap_or_default(); - ecfg.use_fallbacks(); - ecfg - } else { - Default::default() - } - }; - // Adds editorconfig values to encoding as a fallback. - let encoding_with_ecfg = move |ecfg: &ec4rs::Properties| { - encoding.or_else(|| { - ecfg.get_raw::() - .filter_unset() - .into_result() - .ok() - .map(|string| string.as_bytes()) - .and_then(encoding::Encoding::for_label) - }) - }; // Open the file if it exists, otherwise assume it is a new file (and thus empty). - let ((rope, encoding), ecfg) = if path.exists() { + let (rope, config) = if path.exists() { let mut file = std::fs::File::open(path).context(format!("unable to open {:?}", path))?; - let ecfg = get_editorconfig(); - (from_reader(&mut file, encoding_with_ecfg(&ecfg))?, ecfg) + let mut config = doc_config_fn(path); + // Not using destructuring assignment here. + let (rope, encoding) = from_reader(&mut file, config.encoding)?; + config.encoding = Some(encoding); + (rope, config) } else { - let ecfg = get_editorconfig(); - let encoding = encoding_with_ecfg(&ecfg).unwrap_or(encoding::UTF_8); - ((Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding), get_editorconfig()) + let mut config = doc_config_fn(path); + config.encoding = config.encoding.or(Some(encoding::UTF_8)); + (Rope::from(DEFAULT_LINE_ENDING.as_str()), config) }; - let mut doc = Self::from(rope, Some(encoding)); + let mut doc = Self::from(rope, config.encoding); - // set the path and try detecting the language + // Set the path and try detecting the language. doc.set_path(Some(path))?; - if let Some(loader) = config_loader { + if let Some(loader) = lang_config_loader { doc.detect_language(loader); } // Set the indent style. - use ec4rs::property::{IndentSize, IndentStyle as EcIndentStyle}; - match (ecfg.get::(), ecfg.get::()) { - (Ok(EcIndentStyle::Tabs), _) => { - doc.indent_style = IndentStyle::Tabs; - } - (Ok(EcIndentStyle::Spaces), Ok(IndentSize::Value(n))) => { - doc.indent_style = IndentStyle::Spaces( - n.try_into().unwrap_or(u8::MAX) - ); - } - _ => doc.detect_indent_impl() + if let Some(indent_style) = config.indent_style { + doc.indent_style = indent_style; + } else { + doc.detect_indent_impl(); } // Set the line ending. - use ec4rs::property::EndOfLine; - match ecfg.get::() { - Ok(EndOfLine::Cr) => {doc.line_ending = LineEnding::CR}, - Ok(EndOfLine::Lf) => {doc.line_ending = LineEnding::LF}, - Ok(EndOfLine::CrLf) => {doc.line_ending = LineEnding::Crlf}, - Err(_) => doc.detect_line_ending_impl() + if let Some(line_ending) = config.line_ending { + doc.line_ending = line_ending; + } else { + doc.detect_line_ending_impl(); } Ok(doc) @@ -1047,6 +1018,70 @@ impl Default for Document { } } +#[derive(Clone, Copy, PartialEq, Debug, Default)] +pub struct Config { + pub encoding: Option<&'static encoding::Encoding>, + pub line_ending: Option, + pub indent_style: Option, +} + +impl Config { + /// Create a version of this config with any `None` fields + /// filled in with values from the config passed to `other`. + pub fn merged(self, other: Config) -> Config { + // Crate `merge` isn't used here, so this will do. + Config { + encoding: self.encoding.or(other.encoding), + line_ending: self.line_ending.or(other.line_ending), + indent_style: self.indent_style.or(other.indent_style), + } + } + + /// Wraps an optional encoding in a `Config`, using default values for everything else. + pub fn from_encoding(encoding: Option<&'static encoding::Encoding>) -> Config { + let mut config: Config = Config::default(); + config.encoding = encoding; + config + } + + /// Tries to parse a config from editorconfig properties. + pub fn try_from_editorconfig(for_file_at: &std::path::Path) -> Result { + let mut ecfg = ec4rs::config_for(for_file_at)?; + ecfg.use_fallbacks(); + + let mut config: Config = Default::default(); + + // Encoding. + config.encoding = ecfg + .get_raw::() + .filter_unset() + .into_result() + .ok() + .and_then(|string| encoding::Encoding::for_label(string.to_lowercase().as_bytes())); + + // Indent Style. + use ec4rs::property::{IndentSize, IndentStyle as EcIndentStyle}; + config.indent_style = match (ecfg.get::(), ecfg.get::()) { + (Ok(EcIndentStyle::Tabs), _) => Some(IndentStyle::Tabs), + (Ok(EcIndentStyle::Spaces), Ok(IndentSize::Value(n))) => { + Some(IndentStyle::Spaces(n.try_into().unwrap_or(u8::MAX))) + } + _ => None, + }; + + // Line Ending. + use ec4rs::property::EndOfLine; + config.line_ending = match ecfg.get::() { + Ok(EndOfLine::Cr) => Some(LineEnding::CR), + Ok(EndOfLine::Lf) => Some(LineEnding::LF), + Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), + Err(_) => None, + }; + + Ok(config) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0db719ef0d52..e3749ccefc55 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -587,13 +587,8 @@ impl Editor { let id = if let Some(id) = id { id } else { - // TODO: Config variable instead of `false`. - let mut doc = Document::open_with_editorconfig( - &path, - None, - Some(self.syn_loader.clone()), - false - )?; + // TODO: Config variable to enable editorconfig. + let mut doc = Document::open(&path, None, Some(self.syn_loader.clone()))?; let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); From a9ee2613fb65648132233fc52e075dd6d340b355 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Thu, 10 Mar 2022 11:27:56 -0800 Subject: [PATCH 03/17] Add a flag to enable EditorConfig support --- helix-view/src/document.rs | 3 --- helix-view/src/editor.rs | 22 +++++++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 63a0acaf6889..11caabae293e 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -384,9 +384,6 @@ impl Document { lang_config_loader: Option>, doc_config_fn: impl FnOnce(&Path) -> Config, ) -> Result { - // Closure to read the editorconfig files that apply to `path`. - // Ideally this should be done after an existing file is already opened. - // Open the file if it exists, otherwise assume it is a new file (and thus empty). let (rope, config) = if path.exists() { let mut file = std::fs::File::open(path).context(format!("unable to open {:?}", path))?; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e3749ccefc55..953b8a605748 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,6 +1,6 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, - document::{Mode, SCRATCH_BUFFER_NAME}, + document::{self, Mode, SCRATCH_BUFFER_NAME}, graphics::{CursorKind, Rect}, info::Info, input::KeyEvent, @@ -123,6 +123,9 @@ pub struct Config { /// Search configuration. #[serde(default)] pub search: SearchConfig, + /// Whether to use [EditorConfig](https://editorconfig.org/). + /// Defaults to `true`. + pub editorconfig: bool, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -233,6 +236,7 @@ impl Default for Config { cursor_shape: CursorShapeConfig::default(), true_color: false, search: SearchConfig::default(), + editorconfig: true, } } } @@ -587,8 +591,20 @@ impl Editor { let id = if let Some(id) = id { id } else { - // TODO: Config variable to enable editorconfig. - let mut doc = Document::open(&path, None, Some(self.syn_loader.clone()))?; + let syn_loader = Some(self.syn_loader.clone()); + let mut doc = if self.config.editorconfig { + Document::open_with_config(&path, syn_loader, |path| { + match document::Config::try_from_editorconfig(path) { + Ok(cfg) => cfg, + Err(_) => { + //TODO: Log error. + document::Config::default() + } + } + })? + } else { + Document::open(&path, None, syn_loader)? + }; let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); From 69508656063f3ed07a12f5fad5431045ae89b137 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Thu, 10 Mar 2022 11:38:29 -0800 Subject: [PATCH 04/17] Fix Config-related clippy lints --- helix-view/src/document.rs | 59 +++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 11caabae293e..1da56c7780ff 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1036,9 +1036,10 @@ impl Config { /// Wraps an optional encoding in a `Config`, using default values for everything else. pub fn from_encoding(encoding: Option<&'static encoding::Encoding>) -> Config { - let mut config: Config = Config::default(); - config.encoding = encoding; - config + Config { + encoding, + ..Default::default() + } } /// Tries to parse a config from editorconfig properties. @@ -1046,36 +1047,28 @@ impl Config { let mut ecfg = ec4rs::config_for(for_file_at)?; ecfg.use_fallbacks(); - let mut config: Config = Default::default(); - - // Encoding. - config.encoding = ecfg - .get_raw::() - .filter_unset() - .into_result() - .ok() - .and_then(|string| encoding::Encoding::for_label(string.to_lowercase().as_bytes())); - - // Indent Style. - use ec4rs::property::{IndentSize, IndentStyle as EcIndentStyle}; - config.indent_style = match (ecfg.get::(), ecfg.get::()) { - (Ok(EcIndentStyle::Tabs), _) => Some(IndentStyle::Tabs), - (Ok(EcIndentStyle::Spaces), Ok(IndentSize::Value(n))) => { - Some(IndentStyle::Spaces(n.try_into().unwrap_or(u8::MAX))) - } - _ => None, - }; - - // Line Ending. - use ec4rs::property::EndOfLine; - config.line_ending = match ecfg.get::() { - Ok(EndOfLine::Cr) => Some(LineEnding::CR), - Ok(EndOfLine::Lf) => Some(LineEnding::LF), - Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), - Err(_) => None, - }; - - Ok(config) + use ec4rs::property::{Charset, EndOfLine, IndentSize, IndentStyle as EcIndentStyle}; + Ok(Config { + encoding: ecfg + .get_raw::() + .filter_unset() + .into_result() + .ok() + .and_then(|string| encoding::Encoding::for_label(string.to_lowercase().as_bytes())), + indent_style: match (ecfg.get::(), ecfg.get::()) { + (Ok(EcIndentStyle::Tabs), _) => Some(IndentStyle::Tabs), + (Ok(EcIndentStyle::Spaces), Ok(IndentSize::Value(n))) => { + Some(IndentStyle::Spaces(n.try_into().unwrap_or(u8::MAX))) + } + _ => None, + }, + line_ending: match ecfg.get::() { + Ok(EndOfLine::Cr) => Some(LineEnding::CR), + Ok(EndOfLine::Lf) => Some(LineEnding::LF), + Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), + Err(_) => None, + }, + }) } } From 278d9f414cd3229742ae7718474ed6a130fe938f Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 11 Jun 2022 10:36:56 -0700 Subject: [PATCH 05/17] Bump ec4rs to 1.0.0 and apply fixes --- Cargo.lock | 4 ++-- helix-view/Cargo.toml | 2 +- helix-view/src/document.rs | 5 +++-- helix-view/src/editor.rs | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfcaf0cc2c02..c169bb4ea878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,9 +178,9 @@ dependencies = [ [[package]] name = "ec4rs" -version = "1.0.0-rc.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0255cdb161744c81c8a656dbdd2f6f0eec392e64bcf80770c638f1882fed1da5" +checksum = "b3fefc59b5315efdc02320780f635635216a07f06827a55130c357f4bff611c7" [[package]] name = "either" diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index c99841bf9d1e..1baa9a088bdc 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -42,7 +42,7 @@ log = "~0.4" which = "4.2" -ec4rs = "1.0.0-rc.1" +ec4rs = "1.0.0" [target.'cfg(windows)'.dependencies] clipboard-win = { version = "4.4", features = ["std"] } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index f4e8267f97bb..5f5c2dfca4fe 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1068,7 +1068,7 @@ impl Config { /// Tries to parse a config from editorconfig properties. pub fn try_from_editorconfig(for_file_at: &std::path::Path) -> Result { - let mut ecfg = ec4rs::config_for(for_file_at)?; + let mut ecfg = ec4rs::properties_of(for_file_at)?; ecfg.use_fallbacks(); use ec4rs::property::{Charset, EndOfLine, IndentSize, IndentStyle as EcIndentStyle}; @@ -1087,10 +1087,11 @@ impl Config { _ => None, }, line_ending: match ecfg.get::() { + #[cfg(feature = "helix-core/unicode-lines")] Ok(EndOfLine::Cr) => Some(LineEnding::CR), Ok(EndOfLine::Lf) => Some(LineEnding::LF), Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), - Err(_) => None, + _ => None, }, }) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index d053d0d9bd07..905b50e86501 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -769,12 +769,12 @@ impl Editor { id } else { let syn_loader = Some(self.syn_loader.clone()); - let mut doc = if self.config.editorconfig { + let mut doc = if self.config().editorconfig { Document::open_with_config(&path, syn_loader, |path| { match document::Config::try_from_editorconfig(path) { Ok(cfg) => cfg, - Err(_) => { - //TODO: Log error. + Err(e) => { + log::warn!("failed to load EditorConfig: {}", e); document::Config::default() } } From 2bff8f2acd150275c217b3ae2a7bb1227beb6159 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 25 Jun 2022 08:09:37 -0700 Subject: [PATCH 06/17] Update ec4rs --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb95e57d1da4..5ae90f9c4237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,9 +178,9 @@ dependencies = [ [[package]] name = "ec4rs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3fefc59b5315efdc02320780f635635216a07f06827a55130c357f4bff611c7" +checksum = "db540f6c584d6e2960617a2b550632715bbe0a50a4d79c0f682a13da100b34eb" [[package]] name = "either" From 0ec192a04c5de9d46dacf4743a6ad1c1501614e6 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 25 Jun 2022 09:09:13 -0700 Subject: [PATCH 07/17] Add support for EditorConfig tab_width --- helix-view/src/document.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 19347d57416d..ae3edc54eb68 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -96,6 +96,9 @@ pub struct Document { /// Current indent style. pub indent_style: IndentStyle, + /// An override of the language-specified tab width. + pub tab_width_override: Option, + /// The document's default line ending. pub line_ending: LineEnding, @@ -347,6 +350,7 @@ impl Document { text, selections: HashMap::default(), indent_style: DEFAULT_INDENT, + tab_width_override: None, line_ending: DEFAULT_LINE_ENDING, mode: Mode::Normal, restore_cursor: false, @@ -421,6 +425,9 @@ impl Document { doc.detect_line_ending_impl(); } + // Set the tab width. + doc.tab_width_override = config.tab_width; + Ok(doc) } @@ -955,9 +962,13 @@ impl Document { /// Tab size in columns. pub fn tab_width(&self) -> usize { - self.language_config() - .and_then(|config| config.indent.as_ref()) - .map_or(4, |config| config.tab_width) // fallback to 4 columns + if let Some(width) = self.tab_width_override { + width + } else { + self.language_config() + .and_then(|config| config.indent.as_ref()) + .map_or(4, |config| config.tab_width) // fallback to 4 columns + } } /// Returns a string containing a single level of indentation. @@ -1076,6 +1087,7 @@ pub struct Config { pub encoding: Option<&'static encoding::Encoding>, pub line_ending: Option, pub indent_style: Option, + pub tab_width: Option, } impl Config { @@ -1087,6 +1099,7 @@ impl Config { encoding: self.encoding.or(other.encoding), line_ending: self.line_ending.or(other.line_ending), indent_style: self.indent_style.or(other.indent_style), + tab_width: self.tab_width.or(other.tab_width), } } @@ -1103,7 +1116,9 @@ impl Config { let mut ecfg = ec4rs::properties_of(for_file_at)?; ecfg.use_fallbacks(); - use ec4rs::property::{Charset, EndOfLine, IndentSize, IndentStyle as EcIndentStyle}; + use ec4rs::property::{ + Charset, EndOfLine, IndentSize, IndentStyle as EcIndentStyle, TabWidth, + }; Ok(Config { encoding: ecfg .get_raw::() @@ -1125,6 +1140,10 @@ impl Config { Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), _ => None, }, + tab_width: match ecfg.get::() { + Ok(TabWidth::Value(v)) => Some(v), + _ => None, + }, }) } } From ff3d631673a4cba50a507ae8753f526848f805a3 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 25 Jun 2022 10:27:59 -0700 Subject: [PATCH 08/17] Make Document::tab_width_override private --- helix-view/src/document.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index ae3edc54eb68..02696194990c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -97,7 +97,7 @@ pub struct Document { pub indent_style: IndentStyle, /// An override of the language-specified tab width. - pub tab_width_override: Option, + tab_width_override: Option, /// The document's default line ending. pub line_ending: LineEnding, @@ -971,6 +971,15 @@ impl Document { } } + /// Sets the tab size in columns. + /// + /// If `None` is passed, then [Document::tab_width] + /// will return the value specified by the language configuration + /// for this document. + pub fn set_tab_width(&mut self, tab_width_override: Option) { + self.tab_width_override = tab_width_override; + } + /// Returns a string containing a single level of indentation. /// /// TODO: we might not need this function anymore, since the information From d07440d7fb06ae051ba60094050432e45acbdc50 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 25 Jun 2022 12:22:37 -0700 Subject: [PATCH 09/17] Update ec4rs version requirement ec4rs 1.0.0 requires a later version of Rust than Helix is made for at this time. ec4rs 1.0.1 (and later versions in the 1.0.x series) do not. The version requirement has been updated to reflect this. --- helix-view/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 1baa9a088bdc..1e364318788a 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -42,7 +42,7 @@ log = "~0.4" which = "4.2" -ec4rs = "1.0.0" +ec4rs = "1.0.1" [target.'cfg(windows)'.dependencies] clipboard-win = { version = "4.4", features = ["std"] } From c878e200b78ca002f1bbc3a59cec2574c0a89675 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Wed, 13 Jul 2022 19:35:10 -0700 Subject: [PATCH 10/17] Remove unused method in helix_view::document::Config --- helix-view/src/document.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 02696194990c..5ae3d6c4858b 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1100,18 +1100,6 @@ pub struct Config { } impl Config { - /// Create a version of this config with any `None` fields - /// filled in with values from the config passed to `other`. - pub fn merged(self, other: Config) -> Config { - // Crate `merge` isn't used here, so this will do. - Config { - encoding: self.encoding.or(other.encoding), - line_ending: self.line_ending.or(other.line_ending), - indent_style: self.indent_style.or(other.indent_style), - tab_width: self.tab_width.or(other.tab_width), - } - } - /// Wraps an optional encoding in a `Config`, using default values for everything else. pub fn from_encoding(encoding: Option<&'static encoding::Encoding>) -> Config { Config { From 2d8fefa202dd3b9c0b721198c3b7bb5787a25018 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sun, 7 Aug 2022 13:09:59 -0700 Subject: [PATCH 11/17] Rename document::Config to DocumentOptions --- helix-view/src/document.rs | 23 +++++++++++++---------- helix-view/src/editor.rs | 6 +++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 5ae3d6c4858b..29be5ce13285 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -376,18 +376,18 @@ impl Document { encoding: Option<&'static encoding::Encoding>, config_loader: Option>, ) -> Result { - Self::open_with_config(path, config_loader, move |_| { - Config::from_encoding(encoding) + Self::open_with_options(path, config_loader, move |_| { + DocumentOptions::from_encoding(encoding) }) } // TODO: async fn? /// Create a new document from `path`, /// using document settings from a [Config]. - pub fn open_with_config( + pub fn open_with_options( path: &Path, lang_config_loader: Option>, - doc_config_fn: impl FnOnce(&Path) -> Config, + doc_config_fn: impl FnOnce(&Path) -> DocumentOptions, ) -> Result { let (rope, config) = if path.exists() { let mut file = @@ -1091,32 +1091,35 @@ impl Default for Document { } } +/// Options for a new document. +/// +/// This will usually be populated by edito #[derive(Clone, Copy, PartialEq, Debug, Default)] -pub struct Config { +pub struct DocumentOptions { pub encoding: Option<&'static encoding::Encoding>, pub line_ending: Option, pub indent_style: Option, pub tab_width: Option, } -impl Config { +impl DocumentOptions { /// Wraps an optional encoding in a `Config`, using default values for everything else. - pub fn from_encoding(encoding: Option<&'static encoding::Encoding>) -> Config { - Config { + pub fn from_encoding(encoding: Option<&'static encoding::Encoding>) -> DocumentOptions { + DocumentOptions { encoding, ..Default::default() } } /// Tries to parse a config from editorconfig properties. - pub fn try_from_editorconfig(for_file_at: &std::path::Path) -> Result { + pub fn try_from_editorconfig(for_file_at: &std::path::Path) -> Result { let mut ecfg = ec4rs::properties_of(for_file_at)?; ecfg.use_fallbacks(); use ec4rs::property::{ Charset, EndOfLine, IndentSize, IndentStyle as EcIndentStyle, TabWidth, }; - Ok(Config { + Ok(DocumentOptions { encoding: ecfg .get_raw::() .filter_unset() diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e62071db28c5..ba3087c7a5b6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -857,12 +857,12 @@ impl Editor { } else { let syn_loader = Some(self.syn_loader.clone()); let mut doc = if self.config().editorconfig { - Document::open_with_config(&path, syn_loader, |path| { - match document::Config::try_from_editorconfig(path) { + Document::open_with_options(&path, syn_loader, |path| { + match document::DocumentOptions::try_from_editorconfig(path) { Ok(cfg) => cfg, Err(e) => { log::warn!("failed to load EditorConfig: {}", e); - document::Config::default() + document::DocumentOptions::default() } } })? From 7f57658838c0de07b31a429e3e5879ebbd6511cb Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sun, 7 Aug 2022 13:26:54 -0700 Subject: [PATCH 12/17] Change document tab width field --- helix-view/src/document.rs | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 29be5ce13285..705c0063587a 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -96,8 +96,8 @@ pub struct Document { /// Current indent style. pub indent_style: IndentStyle, - /// An override of the language-specified tab width. - tab_width_override: Option, + /// Tab size in columns. + tab_width: usize, /// The document's default line ending. pub line_ending: LineEnding, @@ -350,7 +350,7 @@ impl Document { text, selections: HashMap::default(), indent_style: DEFAULT_INDENT, - tab_width_override: None, + tab_width: 4, line_ending: DEFAULT_LINE_ENDING, mode: Mode::Normal, restore_cursor: false, @@ -426,7 +426,14 @@ impl Document { } // Set the tab width. - doc.tab_width_override = config.tab_width; + let tab_width = config.tab_width.or_else(|| { + doc.language_config() + .and_then(|config| config.indent.as_ref()) + .map(|config| config.tab_width) + }); + if let Some(tab_width) = tab_width { + doc.tab_width = tab_width; + } Ok(doc) } @@ -962,22 +969,7 @@ impl Document { /// Tab size in columns. pub fn tab_width(&self) -> usize { - if let Some(width) = self.tab_width_override { - width - } else { - self.language_config() - .and_then(|config| config.indent.as_ref()) - .map_or(4, |config| config.tab_width) // fallback to 4 columns - } - } - - /// Sets the tab size in columns. - /// - /// If `None` is passed, then [Document::tab_width] - /// will return the value specified by the language configuration - /// for this document. - pub fn set_tab_width(&mut self, tab_width_override: Option) { - self.tab_width_override = tab_width_override; + self.tab_width } /// Returns a string containing a single level of indentation. @@ -1112,7 +1104,9 @@ impl DocumentOptions { } /// Tries to parse a config from editorconfig properties. - pub fn try_from_editorconfig(for_file_at: &std::path::Path) -> Result { + pub fn try_from_editorconfig( + for_file_at: &std::path::Path, + ) -> Result { let mut ecfg = ec4rs::properties_of(for_file_at)?; ecfg.use_fallbacks(); From 7de1bbe38f44f78f65c5a5253627fe3e2d08f812 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sun, 7 Aug 2022 13:44:47 -0700 Subject: [PATCH 13/17] Fix incomplete documentation of DocumentOptions --- helix-view/src/document.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 5354e29036b3..9262c4f7f2fc 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1135,7 +1135,10 @@ impl Default for Document { /// Options for a new document. /// -/// This will usually be populated by edito +/// This will usually be populated by editorconfig, +/// which may not set all of these fields. +/// In that case, Helix will fall back to language config values +/// and then to sensible defaults. #[derive(Clone, Copy, PartialEq, Debug, Default)] pub struct DocumentOptions { pub encoding: Option<&'static encoding::Encoding>, From ff215a826362d3207a3eaf8e576656ab6f0edb2b Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 18 Mar 2023 13:21:27 -0700 Subject: [PATCH 14/17] Re-implement EditorConfig integration --- Cargo.lock | 7 ++ helix-view/src/document.rs | 191 +++++++++++++++++++++++++++++++++---- helix-view/src/editor.rs | 4 + 3 files changed, 186 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af0858efe4d7..5af848ca6672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +[[package]] +name = "ec4rs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db540f6c584d6e2960617a2b550632715bbe0a50a4d79c0f682a13da100b34eb" + [[package]] name = "either" version = "1.8.0" @@ -1233,6 +1239,7 @@ dependencies = [ "chardetng", "clipboard-win", "crossterm", + "ec4rs", "futures-util", "helix-core", "helix-dap", diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index eca6002653f5..33fe9ceab5a9 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -135,6 +135,8 @@ pub struct Document { /// Current indent style. pub indent_style: IndentStyle, + /// Current tab width in columns. + pub tab_width: usize, /// The document's default line ending. pub line_ending: LineEnding, @@ -457,6 +459,101 @@ where *mut_ref = f(mem::take(mut_ref)); } +#[derive(Clone, Copy, Debug, Default)] +pub struct DocumentConfig { + pub config: T, + pub encoding: Option<&'static encoding::Encoding>, + pub line_ending: Option, +} + +impl DocumentConfig { + pub(self) fn transpose(option: Option>) -> DocumentConfig> { + if let Some(doc_config) = option { + DocumentConfig { + config: Some(doc_config.config), + encoding: doc_config.encoding, + line_ending: doc_config.line_ending, + } + } else { + DocumentConfig::default() + } + } +} + +/// Trait representing methods of configuring the document as it's being opened. +pub trait ConfigureDocument { + type Config; + /// Loads document configuration for a file at `path`. + fn load(&self, path: &Path) -> Result, Error>; + /// Applies document configuration except for the non-`config` fields of `DocumentConfig`. + fn configure_document(doc: &mut Document, settings: Self::Config); +} + +#[derive(Clone, Copy, Debug, Default)] +struct Autodetect; +#[derive(Clone, Copy, Debug, Default)] +struct EditorConfig; + +impl ConfigureDocument for Autodetect { + type Config = (); + + fn load(&self, _: &Path) -> Result, Error> { + Ok(DocumentConfig::default()) + } + + fn configure_document(doc: &mut Document, _: Self::Config) { + doc.detect_indent(); + } +} + +impl ConfigureDocument for EditorConfig { + type Config = ec4rs::Properties; + + fn load(&self, path: &Path) -> Result, Error> { + use ec4rs::property::{Charset, EndOfLine}; + use encoding::Encoding; + let mut config = ec4rs::properties_of(path)?; + config.use_fallbacks(); + let encoding = config + .get_raw::() + .filter_unset() + .into_result() + .ok() + .and_then(|string| Encoding::for_label(string.to_lowercase().as_bytes())); + let line_ending = match config.get::() { + Ok(EndOfLine::Lf) => Some(LineEnding::LF), + Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), + #[cfg(feature = "unicode-lines")] + Ok(EndOfLine::Cr) => Some(LineEnding::CR), + _ => None, + }; + Ok(DocumentConfig { + config, + encoding, + line_ending, + }) + } + + fn configure_document(doc: &mut Document, settings: Self::Config) { + use ec4rs::property::{IndentSize, IndentStyle as IndentStyleEc, TabWidth}; + match settings.get::() { + Ok(IndentStyleEc::Tabs) => doc.indent_style = IndentStyle::Tabs, + Ok(IndentStyleEc::Spaces) => { + let spaces = if let Ok(IndentSize::Value(cols)) = settings.get::() { + cols + } else { + doc.tab_width + }; + doc.indent_style = IndentStyle::Spaces(spaces.try_into().unwrap_or(u8::MAX)); + } + _ => doc.detect_indent(), + } + if let Ok(TabWidth::Value(width)) = settings.get::() { + doc.tab_width = width; + } + } +} + use helix_lsp::lsp; use url::Url; @@ -479,6 +576,7 @@ impl Document { inlay_hints: HashMap::default(), inlay_hints_oudated: false, indent_style: DEFAULT_INDENT, + tab_width: 4, line_ending: DEFAULT_LINE_ENDING, restore_cursor: false, syntax: None, @@ -511,17 +609,50 @@ impl Document { config_loader: Option>, config: Arc>, ) -> Result { - // Open the file if it exists, otherwise assume it is a new file (and thus empty). - let (rope, encoding) = if path.exists() { - let mut file = - std::fs::File::open(path).context(format!("unable to open {:?}", path))?; - from_reader(&mut file, encoding)? + if config.load().editorconfig { + Document::open_with_cfg(&EditorConfig, path, encoding, config_loader, config) } else { - let encoding = encoding.unwrap_or(encoding::UTF_8); - (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding) + Document::open_with_cfg(&Autodetect, path, encoding, config_loader, config) + } + } + // TODO: async fn? + fn open_with_cfg( + doc_config_loader: &C, + path: &Path, + encoding: Option<&'static encoding::Encoding>, + config_loader: Option>, + config: Arc>, + ) -> Result { + let (rope, doc_config) = match std::fs::File::open(path) { + // Match errors that we should NOT ignore. + Err(e) if !matches!(e.kind(), std::io::ErrorKind::NotFound) => { + return Err(e).context(format!("unable to open {:?}", path)); + } + result => { + // Load doc_config for the file at this path. + let mut doc_config = DocumentConfig::transpose( + doc_config_loader + .load(path) + .map_err(|e| { + log::warn!("unable to load document config for {:?}: {}", path, e) + }) + .ok(), + ); + // Override the doc_config encoding. + doc_config.encoding = encoding.or(doc_config.encoding); + if let Ok(mut file) = result { + let (rope, encoding) = from_reader(&mut file, doc_config.encoding)?; + doc_config.encoding = Some(encoding); + (rope, doc_config) + } else { + // If we're here, the error can be recovered from. + // Treat this as a new file. + let line_ending = doc_config.line_ending.get_or_insert(DEFAULT_LINE_ENDING); + (Rope::from(line_ending.as_str()), doc_config) + } + } }; - - let mut doc = Self::from(rope, Some(encoding), config); + let mut doc = Self::from(rope, doc_config.encoding, config); // set the path and try detecting the language doc.set_path(Some(path))?; @@ -529,7 +660,26 @@ impl Document { doc.detect_language(loader); } - doc.detect_indent_and_line_ending(); + // Set the tab witdh from language config, allowing it to be overridden later. + // Default of 4 is set in Document::from. + if let Some(indent) = doc + .language_config() + .and_then(|config| config.indent.as_ref()) + { + doc.tab_width = indent.tab_width + } + + if let Some(doc_config) = doc_config.config { + C::configure_document(&mut doc, doc_config); + } else { + Autodetect::configure_document(&mut doc, ()); + } + + if let Some(line_ending) = doc_config.line_ending { + doc.line_ending = line_ending; + } else { + doc.detect_line_ending() + } Ok(doc) } @@ -754,17 +904,28 @@ 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. - pub fn detect_indent_and_line_ending(&mut self) { + /// configured in `languages.toml`, with a fallback to tabs if it isn't specified. + pub fn detect_indent(&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)) }); + } + + /// Detect the line endings used in the file, with a fallback to the default OS line ending. + pub fn detect_line_ending(&mut self) { self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING); } + /// 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. + pub fn detect_indent_and_line_ending(&mut self) { + self.detect_indent(); + self.detect_line_ending(); + } + /// Reload the document from its path. pub fn reload( &mut self, @@ -1301,9 +1462,7 @@ impl Document { /// The width that the tab character is rendered at pub fn tab_width(&self) -> usize { - self.language_config() - .and_then(|config| config.indent.as_ref()) - .map_or(4, |config| config.tab_width) // fallback to 4 columns + self.tab_width } // The width (in spaces) of a level of indentation. diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7207baf38a2e..0b5cfa0082b5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -281,6 +281,9 @@ pub struct Config { /// Whether to color modes with different colors. Defaults to `false`. pub color_modes: bool, pub soft_wrap: SoftWrap, + /// Whether to import settings from [EditorConfig](https://editorconfig.org/). + /// Defaults to `true`. + pub editorconfig: bool, } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -751,6 +754,7 @@ impl Default for Config { soft_wrap: SoftWrap::default(), text_width: 80, completion_replace: false, + editorconfig: true, } } } From 91af0f19d0669c32a91eee029aeb4daac4fdf61d Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Sat, 18 Mar 2023 13:52:10 -0700 Subject: [PATCH 15/17] Make ConfigureDocument and DocumentConfig private Currently Document::open_with_cfg is private, which is the only place they're used. --- helix-view/src/document.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 33fe9ceab5a9..eddab67c6362 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -460,7 +460,7 @@ where } #[derive(Clone, Copy, Debug, Default)] -pub struct DocumentConfig { +struct DocumentConfig { pub config: T, pub encoding: Option<&'static encoding::Encoding>, pub line_ending: Option, @@ -481,7 +481,7 @@ impl DocumentConfig { } /// Trait representing methods of configuring the document as it's being opened. -pub trait ConfigureDocument { +trait ConfigureDocument { type Config; /// Loads document configuration for a file at `path`. fn load(&self, path: &Path) -> Result, Error>; From 6fbc8afab48a10e40b1d993e47c0e79c1512df79 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Fri, 31 Mar 2023 09:39:11 -0700 Subject: [PATCH 16/17] Fixup documentation and indent size handling --- helix-view/src/document.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index eddab67c6362..3238b3e1e0fc 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -489,8 +489,10 @@ trait ConfigureDocument { fn configure_document(doc: &mut Document, settings: Self::Config); } +/// Document configuration strategy that uses fallback auto-detection as a first resort. #[derive(Clone, Copy, Debug, Default)] struct Autodetect; +/// Document configuration strategy that loads configuration from `.editorconfig` files. #[derive(Clone, Copy, Debug, Default)] struct EditorConfig; @@ -544,7 +546,16 @@ impl ConfigureDocument for EditorConfig { } else { doc.tab_width }; - doc.indent_style = IndentStyle::Spaces(spaces.try_into().unwrap_or(u8::MAX)); + // Constrain spaces to only supported values for IndentStyle::Spaces. + let spaces_u8 = if spaces > 8 { + 8u8 + } else if spaces > 0 { + // Shouldn't panic. Overflow cases are covered by the above branch. + spaces as u8 + } else { + 4u8 + }; + doc.indent_style = IndentStyle::Spaces(spaces_u8); } _ => doc.detect_indent(), } From a359becddd33fb1826aded7d60ad54d0753cb639 Mon Sep 17 00:00:00 2001 From: TheDaemoness Date: Fri, 31 Mar 2023 18:33:43 -0700 Subject: [PATCH 17/17] Remove DocumentConfig This was a fairly unintuitive type whose functionality could be replicated by adding some associated functions to ConfigureDocument (which this commit also does). --- helix-view/src/document.rs | 111 ++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 3238b3e1e0fc..46f7b38fedd6 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -459,34 +459,17 @@ where *mut_ref = f(mem::take(mut_ref)); } -#[derive(Clone, Copy, Debug, Default)] -struct DocumentConfig { - pub config: T, - pub encoding: Option<&'static encoding::Encoding>, - pub line_ending: Option, -} - -impl DocumentConfig { - pub(self) fn transpose(option: Option>) -> DocumentConfig> { - if let Some(doc_config) = option { - DocumentConfig { - config: Some(doc_config.config), - encoding: doc_config.encoding, - line_ending: doc_config.line_ending, - } - } else { - DocumentConfig::default() - } - } -} - -/// Trait representing methods of configuring the document as it's being opened. +/// Trait representing ways of configuring the document as it's being opened. trait ConfigureDocument { type Config; /// Loads document configuration for a file at `path`. - fn load(&self, path: &Path) -> Result, Error>; - /// Applies document configuration except for the non-`config` fields of `DocumentConfig`. - fn configure_document(doc: &mut Document, settings: Self::Config); + fn load(&self, path: &Path) -> Result; + /// Retrieves the encoding from a `Config`. + fn encoding(config: &Self::Config) -> Option<&'static encoding::Encoding>; + /// Retrieves the line ending from a `Config`. + fn line_ending(config: &Self::Config) -> Option; + /// Applies any document configuration not handled by one of the other methods. + fn configure_document(doc: &mut Document, config: Self::Config); } /// Document configuration strategy that uses fallback auto-detection as a first resort. @@ -499,8 +482,16 @@ struct EditorConfig; impl ConfigureDocument for Autodetect { type Config = (); - fn load(&self, _: &Path) -> Result, Error> { - Ok(DocumentConfig::default()) + fn load(&self, _: &Path) -> Result { + Ok(()) + } + + fn encoding(_: &Self::Config) -> Option<&'static encoding::Encoding> { + None + } + + fn line_ending(_: &Self::Config) -> Option { + None } fn configure_document(doc: &mut Document, _: Self::Config) { @@ -511,29 +502,32 @@ impl ConfigureDocument for Autodetect { impl ConfigureDocument for EditorConfig { type Config = ec4rs::Properties; - fn load(&self, path: &Path) -> Result, Error> { - use ec4rs::property::{Charset, EndOfLine}; - use encoding::Encoding; + fn load(&self, path: &Path) -> Result { let mut config = ec4rs::properties_of(path)?; config.use_fallbacks(); - let encoding = config + Ok(config) + } + + fn encoding(config: &Self::Config) -> Option<&'static encoding::Encoding> { + use ec4rs::property::Charset; + use encoding::Encoding; + config .get_raw::() .filter_unset() .into_result() .ok() - .and_then(|string| Encoding::for_label(string.to_lowercase().as_bytes())); - let line_ending = match config.get::() { + .and_then(|string| Encoding::for_label(string.to_lowercase().as_bytes())) + } + + fn line_ending(config: &Self::Config) -> Option { + use ec4rs::property::EndOfLine; + match config.get::() { Ok(EndOfLine::Lf) => Some(LineEnding::LF), Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf), #[cfg(feature = "unicode-lines")] Ok(EndOfLine::Cr) => Some(LineEnding::CR), _ => None, - }; - Ok(DocumentConfig { - config, - encoding, - line_ending, - }) + } } fn configure_document(doc: &mut Document, settings: Self::Config) { @@ -634,36 +628,39 @@ impl Document { config_loader: Option>, config: Arc>, ) -> Result { - let (rope, doc_config) = match std::fs::File::open(path) { + let (rope, doc_config, encoding, line_ending) = match std::fs::File::open(path) { // Match errors that we should NOT ignore. Err(e) if !matches!(e.kind(), std::io::ErrorKind::NotFound) => { return Err(e).context(format!("unable to open {:?}", path)); } result => { // Load doc_config for the file at this path. - let mut doc_config = DocumentConfig::transpose( - doc_config_loader - .load(path) - .map_err(|e| { - log::warn!("unable to load document config for {:?}: {}", path, e) - }) - .ok(), - ); + let doc_config = doc_config_loader + .load(path) + .map_err(|e| log::warn!("unable to load document config for {:?}: {}", path, e)) + .ok(); // Override the doc_config encoding. - doc_config.encoding = encoding.or(doc_config.encoding); + let encoding = encoding.or_else(|| doc_config.as_ref().and_then(C::encoding)); if let Ok(mut file) = result { - let (rope, encoding) = from_reader(&mut file, doc_config.encoding)?; - doc_config.encoding = Some(encoding); - (rope, doc_config) + let (rope, encoding) = from_reader(&mut file, encoding)?; + (rope, doc_config, Some(encoding), None) } else { // If we're here, the error can be recovered from. // Treat this as a new file. - let line_ending = doc_config.line_ending.get_or_insert(DEFAULT_LINE_ENDING); - (Rope::from(line_ending.as_str()), doc_config) + let line_ending = doc_config + .as_ref() + .and_then(C::line_ending) + .unwrap_or(DEFAULT_LINE_ENDING); + ( + Rope::from(line_ending.as_str()), + doc_config, + encoding, + Some(line_ending), + ) } } }; - let mut doc = Self::from(rope, doc_config.encoding, config); + let mut doc = Self::from(rope, encoding, config); // set the path and try detecting the language doc.set_path(Some(path))?; @@ -680,13 +677,13 @@ impl Document { doc.tab_width = indent.tab_width } - if let Some(doc_config) = doc_config.config { + if let Some(doc_config) = doc_config { C::configure_document(&mut doc, doc_config); } else { Autodetect::configure_document(&mut doc, ()); } - if let Some(line_ending) = doc_config.line_ending { + if let Some(line_ending) = line_ending { doc.line_ending = line_ending; } else { doc.detect_line_ending()