aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/tests/data/indent/cpp.cpp48
l---------helix-core/tests/data/indent/indent.rs1
-rw-r--r--helix-core/tests/data/indent/languages.toml13
-rw-r--r--helix-core/tests/indent.rs172
4 files changed, 211 insertions, 23 deletions
diff --git a/helix-core/tests/data/indent/cpp.cpp b/helix-core/tests/data/indent/cpp.cpp
new file mode 100644
index 00000000..6e7f3a88
--- /dev/null
+++ b/helix-core/tests/data/indent/cpp.cpp
@@ -0,0 +1,48 @@
+std::vector<std::string>
+fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
+ char* parm5, bool parm6);
+
+std::vector<std::string>
+fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
+ char* parm5, bool parm6) {
+ auto lambda = []() {
+ return 0;
+ };
+ auto lambda_with_a_really_long_name_that_uses_a_whole_line
+ = [](int some_more_aligned_parameters,
+ std::string parm2) {
+ do_smth();
+ };
+ if (brace_on_same_line) {
+ do_smth();
+ } else if (brace_on_next_line)
+ {
+ do_smth();
+ } else if (another_condition) {
+ do_smth();
+ }
+ else {
+ do_smth();
+ }
+ if (inline_if_statement)
+ do_smth();
+ if (another_inline_if_statement)
+ return [](int parm1, char* parm2) {
+ this_is_a_really_pointless_lambda();
+ };
+
+ switch (var) {
+ case true:
+ return -1;
+ case false:
+ return 42;
+ }
+}
+
+class MyClass : public MyBaseClass {
+public:
+ MyClass();
+ void public_fn();
+private:
+ super_secret_private_fn();
+}
diff --git a/helix-core/tests/data/indent/indent.rs b/helix-core/tests/data/indent/indent.rs
deleted file mode 120000
index 2ac16cf9..00000000
--- a/helix-core/tests/data/indent/indent.rs
+++ /dev/null
@@ -1 +0,0 @@
-../../../src/indent.rs \ No newline at end of file
diff --git a/helix-core/tests/data/indent/languages.toml b/helix-core/tests/data/indent/languages.toml
index 3206f124..fa02e451 100644
--- a/helix-core/tests/data/indent/languages.toml
+++ b/helix-core/tests/data/indent/languages.toml
@@ -11,3 +11,16 @@ indent = { tab-width = 4, unit = " " }
[[grammar]]
name = "rust"
source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
+
+[[language]]
+name = "cpp"
+scope = "source.cpp"
+injection-regex = "cpp"
+file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H"]
+roots = []
+comment-token = "//"
+indent = { tab-width = 2, unit = " " }
+
+[[grammar]]
+name = "cpp"
+source = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "2d2c4aee8672af4c7c8edff68e7dd4c07e88d2b1" }
diff --git a/helix-core/tests/indent.rs b/helix-core/tests/indent.rs
index 1dec9989..26010c90 100644
--- a/helix-core/tests/indent.rs
+++ b/helix-core/tests/indent.rs
@@ -1,20 +1,122 @@
use helix_core::{
indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
- syntax::Loader,
+ syntax::{Configuration, Loader},
Syntax,
};
-use std::path::PathBuf;
+use ropey::Rope;
+use std::{ops::Range, path::PathBuf, process::Command};
#[test]
fn test_treesitter_indent_rust() {
- test_treesitter_indent("rust.rs", "source.rust");
+ standard_treesitter_test("rust.rs", "source.rust");
}
+
+#[test]
+fn test_treesitter_indent_cpp() {
+ standard_treesitter_test("cpp.cpp", "source.cpp");
+}
+
#[test]
-fn test_treesitter_indent_rust_2() {
- test_treesitter_indent("indent.rs", "source.rust");
- // TODO Use commands.rs as indentation test.
- // Currently this fails because we can't align the parameters of a closure yet
- // test_treesitter_indent("commands.rs", "source.rust");
+fn test_treesitter_indent_rust_helix() {
+ // We pin a specific git revision to prevent unrelated changes from causing the indent tests to fail.
+ // Ideally, someone updates this once in a while and fixes any errors that occur.
+ let rev = "af382768cdaf89ff547dbd8f644a1bddd90e7c8f";
+ let files = Command::new("git")
+ .args([
+ "ls-tree",
+ "-r",
+ "--name-only",
+ "--full-tree",
+ rev,
+ "helix-term/src",
+ ])
+ .output()
+ .unwrap();
+ let files = String::from_utf8(files.stdout).unwrap();
+
+ let ignored_files = vec![
+ // Contains many macros that tree-sitter does not parse in a meaningful way and is otherwise not very interesting
+ "helix-term/src/health.rs",
+ ];
+
+ for file in files.split_whitespace() {
+ if ignored_files.contains(&file) {
+ continue;
+ }
+ let ignored_lines: Vec<Range<usize>> = match file {
+ "helix-term/src/application.rs" => vec![
+ // We can't handle complicated indent rules inside macros (`json!` in this case) since
+ // the tree-sitter grammar only parses them as `token_tree` and `identifier` nodes.
+ 1045..1051,
+ ],
+ "helix-term/src/commands.rs" => vec![
+ // This is broken because of the current handling of `call_expression`
+ // (i.e. having an indent query for it but outdenting again in specific cases).
+ // The indent query is needed to correctly handle multi-line arguments in function calls
+ // inside indented `field_expression` nodes (which occurs fairly often).
+ //
+ // Once we have the `@indent.always` capture type, it might be possible to just have an indent
+ // capture for the `arguments` field of a call expression. That could enable us to correctly
+ // handle this.
+ 2226..2230,
+ ],
+ "helix-term/src/commands/dap.rs" => vec![
+ // Complex `format!` macro
+ 46..52,
+ ],
+ "helix-term/src/commands/lsp.rs" => vec![
+ // Macro
+ 624..627,
+ // Return type declaration of a closure. `cargo fmt` adds an additional space here,
+ // which we cannot (yet) model with our indent queries.
+ 878..879,
+ // Same as in `helix-term/src/commands.rs`
+ 1335..1343,
+ ],
+ "helix-term/src/config.rs" => vec![
+ // Multiline string
+ 146..152,
+ ],
+ "helix-term/src/keymap.rs" => vec![
+ // Complex macro (see above)
+ 456..470,
+ // Multiline string without indent
+ 563..567,
+ ],
+ "helix-term/src/main.rs" => vec![
+ // Multiline string
+ 44..70,
+ ],
+ "helix-term/src/ui/completion.rs" => vec![
+ // Macro
+ 218..232,
+ ],
+ "helix-term/src/ui/editor.rs" => vec![
+ // The chained function calls here are not indented, probably because of the comment
+ // in between. Since `cargo fmt` doesn't even attempt to format it, there's probably
+ // no point in trying to indent this correctly.
+ 342..350,
+ ],
+ "helix-term/src/ui/lsp.rs" => vec![
+ // Macro
+ 56..61,
+ ],
+ "helix-term/src/ui/statusline.rs" => vec![
+ // Same as in `helix-term/src/commands.rs`
+ 436..442,
+ 450..456,
+ ],
+ _ => Vec::new(),
+ };
+
+ let git_object = rev.to_string() + ":" + file;
+ let content = Command::new("git")
+ .args(["cat-file", "blob", &git_object])
+ .output()
+ .unwrap();
+ let doc = Rope::from_reader(&mut content.stdout.as_slice()).unwrap();
+ test_treesitter_indent(file, doc, "source.rust", ignored_lines);
+ }
}
#[test]
@@ -50,20 +152,41 @@ fn test_indent_level_for_line_with_spaces_and_tabs() {
assert_eq!(indent_level, 2)
}
-fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
+fn indent_tests_dir() -> PathBuf {
let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_dir.push("tests/data/indent");
+ test_dir
+}
+
+fn indent_test_path(name: &str) -> PathBuf {
+ let mut path = indent_tests_dir();
+ path.push(name);
+ path
+}
- let mut test_file = test_dir.clone();
- test_file.push(file_name);
- let test_file = std::fs::File::open(test_file).unwrap();
+fn indent_tests_config() -> Configuration {
+ let mut config_path = indent_tests_dir();
+ config_path.push("languages.toml");
+ let config = std::fs::read_to_string(config_path).unwrap();
+ toml::from_str(&config).unwrap()
+}
+
+fn standard_treesitter_test(file_name: &str, lang_scope: &str) {
+ let test_path = indent_test_path(file_name);
+ let test_file = std::fs::File::open(test_path).unwrap();
let doc = ropey::Rope::from_reader(test_file).unwrap();
+ test_treesitter_indent(file_name, doc, lang_scope, Vec::new())
+}
- let mut config_file = test_dir;
- config_file.push("languages.toml");
- let config = std::fs::read_to_string(config_file).unwrap();
- let config = toml::from_str(&config).unwrap();
- let loader = Loader::new(config);
+/// Test that all the lines in the given file are indented as expected.
+/// ignored_lines is a list of (1-indexed) line ranges that are excluded from this test.
+fn test_treesitter_indent(
+ test_name: &str,
+ doc: Rope,
+ lang_scope: &str,
+ ignored_lines: Vec<std::ops::Range<usize>>,
+) {
+ let loader = Loader::new(indent_tests_config());
// set runtime path so we can find the queries
let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -71,6 +194,7 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
let language_config = loader.language_config_for_scope(lang_scope).unwrap();
+ let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit);
let highlight_config = language_config.highlight_config(&[]).unwrap();
let text = doc.slice(..);
let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
@@ -78,14 +202,17 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
for i in 0..doc.len_lines() {
let line = text.line(i);
+ if ignored_lines.iter().any(|range| range.contains(&(i + 1))) {
+ continue;
+ }
if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
- let tab_and_indent_width: usize = 4;
+ let tab_width: usize = 4;
let suggested_indent = treesitter_indent_for_pos(
indent_query,
&syntax,
- &IndentStyle::Spaces(tab_and_indent_width as u8),
- tab_and_indent_width,
- tab_and_indent_width,
+ &indent_style,
+ tab_width,
+ indent_style.indent_width(tab_width),
text,
i,
text.line_to_char(i) + pos,
@@ -94,7 +221,8 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
.unwrap();
assert!(
line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
- "Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
+ "Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
+ test_name,
i+1,
line.slice(..line.len_chars()-1),
suggested_indent,