@@ -1,6 +1,10 @@
1+use std::collections::HashMap;
2+
3+use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
4+
15use crate::{
26 chars::{char_is_line_ending, char_is_whitespace},
3− syntax::{IndentQuery, LanguageConfiguration, Syntax},
7+ syntax::{LanguageConfiguration, RopeProvider, Syntax},
48 tree_sitter::Node,
59 Rope, RopeSlice,
610};
@@ -186,103 +190,405 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
186190 len / tab_width
187191}
188192
189−/// Find the highest syntax node at position.
190−/// This is to identify the column where this node (e.g., an HTML closing tag) ends.
191−fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Node> {
192− let tree = syntax.tree();
193−
194− // named_descendant
195− let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
196−
197− while let Some(parent) = node.parent() {
198− if parent.start_byte() == node.start_byte() {
199− node = parent
193+/// Computes for node and all ancestors whether they are the first node on their line.
194+/// The first entry in the return value represents the root node, the last one the node itself
195+fn get_first_in_line(mut node: Node, byte_pos: usize, new_line: bool) -> Vec<bool> {
196+ let mut first_in_line = Vec::new();
197+ loop {
198+ if let Some(prev) = node.prev_sibling() {
199+ // If we insert a new line, the first node at/after the cursor is considered to be the first in its line
200+ let first = prev.end_position().row != node.start_position().row
201+ || (new_line && node.start_byte() >= byte_pos && prev.start_byte() < byte_pos);
202+ first_in_line.push(Some(first));
203+ } else {
204+ // Nodes that have no previous siblings are first in their line if and only if their parent is
205+ // (which we don't know yet)
206+ first_in_line.push(None);
207+ }
208+ if let Some(parent) = node.parent() {
209+ node = parent;
200210 } else {
201211 break;
202212 }
203213 }
204214
205− Some(node)
215+ let mut result = Vec::with_capacity(first_in_line.len());
216+ let mut parent_is_first = true; // The root node is by definition the first node in its line
217+ for first in first_in_line.into_iter().rev() {
218+ if let Some(first) = first {
219+ result.push(first);
220+ parent_is_first = first;
221+ } else {
222+ result.push(parent_is_first);
223+ }
224+ }
225+ result
206226}
207227
208−/// Calculate the indentation at a given treesitter node.
209−/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
210−/// This is because the indentation is only increased starting at the second line of the node.
211−fn calculate_indentation(
212− query: &IndentQuery,
213− node: Option<Node>,
214− line: usize,
215− newline: bool,
216−) -> usize {
217− let mut increment: isize = 0;
218−
219− let mut node = match node {
220− Some(node) => node,
221− None => return 0,
222− };
228+/// The total indent for some line of code.
229+/// This is usually constructed in one of 2 ways:
230+/// - Successively add indent captures to get the (added) indent from a single line
231+/// - Successively add the indent results for each line
232+#[derive(Default)]
233+struct Indentation {
234+ /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
235+ /// The string that this results in depends on the indent style (spaces or tabs, etc.)
236+ indent: usize,
237+ outdent: usize,
238+}
239+impl Indentation {
240+ /// Add some other [IndentResult] to this.
241+ /// The added indent should be the total added indent from one line
242+ fn add_line(&mut self, added: &Indentation) {
243+ if added.indent > 0 && added.outdent == 0 {
244+ self.indent += 1;
245+ } else if added.outdent > 0 && added.indent == 0 {
246+ self.outdent += 1;
247+ }
248+ }
249+ /// Add an indent capture to this indent.
250+ /// All the captures that are added in this way should be on the same line.
251+ fn add_capture(&mut self, added: IndentCaptureType) {
252+ match added {
253+ IndentCaptureType::Indent => {
254+ self.indent = 1;
255+ }
256+ IndentCaptureType::Outdent => {
257+ self.outdent = 1;
258+ }
259+ }
260+ }
261+ fn as_string(&self, indent_style: &IndentStyle) -> String {
262+ let indent_level = if self.indent >= self.outdent {
263+ self.indent - self.outdent
264+ } else {
265+ log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
266+ 0
267+ };
268+ indent_style.as_str().repeat(indent_level)
269+ }
270+}
223271
224− let mut current_line = line;
225− let mut consider_indent = newline;
226− let mut increment_from_line: isize = 0;
272+/// An indent definition which corresponds to a capture from the indent query
273+struct IndentCapture {
274+ capture_type: IndentCaptureType,
275+ scope: IndentScope,
276+}
277+#[derive(Clone, Copy)]
278+enum IndentCaptureType {
279+ Indent,
280+ Outdent,
281+}
282+impl IndentCaptureType {
283+ fn default_scope(&self) -> IndentScope {
284+ match self {
285+ IndentCaptureType::Indent => IndentScope::Tail,
286+ IndentCaptureType::Outdent => IndentScope::All,
287+ }
288+ }
289+}
290+/// This defines which part of a node an [IndentCapture] applies to.
291+/// Each [IndentCaptureType] has a default scope, but the scope can be changed
292+/// with `#set!` property declarations.
293+#[derive(Clone, Copy)]
294+enum IndentScope {
295+ /// The indent applies to the whole node
296+ All,
297+ /// The indent applies to everything except for the first line of the node
298+ Tail,
299+}
227300
228− loop {
229− let node_kind = node.kind();
230− let start = node.start_position().row;
231− if current_line != start {
232− // Indent/dedent by at most one per line:
233− // .map(|a| { <-- ({ is two scopes
234− // let len = 1; <-- indents one level
235− // }) <-- }) is two scopes
236− if consider_indent || increment_from_line < 0 {
237− increment += increment_from_line.signum();
301+/// Execute the indent query.
302+/// Returns for each node (identified by its id) a list of indent captures for that node.
303+fn query_indents(
304+ query: &Query,
305+ syntax: &Syntax,
306+ cursor: &mut QueryCursor,
307+ text: RopeSlice,
308+ range: std::ops::Range<usize>,
309+ // Position of the (optional) newly inserted line break.
310+ // Given as (line, byte_pos)
311+ new_line_break: Option<(usize, usize)>,
312+) -> HashMap<usize, Vec<IndentCapture>> {
313+ let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
314+ cursor.set_byte_range(range);
315+ // Iterate over all captures from the query
316+ for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
317+ // Skip matches where not all custom predicates are fulfilled
318+ if !query.general_predicates(m.pattern_index).iter().all(|pred| {
319+ match pred.operator.as_ref() {
320+ "not-kind-eq?" => match (pred.args.get(0), pred.args.get(1)) {
321+ (
322+ Some(QueryPredicateArg::Capture(capture_idx)),
323+ Some(QueryPredicateArg::String(kind)),
324+ ) => {
325+ let node = m.nodes_for_capture_index(*capture_idx).next();
326+ match node {
327+ Some(node) => node.kind()!=kind.as_ref(),
328+ _ => true,
329+ }
330+ }
331+ _ => {
332+ panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
333+ }
334+ },
335+ "same-line?" | "not-same-line?" => {
336+ match (pred.args.get(0), pred.args.get(1)) {
337+ (
338+ Some(QueryPredicateArg::Capture(capt1)),
339+ Some(QueryPredicateArg::Capture(capt2))
340+ ) => {
341+ let get_line_num = |node: Node| {
342+ let mut node_line = node.start_position().row;
343+ // Adjust for the new line that will be inserted
344+ if let Some((line, byte)) = new_line_break {
345+ if node_line==line && node.start_byte()>=byte {
346+ node_line += 1;
347+ }
348+ }
349+ node_line
350+ };
351+ let n1 = m.nodes_for_capture_index(*capt1).next();
352+ let n2 = m.nodes_for_capture_index(*capt2).next();
353+ match (n1, n2) {
354+ (Some(n1), Some(n2)) => {
355+ let same_line = get_line_num(n1)==get_line_num(n2);
356+ same_line==(pred.operator.as_ref()=="same-line?")
357+ }
358+ _ => true,
359+ }
360+ }
361+ _ => {
362+ panic!("Invalid indent query: Arguments to \"{}\" must be 2 captures", pred.operator);
363+ }
364+ }
365+ }
366+ _ => {
367+ panic!(
368+ "Invalid indent query: Unknown predicate (\"{}\")",
369+ pred.operator
370+ );
371+ }
238372 }
239− increment_from_line = 0;
240− current_line = start;
241− consider_indent = true;
373+ }) {
374+ continue;
242375 }
243−
244− if query.outdent.contains(node_kind) {
245− increment_from_line -= 1;
376+ for capture in m.captures {
377+ let capture_type = query.capture_names()[capture.index as usize].as_str();
378+ let capture_type = match capture_type {
379+ "indent" => IndentCaptureType::Indent,
380+ "outdent" => IndentCaptureType::Outdent,
381+ _ => {
382+ // Ignore any unknown captures (these may be needed for predicates such as #match?)
383+ continue;
384+ }
385+ };
386+ let scope = capture_type.default_scope();
387+ let mut indent_capture = IndentCapture {
388+ capture_type,
389+ scope,
390+ };
391+ // Apply additional settings for this capture
392+ for property in query.property_settings(m.pattern_index) {
393+ match property.key.as_ref() {
394+ "scope" => {
395+ indent_capture.scope = match property.value.as_deref() {
396+ Some("all") => IndentScope::All,
397+ Some("tail") => IndentScope::Tail,
398+ Some(s) => {
399+ panic!("Invalid indent query: Unknown value for \"scope\" property (\"{}\")", s);
400+ }
401+ None => {
402+ panic!(
403+ "Invalid indent query: Missing value for \"scope\" property"
404+ );
405+ }
406+ }
407+ }
408+ _ => {
409+ panic!(
410+ "Invalid indent query: Unknown property \"{}\"",
411+ property.key
412+ );
413+ }
414+ }
415+ }
416+ indent_captures
417+ .entry(capture.node.id())
418+ // Most entries only need to contain a single IndentCapture
419+ .or_insert_with(|| Vec::with_capacity(1))
420+ .push(indent_capture);
246421 }
247− if query.indent.contains(node_kind) {
248− increment_from_line += 1;
422+ }
423+ indent_captures
424+}
425+
426+/// Use the syntax tree to determine the indentation for a given position.
427+/// This can be used in 2 ways:
428+///
429+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
430+/// - In this case, pos should be inside the first tree-sitter node on that line.
431+/// In most cases, this can just be the first non-whitespace on that line.
432+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
433+/// after pos were moved to a new line.
434+///
435+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
436+/// Each of these nodes produces some [AddedIndent] for:
437+///
438+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
439+/// - The line after the node. This is defined by:
440+/// - The scope `tail`.
441+/// - The scope `all` if this node is not the first node on its line.
442+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
443+/// The indents from different nodes for the same line are then combined.
444+/// The [IndentResult] is simply the sum of the [AddedIndent] for all lines.
445+///
446+/// Specifying which line exactly an [AddedIndent] applies to is important because indents on the same line combine differently than indents on different lines:
447+/// ```ignore
448+/// some_function(|| {
449+/// // Both the function parameters as well as the contained block should be indented.
450+/// // Because they are on the same line, this only yields one indent level
451+/// });
452+/// ```
453+///
454+/// ```ignore
455+/// some_function(
456+/// parm1,
457+/// || {
458+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
459+/// },
460+/// );
461+/// ```
462+pub fn treesitter_indent_for_pos(
463+ query: &Query,
464+ syntax: &Syntax,
465+ indent_style: &IndentStyle,
466+ text: RopeSlice,
467+ line: usize,
468+ pos: usize,
469+ new_line: bool,
470+) -> Option<String> {
471+ let byte_pos = text.char_to_byte(pos);
472+ let mut node = syntax
473+ .tree()
474+ .root_node()
475+ .descendant_for_byte_range(byte_pos, byte_pos)?;
476+ let mut first_in_line = get_first_in_line(node, byte_pos, new_line);
477+ let new_line_break = if new_line {
478+ Some((line, byte_pos))
479+ } else {
480+ None
481+ };
482+ let query_result = crate::syntax::PARSER.with(|ts_parser| {
483+ let mut ts_parser = ts_parser.borrow_mut();
484+ let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
485+ let query_result = query_indents(
486+ query,
487+ syntax,
488+ &mut cursor,
489+ text,
490+ byte_pos..byte_pos + 1,
491+ new_line_break,
492+ );
493+ ts_parser.cursors.push(cursor);
494+ query_result
495+ });
496+
497+ let mut result = Indentation::default();
498+ // We always keep track of all the indent changes on one line, in order to only indent once
499+ // even if there are multiple "indent" nodes on the same line
500+ let mut indent_for_line = Indentation::default();
501+ let mut indent_for_line_below = Indentation::default();
502+ loop {
503+ // This can safely be unwrapped because `first_in_line` contains
504+ // one entry for each ancestor of the node (which is what we iterate over)
505+ let is_first = *first_in_line.last().unwrap();
506+ // Apply all indent definitions for this node
507+ if let Some(definitions) = query_result.get(&node.id()) {
508+ for definition in definitions {
509+ match definition.scope {
510+ IndentScope::All => {
511+ if is_first {
512+ indent_for_line.add_capture(definition.capture_type);
513+ } else {
514+ indent_for_line_below.add_capture(definition.capture_type);
515+ }
516+ }
517+ IndentScope::Tail => {
518+ indent_for_line_below.add_capture(definition.capture_type);
519+ }
520+ }
521+ }
249522 }
250523
251524 if let Some(parent) = node.parent() {
525+ let mut node_line = node.start_position().row;
526+ let mut parent_line = parent.start_position().row;
527+ if node_line == line && new_line {
528+ // Also consider the line that will be inserted
529+ if node.start_byte() >= byte_pos {
530+ node_line += 1;
531+ }
532+ if parent.start_byte() >= byte_pos {
533+ parent_line += 1;
534+ }
535+ };
536+ if node_line != parent_line {
537+ if node_line < line + (new_line as usize) {
538+ // Don't add indent for the line below the line of the query
539+ result.add_line(&indent_for_line_below);
540+ }
541+ if node_line == parent_line + 1 {
542+ indent_for_line_below = indent_for_line;
543+ } else {
544+ result.add_line(&indent_for_line);
545+ indent_for_line_below = Indentation::default();
546+ }
547+ indent_for_line = Indentation::default();
548+ }
549+
252550 node = parent;
551+ first_in_line.pop();
253552 } else {
553+ result.add_line(&indent_for_line_below);
554+ result.add_line(&indent_for_line);
254555 break;
255556 }
256557 }
257− if consider_indent || increment_from_line < 0 {
258− increment += increment_from_line.signum();
259− }
260− increment.max(0) as usize
558+ Some(result.as_string(indent_style))
261559}
262560
263−// TODO: two usecases: if we are triggering this for a new, blank line:
264−// - it should return 0 when mass indenting stuff
265−// - it should look up the wrapper node and count it too when we press o/O
266−pub fn suggested_indent_for_pos(
561+/// Returns the indentation for a new line.
562+/// This is done either using treesitter, or if that's not available by copying the indentation from the current line
563+#[allow(clippy::too_many_arguments)]
564+pub fn indent_for_newline(
267565 language_config: Option<&LanguageConfiguration>,
268566 syntax: Option<&Syntax>,
567+ indent_style: &IndentStyle,
568+ tab_width: usize,
269569 text: RopeSlice,
270− pos: usize,
271− line: usize,
272− new_line: bool,
273−) -> Option<usize> {
570+ line_before: usize,
571+ line_before_end_pos: usize,
572+ current_line: usize,
573+) -> String {
274574 if let (Some(query), Some(syntax)) = (
275575 language_config.and_then(|config| config.indent_query()),
276576 syntax,
277577 ) {
278− let byte_start = text.char_to_byte(pos);
279− let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
280− // TODO: special case for comments
281− // TODO: if preserve_leading_whitespace
282− Some(calculate_indentation(query, node, line, new_line))
283− } else {
284− None
578+ if let Some(indent) = treesitter_indent_for_pos(
579+ query,
580+ syntax,
581+ indent_style,
582+ text,
583+ line_before,
584+ line_before_end_pos,
585+ true,
586+ ) {
587+ return indent;
588+ };
285589 }
590+ let indent_level = indent_level_for_line(text.line(current_line), tab_width);
591+ indent_style.as_str().repeat(indent_level)
286592}
287593
288594pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
@@ -326,156 +632,4 @@ mod test {
326632 let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
327633 assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
328634 }
329−
330− #[test]
331− fn test_suggested_indent_for_line() {
332− let doc = Rope::from(
333− "
334−use std::{
335− io::{self, stdout, Stdout, Write},
336− path::PathBuf,
337− sync::Arc,
338− time::Duration,
339−}
340−mod test {
341− fn hello_world() {
342− 1 + 1;
343−
344− let does_indentation_work = 1;
345−
346− let test_function = function_with_param(this_param,
347− that_param
348− );
349−
350− let test_function = function_with_param(
351− this_param,
352− that_param
353− );
354−
355− let test_function = function_with_proper_indent(param1,
356− param2,
357− );
358−
359− let selection = Selection::new(
360− changes
361− .clone()
362− .map(|(start, end, text): (usize, usize, Option<Tendril>)| {
363− let len = text.map(|text| text.len()).unwrap() - 1; // minus newline
364− let pos = start + len;
365− Range::new(pos, pos)
366− })
367− .collect(),
368− 0,
369− );
370−
371− return;
372− }
373−}
374−
375−impl<A, D> MyTrait<A, D> for YourType
376−where
377− A: TraitB + TraitC,
378− D: TraitE + TraitF,
379−{
380−
381−}
382−#[test]
383−//
384−match test {
385− Some(a) => 1,
386− None => {
387− unimplemented!()
388− }
389−}
390−std::panic::set_hook(Box::new(move |info| {
391− hook(info);
392−}));
393−
394−{ { {
395− 1
396−}}}
397−
398−pub fn change<I>(document: &Document, changes: I) -> Self
399−where
400− I: IntoIterator<Item = Change> + ExactSizeIterator,
401−{
402− [
403− 1,
404− 2,
405− 3,
406− ];
407− (
408− 1,
409− 2
410− );
411− true
412−}
413−",
414− );
415−
416− let doc = doc;
417− use crate::diagnostic::Severity;
418− use crate::syntax::{
419− Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
420− };
421− use once_cell::sync::OnceCell;
422− let loader = Loader::new(Configuration {
423− language: vec![LanguageConfiguration {
424− scope: "source.rust".to_string(),
425− file_types: vec!["rs".to_string()],
426− shebangs: vec![],
427− language_id: "Rust".to_string(),
428− highlight_config: OnceCell::new(),
429− config: None,
430− //
431− injection_regex: None,
432− roots: vec![],
433− comment_token: None,
434− auto_format: false,
435− diagnostic_severity: Severity::Warning,
436− grammar: None,
437− language_server: None,
438− indent: Some(IndentationConfiguration {
439− tab_width: 4,
440− unit: String::from(" "),
441− }),
442− indent_query: OnceCell::new(),
443− textobject_query: OnceCell::new(),
444− debugger: None,
445− auto_pairs: None,
446− }],
447− });
448−
449− // set runtime path so we can find the queries
450− let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
451− runtime.push("../runtime");
452− std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
453−
454− let language_config = loader.language_config_for_scope("source.rust").unwrap();
455− let highlight_config = language_config.highlight_config(&[]).unwrap();
456− let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader));
457− let text = doc.slice(..);
458− let tab_width = 4;
459−
460− for i in 0..doc.len_lines() {
461− let line = text.line(i);
462− if let Some(pos) = crate::find_first_non_whitespace_char(line) {
463− let indent = indent_level_for_line(line, tab_width);
464− assert_eq!(
465− suggested_indent_for_pos(
466− Some(&language_config),
467− Some(&syntax),
468− text,
469− text.line_to_char(i) + pos,
470− i,
471− false
472− ),
473− Some(indent),
474− "line {}: \"{}\"",
475− i,
476− line
477− );
478− }
479− }
480− }
481635}