Skip to content

Commit

Permalink
Add configurable prefixes for custom label commands. (#1139)
Browse files Browse the repository at this point in the history
This commit extends the configs with a map from label command name to the respective prefix:
*  Prepends user-defined prefixes to labels in the semantics
pass. Note that this yields to a mismatch between the actual length of a
span and its range, but there are no (anticipated) bad side-effects due
to this.
* Adds two tests that ensure completion for custom labels with custom
prefix works as expected. The `\ref` command should list the prefix,
while a custom reference command with a configured prefix should not.
* Implements key functionality for renaming labels with prefixes.
A sane assumption it does is that all labels found as candidate for
renaming share a common prefix, up to looking it up first and prepending it, e.g., for `\goal{foo}`.
Instead of storing a renaming candidate for each entry, it only keeps
track of the prefixes and prepends them accordingly, depending on the
renaming candidate, by swapping the `TextRange` with `RenameInformation` inside the `RenameResult`.
Unfortunately, this pollutes a bit the other renaming ops, such as
commands or citations, which don't have prefixes.
Nevertheless, changes there have been incremental and `RenameInformation` implements a `From<TextRange>` to easily map an existing `TextRange` into it, simply assuming an empty prefix.
In terms of tests, the `prefix` should be ignored.
  • Loading branch information
DasNaCl authored Jun 13, 2024
1 parent 75cde13 commit 623f01e
Show file tree
Hide file tree
Showing 17 changed files with 529 additions and 40 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion crates/base-db/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ impl Document {
Language::Tex => {
let green = parser::parse_latex(&text, &params.config.syntax);
let mut semantics = semantics::tex::Semantics::default();
semantics.process_root(&latex::SyntaxNode::new_root(green.clone()));
semantics.process_root(
&params.config.syntax,
&latex::SyntaxNode::new_root(green.clone()),
);
DocumentData::Tex(TexDocumentData { green, semantics })
}
Language::Bib => {
Expand Down
68 changes: 55 additions & 13 deletions crates/base-db/src/semantics/tex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,28 @@ use rustc_hash::FxHashSet;
use syntax::latex::{self, HasBrack, HasCurly};
use titlecase::titlecase;

use parser::SyntaxConfig;

use super::Span;
use crate::semantics::tex::latex::SyntaxToken;

fn maybe_prepend_prefix(
map: &Vec<(String, String)>,
command: &Option<SyntaxToken>,
name: &Span,
) -> Span {
match command {
Some(x) => Span::new(
map.iter()
.find_map(|(k, v)| if k == &x.text()[1..] { Some(v) } else { None })
.unwrap_or(&String::new())
.to_owned()
+ &name.text,
name.range,
),
None => name.clone(),
}
}

#[derive(Debug, Clone, Default)]
pub struct Semantics {
Expand All @@ -19,11 +40,11 @@ pub struct Semantics {
}

impl Semantics {
pub fn process_root(&mut self, root: &latex::SyntaxNode) {
pub fn process_root(&mut self, conf: &SyntaxConfig, root: &latex::SyntaxNode) {
for node in root.descendants_with_tokens() {
match node {
latex::SyntaxElement::Node(node) => {
self.process_node(&node);
self.process_node(conf, &node);
}
latex::SyntaxElement::Token(token) => {
if token.kind() == latex::COMMAND_NAME {
Expand All @@ -40,17 +61,17 @@ impl Semantics {
.any(|link| link.kind == LinkKind::Cls && link.path.text == "subfiles");
}

fn process_node(&mut self, node: &latex::SyntaxNode) {
fn process_node(&mut self, conf: &SyntaxConfig, node: &latex::SyntaxNode) {
if let Some(include) = latex::Include::cast(node.clone()) {
self.process_include(include);
} else if let Some(import) = latex::Import::cast(node.clone()) {
self.process_import(import);
} else if let Some(label) = latex::LabelDefinition::cast(node.clone()) {
self.process_label_definition(label);
self.process_label_definition(conf, label);
} else if let Some(label) = latex::LabelReference::cast(node.clone()) {
self.process_label_reference(label);
self.process_label_reference(conf, label);
} else if let Some(label) = latex::LabelReferenceRange::cast(node.clone()) {
self.process_label_reference_range(label);
self.process_label_reference_range(conf, label);
} else if let Some(citation) = latex::Citation::cast(node.clone()) {
self.process_citation(citation);
} else if let Some(environment) = latex::Environment::cast(node.clone()) {
Expand Down Expand Up @@ -111,7 +132,7 @@ impl Semantics {
});
}

fn process_label_definition(&mut self, label: latex::LabelDefinition) {
fn process_label_definition(&mut self, conf: &SyntaxConfig, label: latex::LabelDefinition) {
let Some(name) = label.name().and_then(|group| group.key()) else {
return;
};
Expand Down Expand Up @@ -190,13 +211,14 @@ impl Semantics {

self.labels.push(Label {
kind: LabelKind::Definition,
name,
cmd: label.command().map(|x| x.text()[1..].to_string()),
name: maybe_prepend_prefix(&conf.label_definition_prefixes, &label.command(), &name),
targets: objects,
full_range,
});
}

fn process_label_reference(&mut self, label: latex::LabelReference) {
fn process_label_reference(&mut self, conf: &SyntaxConfig, label: latex::LabelReference) {
let Some(name_list) = label.name_list() else {
return;
};
Expand All @@ -207,22 +229,36 @@ impl Semantics {
if !name.text.contains('#') {
self.labels.push(Label {
kind: LabelKind::Reference,
name,
cmd: label.command().map(|x| x.text()[1..].to_string()),
name: maybe_prepend_prefix(
&conf.label_reference_prefixes,
&label.command(),
&name,
),
targets: Vec::new(),
full_range,
});
}
}
}

fn process_label_reference_range(&mut self, label: latex::LabelReferenceRange) {
fn process_label_reference_range(
&mut self,
conf: &SyntaxConfig,
label: latex::LabelReferenceRange,
) {
let full_range = latex::small_range(&label);
if let Some(from) = label.from().and_then(|group| group.key()) {
let name = Span::from(&from);
if !name.text.contains('#') {
self.labels.push(Label {
kind: LabelKind::ReferenceRange,
name,
cmd: label.command().map(|x| x.text()[1..].to_string()),
name: maybe_prepend_prefix(
&conf.label_reference_prefixes,
&label.command(),
&name,
),
targets: Vec::new(),
full_range,
});
Expand All @@ -234,7 +270,12 @@ impl Semantics {
if !name.text.contains('#') {
self.labels.push(Label {
kind: LabelKind::ReferenceRange,
name,
cmd: label.command().map(|x| x.text()[1..].to_string()),
name: maybe_prepend_prefix(
&conf.label_reference_prefixes,
&label.command(),
&name,
),
targets: Vec::new(),
full_range,
});
Expand Down Expand Up @@ -336,6 +377,7 @@ pub enum LabelKind {
#[derive(Debug, Clone)]
pub struct Label {
pub kind: LabelKind,
pub cmd: Option<String>,
pub name: Span,
pub targets: Vec<LabelTarget>,
pub full_range: TextRange,
Expand Down
1 change: 1 addition & 0 deletions crates/completion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ criterion = "0.5.1"
distro = { path = "../distro" }
expect-test = "1.5.0"
test-utils = { path = "../test-utils" }
parser = { path = "../parser" }

[lib]
doctest = false
Expand Down
2 changes: 1 addition & 1 deletion crates/completion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<'a> CompletionItemData<'a> {
Self::Citation(data) => &data.entry.name.text,
Self::Environment(data) => data.name,
Self::GlossaryEntry(data) => &data.name,
Self::Label(data) => data.name,
Self::Label(data) => &data.name,
Self::Color(name) => name,
Self::ColorModel(name) => name,
Self::File(name) => name,
Expand Down
46 changes: 38 additions & 8 deletions crates/completion/src/providers/label_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,30 @@ use crate::{
CompletionItem, CompletionItemData, CompletionParams,
};

fn trim_prefix<'a>(prefix: Option<&'a str>, text: &'a str) -> &'a str {
prefix
.and_then(|pref| text.strip_prefix(pref))
.unwrap_or(text)
}

pub fn complete_label_references<'a>(
params: &'a CompletionParams<'a>,
builder: &mut CompletionBuilder<'a>,
) -> Option<()> {
let FindResult { cursor, is_math } =
find_reference(params).or_else(|| find_reference_range(params))?;
let FindResult {
cursor,
is_math,
command,
} = find_reference(params).or_else(|| find_reference_range(params))?;
let ref_pref = params
.feature
.workspace
.config()
.syntax
.label_reference_prefixes
.iter()
.find_map(|(k, v)| if *k == command { Some(v) } else { None })
.map(|x| x.as_str());

for document in &params.feature.project.documents {
let DocumentData::Tex(data) = &document.data else {
Expand All @@ -29,6 +47,10 @@ pub fn complete_label_references<'a>(
.iter()
.filter(|label| label.kind == LabelKind::Definition)
{
if ref_pref.map_or(false, |pref| !label.name.text.starts_with(pref)) {
continue;
}
let labeltext = trim_prefix(ref_pref, &label.name.text);
match render_label(params.feature.workspace, &params.feature.project, label) {
Some(rendered_label) => {
if is_math && !matches!(rendered_label.object, RenderedObject::Equation) {
Expand All @@ -41,11 +63,12 @@ pub fn complete_label_references<'a>(
_ => None,
};

let keywords = format!("{} {}", label.name.text, rendered_label.reference());
let keywords = format!("{} {}", labeltext, rendered_label.reference());

if let Some(score) = builder.matcher.score(&keywords, &cursor.text) {
let name = trim_prefix(ref_pref, &label.name.text);
let data = CompletionItemData::Label(crate::LabelData {
name: &label.name.text,
name,
header,
footer,
object: Some(rendered_label.object),
Expand All @@ -59,12 +82,13 @@ pub fn complete_label_references<'a>(
}
None => {
if let Some(score) = builder.matcher.score(&label.name.text, &cursor.text) {
let name = trim_prefix(ref_pref, &label.name.text);
let data = CompletionItemData::Label(crate::LabelData {
name: &label.name.text,
name,
header: None,
footer: None,
object: None,
keywords: label.name.text.clone(),
keywords: labeltext.to_string(),
});

builder
Expand All @@ -82,20 +106,26 @@ pub fn complete_label_references<'a>(
struct FindResult {
cursor: Span,
is_math: bool,
command: String,
}

fn find_reference(params: &CompletionParams) -> Option<FindResult> {
let (cursor, group) = find_curly_group_word_list(params)?;
let reference = latex::LabelReference::cast(group.syntax().parent()?)?;
let is_math = reference.command()?.text() == "\\eqref";
Some(FindResult { cursor, is_math })
Some(FindResult {
cursor,
is_math,
command: reference.command()?.text()[1..].to_string(),
})
}

fn find_reference_range(params: &CompletionParams) -> Option<FindResult> {
let (cursor, group) = find_curly_group_word(params)?;
latex::LabelReferenceRange::cast(group.syntax().parent()?)?;
let refrange = latex::LabelReferenceRange::cast(group.syntax().parent()?)?;
Some(FindResult {
cursor,
is_math: false,
command: refrange.command()?.text()[1..].to_string(),
})
}
Loading

0 comments on commit 623f01e

Please sign in to comment.