Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EditorConfig support #1777

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aa65111
Add initial editorconfig parsing on document open
TheDaemoness Mar 8, 2022
0d433fd
Add document::Config and parse editorconfig into it
TheDaemoness Mar 10, 2022
a9ee261
Add a flag to enable EditorConfig support
TheDaemoness Mar 10, 2022
6950865
Fix Config-related clippy lints
TheDaemoness Mar 10, 2022
c120e35
Merge branch 'master' of https://github.com/helix-editor/helix into h…
TheDaemoness Jun 11, 2022
86c0923
Merge upstream changes
TheDaemoness Jun 11, 2022
278d9f4
Bump ec4rs to 1.0.0 and apply fixes
TheDaemoness Jun 11, 2022
73a8f25
Merge branch 'helix-editor:master' into master
TheDaemoness Jun 25, 2022
2bff8f2
Update ec4rs
TheDaemoness Jun 25, 2022
0ec192a
Add support for EditorConfig tab_width
TheDaemoness Jun 25, 2022
ff3d631
Make Document::tab_width_override private
TheDaemoness Jun 25, 2022
d07440d
Update ec4rs version requirement
TheDaemoness Jun 25, 2022
e177b85
Merge branch 'helix-editor:master' into master
TheDaemoness Jul 14, 2022
c878e20
Remove unused method in helix_view::document::Config
TheDaemoness Jul 14, 2022
2d8fefa
Rename document::Config to DocumentOptions
TheDaemoness Aug 7, 2022
7f57658
Change document tab width field
TheDaemoness Aug 7, 2022
77dffd7
Merge branch 'master' of https://github.com/helix-editor/helix
TheDaemoness Aug 7, 2022
7de1bbe
Fix incomplete documentation of DocumentOptions
TheDaemoness Aug 7, 2022
5d53965
Merge from upstream, discard EditorConfig-related changes
TheDaemoness Mar 18, 2023
ff215a8
Re-implement EditorConfig integration
TheDaemoness Mar 18, 2023
91af0f1
Make ConfigureDocument and DocumentConfig private
TheDaemoness Mar 18, 2023
a4f9325
Merge branch 'master' into master
TheDaemoness Mar 31, 2023
6fbc8af
Fixup documentation and indent size handling
TheDaemoness Mar 31, 2023
a359bec
Remove DocumentConfig
TheDaemoness Apr 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions helix-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ log = "~0.4"

which = "4.2"

ec4rs = "1.0.0-rc.1"
TheDaemoness marked this conversation as resolved.
Show resolved Hide resolved

[target.'cfg(windows)'.dependencies]
clipboard-win = { version = "4.4", features = ["std"] }

Expand Down
112 changes: 103 additions & 9 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,25 +371,54 @@ impl Document {
encoding: Option<&'static encoding::Encoding>,
config_loader: Option<Arc<syntax::Loader>>,
) -> Result<Self, Error> {
// Open the file if it exists, otherwise assume it is a new file (and thus empty).
let (rope, encoding) = if path.exists() {
Self::open_with_config(path, config_loader, move |_| {
Config::from_encoding(encoding)
})
}

// TODO: async fn?
/// Create a new document from `path`,
/// using document settings from a [Config].
pub fn open_with_config(
path: &Path,
lang_config_loader: Option<Arc<syntax::Loader>>,
doc_config_fn: impl FnOnce(&Path) -> Config,
) -> Result<Self, Error> {
let (rope, config) = if path.exists() {
let mut file =
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
from_reader(&mut file, encoding)?
let mut config = doc_config_fn(path);
TheDaemoness marked this conversation as resolved.
Show resolved Hide resolved
// Not using destructuring assignment here.
let (rope, encoding) = from_reader(&mut file, config.encoding)?;
config.encoding = Some(encoding);
(rope, config)
} else {
let encoding = encoding.unwrap_or(encoding::UTF_8);
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
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);
}

doc.detect_indent_and_line_ending();
// Set the indent style.
if let Some(indent_style) = config.indent_style {
doc.indent_style = indent_style;
} else {
doc.detect_indent_impl();
}

// Set the line ending.
if let Some(line_ending) = config.line_ending {
doc.line_ending = line_ending;
} else {
doc.detect_line_ending_impl();
}

Ok(doc)
}
Expand Down Expand Up @@ -517,11 +546,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);
}

Expand Down Expand Up @@ -978,6 +1015,63 @@ impl Default for Document {
}
}

#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct Config {
pub encoding: Option<&'static encoding::Encoding>,
pub line_ending: Option<LineEnding>,
pub indent_style: Option<IndentStyle>,
}

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 {
Config {
encoding,
..Default::default()
}
}

/// Tries to parse a config from editorconfig properties.
pub fn try_from_editorconfig(for_file_at: &std::path::Path) -> Result<Config, ec4rs::Error> {
let mut ecfg = ec4rs::config_for(for_file_at)?;
ecfg.use_fallbacks();

use ec4rs::property::{Charset, EndOfLine, IndentSize, IndentStyle as EcIndentStyle};
Ok(Config {
encoding: ecfg
.get_raw::<Charset>()
.filter_unset()
.into_result()
.ok()
.and_then(|string| encoding::Encoding::for_label(string.to_lowercase().as_bytes())),
indent_style: match (ecfg.get::<EcIndentStyle>(), ecfg.get::<IndentSize>()) {
(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::<EndOfLine>() {
Ok(EndOfLine::Cr) => Some(LineEnding::CR),
Ok(EndOfLine::Lf) => Some(LineEnding::LF),
Ok(EndOfLine::CrLf) => Some(LineEnding::Crlf),
Err(_) => None,
},
})
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
21 changes: 19 additions & 2 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -233,6 +236,7 @@ impl Default for Config {
cursor_shape: CursorShapeConfig::default(),
true_color: false,
search: SearchConfig::default(),
editorconfig: true,
}
}
}
Expand Down Expand Up @@ -587,7 +591,20 @@ impl Editor {
let id = if let Some(id) = id {
id
} else {
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);

Expand Down