Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inlay hints for citations #944

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `texlab.inlayHints.labelDefinitions` and `texlab.inlayHints.labelReferences` options ([#753](https://github.com/latex-lsp/texlab/issues/753))
- Display inlay hints for label references by default ([#753](https://github.com/latex-lsp/texlab/issues/753))
- Add inlay hints for label references and citations ([#753](https://github.com/latex-lsp/texlab/issues/753))
- Add new options to configure inlay hints ([#753](https://github.com/latex-lsp/texlab/issues/753)):
- `texlab.inlayHints.labelDefinitions`
- `texlab.inlayHints.labelReferences`
- `texlab.inlayHints.citations`

## [5.10.1] - 2023-10-10

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/base-db/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub struct SymbolConfig {
pub struct InlayHintConfig {
pub label_definitions: bool,
pub label_references: bool,
pub citations: bool,
}

#[derive(Debug)]
Expand Down Expand Up @@ -187,6 +188,7 @@ impl Default for InlayHintConfig {
Self {
label_definitions: true,
label_references: true,
citations: false,
}
}
}
Expand Down
43 changes: 40 additions & 3 deletions crates/citeproc/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@ use syntax::bibtex;
use titlecase::titlecase;
use url::Url;

use crate::{Mode, Options};

use super::{
entry::{EntryData, EntryKind},
output::{Inline, InlineBuilder, Punct},
};

#[derive(Debug, Default)]
pub struct Driver {
#[derive(Debug)]
pub struct Driver<'a> {
builder: InlineBuilder,
options: &'a Options,
}

impl Driver {
impl<'a> Driver<'a> {
pub fn new(options: &'a Options) -> Self {
Self {
builder: InlineBuilder::default(),
options,
}
}

pub fn process(&mut self, entry: &bibtex::Entry) {
let entry = EntryData::from(entry);
match entry.kind {
Expand Down Expand Up @@ -531,6 +541,8 @@ impl Driver {
}

fn introduction(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let author = entry.author.remove(&AuthorField::Introduction)?;
self.builder.push(
Inline::Regular(format!("With an intro. by {}", author)),
Expand All @@ -542,6 +554,8 @@ impl Driver {
}

fn foreword(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let author = entry.author.remove(&AuthorField::Commentator)?;
self.builder.push(
Inline::Regular(format!("With a forew. by {}", author)),
Expand All @@ -553,6 +567,8 @@ impl Driver {
}

fn afterword(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let author = entry.author.remove(&AuthorField::Commentator)?;
self.builder.push(
Inline::Regular(format!("With an afterw. by {}", author)),
Expand All @@ -564,6 +580,8 @@ impl Driver {
}

fn note(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let note = entry.text.remove(&TextField::Note)?;
self.builder
.push(Inline::Regular(note.text), Punct::Dot, Punct::Comma);
Expand Down Expand Up @@ -614,6 +632,8 @@ impl Driver {
}

fn eid(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let eid = entry.text.remove(&TextField::Eid)?;
self.builder
.push(Inline::Regular(eid.text), Punct::Comma, Punct::Space);
Expand All @@ -622,6 +642,8 @@ impl Driver {
}

fn isbn(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let isbn = entry.text.remove(&TextField::Isbn)?;
self.builder.push(
Inline::Regular("ISBN".to_string()),
Expand All @@ -636,6 +658,8 @@ impl Driver {
}

fn issn(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let issn = entry.text.remove(&TextField::Issn)?;
self.builder.push(
Inline::Regular("ISSN".to_string()),
Expand All @@ -650,6 +674,8 @@ impl Driver {
}

fn url(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let url = entry.text.remove(&TextField::Url)?;

self.builder
Expand All @@ -672,6 +698,8 @@ impl Driver {
}

fn doi(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let doi = entry.text.remove(&TextField::Doi)?;
self.builder
.push(Inline::Regular("DOI".to_string()), Punct::Dot, Punct::Colon);
Expand All @@ -690,6 +718,8 @@ impl Driver {
}

fn eprint(&mut self, entry: &mut EntryData) -> Option<()> {
self.check_detailed_mode()?;

let eprint = entry.text.remove(&TextField::Eprint)?;
let eprint_type = entry
.text
Expand Down Expand Up @@ -722,6 +752,13 @@ impl Driver {
Some(())
}

fn check_detailed_mode(&self) -> Option<()> {
match self.options.mode {
Mode::Detailed => Some(()),
Mode::Overview => None,
}
}

pub fn finish(self) -> impl Iterator<Item = (Inline, Punct)> {
self.builder.finish()
}
Expand Down
21 changes: 19 additions & 2 deletions crates/citeproc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,27 @@ use unicode_normalization::UnicodeNormalization;

use self::{driver::Driver, output::Inline};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub enum Mode {
Detailed,
Overview,
}

impl Default for Mode {
fn default() -> Self {
Self::Detailed
}
}

#[derive(Debug, Default)]
pub struct Options {
pub mode: Mode,
}

#[must_use]
pub fn render(entry: &bibtex::Entry) -> Option<String> {
pub fn render(entry: &bibtex::Entry, options: &Options) -> Option<String> {
let mut output = String::new();
let mut driver = Driver::default();
let mut driver = Driver::new(options);
driver.process(entry);
driver.finish().for_each(|(inline, punct)| {
let text = match inline {
Expand Down
4 changes: 3 additions & 1 deletion crates/citeproc/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ use parser::parse_bibtex;
use rowan::ast::AstNode;
use syntax::bibtex;

use crate::Options;

fn check(input: &str, expect: Expect) {
let green = parse_bibtex(input);
let root = bibtex::Root::cast(bibtex::SyntaxNode::new_root(green)).unwrap();
let entry = root.entries().next().unwrap();
let output = super::render(&entry).unwrap();
let output = super::render(&entry, &Options::default()).unwrap();
expect.assert_eq(&output);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/hover/src/citation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub(super) fn find_hover<'db>(params: &HoverParams<'db>) -> Option<Hover<'db>> {
let data = document.data.as_bib()?;
let root = bibtex::Root::cast(data.root_node())?;
let entry = root.find_entry(name)?;
citeproc::render(&entry)
citeproc::render(&entry, &citeproc::Options::default())
})?;

let data = HoverData::Citation(text);
Expand Down
1 change: 1 addition & 0 deletions crates/inlay-hints/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rust-version.workspace = true

[dependencies]
base-db = { path = "../base-db" }
citeproc = { path = "../citeproc" }
rowan = "0.15.11"
rustc-hash = "1.1.0"
syntax = { path = "../syntax" }
Expand Down
56 changes: 56 additions & 0 deletions crates/inlay-hints/src/citations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use base_db::{
semantics::{bib::Entry, tex::Citation},
util::queries::Object,
Document,
};
use rowan::ast::AstNode;
use rustc_hash::FxHashMap;
use syntax::bibtex;

use crate::{InlayHint, InlayHintBuilder, InlayHintData};

pub(super) fn find_hints(builder: &mut InlayHintBuilder) -> Option<()> {
let params = &builder.params.feature;
let data = params.document.data.as_tex()?;
let range = builder.params.range;

let entries = Entry::find_all(&params.project)
.map(|(document, entry)| (entry.name_text(), (document, entry)))
.collect::<FxHashMap<_, _>>();

for citation in data
.semantics
.citations
.iter()
.filter(|citation| citation.name.range.intersect(range).is_some())
{
if let Some(hint) = process_citation(&entries, citation) {
builder.hints.push(hint);
}
}

Some(())
}

fn process_citation<'a>(
entries: &FxHashMap<&str, (&'a Document, &'a Entry)>,
citation: &'a Citation,
) -> Option<InlayHint<'a>> {
let offset = citation.name.range.end();
let (document, entry) = entries.get(citation.name.text.as_str())?;

let data = document.data.as_bib()?;
let root = &data.root_node();
let name = root
.token_at_offset(entry.name.range.start())
.right_biased()?;

let entry = name.parent_ancestors().find_map(bibtex::Entry::cast)?;
let options = citeproc::Options {
mode: citeproc::Mode::Overview,
};

let text = citeproc::render(&entry, &options)?;
let data = InlayHintData::Citation(text);
Some(InlayHint { offset, data })
}
3 changes: 3 additions & 0 deletions crates/inlay-hints/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod citations;
mod label;

use base_db::{util::RenderedLabel, FeatureParams};
Expand All @@ -18,6 +19,7 @@ pub struct InlayHint<'a> {
pub enum InlayHintData<'a> {
LabelDefinition(RenderedLabel<'a>),
LabelReference(RenderedLabel<'a>),
Citation(String),
}

pub fn find_all<'a>(params: InlayHintParams<'a>) -> Option<Vec<InlayHint>> {
Expand All @@ -27,6 +29,7 @@ pub fn find_all<'a>(params: InlayHintParams<'a>) -> Option<Vec<InlayHint>> {
};

label::find_hints(&mut builder);
citations::find_hints(&mut builder);
Some(builder.hints)
}

Expand Down
10 changes: 10 additions & 0 deletions crates/texlab/src/features/inlay_hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ pub fn find_all(
data: None,
}
}
InlayHintData::Citation(text) => lsp_types::InlayHint {
position,
label: lsp_types::InlayHintLabel::String(format!(" {text} ")),
kind: None,
text_edits: None,
tooltip: None,
padding_left: Some(true),
padding_right: None,
data: None,
},
})
});

Expand Down
4 changes: 3 additions & 1 deletion crates/texlab/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,9 @@ impl Server {
{
item.documentation = bibtex::Root::cast(data.root_node())
.and_then(|root| root.find_entry(&key))
.and_then(|entry| citeproc::render(&entry))
.and_then(|entry| {
citeproc::render(&entry, &citeproc::Options::default())
})
.map(|value| {
Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
Expand Down
2 changes: 2 additions & 0 deletions crates/texlab/src/server/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub struct DiagnosticsOptions {
pub struct InlayHintOptions {
pub label_definitions: Option<bool>,
pub label_references: Option<bool>,
pub citations: Option<bool>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
Expand Down Expand Up @@ -254,6 +255,7 @@ impl From<Options> for Config {

config.inlay_hints.label_definitions = value.inlay_hints.label_definitions.unwrap_or(true);
config.inlay_hints.label_references = value.inlay_hints.label_references.unwrap_or(true);
config.inlay_hints.citations = value.inlay_hints.citations.unwrap_or(false);

config.completion.matcher = match value.completion.matcher {
CompletionMatcher::Fuzzy => base_db::MatchingAlgo::Skim,
Expand Down