Skip to content

Commit

Permalink
Add hover handler for command symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
pfoerster committed Nov 11, 2024
1 parent 75efe5c commit ea5dc3b
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 36 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Allow hovering over symbol-like commands (e. g. `\pi`) to show a unicode preview
or a preview image if the client supports it ([#1261](https://github.com/latex-lsp/texlab/issues/1261))

## [5.21.0] - 2024-10-26

### Added
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.

1 change: 1 addition & 0 deletions crates/completion-data/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition.workspace = true
rust-version.workspace = true

[dependencies]
base-db = { path = "../base-db" }
flate2 = "1.0.34"
itertools.workspace = true
once_cell.workspace = true
Expand Down
21 changes: 21 additions & 0 deletions crates/completion-data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,24 @@ pub static DATABASE: Lazy<Database<'static>> = Lazy::new(|| {

db
});

pub fn included_packages<'a>(
params: &'a base_db::FeatureParams<'a>,
) -> impl Iterator<Item = &'static crate::Package<'static>> + 'a {
let db = &crate::DATABASE;
let documents = params.project.documents.iter();
let links = documents
.filter_map(|document| document.data.as_tex())
.flat_map(|data| data.semantics.links.iter());

links
.filter_map(|link| link.package_name())
.filter_map(|name| db.find(&name))
.chain(std::iter::once(db.kernel()))
.flat_map(|pkg| {
pkg.references
.iter()
.filter_map(|name| db.find(name))
.chain(std::iter::once(pkg))
})
}
3 changes: 2 additions & 1 deletion crates/completion/src/providers/argument.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use base_db::semantics::Span;
use completion_data::included_packages;
use rowan::{ast::AstNode, TokenAtOffset};
use syntax::latex;

use crate::{
util::{included_packages, is_inside_latex_curly, CompletionBuilder},
util::{is_inside_latex_curly, CompletionBuilder},
ArgumentData, CompletionItem, CompletionItemData, CompletionParams,
};

Expand Down
3 changes: 2 additions & 1 deletion crates/completion/src/providers/command.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use base_db::{semantics::Span, DocumentData};
use completion_data::included_packages;
use rowan::{TextRange, TextSize};
use syntax::{bibtex, latex};

use crate::{
util::{included_packages, CompletionBuilder, ProviderContext},
util::{CompletionBuilder, ProviderContext},
CommandData, CompletionItem, CompletionItemData, CompletionParams,
};

Expand Down
3 changes: 2 additions & 1 deletion crates/completion/src/providers/environment.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use base_db::semantics::Span;
use completion_data::included_packages;
use rowan::ast::AstNode;
use syntax::latex;

use crate::{
util::{find_curly_group_word, included_packages, CompletionBuilder, ProviderContext},
util::{find_curly_group_word, CompletionBuilder, ProviderContext},
CompletionItem, CompletionItemData, CompletionParams, EnvironmentData,
};

Expand Down
21 changes: 0 additions & 21 deletions crates/completion/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,6 @@ mod patterns;
pub use builder::*;
pub use patterns::*;

pub fn included_packages<'a>(
params: &'a base_db::FeatureParams<'a>,
) -> impl Iterator<Item = &completion_data::Package<'_>> + 'a {
let db = &completion_data::DATABASE;
let documents = params.project.documents.iter();
let links = documents
.filter_map(|document| document.data.as_tex())
.flat_map(|data| data.semantics.links.iter());

links
.filter_map(|link| link.package_name())
.filter_map(|name| db.find(&name))
.chain(std::iter::once(db.kernel()))
.flat_map(|pkg| {
pkg.references
.iter()
.filter_map(|name| db.find(name))
.chain(std::iter::once(pkg))
})
}

pub struct ProviderContext<'a, 'b> {
pub builder: &'b mut CompletionBuilder<'a>,
pub params: &'a crate::CompletionParams<'a>,
Expand Down
19 changes: 19 additions & 0 deletions crates/hover/src/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use syntax::latex;

use crate::{Hover, HoverData, HoverParams};

pub(super) fn find_hover<'a>(params: &HoverParams<'a>) -> Option<Hover<'a>> {
let data = params.feature.document.data.as_tex()?;
let name = data
.root_node()
.token_at_offset(params.offset)
.find(|token| token.kind() == latex::COMMAND_NAME)?;

let command = completion_data::included_packages(&params.feature)
.flat_map(|package| package.commands.iter())
.find(|command| command.name == &name.text()[1..])?;

let range = name.text_range();
let data = HoverData::Command(command);
Some(Hover { range, data })
}
17 changes: 10 additions & 7 deletions crates/hover/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod citation;
mod command;
mod entry_type;
mod field_type;
mod label;
Expand All @@ -19,18 +20,19 @@ pub struct HoverParams<'a> {
}

#[derive(Debug, Clone)]
pub struct Hover<'db> {
pub struct Hover<'a> {
pub range: TextRange,
pub data: HoverData<'db>,
pub data: HoverData<'a>,
}

