Skip to content

Commit

Permalink
Improve behavior of folding feature
Browse files Browse the repository at this point in the history
Do not fold the entire structure but the contents inside.

See #915.
  • Loading branch information
pfoerster committed Aug 12, 2023
1 parent 16ee252 commit 5112ce6
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 54 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions crates/folding/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "folding"
version = "0.0.0"
license.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true

[dependencies]
base-db = { path = "../base-db" }
rowan = "0.15.11"
syntax = { path = "../syntax" }

[dev-dependencies]
expect-test = "1.4.1"
test-utils = { path = "../test-utils" }

[lib]
doctest = false
92 changes: 92 additions & 0 deletions crates/folding/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use base_db::{Document, DocumentData};
use rowan::{ast::AstNode, TextRange};
use syntax::{
bibtex::{self, HasDelims, HasName},
latex,
};

#[derive(Debug)]
pub struct FoldingRange {
pub range: TextRange,
}

pub fn find_all(document: &Document) -> Vec<FoldingRange> {
let mut builder = FoldingBuilder::default();

if let DocumentData::Tex(data) = &document.data {
for node in data.root_node().descendants() {
if let Some(section) = latex::Section::cast(node.clone()) {
builder.fold_section(&section);
} else if let Some(item) = latex::EnumItem::cast(node.clone()) {
builder.fold_enum_item(&item);
} else if let Some(env) = latex::Environment::cast(node) {
builder.fold_environment(env);
}
}
} else if let DocumentData::Bib(data) = &document.data {
for node in data.root_node().descendants() {
if let Some(entry) = bibtex::Entry::cast(node.clone()) {
builder.fold_entry(&entry);
} else if let Some(string) = bibtex::StringDef::cast(node) {
builder.fold_entry(&string);
}
}
}

builder.ranges
}

#[derive(Debug, Default)]
struct FoldingBuilder {
ranges: Vec<FoldingRange>,
}

impl FoldingBuilder {
fn fold_section(&mut self, section: &latex::Section) -> Option<()> {
let start = section
.name()
.map(|name| latex::small_range(&name).end())
.or_else(|| section.command().map(|cmd| cmd.text_range().end()))?;
let end = section.syntax().text_range().end();

self.ranges.push(FoldingRange {
range: TextRange::new(start, end),
});

Some(())
}

fn fold_enum_item(&mut self, item: &latex::EnumItem) -> Option<()> {
let start = item
.label()
.map(|label| latex::small_range(&label).end())
.or_else(|| item.command().map(|cmd| cmd.text_range().end()))?;

let end = item.syntax().text_range().end();
self.ranges.push(FoldingRange {
range: TextRange::new(start, end),
});

Some(())
}

fn fold_environment(&mut self, env: latex::Environment) -> Option<()> {
let start = latex::small_range(&env.begin()?).end();
let end = latex::small_range(&env.end()?).start();
self.ranges.push(FoldingRange {
range: TextRange::new(start, end),
});

Some(())
}

fn fold_entry(&mut self, entry: &(impl HasName + HasDelims)) -> Option<()> {
let start = entry.name_token()?.text_range().end();
let end = entry.right_delim_token()?.text_range().start();
self.ranges.push(FoldingRange {
range: TextRange::new(start, end),
});

Some(())
}
}
1 change: 1 addition & 0 deletions crates/texlab/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ distro = { path = "../distro" }
encoding_rs = "0.8.32"
encoding_rs_io = "0.1.7"
fern = "0.6.2"
folding = { path = "../folding" }
fuzzy-matcher = { version = "0.3.7", features = ["compact"] }
hover = { path = "../hover" }
itertools = "0.11.0"
Expand Down
67 changes: 13 additions & 54 deletions crates/texlab/src/features/folding.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,21 @@
use base_db::{DocumentData, Workspace};
use lsp_types::{FoldingRange, FoldingRangeKind, Range, Url};
use rowan::ast::AstNode;
use syntax::{bibtex, latex};
use base_db::Workspace;
use lsp_types::{FoldingRange, Url};

use crate::util::line_index_ext::LineIndexExt;

pub fn find_all(workspace: &Workspace, uri: &Url) -> Option<Vec<FoldingRange>> {
let document = workspace.lookup(uri)?;
let line_index = &document.line_index;
let foldings = match &document.data {
DocumentData::Tex(data) => {
let mut results = Vec::new();
for node in data.root_node().descendants() {
if let Some(folding) = latex::Environment::cast(node.clone())
.map(|node| latex::small_range(&node))
.or_else(|| {
latex::Section::cast(node.clone()).map(|node| latex::small_range(&node))
})
.or_else(|| latex::EnumItem::cast(node).map(|node| latex::small_range(&node)))
.map(|node| line_index.line_col_lsp_range(node))
.map(create_range)
{
results.push(folding);
}
}

results
}
DocumentData::Bib(data) => {
let root = data.root_node();
root.descendants()
.filter(|node| {
matches!(
node.kind(),
bibtex::PREAMBLE | bibtex::STRING | bibtex::ENTRY
)
})
.map(|node| create_range(line_index.line_col_lsp_range(node.text_range())))
.collect()
}
DocumentData::Aux(_)
| DocumentData::Log(_)
| DocumentData::Root
| DocumentData::Tectonic => {
return None;
let foldings = folding::find_all(document).into_iter().map(|folding| {
let range = document.line_index.line_col_lsp_range(folding.range);
FoldingRange {
start_line: range.start.line,
start_character: Some(range.start.character),
end_line: range.end.line,
end_character: Some(range.end.character),
collapsed_text: None,
kind: None,
}
};

Some(foldings)
}
});

fn create_range(range: Range) -> FoldingRange {
FoldingRange {
start_line: range.start.line,
start_character: Some(range.start.character),
end_line: range.end.line,
end_character: Some(range.end.character),
collapsed_text: None,
kind: Some(FoldingRangeKind::Region),
}
Some(foldings.collect())
}

0 comments on commit 5112ce6

Please sign in to comment.