Skip to content

Commit 1d925d0

Browse files
authored
Correct for minimum indent in braced expressions (#798)
1 parent 9f45875 commit 1d925d0

File tree

1 file changed

+74
-2
lines changed

1 file changed

+74
-2
lines changed

crates/ark/src/lsp/indent.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pub fn indent_edit(doc: &Document, line: usize) -> anyhow::Result<Option<Vec<Ark
8989
(brace_parent_indent(parent), config.indent_size)
9090
};
9191

92+
let (old_indent, old_indent_byte) = line_indent(text, line, config);
93+
9294
// Structured in two stages as in Emacs TS rules: first match, then
9395
// return anchor and indent size. We can add more rules here as needed.
9496
let (anchor, indent) = match bol_parent {
@@ -109,11 +111,42 @@ pub fn indent_edit(doc: &Document, line: usize) -> anyhow::Result<Option<Vec<Ark
109111

110112
(node_line_indent(anchor), config.indent_size)
111113
},
112-
_ => return Ok(None),
114+
_ => {
115+
// Find nearest containing braced expression or top-level node. We'll use
116+
// that to prevent ever indenting past these in unhandled cases for which we
117+
// don't have rules yet: https://github.com/posit-dev/positron/issues/1683
118+
119+
// First climb one level if cursor is in front of a `{` character.
120+
// In that case `node` is the `{` token which is an immediate child
121+
// of the containing `{` expression. We want to indent that braced
122+
// expression relative to the next enclosing `{` expression.
123+
let mut node = node;
124+
if let Some(c) = text_at_indent().next() {
125+
if c == '{' {
126+
if let Some(parent) = node.parent() {
127+
node = parent;
128+
}
129+
}
130+
}
131+
132+
// Find nearest enclosing brace. If there is none, just use current indentation.
133+
let Some(enclosing_brace) = find_enclosing_brace(node) else {
134+
return Ok(None);
135+
};
136+
let (anchor, indent) = brace_indent(enclosing_brace);
137+
138+
// Only correct if we're too far on the left, past the indentation
139+
// implied by the enclosing brace
140+
let min_indent = anchor + indent;
141+
if old_indent >= min_indent {
142+
return Ok(None);
143+
}
144+
145+
(anchor, indent)
146+
},
113147
};
114148

115149
let new_indent = anchor + indent;
116-
let (old_indent, old_indent_byte) = line_indent(text, line, config);
117150

118151
if old_indent == new_indent {
119152
return Ok(None);
@@ -204,6 +237,15 @@ pub fn new_line_indent(config: &IndentationConfig, indent: usize) -> String {
204237
}
205238
}
206239

240+
/// Find the nearest node that is a braced expression
241+
pub fn find_enclosing_brace(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
242+
if let Some(parent) = node.parent() {
243+
parent.ancestors().find(|n| n.is_braced_expression())
244+
} else {
245+
None
246+
}
247+
}
248+
207249
#[cfg(test)]
208250
mod tests {
209251
use stdext::assert_match;
@@ -406,6 +448,36 @@ mod tests {
406448
assert_eq!(text, String::from("function(\n ) {\n \n}"));
407449
}
408450

451+
#[test]
452+
fn test_line_indent_minimum() {
453+
// https://github.com/posit-dev/positron/issues/1683
454+
let mut text = String::from("function() {\n ({\n }\n)\n}");
455+
let doc = test_doc(&text);
456+
457+
let edit = indent_edit(&doc, 3).unwrap().unwrap();
458+
apply_text_edits(edit, &mut text).unwrap();
459+
assert_eq!(text, String::from("function() {\n ({\n }\n )\n}"));
460+
}
461+
462+
#[test]
463+
fn test_line_indent_minimum_nested() {
464+
// Nested R function test with multiple levels of nesting
465+
let mut text = String::from("{\n {\n ({\n }\n )\n }\n}");
466+
let doc = test_doc(&text);
467+
468+
let edit = indent_edit(&doc, 4).unwrap().unwrap();
469+
apply_text_edits(edit, &mut text).unwrap();
470+
assert_eq!(text, String::from("{\n {\n ({\n }\n )\n }\n}"));
471+
}
472+
473+
#[test]
474+
fn test_line_indent_function_opening_brace_own_line() {
475+
let text = String::from("object <- function()\n{\n body\n}");
476+
let doc = test_doc(&text);
477+
478+
assert_match!(indent_edit(&doc, 1).unwrap(), None);
479+
}
480+
409481
#[test]
410482
fn test_new_line_indent() {
411483
let tab_cfg = IndentationConfig {

0 commit comments

Comments
 (0)