#[derive(Debug, Clone)]
pub enum HoverData<'db> {
pub enum HoverData<'a> {
Citation(String),
Package(&'db str),
EntryType(BibtexEntryType<'db>),
FieldType(BibtexFieldType<'db>),
Label(RenderedLabel<'db>),
Package(&'a str),
Command(&'a completion_data::Command<'static>),
EntryType(BibtexEntryType<'a>),
FieldType(BibtexFieldType<'a>),
Label(RenderedLabel<'a>),
StringRef(String),
}

Expand All @@ -41,6 +43,7 @@ pub fn find<'a>(params: &HoverParams<'a>) -> Option<Hover<'a>> {
.or_else(|| field_type::find_hover(params))
.or_else(|| label::find_hover(params))
.or_else(|| string_ref::find_hover(params))
.or_else(|| command::find_hover(params))
}

#[cfg(test)]
Expand Down
66 changes: 66 additions & 0 deletions crates/hover/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,69 @@ fn test_latex_label_ntheorem() {
"#]],
);
}

#[test]
fn test_symbol_command_kernel() {
check(
r#"
%! main.tex
\pi
|
^^^"#,
expect![[r#"
Some(
Command(
Command {
name: "pi",
image: Some(
"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",
),
glyph: Some(
"π",
),
parameters: [],
},
),
)
"#]],
);
}

#[test]
fn test_symbol_command_package() {
check(
r#"
%! main.tex
\usepackage{amsmath}
\varDelta
|
^^^^^^^^^"#,
expect![[r#"
Some(
Command(
Command {
name: "varDelta",
image: Some(
"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",
),
glyph: None,
parameters: [],
},
),
)
"#]],
);
}

#[test]
fn test_symbol_command_package_not_included() {
check(
r#"
%! main.tex
\varDelta
|"#,
expect![[r#"
None
"#]],
);
}
10 changes: 7 additions & 3 deletions crates/texlab/src/features/hover.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use base_db::Workspace;

use crate::util::{from_proto, to_proto};
use crate::util::{from_proto, to_proto, ClientFlags};

pub fn find(workspace: &Workspace, params: lsp_types::HoverParams) -> Option<lsp_types::Hover> {
pub fn find(
workspace: &Workspace,
params: lsp_types::HoverParams,
client_flags: &ClientFlags,
) -> Option<lsp_types::Hover> {
let params = from_proto::hover_params(workspace, params)?;
let hover = ::hover::find(&params)?;
to_proto::hover(hover, &params.feature.document.line_index)
to_proto::hover(hover, &params.feature.document.line_index, client_flags)
}
3 changes: 2 additions & 1 deletion crates/texlab/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,9 @@ impl Server {
fn hover(&mut self, id: RequestId, mut params: HoverParams) -> Result<()> {
normalize_uri(&mut params.text_document_position_params.text_document.uri);
let uri_and_pos = &params.text_document_position_params;
let client_flags = Arc::clone(&self.client_flags);
self.update_cursor(&uri_and_pos.text_document.uri, uri_and_pos.position);
self.run_query(id, move |db| hover::find(db, params));
self.run_query(id, move |db| hover::find(db, params, &client_flags));
Ok(())
}

Expand Down
4 changes: 4 additions & 0 deletions crates/texlab/src/util/client_flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ pub struct ClientFlags {
/// This is used to include images via base64 encoding.
pub completion_markdown: bool,

/// If `true`, the server can include markdown in hover results.
/// This is used to include images via base64 encoding.
pub hover_markdown: bool,

/// If `true`, the server can include snippets like `\begin{...}` in completion items.
pub completion_snippets: bool,

Expand Down
11 changes: 11 additions & 0 deletions crates/texlab/src/util/from_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ pub fn client_flags(
formats.contains(&lsp_types::MarkupKind::Markdown)
});

let hover_markdown = capabilities
.text_document
.as_ref()
.and_then(|cap| cap.hover.as_ref())
.and_then(|cap| cap.content_format.as_ref())
.map_or(false, |cap| {
// Use the preferred format of the editor instead of just markdown (if available).
cap.first() == Some(&lsp_types::MarkupKind::Markdown)
});

let completion_snippets = capabilities
.text_document
.as_ref()
Expand Down Expand Up @@ -92,6 +102,7 @@ pub fn client_flags(
ClientFlags {
hierarchical_document_symbols,
completion_markdown,
hover_markdown,
completion_snippets,
completion_kinds,
completion_always_incomplete,
Expand Down
29 changes: 28 additions & 1 deletion crates/texlab/src/util/to_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,33 @@ pub fn document_highlight(
Some(lsp_types::DocumentHighlight { range, kind })
}

pub fn hover(hover: Hover, line_index: &LineIndex) -> Option<lsp_types::Hover> {
pub fn hover(
hover: Hover,
line_index: &LineIndex,
client_flags: &ClientFlags,
) -> Option<lsp_types::Hover> {
fn inline_image(
command: &completion_data::Command,

client_flags: &ClientFlags,
) -> Option<lsp_types::MarkupContent> {
let name = &command.name;
command
.image
.map(|base64| format!("![{name}](data:image/png;base64,{base64}|width=48,height=48)"))
.filter(|_| client_flags.hover_markdown)
.map(|value| lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value,
})
.or_else(|| {
Some(lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::PlainText,
value: command.glyph.as_deref()?.into(),
})
})
}

let contents = match hover.data {
HoverData::Citation(text) => lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
Expand All @@ -456,6 +482,7 @@ pub fn hover(hover: Hover, line_index: &LineIndex) -> Option<lsp_types::Hover> {
kind: lsp_types::MarkupKind::PlainText,
value: description.into(),
},
HoverData::Command(command) => inline_image(command, client_flags)?,
HoverData::EntryType(type_) => lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: type_.documentation?.into(),
Expand Down

0 comments on commit ea5dc3b

Please sign in to comment.