Skip to content

Commit

Permalink
Implement "Goto Definition" for BibTeX strings
Browse files Browse the repository at this point in the history
  • Loading branch information
pfoerster committed Oct 7, 2019
1 parent f4907a8 commit 1d9799b
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 24 deletions.
12 changes: 3 additions & 9 deletions src/citeproc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ pub struct JavaScriptEngine {
impl JavaScriptEngine {
fn new() -> Self {
let ducc = Ducc::new();
ducc.exec::<()>(JS_CODE, None, ExecSettings::default()).unwrap();
ducc.exec::<()>(JS_CODE, None, ExecSettings::default())
.unwrap();
Self { ducc }
}

Expand Down Expand Up @@ -57,14 +58,7 @@ pub fn render_citation(tree: &BibtexSyntaxTree, key: &str) -> Option<MarkupConte
}

fn replace_strings(tree: &BibtexSyntaxTree, old_entry: &BibtexEntry) -> BibtexEntry {
let mut strings = Vec::new();
for child in &tree.root.children {
if let BibtexDeclaration::String(string) = &child {
if string.value.is_some() {
strings.push(string);
}
}
}
let strings = tree.strings();

let mut new_entry = old_entry.clone();
for field in &mut new_entry.fields {
Expand Down
132 changes: 132 additions & 0 deletions src/definition/bibtex_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use crate::syntax::*;
use crate::workspace::*;
use futures_boxed::boxed;
use lsp_types::{LocationLink, Position, TextDocumentPositionParams};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct BibtexStringDefinitionProvider;

impl FeatureProvider for BibtexStringDefinitionProvider {
type Params = TextDocumentPositionParams;
type Output = Vec<LocationLink>;

#[boxed]
async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output {
if let SyntaxTree::Bibtex(tree) = &request.document().tree {
if let Some(reference) = Self::find_reference(tree, request.params.position) {
return Self::find_definitions(&request.view.document.uri, tree, reference);
}
}
Vec::new()
}
}

impl BibtexStringDefinitionProvider {
fn find_reference(tree: &BibtexSyntaxTree, position: Position) -> Option<&BibtexToken> {
let mut nodes = tree.find(position);
nodes.reverse();
match (&nodes[0], &nodes[1]) {
(BibtexNode::Word(word), BibtexNode::Field(_))
| (BibtexNode::Word(word), BibtexNode::Concat(_)) => Some(&word.token),
_ => None,
}
}

fn find_definitions(
uri: &Uri,
tree: &BibtexSyntaxTree,
reference: &BibtexToken,
) -> Vec<LocationLink> {
let mut links = Vec::new();
for string in tree.strings() {
if let Some(name) = &string.name {
if name.text() == reference.text() {
links.push(LocationLink {
origin_selection_range: Some(reference.range()),
target_uri: uri.clone().into(),
target_range: string.range(),
target_selection_range: name.range(),
});
}
}
}
links
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::range::RangeExt;
use lsp_types::{Position, Range};

#[test]
fn test_simple() {
let links = test_feature(
BibtexStringDefinitionProvider,
FeatureSpec {
files: vec![FeatureSpec::file(
"foo.bib",
"@string{foo = {bar}}\n@article{bar, author = foo}",
)],
main_file: "foo.bib",
position: Position::new(1, 24),
..FeatureSpec::default()
},
);

assert_eq!(
links,
vec![LocationLink {
origin_selection_range: Some(Range::new_simple(1, 23, 1, 26)),
target_uri: FeatureSpec::uri("foo.bib"),
target_range: Range::new_simple(0, 0, 0, 20),
target_selection_range: Range::new_simple(0, 8, 0, 11)
}]
);
}

#[test]
fn test_concat() {
let links = test_feature(
BibtexStringDefinitionProvider,
FeatureSpec {
files: vec![FeatureSpec::file(
"foo.bib",
"@string{foo = {bar}}\n@article{bar, author = foo # \"bar\"}",
)],
main_file: "foo.bib",
position: Position::new(1, 24),
..FeatureSpec::default()
},
);

assert_eq!(
links,
vec![LocationLink {
origin_selection_range: Some(Range::new_simple(1, 23, 1, 26)),
target_uri: FeatureSpec::uri("foo.bib"),
target_range: Range::new_simple(0, 0, 0, 20),
target_selection_range: Range::new_simple(0, 8, 0, 11)
}]
);
}

#[test]
fn test_field() {
let links = test_feature(
BibtexStringDefinitionProvider,
FeatureSpec {
files: vec![FeatureSpec::file(
"foo.bib",
"@string{foo = {bar}}\n@article{bar, author = foo}",
)],
main_file: "foo.bib",
position: Position::new(1, 18),
..FeatureSpec::default()
},
);

assert!(links.is_empty());
}
}
3 changes: 3 additions & 0 deletions src/definition/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod bibtex_string;
mod latex_citation;
mod latex_command;
mod latex_label;

use self::bibtex_string::BibtexStringDefinitionProvider;
use self::latex_citation::LatexCitationDefinitionProvider;
use self::latex_command::LatexCommandDefinitionProvider;
use self::latex_label::LatexLabelDefinitionProvider;
Expand All @@ -18,6 +20,7 @@ impl DefinitionProvider {
pub fn new() -> Self {
Self {
provider: ConcatProvider::new(vec![
Box::new(BibtexStringDefinitionProvider),
Box::new(LatexCitationDefinitionProvider),
Box::new(LatexCommandDefinitionProvider),
Box::new(LatexLabelDefinitionProvider),
Expand Down
10 changes: 10 additions & 0 deletions src/syntax/bibtex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ impl BibtexSyntaxTree {
entries
}

pub fn strings(&self) -> Vec<&BibtexString> {
let mut strings: Vec<&BibtexString> = Vec::new();
for declaration in &self.root.children {
if let BibtexDeclaration::String(string) = declaration {
strings.push(&string);
}
}
strings
}

pub fn find(&self, position: Position) -> Vec<BibtexNode> {
let mut finder = BibtexFinder::new(position);
finder.visit_root(&self.root);
Expand Down
38 changes: 23 additions & 15 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,23 @@ fn verify_origin_selection_range(
);
}

#[tokio::test]
async fn definition_bibtex_string() {
let (scenario, mut links) = run_definition_link("bibtex/string", "foo.bib", 5, 14).await;
assert_eq!(links.len(), 1);
let link = links.pop().unwrap();
verify_origin_selection_range(&link, 5, 13, 5, 16);
assert_eq!(link.target_uri, scenario.uri("foo.bib").into());
assert_eq!(link.target_range, Range::new_simple(2, 0, 2, 20));
assert_eq!(link.target_selection_range, Range::new_simple(2, 8, 2, 11));
}

#[tokio::test]
async fn definition_latex_citation() {
let (scenario, mut links) = run_definition_link("latex/citation", "foo.tex", 1, 7).await;
assert_eq!(links.len(), 1);
let link = links.pop().unwrap();
assert_eq!(
link.origin_selection_range.unwrap(),
Range::new_simple(1, 6, 1, 9)
);
verify_origin_selection_range(&link, 1, 6, 1, 9);
assert_eq!(link.target_uri, scenario.uri("bar.bib").into());
assert_eq!(link.target_range, Range::new_simple(2, 0, 2, 14));
assert_eq!(link.target_selection_range, Range::new_simple(2, 9, 2, 12));
Expand All @@ -391,17 +399,6 @@ async fn definition_latex_command() {
assert_eq!(link.target_selection_range, Range::new_simple(0, 0, 0, 22));
}

#[tokio::test]
async fn definition_latex_math_operator() {
let (scenario, mut links) = run_definition_link("latex/math_operator", "foo.tex", 2, 2).await;
assert_eq!(links.len(), 1);
let link = links.pop().unwrap();
verify_origin_selection_range(&link, 2, 0, 2, 4);
assert_eq!(link.target_uri, scenario.uri("foo.tex").into());
assert_eq!(link.target_range, Range::new_simple(0, 0, 0, 31));
assert_eq!(link.target_selection_range, Range::new_simple(0, 0, 0, 31));
}

#[tokio::test]
async fn definition_latex_label_default() {
let (scenario, mut links) = run_definition_link("latex/label", "default.tex", 1, 7).await;
Expand Down Expand Up @@ -468,6 +465,17 @@ async fn definition_latex_label_theorem() {
assert_eq!(link.target_selection_range, Range::new_simple(4, 0, 4, 15));
}

#[tokio::test]
async fn definition_latex_math_operator() {
let (scenario, mut links) = run_definition_link("latex/math_operator", "foo.tex", 2, 2).await;
assert_eq!(links.len(), 1);
let link = links.pop().unwrap();
verify_origin_selection_range(&link, 2, 0, 2, 4);
assert_eq!(link.target_uri, scenario.uri("foo.tex").into());
assert_eq!(link.target_range, Range::new_simple(0, 0, 0, 31));
assert_eq!(link.target_selection_range, Range::new_simple(0, 0, 0, 31));
}

#[tokio::test]
async fn diagnostics_bibtex() {
let scenario = TestScenario::new("diagnostics/bibtex", &DEFAULT_CAPABILITIES).await;
Expand Down
7 changes: 7 additions & 0 deletions tests/scenarios/definition/bibtex/string/foo.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@string{foo = "Foo"}

@string{bar = "Bar"}

@article{baz,
author = bar
}

0 comments on commit 1d9799b

Please sign in to comment.