From bc6e981e812afc4e7be8cb5fde88dcbae652ddad Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Thu, 7 Mar 2024 21:34:47 +0800 Subject: [PATCH] feat: support folding_range api --- .../src/analysis/lexical_hierarchy.rs | 222 +++++++++++++++++ crates/tinymist-query/src/analysis/mod.rs | 6 +- .../analysis/{analyze.rs => track_values.rs} | 0 crates/tinymist-query/src/document_symbol.rs | 225 +----------------- crates/tinymist-query/src/folding_range.rs | 137 ++++++++++- crates/tinymist-query/src/lib.rs | 9 +- crates/tinymist-query/src/prelude.rs | 5 +- crates/tinymist-query/src/symbol.rs | 21 +- crates/tinymist/src/lib.rs | 23 +- 9 files changed, 405 insertions(+), 243 deletions(-) create mode 100644 crates/tinymist-query/src/analysis/lexical_hierarchy.rs rename crates/tinymist-query/src/analysis/{analyze.rs => track_values.rs} (100%) diff --git a/crates/tinymist-query/src/analysis/lexical_hierarchy.rs b/crates/tinymist-query/src/analysis/lexical_hierarchy.rs new file mode 100644 index 00000000..cc141ff8 --- /dev/null +++ b/crates/tinymist-query/src/analysis/lexical_hierarchy.rs @@ -0,0 +1,222 @@ +use std::ops::Range; + +use anyhow::anyhow; +use tower_lsp::lsp_types::SymbolKind; +use typst::syntax::{ast, LinkedNode, Source, SyntaxKind}; +use typst_ts_core::typst::prelude::{eco_vec, EcoVec}; + +#[derive(Debug, Clone, Copy, Hash)] +pub(crate) enum LexicalKind { + Namespace(i16), + Variable, + Function, + Constant, + Block, +} + +impl TryFrom for SymbolKind { + type Error = (); + + fn try_from(value: LexicalKind) -> Result { + match value { + LexicalKind::Namespace(..) => Ok(SymbolKind::NAMESPACE), + LexicalKind::Variable => Ok(SymbolKind::VARIABLE), + LexicalKind::Function => Ok(SymbolKind::FUNCTION), + LexicalKind::Constant => Ok(SymbolKind::CONSTANT), + LexicalKind::Block => Err(()), + } + } +} + +#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq)] +pub(crate) enum LexicalScopeKind { + #[default] + Symbol, + Block, +} + +#[derive(Debug, Clone, Hash)] +pub(crate) struct LexicalInfo { + pub name: String, + pub kind: LexicalKind, + pub range: Range, +} + +#[derive(Debug, Clone, Hash)] +pub(crate) struct LexicalHierarchy { + pub info: LexicalInfo, + pub children: Option>>, +} + +pub(crate) fn get_lexical_hierarchy( + source: Source, + g: LexicalScopeKind, +) -> Option> { + fn symbreak(sym: LexicalInfo, curr: EcoVec) -> LexicalHierarchy { + LexicalHierarchy { + info: sym, + children: if curr.is_empty() { + None + } else { + Some(comemo::Prehashed::new(curr)) + }, + } + } + + #[derive(Default)] + struct LexicalHierarchyWorker { + g: LexicalScopeKind, + stack: Vec<(LexicalInfo, EcoVec)>, + } + + impl LexicalHierarchyWorker { + fn symbreak(&mut self) { + let (symbol, children) = self.stack.pop().unwrap(); + let current = &mut self.stack.last_mut().unwrap().1; + + // symbol.wide_range = children + // .iter() + // .map(|c| c.info.wide_range.clone()) + // .fold(symbol.range.clone(), |acc, r| { + // acc.start.min(r.start)..acc.end.max(r.end) + // }); + + current.push(symbreak(symbol, children)); + } + + /// Get all symbols for a node recursively. + fn get_symbols(&mut self, node: LinkedNode) -> anyhow::Result<()> { + let own_symbol = get_ident(&node, self.g)?; + + if let Some(symbol) = own_symbol { + if let LexicalKind::Namespace(level) = symbol.kind { + 'heading_break: while let Some((w, _)) = self.stack.last() { + match w.kind { + LexicalKind::Namespace(l) if l < level => break 'heading_break, + LexicalKind::Block => break 'heading_break, + _ if self.stack.len() <= 1 => break 'heading_break, + _ => {} + } + + self.symbreak(); + } + } + let is_heading = matches!(symbol.kind, LexicalKind::Namespace(..)); + + self.stack.push((symbol, eco_vec![])); + let stack_height = self.stack.len(); + + for child in node.children() { + self.get_symbols(child)?; + } + + if is_heading { + while stack_height < self.stack.len() { + self.symbreak(); + } + } else { + while stack_height <= self.stack.len() { + self.symbreak(); + } + } + } else { + for child in node.children() { + self.get_symbols(child)?; + } + } + + Ok(()) + } + } + + /// Get symbol for a leaf node of a valid type, or `None` if the node is an + /// invalid type. + #[allow(deprecated)] + fn get_ident(node: &LinkedNode, g: LexicalScopeKind) -> anyhow::Result> { + let (name, kind) = match node.kind() { + SyntaxKind::Label if LexicalScopeKind::Block != g => { + let ast_node = node + .cast::() + .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; + let name = ast_node.get().to_string(); + + (name, LexicalKind::Constant) + } + SyntaxKind::CodeBlock | SyntaxKind::ContentBlock if LexicalScopeKind::Symbol != g => { + (String::new(), LexicalKind::Block) + } + SyntaxKind::Ident if LexicalScopeKind::Block != g => { + let ast_node = node + .cast::() + .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; + let name = ast_node.get().to_string(); + let Some(parent) = node.parent() else { + return Ok(None); + }; + let kind = match parent.kind() { + // for variable definitions, the Let binding holds an Ident + SyntaxKind::LetBinding => LexicalKind::Variable, + // for function definitions, the Let binding holds a Closure which holds the + // Ident + SyntaxKind::Closure => { + let Some(grand_parent) = parent.parent() else { + return Ok(None); + }; + match grand_parent.kind() { + SyntaxKind::LetBinding => LexicalKind::Function, + _ => return Ok(None), + } + } + _ => return Ok(None), + }; + + (name, kind) + } + SyntaxKind::Markup => { + let name = node.get().to_owned().into_text().to_string(); + if name.is_empty() { + return Ok(None); + } + let Some(parent) = node.parent() else { + return Ok(None); + }; + let kind = match parent.kind() { + SyntaxKind::Heading => LexicalKind::Namespace( + parent.cast::().unwrap().level().get() as i16, + ), + _ => return Ok(None), + }; + + (name, kind) + } + _ => return Ok(None), + }; + + Ok(Some(LexicalInfo { + name, + kind, + range: node.range(), + })) + } + + let root = LinkedNode::new(source.root()); + + let mut worker = LexicalHierarchyWorker { + g, + ..LexicalHierarchyWorker::default() + }; + worker.stack.push(( + LexicalInfo { + name: "deadbeef".to_string(), + kind: LexicalKind::Namespace(-1), + range: 0..0, + }, + eco_vec![], + )); + let res = worker.get_symbols(root).ok(); + + while worker.stack.len() > 1 { + worker.symbreak(); + } + res.map(|_| worker.stack.pop().unwrap().1) +} diff --git a/crates/tinymist-query/src/analysis/mod.rs b/crates/tinymist-query/src/analysis/mod.rs index 92d19eb5..fb15afe8 100644 --- a/crates/tinymist-query/src/analysis/mod.rs +++ b/crates/tinymist-query/src/analysis/mod.rs @@ -1 +1,5 @@ -pub mod analyze; +pub mod track_values; +pub use track_values::*; + +pub mod lexical_hierarchy; +pub(crate) use lexical_hierarchy::*; diff --git a/crates/tinymist-query/src/analysis/analyze.rs b/crates/tinymist-query/src/analysis/track_values.rs similarity index 100% rename from crates/tinymist-query/src/analysis/analyze.rs rename to crates/tinymist-query/src/analysis/track_values.rs diff --git a/crates/tinymist-query/src/document_symbol.rs b/crates/tinymist-query/src/document_symbol.rs index 2f526d44..99dc7348 100644 --- a/crates/tinymist-query/src/document_symbol.rs +++ b/crates/tinymist-query/src/document_symbol.rs @@ -1,8 +1,7 @@ -use std::ops::Range; - -use typst_ts_core::typst::prelude::{eco_vec, EcoVec}; - -use crate::prelude::*; +use crate::{ + analysis::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind}, + prelude::*, +}; #[derive(Debug, Clone)] pub struct DocumentSymbolRequest { @@ -17,11 +16,10 @@ impl DocumentSymbolRequest { ) -> Option { let source = get_suitable_source_in_workspace(world, &self.path).ok()?; - let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeGranularity::None); + let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Symbol)?; - let symbols = - symbols.map(|symbols| filter_document_symbols(&symbols, &source, position_encoding)); - symbols.map(DocumentSymbolResponse::Nested) + let symbols = filter_document_symbols(&symbols, &source, position_encoding); + Some(DocumentSymbolResponse::Nested(symbols)) } } @@ -40,13 +38,7 @@ fn filter_document_symbols( DocumentSymbol { name: e.info.name.clone(), detail: None, - kind: match e.info.kind { - LexicalKind::Namespace(..) => SymbolKind::NAMESPACE, - LexicalKind::Variable => SymbolKind::VARIABLE, - LexicalKind::Function => SymbolKind::FUNCTION, - LexicalKind::Constant => SymbolKind::CONSTANT, - LexicalKind::Block => unreachable!(), - }, + kind: e.info.kind.try_into().unwrap(), tags: None, deprecated: None, range: rng, @@ -61,209 +53,8 @@ fn filter_document_symbols( .collect() } -#[derive(Debug, Clone, Hash)] -pub(crate) enum LexicalKind { - Namespace(i16), - Variable, - Function, - Constant, - Block, -} - -#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq)] -pub(crate) enum LexicalScopeGranularity { - #[default] - None, - Block, -} - -#[derive(Debug, Clone, Hash)] -pub(crate) struct LexicalInfo { - pub name: String, - pub kind: LexicalKind, - pub range: Range, -} - -#[derive(Debug, Clone, Hash)] -pub(crate) struct LexicalHierarchy { - pub info: LexicalInfo, - pub children: Option>>, -} - -pub(crate) fn get_lexical_hierarchy( - source: Source, - g: LexicalScopeGranularity, -) -> Option> { - fn symbreak(sym: LexicalInfo, curr: EcoVec) -> LexicalHierarchy { - LexicalHierarchy { - info: sym, - children: if curr.is_empty() { - None - } else { - Some(comemo::Prehashed::new(curr)) - }, - } - } - - #[derive(Default)] - struct LexicalHierarchyWorker { - g: LexicalScopeGranularity, - stack: Vec<(LexicalInfo, EcoVec)>, - } - - impl LexicalHierarchyWorker { - fn symbreak(&mut self) { - let (symbol, children) = self.stack.pop().unwrap(); - let current = &mut self.stack.last_mut().unwrap().1; - current.push(symbreak(symbol, children)); - } - - /// Get all symbols for a node recursively. - fn get_symbols(&mut self, node: LinkedNode) -> anyhow::Result<()> { - let own_symbol = get_ident(&node, self.g)?; - - if let Some(symbol) = own_symbol { - if let LexicalKind::Namespace(level) = symbol.kind { - 'heading_break: while let Some((w, _)) = self.stack.last() { - match w.kind { - LexicalKind::Namespace(l) if l < level => break 'heading_break, - LexicalKind::Block => break 'heading_break, - _ if self.stack.len() <= 1 => break 'heading_break, - _ => {} - } - - self.symbreak(); - } - } - let is_heading = matches!(symbol.kind, LexicalKind::Namespace(..)); - - self.stack.push((symbol, eco_vec![])); - let stack_height = self.stack.len(); - - for child in node.children() { - self.get_symbols(child)?; - } - - if is_heading { - while stack_height < self.stack.len() { - self.symbreak(); - } - } else { - while stack_height <= self.stack.len() { - self.symbreak(); - } - } - } else { - for child in node.children() { - self.get_symbols(child)?; - } - } - - Ok(()) - } - } - - /// Get symbol for a leaf node of a valid type, or `None` if the node is an - /// invalid type. - #[allow(deprecated)] - fn get_ident( - node: &LinkedNode, - g: LexicalScopeGranularity, - ) -> anyhow::Result> { - let (name, kind) = match node.kind() { - SyntaxKind::Label => { - let ast_node = node - .cast::() - .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; - let name = ast_node.get().to_string(); - - (name, LexicalKind::Constant) - } - SyntaxKind::CodeBlock | SyntaxKind::ContentBlock - if LexicalScopeGranularity::None != g => - { - (String::new(), LexicalKind::Block) - } - SyntaxKind::Ident => { - let ast_node = node - .cast::() - .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; - let name = ast_node.get().to_string(); - let Some(parent) = node.parent() else { - return Ok(None); - }; - let kind = match parent.kind() { - // for variable definitions, the Let binding holds an Ident - SyntaxKind::LetBinding => LexicalKind::Variable, - // for function definitions, the Let binding holds a Closure which holds the - // Ident - SyntaxKind::Closure => { - let Some(grand_parent) = parent.parent() else { - return Ok(None); - }; - match grand_parent.kind() { - SyntaxKind::LetBinding => LexicalKind::Function, - _ => return Ok(None), - } - } - _ => return Ok(None), - }; - - (name, kind) - } - SyntaxKind::Markup => { - let name = node.get().to_owned().into_text().to_string(); - if name.is_empty() { - return Ok(None); - } - let Some(parent) = node.parent() else { - return Ok(None); - }; - let kind = match parent.kind() { - SyntaxKind::Heading => LexicalKind::Namespace( - parent.cast::().unwrap().level().get() as i16, - ), - _ => return Ok(None), - }; - - (name, kind) - } - _ => return Ok(None), - }; - - Ok(Some(LexicalInfo { - name, - kind, - range: node.range(), - })) - } - - let root = LinkedNode::new(source.root()); - - let mut worker = LexicalHierarchyWorker { - g, - ..LexicalHierarchyWorker::default() - }; - worker.stack.push(( - LexicalInfo { - name: "deadbeef".to_string(), - kind: LexicalKind::Namespace(-1), - range: 0..0, - }, - eco_vec![], - )); - let res = worker.get_symbols(root).ok(); - - while worker.stack.len() > 1 { - worker.symbreak(); - } - res.map(|_| worker.stack.pop().unwrap().1) -} - #[cfg(test)] mod tests { - use insta::assert_snapshot; - use super::*; use crate::tests::*; diff --git a/crates/tinymist-query/src/folding_range.rs b/crates/tinymist-query/src/folding_range.rs index f5c2f166..65c30236 100644 --- a/crates/tinymist-query/src/folding_range.rs +++ b/crates/tinymist-query/src/folding_range.rs @@ -1,8 +1,12 @@ -use crate::{get_lexical_hierarchy, prelude::*, LexicalScopeGranularity}; +use crate::{ + analysis::{get_lexical_hierarchy, LexicalHierarchy, LexicalKind, LexicalScopeKind}, + prelude::*, +}; #[derive(Debug, Clone)] pub struct FoldingRangeRequest { pub path: PathBuf, + pub line_folding_only: bool, } impl FoldingRangeRequest { @@ -11,14 +15,100 @@ impl FoldingRangeRequest { world: &TypstSystemWorld, position_encoding: PositionEncoding, ) -> Option> { + let line_folding_only = self.line_folding_only; + let source = get_suitable_source_in_workspace(world, &self.path).ok()?; - let symbols = get_lexical_hierarchy(source, LexicalScopeGranularity::Block)?; + let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Block)?; - let _ = symbols; - let _ = position_encoding; + let mut results = vec![]; + let LspPosition { line, character } = + typst_to_lsp::offset_to_position(source.text().len(), position_encoding, &source); + let loc = (line, Some(character)); - None + calc_folding_range( + &symbols, + &source, + position_encoding, + line_folding_only, + loc, + loc, + true, + &mut results, + ); + trace!("FoldingRangeRequest(line_folding_only={line_folding_only}) symbols: {symbols:#?} results: {results:#?}"); + + Some(results) + } +} + +type LoC = (u32, Option); + +#[allow(clippy::too_many_arguments)] +#[allow(deprecated)] +fn calc_folding_range( + symbols: &[LexicalHierarchy], + source: &Source, + position_encoding: PositionEncoding, + line_folding_only: bool, + parent_last_loc: LoC, + last_loc: LoC, + is_last_range: bool, + ranges: &mut Vec, +) { + for (i, e) in symbols.iter().enumerate() { + let rng = typst_to_lsp::range(e.info.range.clone(), source, position_encoding).raw_range; + let is_not_last_range = i + 1 < symbols.len(); + let is_not_final_last_range = !is_last_range || is_not_last_range; + + let mut range = FoldingRange { + start_line: rng.start.line, + start_character: Some(rng.start.character), + end_line: rng.end.line, + end_character: line_folding_only.then_some(rng.end.character), + kind: None, + collapsed_text: Some(e.info.name.clone()), + }; + + let next_start = if is_not_last_range { + let next = &symbols[i + 1]; + let next_rng = + typst_to_lsp::range(next.info.range.clone(), source, position_encoding).raw_range; + (next_rng.start.line, Some(next_rng.start.character)) + } else if is_not_final_last_range { + parent_last_loc + } else { + last_loc + }; + + if matches!(e.info.kind, LexicalKind::Namespace(..)) { + range.end_line = range.end_line.max(if is_not_last_range { + next_start.0.saturating_sub(1) + } else { + next_start.0 + }); + } + + if let Some(ch) = &e.children { + let parent_last_loc = if is_not_last_range { + (rng.end.line, Some(rng.end.character)) + } else { + parent_last_loc + }; + + calc_folding_range( + ch, + source, + position_encoding, + line_folding_only, + parent_last_loc, + last_loc, + !is_not_final_last_range, + ranges, + ); + } + + ranges.push(range); } } @@ -29,10 +119,41 @@ mod tests { #[test] fn test_folding_range_request() { - run_with_source("let a = 1;", |world, path| { - let request = FoldingRangeRequest { path }; + run_with_source("#let a = 1;", |world, path| { + let request = FoldingRangeRequest { + path, + line_folding_only: true, + }; let result = request.request(world, PositionEncoding::Utf16); - assert_eq!(result, None); + assert_snapshot!(JsonRepr::new_pure(result.unwrap()), @"[]"); + }); + let t = r#"#let a = { + let b = { + + } +}"#; + run_with_source(t, |world, path| { + let request = FoldingRangeRequest { + path, + line_folding_only: true, + }; + let result = request.request(world, PositionEncoding::Utf16); + assert_snapshot!(JsonRepr::new_pure(result.unwrap()), @r###" + [ + { + "collapsedText": "", + "endLine": 0, + "startCharacter": 9, + "startLine": 0 + }, + { + "collapsedText": "", + "endLine": 3, + "startCharacter": 10, + "startLine": 1 + } + ] + "###); }); } } diff --git a/crates/tinymist-query/src/lib.rs b/crates/tinymist-query/src/lib.rs index cea02f2d..a9464e0c 100644 --- a/crates/tinymist-query/src/lib.rs +++ b/crates/tinymist-query/src/lib.rs @@ -40,9 +40,11 @@ mod tests { use serde::Serialize; use serde_json::{ser::PrettyFormatter, Serializer, Value}; use typst_ts_compiler::ShadowApiExt; - pub use typst_ts_compiler::TypstSystemWorld; use typst_ts_core::{config::CompileOpts, Bytes}; + pub use insta::assert_snapshot; + pub use typst_ts_compiler::TypstSystemWorld; + pub fn run_with_source( source: &str, f: impl FnOnce(&mut TypstSystemWorld, PathBuf) -> T, @@ -73,6 +75,11 @@ mod tests { pub struct JsonRepr(Value); impl JsonRepr { + pub fn new_pure(v: impl serde::Serialize) -> Self { + let s = serde_json::to_value(v).unwrap(); + Self(s) + } + // pub fn new(v: impl serde::Serialize) -> Self { // let s = serde_json::to_value(v).unwrap(); // Self(REDACT_URI.redact(s)) diff --git a/crates/tinymist-query/src/prelude.rs b/crates/tinymist-query/src/prelude.rs index 36505016..33753913 100644 --- a/crates/tinymist-query/src/prelude.rs +++ b/crates/tinymist-query/src/prelude.rs @@ -5,7 +5,6 @@ pub use std::{ sync::Arc, }; -pub use anyhow::anyhow; pub use itertools::{Format, Itertools}; pub use log::{error, trace}; pub use tower_lsp::lsp_types::{ @@ -13,7 +12,7 @@ pub use tower_lsp::lsp_types::{ Documentation, FoldingRange, Hover, Location as LspLocation, MarkupContent, MarkupKind, Position as LspPosition, SelectionRange, SemanticTokens, SemanticTokensDelta, SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp, SignatureInformation, - SymbolInformation, SymbolKind, Url, + SymbolInformation, Url, }; pub use typst::diag::{EcoString, FileError, FileResult, Tracepoint}; pub use typst::foundations::{Func, ParamInfo, Value}; @@ -26,7 +25,7 @@ use typst_ts_compiler::service::WorkspaceProvider; pub use typst_ts_compiler::TypstSystemWorld; pub use typst_ts_core::{TypstDocument, TypstFileId}; -pub use crate::analysis::analyze::analyze_expr; +pub use crate::analysis::analyze_expr; pub use crate::lsp_typst_boundary::{ lsp_to_typst, typst_to_lsp, LspDiagnostic, LspRange, LspRawRange, LspSeverity, PositionEncoding, TypstDiagnostic, TypstSeverity, TypstSpan, diff --git a/crates/tinymist-query/src/symbol.rs b/crates/tinymist-query/src/symbol.rs index a8979ab7..4ed433cd 100644 --- a/crates/tinymist-query/src/symbol.rs +++ b/crates/tinymist-query/src/symbol.rs @@ -1,7 +1,9 @@ use typst_ts_compiler::NotifyApi; -use crate::document_symbol::get_lexical_hierarchy; -use crate::{prelude::*, LexicalHierarchy, LexicalKind, LexicalScopeGranularity}; +use crate::{ + analysis::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind}, + prelude::*, +}; #[derive(Debug, Clone)] pub struct SymbolRequest { @@ -23,12 +25,13 @@ impl SymbolRequest { return; }; let uri = Url::from_file_path(path).unwrap(); - let res = get_lexical_hierarchy(source.clone(), LexicalScopeGranularity::None) - .and_then(|symbols| { + let res = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Symbol).and_then( + |symbols| { self.pattern.as_ref().map(|pattern| { filter_document_symbols(&symbols, pattern, &source, &uri, position_encoding) }) - }); + }, + ); if let Some(mut res) = res { symbols.append(&mut res) @@ -60,13 +63,7 @@ fn filter_document_symbols( SymbolInformation { name: e.info.name.clone(), - kind: match e.info.kind { - LexicalKind::Namespace(..) => SymbolKind::NAMESPACE, - LexicalKind::Variable => SymbolKind::VARIABLE, - LexicalKind::Function => SymbolKind::FUNCTION, - LexicalKind::Constant => SymbolKind::CONSTANT, - LexicalKind::Block => unreachable!(), - }, + kind: e.info.kind.try_into().unwrap(), tags: None, deprecated: None, location: LspLocation { diff --git a/crates/tinymist/src/lib.rs b/crates/tinymist/src/lib.rs index 657525ee..1cdaa14e 100644 --- a/crates/tinymist/src/lib.rs +++ b/crates/tinymist/src/lib.rs @@ -193,6 +193,7 @@ pub struct ConstConfig { pub supports_semantic_tokens_dynamic_registration: bool, pub supports_document_formatting_dynamic_registration: bool, pub supports_config_change_registration: bool, + pub line_folding_only: bool, } pub struct TypstServer { @@ -348,6 +349,7 @@ impl LanguageServer for TypstServer { document_symbol_provider: Some(OneOf::Left(true)), workspace_symbol_provider: Some(OneOf::Left(true)), selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), + folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), workspace: Some(WorkspaceServerCapabilities { workspace_folders: Some(WorkspaceFoldersServerCapabilities { supported: Some(true), @@ -562,8 +564,16 @@ impl LanguageServer for TypstServer { ) -> jsonrpc::Result>> { let uri = params.text_document.uri; let path = uri.to_file_path().unwrap(); + let line_folding_only = self.const_config().line_folding_only; - run_query!(self, FoldingRange, FoldingRangeRequest { path }) + run_query!( + self, + FoldingRange, + FoldingRangeRequest { + path, + line_folding_only + } + ) } async fn selection_range( @@ -727,6 +737,7 @@ impl From<&InitializeParams> for ConstConfig { supports_document_formatting_dynamic_registration: params .supports_document_formatting_dynamic_registration(), supports_config_change_registration: params.supports_config_change_registration(), + line_folding_only: params.line_folding_only(), } } } @@ -738,6 +749,7 @@ pub trait InitializeParamsExt { fn document_formatting_capabilities(&self) -> Option<&DocumentFormattingClientCapabilities>; fn supports_semantic_tokens_dynamic_registration(&self) -> bool; fn supports_document_formatting_dynamic_registration(&self) -> bool; + fn line_folding_only(&self) -> bool; fn root_paths(&self) -> Vec; } @@ -761,6 +773,15 @@ impl InitializeParamsExt for InitializeParams { .unwrap_or(false) } + fn line_folding_only(&self) -> bool { + self.capabilities + .text_document + .as_ref() + .and_then(|workspace| workspace.folding_range.as_ref()) + .and_then(|folding| folding.line_folding_only) + .unwrap_or(false) + } + fn semantic_tokens_capabilities(&self) -> Option<&SemanticTokensClientCapabilities> { self.capabilities .text_document