mirror of
https://github.com/helix-editor/helix.git
synced 2025-04-02 02:17:44 +03:00
Support EditorConfig (#13056)
This commit is contained in:
parent
f6cb90593d
commit
3a63e85b6a
6 changed files with 415 additions and 22 deletions
|
@ -60,6 +60,7 @@
|
|||
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
|
||||
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable"
|
||||
| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win-32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
|
||||
| `editor-config` | Whether to read settings from [EditorConfig](https://editorconfig.org) files | `true` |
|
||||
|
||||
### `[editor.clipboard-provider]` Section
|
||||
|
||||
|
|
333
helix-core/src/editor_config.rs
Normal file
333
helix-core/src/editor_config.rs
Normal file
|
@ -0,0 +1,333 @@
|
|||
//! Support for [EditorConfig](https://EditorConfig.org) configuration loading.
|
||||
//!
|
||||
//! EditorConfig is an editor-agnostic format for specifying configuration in an INI-like, human
|
||||
//! friendly syntax in `.editorconfig` files (which are intended to be checked into VCS). This
|
||||
//! module provides functions to search for all `.editorconfig` files that apply to a given path
|
||||
//! and returns an `EditorConfig` type containing any specified configuration options.
|
||||
//!
|
||||
//! At time of writing, this module follows the [spec](https://spec.editorconfig.org/) at
|
||||
//! version 0.17.2.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
num::{NonZeroU16, NonZeroU8},
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use encoding_rs::Encoding;
|
||||
use globset::{GlobBuilder, GlobMatcher};
|
||||
|
||||
use crate::{
|
||||
indent::{IndentStyle, MAX_INDENT},
|
||||
LineEnding,
|
||||
};
|
||||
|
||||
/// Configuration declared for a path in `.editorconfig` files.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct EditorConfig {
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
pub tab_width: Option<NonZeroU8>,
|
||||
pub line_ending: Option<LineEnding>,
|
||||
pub encoding: Option<&'static Encoding>,
|
||||
// pub spelling_language: Option<SpellingLanguage>,
|
||||
pub trim_trailing_whitespace: Option<bool>,
|
||||
pub insert_final_newline: Option<bool>,
|
||||
pub max_line_length: Option<NonZeroU16>,
|
||||
}
|
||||
|
||||
impl EditorConfig {
|
||||
/// Finds any configuration in `.editorconfig` files which applies to the given path.
|
||||
///
|
||||
/// If no configuration applies then `EditorConfig::default()` is returned.
|
||||
pub fn find(path: &Path) -> Self {
|
||||
let mut configs = Vec::new();
|
||||
// <https://spec.editorconfig.org/#file-processing>
|
||||
for ancestor in path.ancestors() {
|
||||
let editor_config_file = ancestor.join(".editorconfig");
|
||||
let Ok(contents) = fs::read_to_string(&editor_config_file) else {
|
||||
continue;
|
||||
};
|
||||
let ini = match contents.parse::<Ini>() {
|
||||
Ok(ini) => ini,
|
||||
Err(err) => {
|
||||
log::warn!("Ignoring EditorConfig file at '{editor_config_file:?}' because a glob failed to compile: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let is_root = ini.pairs.get("root").map(AsRef::as_ref) == Some("true");
|
||||
configs.push((ini, ancestor));
|
||||
// > The search shall stop if an EditorConfig file is found with the `root` key set to
|
||||
// > `true` in the preamble or when reaching the root filesystem directory.
|
||||
if is_root {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut pairs = Pairs::new();
|
||||
// Reverse the configuration stack so that the `.editorconfig` files closest to `path`
|
||||
// are applied last and overwrite settings in files closer to the search ceiling.
|
||||
//
|
||||
// > If multiple EditorConfig files have matching sections, the pairs from the closer
|
||||
// > EditorConfig file are read last, so pairs in closer files take precedence.
|
||||
for (config, dir) in configs.into_iter().rev() {
|
||||
let relative_path = path.strip_prefix(dir).expect("dir is an ancestor of path");
|
||||
|
||||
for section in config.sections {
|
||||
if section.glob.is_match(relative_path) {
|
||||
log::info!(
|
||||
"applying EditorConfig from section '{}' in file {:?}",
|
||||
section.glob.glob(),
|
||||
dir.join(".editorconfig")
|
||||
);
|
||||
pairs.extend(section.pairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::from_pairs(pairs)
|
||||
}
|
||||
|
||||
fn from_pairs(pairs: Pairs) -> Self {
|
||||
enum IndentSize {
|
||||
Tab,
|
||||
Spaces(NonZeroU8),
|
||||
}
|
||||
|
||||
// <https://spec.editorconfig.org/#supported-pairs>
|
||||
let indent_size = pairs.get("indent_size").and_then(|value| {
|
||||
if value.as_ref() == "tab" {
|
||||
Some(IndentSize::Tab)
|
||||
} else if let Ok(spaces) = value.parse::<NonZeroU8>() {
|
||||
Some(IndentSize::Spaces(spaces))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let tab_width = pairs
|
||||
.get("tab_width")
|
||||
.and_then(|value| value.parse::<NonZeroU8>().ok())
|
||||
.or(match indent_size {
|
||||
Some(IndentSize::Spaces(spaces)) => Some(spaces),
|
||||
_ => None,
|
||||
});
|
||||
let indent_style = pairs
|
||||
.get("indent_style")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"tab" => Some(IndentStyle::Tabs),
|
||||
"space" => {
|
||||
let spaces = match indent_size {
|
||||
Some(IndentSize::Spaces(spaces)) => spaces.get(),
|
||||
Some(IndentSize::Tab) => tab_width.map(|n| n.get()).unwrap_or(4),
|
||||
None => 4,
|
||||
};
|
||||
Some(IndentStyle::Spaces(spaces.clamp(1, MAX_INDENT)))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let line_ending = pairs
|
||||
.get("end_of_line")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"lf" => Some(LineEnding::LF),
|
||||
"crlf" => Some(LineEnding::Crlf),
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
"cr" => Some(LineEnding::CR),
|
||||
_ => None,
|
||||
});
|
||||
let encoding = pairs.get("charset").and_then(|value| match value.as_ref() {
|
||||
"latin1" => Some(encoding_rs::WINDOWS_1252),
|
||||
"utf-8" => Some(encoding_rs::UTF_8),
|
||||
// `utf-8-bom` is intentionally ignored.
|
||||
// > `utf-8-bom` is discouraged.
|
||||
"utf-16le" => Some(encoding_rs::UTF_16LE),
|
||||
"utf-16be" => Some(encoding_rs::UTF_16BE),
|
||||
_ => None,
|
||||
});
|
||||
let trim_trailing_whitespace =
|
||||
pairs
|
||||
.get("trim_trailing_whitespace")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"true" => Some(true),
|
||||
"false" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
let insert_final_newline = pairs
|
||||
.get("insert_final_newline")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"true" => Some(true),
|
||||
"false" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
// This option is not in the spec but is supported by some editors.
|
||||
// <https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#max_line_length>
|
||||
let max_line_length = pairs
|
||||
.get("max_line_length")
|
||||
.and_then(|value| value.parse::<NonZeroU16>().ok());
|
||||
|
||||
Self {
|
||||
indent_style,
|
||||
tab_width,
|
||||
line_ending,
|
||||
encoding,
|
||||
trim_trailing_whitespace,
|
||||
insert_final_newline,
|
||||
max_line_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Pairs = HashMap<Box<str>, Box<str>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Section {
|
||||
glob: GlobMatcher,
|
||||
pairs: Pairs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Ini {
|
||||
pairs: Pairs,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl FromStr for Ini {
|
||||
type Err = globset::Error;
|
||||
|
||||
fn from_str(source: &str) -> Result<Self, Self::Err> {
|
||||
// <https://spec.editorconfig.org/#file-format>
|
||||
let mut ini = Ini::default();
|
||||
// > EditorConfig files are in an INI-like file format. To read an EditorConfig file, take
|
||||
// > one line at a time, from beginning to end. For each line:
|
||||
for full_line in source.lines() {
|
||||
// > 1. Remove all leading and trailing whitespace.
|
||||
let line = full_line.trim();
|
||||
// > 2. Process the remaining text as specified for its type below.
|
||||
// > The types of lines are:
|
||||
// > * Blank: contains nothing. Blank lines are ignored.
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// > * Comment: starts with a ';' or '#'. Comment lines are ignored.
|
||||
if line.starts_with([';', '#']) {
|
||||
continue;
|
||||
}
|
||||
if let Some(section) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
|
||||
// > * Section Header: starts with a `[` and ends with a `]`. These lines define
|
||||
// > globs...
|
||||
|
||||
// <https://spec.editorconfig.org/#glob-expressions>
|
||||
// We need to modify the glob string slightly since EditorConfig's glob flavor
|
||||
// doesn't match `globset`'s exactly. `globset` only allows '**' at the beginning
|
||||
// or end of a glob or between two '/'s. (This replacement is not very fancy but
|
||||
// should cover most practical cases.)
|
||||
let mut glob_str = section.replace("**.", "**/*.");
|
||||
if !is_glob_relative(section) {
|
||||
glob_str.insert_str(0, "**/");
|
||||
}
|
||||
let glob = GlobBuilder::new(&glob_str)
|
||||
.literal_separator(true)
|
||||
.backslash_escape(true)
|
||||
.build()?;
|
||||
ini.sections.push(Section {
|
||||
glob: glob.compile_matcher(),
|
||||
pairs: Pairs::new(),
|
||||
});
|
||||
} else if let Some((key, value)) = line.split_once('=') {
|
||||
// > * Key-Value Pair (or Pair): contains a key and a value, separated by an `=`.
|
||||
// > * Key: The part before the first `=` on the line.
|
||||
// > * Value: The part, if any, after the first `=` on the line.
|
||||
// > * Keys and values are trimmed of leading and trailing whitespace, but
|
||||
// > include any whitespace that is between non-whitespace characters.
|
||||
// > * If a value is not provided, then the value is an empty string.
|
||||
let key = key.trim().to_lowercase().into_boxed_str();
|
||||
let value = value.trim().to_lowercase().into_boxed_str();
|
||||
if let Some(section) = ini.sections.last_mut() {
|
||||
section.pairs.insert(key, value);
|
||||
} else {
|
||||
ini.pairs.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ini)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether a glob is relative to the directory of the config file.
|
||||
fn is_glob_relative(source: &str) -> bool {
|
||||
// > If the glob contains a path separator (a `/` not inside square brackets), then the
|
||||
// > glob is relative to the directory level of the particular `.editorconfig` file itself.
|
||||
let mut idx = 0;
|
||||
while let Some(open) = source[idx..].find('[').map(|open| idx + open) {
|
||||
if source[..open].contains('/') {
|
||||
return true;
|
||||
}
|
||||
idx = source[open..]
|
||||
.find(']')
|
||||
.map_or(source.len(), |close| idx + close);
|
||||
}
|
||||
source[idx..].contains('/')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_glob_relative_test() {
|
||||
assert!(is_glob_relative("subdir/*.c"));
|
||||
assert!(!is_glob_relative("*.txt"));
|
||||
assert!(!is_glob_relative("[a/b].c"));
|
||||
}
|
||||
|
||||
fn editor_config(path: impl AsRef<Path>, source: &str) -> EditorConfig {
|
||||
let path = path.as_ref();
|
||||
let ini = source.parse::<Ini>().unwrap();
|
||||
let pairs = ini
|
||||
.sections
|
||||
.into_iter()
|
||||
.filter(|section| section.glob.is_match(path))
|
||||
.fold(Pairs::new(), |mut acc, section| {
|
||||
acc.extend(section.pairs);
|
||||
acc
|
||||
});
|
||||
EditorConfig::from_pairs(pairs)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_test() {
|
||||
let source = r#"
|
||||
[*]
|
||||
indent_style = space
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[docs/**.txt]
|
||||
insert_final_newline = true
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
editor_config("a.txt", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Spaces(4)),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
editor_config("pkg/Makefile", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Tabs),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
editor_config("docs/config/editor.txt", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Spaces(4)),
|
||||
insert_final_newline: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ pub mod config;
|
|||
pub mod diagnostic;
|
||||
pub mod diff;
|
||||
pub mod doc_formatter;
|
||||
pub mod editor_config;
|
||||
pub mod fuzzy;
|
||||
pub mod graphemes;
|
||||
pub mod history;
|
||||
|
|
|
@ -326,13 +326,13 @@ fn write_impl(cx: &mut compositor::Context, path: Option<&str>, force: bool) ->
|
|||
let jobs = &mut cx.jobs;
|
||||
let (view, doc) = current!(cx.editor);
|
||||
|
||||
if config.trim_trailing_whitespace {
|
||||
if doc.trim_trailing_whitespace() {
|
||||
trim_trailing_whitespace(doc, view.id);
|
||||
}
|
||||
if config.trim_final_newlines {
|
||||
trim_final_newlines(doc, view.id);
|
||||
}
|
||||
if config.insert_final_newline {
|
||||
if doc.insert_final_newline() {
|
||||
insert_final_newline(doc, view.id);
|
||||
}
|
||||
|
||||
|
@ -738,13 +738,13 @@ pub fn write_all_impl(
|
|||
let doc = doc_mut!(cx.editor, &doc_id);
|
||||
let view = view_mut!(cx.editor, target_view);
|
||||
|
||||
if config.trim_trailing_whitespace {
|
||||
if doc.trim_trailing_whitespace() {
|
||||
trim_trailing_whitespace(doc, target_view);
|
||||
}
|
||||
if config.trim_final_newlines {
|
||||
trim_final_newlines(doc, target_view);
|
||||
}
|
||||
if config.insert_final_newline {
|
||||
if doc.insert_final_newline() {
|
||||
insert_final_newline(doc, target_view);
|
||||
}
|
||||
|
||||
|
@ -2146,7 +2146,6 @@ fn reflow(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyho
|
|||
}
|
||||
|
||||
let scrolloff = cx.editor.config().scrolloff;
|
||||
let cfg_text_width: usize = cx.editor.config().text_width;
|
||||
let (view, doc) = current!(cx.editor);
|
||||
|
||||
// Find the text_width by checking the following sources in order:
|
||||
|
@ -2157,8 +2156,7 @@ fn reflow(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyho
|
|||
.first()
|
||||
.map(|num| num.parse::<usize>())
|
||||
.transpose()?
|
||||
.or_else(|| doc.language_config().and_then(|config| config.text_width))
|
||||
.unwrap_or(cfg_text_width);
|
||||
.unwrap_or_else(|| doc.text_width());
|
||||
|
||||
let rope = doc.text();
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ use std::sync::{Arc, Weak};
|
|||
use std::time::SystemTime;
|
||||
|
||||
use helix_core::{
|
||||
editor_config::EditorConfig,
|
||||
encoding,
|
||||
history::{History, State, UndoKind},
|
||||
indent::{auto_detect_indent_style, IndentStyle},
|
||||
|
@ -50,6 +51,7 @@ use crate::{
|
|||
const BUF_SIZE: usize = 8192;
|
||||
|
||||
const DEFAULT_INDENT: IndentStyle = IndentStyle::Tabs;
|
||||
const DEFAULT_TAB_WIDTH: usize = 4;
|
||||
|
||||
pub const DEFAULT_LANGUAGE_NAME: &str = "text";
|
||||
|
||||
|
@ -157,6 +159,7 @@ pub struct Document {
|
|||
|
||||
/// Current indent style.
|
||||
pub indent_style: IndentStyle,
|
||||
editor_config: EditorConfig,
|
||||
|
||||
/// The document's default line ending.
|
||||
pub line_ending: LineEnding,
|
||||
|
@ -678,6 +681,7 @@ impl Document {
|
|||
inlay_hints_oudated: false,
|
||||
view_data: Default::default(),
|
||||
indent_style: DEFAULT_INDENT,
|
||||
editor_config: EditorConfig::default(),
|
||||
line_ending,
|
||||
restore_cursor: false,
|
||||
syntax: None,
|
||||
|
@ -712,7 +716,7 @@ impl Document {
|
|||
/// overwritten with the `encoding` parameter.
|
||||
pub fn open(
|
||||
path: &Path,
|
||||
encoding: Option<&'static Encoding>,
|
||||
mut encoding: Option<&'static Encoding>,
|
||||
config_loader: Option<Arc<ArcSwap<syntax::Loader>>>,
|
||||
config: Arc<dyn DynAccess<Config>>,
|
||||
) -> Result<Self, DocumentOpenError> {
|
||||
|
@ -721,12 +725,21 @@ impl Document {
|
|||
return Err(DocumentOpenError::IrregularFile);
|
||||
}
|
||||
|
||||
let editor_config = if config.load().editor_config {
|
||||
EditorConfig::find(path)
|
||||
} else {
|
||||
EditorConfig::default()
|
||||
};
|
||||
encoding = encoding.or(editor_config.encoding);
|
||||
|
||||
// Open the file if it exists, otherwise assume it is a new file (and thus empty).
|
||||
let (rope, encoding, has_bom) = if path.exists() {
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
from_reader(&mut file, encoding)?
|
||||
} else {
|
||||
let line_ending: LineEnding = config.load().default_line_ending.into();
|
||||
let line_ending = editor_config
|
||||
.line_ending
|
||||
.unwrap_or_else(|| config.load().default_line_ending.into());
|
||||
let encoding = encoding.unwrap_or(encoding::UTF_8);
|
||||
(Rope::from(line_ending.as_str()), encoding, false)
|
||||
};
|
||||
|
@ -739,6 +752,7 @@ impl Document {
|
|||
doc.detect_language(loader);
|
||||
}
|
||||
|
||||
doc.editor_config = editor_config;
|
||||
doc.detect_indent_and_line_ending();
|
||||
|
||||
Ok(doc)
|
||||
|
@ -1090,16 +1104,32 @@ impl Document {
|
|||
/// configured in `languages.toml`, with a fallback to tabs if it isn't specified. 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))
|
||||
});
|
||||
if let Some(line_ending) = auto_detect_line_ending(&self.text) {
|
||||
self.indent_style = if let Some(indent_style) = self.editor_config.indent_style {
|
||||
indent_style
|
||||
} else {
|
||||
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))
|
||||
})
|
||||
};
|
||||
if let Some(line_ending) = self
|
||||
.editor_config
|
||||
.line_ending
|
||||
.or_else(|| auto_detect_line_ending(&self.text))
|
||||
{
|
||||
self.line_ending = line_ending;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn detect_editor_config(&mut self) {
|
||||
if self.config.load().editor_config {
|
||||
if let Some(path) = self.path.as_ref() {
|
||||
self.editor_config = EditorConfig::find(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pickup_last_saved_time(&mut self) {
|
||||
self.last_saved_time = match self.path() {
|
||||
Some(path) => match path.metadata() {
|
||||
|
@ -1819,9 +1849,14 @@ 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.editor_config
|
||||
.tab_width
|
||||
.map(|n| n.get() as usize)
|
||||
.unwrap_or_else(|| {
|
||||
self.language_config()
|
||||
.and_then(|config| config.indent.as_ref())
|
||||
.map_or(DEFAULT_TAB_WIDTH, |config| config.tab_width)
|
||||
})
|
||||
}
|
||||
|
||||
// The width (in spaces) of a level of indentation.
|
||||
|
@ -1829,6 +1864,20 @@ impl Document {
|
|||
self.indent_style.indent_width(self.tab_width())
|
||||
}
|
||||
|
||||
/// Whether the document should have a trailing line ending appended on save.
|
||||
pub fn insert_final_newline(&self) -> bool {
|
||||
self.editor_config
|
||||
.insert_final_newline
|
||||
.unwrap_or_else(|| self.config.load().insert_final_newline)
|
||||
}
|
||||
|
||||
/// Whether the document should trim whitespace preceding line endings on save.
|
||||
pub fn trim_trailing_whitespace(&self) -> bool {
|
||||
self.editor_config
|
||||
.trim_trailing_whitespace
|
||||
.unwrap_or_else(|| self.config.load().trim_trailing_whitespace)
|
||||
}
|
||||
|
||||
pub fn changes(&self) -> &ChangeSet {
|
||||
&self.changes
|
||||
}
|
||||
|
@ -2087,12 +2136,17 @@ impl Document {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn text_width(&self) -> usize {
|
||||
self.editor_config
|
||||
.max_line_length
|
||||
.map(|n| n.get() as usize)
|
||||
.or_else(|| self.language_config().and_then(|config| config.text_width))
|
||||
.unwrap_or_else(|| self.config.load().text_width)
|
||||
}
|
||||
|
||||
pub fn text_format(&self, mut viewport_width: u16, theme: Option<&Theme>) -> TextFormat {
|
||||
let config = self.config.load();
|
||||
let text_width = self
|
||||
.language_config()
|
||||
.and_then(|config| config.text_width)
|
||||
.unwrap_or(config.text_width);
|
||||
let text_width = self.text_width();
|
||||
let mut soft_wrap_at_text_width = self
|
||||
.language_config()
|
||||
.and_then(|config| {
|
||||
|
|
|
@ -366,6 +366,9 @@ pub struct Config {
|
|||
pub end_of_line_diagnostics: DiagnosticFilter,
|
||||
// Set to override the default clipboard provider
|
||||
pub clipboard_provider: ClipboardProvider,
|
||||
/// Whether to read settings from [EditorConfig](https://editorconfig.org) files. Defaults to
|
||||
/// `true`.
|
||||
pub editor_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
|
||||
|
@ -1009,6 +1012,7 @@ impl Default for Config {
|
|||
inline_diagnostics: InlineDiagnosticsConfig::default(),
|
||||
end_of_line_diagnostics: DiagnosticFilter::Disable,
|
||||
clipboard_provider: ClipboardProvider::default(),
|
||||
editor_config: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1456,6 +1460,7 @@ impl Editor {
|
|||
// we have fully unregistered this document from its LS
|
||||
doc.language_servers.clear();
|
||||
doc.set_path(Some(path));
|
||||
doc.detect_editor_config();
|
||||
self.refresh_doc_language(doc_id)
|
||||
}
|
||||
|
||||
|
@ -1463,6 +1468,7 @@ impl Editor {
|
|||
let loader = self.syn_loader.clone();
|
||||
let doc = doc_mut!(self, &doc_id);
|
||||
doc.detect_language(loader);
|
||||
doc.detect_editor_config();
|
||||
doc.detect_indent_and_line_ending();
|
||||
self.refresh_language_servers(doc_id);
|
||||
let doc = doc_mut!(self, &doc_id);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue