Skip to content

Commit 8e36075

Browse files
committed
add a separate "helix:" modeline for setting helix-specific config
for example, if the language name differs between vim and helix
1 parent e960e1e commit 8e36075

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

helix-core/src/modeline.rs

+61-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ use once_cell::sync::Lazy;
44

55
use crate::indent::IndentStyle;
66
use crate::regex::Regex;
7+
use crate::syntax::ModelineConfig;
78
use crate::{LineEnding, RopeSlice};
89

910
// 5 is the vim default
1011
const LINES_TO_CHECK: usize = 5;
1112
const LENGTH_TO_CHECK: usize = 256;
1213

13-
static MODELINE_REGEX: Lazy<Regex> =
14+
static VIM_MODELINE_REGEX: Lazy<Regex> =
1415
Lazy::new(|| Regex::new(r"^(\S*\s+)?(vi|[vV]im[<=>]?\d*|ex):\s*(set?\s+)?").unwrap());
16+
static HELIX_MODELINE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\S*\s+)?helix:").unwrap());
1517

1618
#[derive(Default, Debug, Eq, PartialEq)]
1719
pub struct Modeline {
@@ -65,7 +67,8 @@ impl Modeline {
6567
};
6668
c == ' ' || c == '\t'
6769
};
68-
if let Some(pos) = MODELINE_REGEX.find(line) {
70+
71+
if let Some(pos) = VIM_MODELINE_REGEX.find(line) {
6972
for option in line[pos.end()..].split(split_modeline) {
7073
let parts: Vec<_> = option.split('=').collect();
7174
match parts[0] {
@@ -93,6 +96,27 @@ impl Modeline {
9396
}
9497
}
9598
}
99+
100+
if let Some(pos) = HELIX_MODELINE_REGEX.find(line) {
101+
let config = &line[pos.end()..];
102+
match toml::from_str::<ModelineConfig>(config) {
103+
Ok(modeline) => {
104+
if let Some(language) = modeline.language {
105+
self.language = Some(language);
106+
}
107+
if let Some(indent) = modeline.indent {
108+
self.indent_style = Some(IndentStyle::from_str(&indent.unit));
109+
}
110+
if let Some(line_ending) = modeline.line_ending {
111+
self.line_ending = LineEnding::from_str(&line_ending);
112+
if self.line_ending.is_none() {
113+
log::warn!("could not interpret line ending {line_ending:?}");
114+
}
115+
}
116+
}
117+
Err(e) => log::warn!("{e}"),
118+
}
119+
}
96120
}
97121
}
98122

@@ -223,6 +247,41 @@ mod test {
223247
..Default::default()
224248
},
225249
),
250+
(
251+
"# helix: language = 'perl'",
252+
Modeline {
253+
language: Some("perl".to_string()),
254+
..Default::default()
255+
},
256+
),
257+
(
258+
"# helix: indent = { unit = ' ' }",
259+
Modeline {
260+
indent_style: Some(IndentStyle::Spaces(3)),
261+
..Default::default()
262+
},
263+
),
264+
(
265+
"# helix: indent = { unit = \"\t\" }",
266+
Modeline {
267+
indent_style: Some(IndentStyle::Tabs),
268+
..Default::default()
269+
},
270+
),
271+
(
272+
"# helix: indent = { unit = \"\\t\" }",
273+
Modeline {
274+
indent_style: Some(IndentStyle::Tabs),
275+
..Default::default()
276+
},
277+
),
278+
(
279+
"# helix: line-ending = \"\\r\\n\"",
280+
Modeline {
281+
line_ending: Some(LineEnding::Crlf),
282+
..Default::default()
283+
},
284+
),
226285
];
227286
for (line, expected) in tests {
228287
let mut got = Modeline::default();

helix-core/src/syntax.rs

+18
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,18 @@ pub struct LanguageConfiguration {
156156
pub workspace_lsp_roots: Option<Vec<PathBuf>>,
157157
}
158158

159+
/// The subset of LanguageConfig which can be read from a modeline.
160+
#[derive(Debug, Serialize, Deserialize)]
161+
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
162+
pub struct ModelineConfig {
163+
/// the language name (corresponds to language_id in LanguageConfig)
164+
pub language: Option<String>,
165+
/// the indent settings (only unit is supported in modelines)
166+
pub indent: Option<ModelineIndentationConfiguration>,
167+
/// the line ending to use (as a literal string)
168+
pub line_ending: Option<String>,
169+
}
170+
159171
#[derive(Debug, PartialEq, Eq, Hash)]
160172
pub enum FileType {
161173
/// The extension of the file, either the `Path::extension` or the full
@@ -436,6 +448,12 @@ pub struct DebuggerQuirks {
436448
pub absolute_paths: bool,
437449
}
438450

451+
#[derive(Debug, Serialize, Deserialize)]
452+
#[serde(rename_all = "kebab-case")]
453+
pub struct ModelineIndentationConfiguration {
454+
pub unit: String,
455+
}
456+
439457
#[derive(Debug, Serialize, Deserialize)]
440458
#[serde(rename_all = "kebab-case")]
441459
pub struct IndentationConfiguration {

0 commit comments

Comments
 (0)