summaryrefslogtreecommitdiff
path: root/support/texlab
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2024-01-28 03:00:53 +0000
committerNorbert Preining <norbert@preining.info>2024-01-28 03:00:53 +0000
commit7084e3008c8fc947579f46c6b8a08dfd180e72ef (patch)
tree9c99041cba4afefac3859d6a2fe4cea289458468 /support/texlab
parentb93d257f657e619e22b8b7a27446118ce041727e (diff)
CTAN sync 202401280300
Diffstat (limited to 'support/texlab')
-rw-r--r--support/texlab/CHANGELOG.md6
-rw-r--r--support/texlab/Cargo.lock21
-rw-r--r--support/texlab/crates/base-db/src/semantics/tex.rs8
-rw-r--r--support/texlab/crates/completion/src/providers/include.rs13
-rw-r--r--support/texlab/crates/definition/src/lib.rs6
-rw-r--r--support/texlab/crates/definition/src/tests.rs2
-rw-r--r--support/texlab/crates/diagnostics/Cargo.toml10
-rw-r--r--support/texlab/crates/diagnostics/src/build_log.rs104
-rw-r--r--support/texlab/crates/diagnostics/src/chktex.rs (renamed from support/texlab/crates/texlab/src/util/chktex.rs)49
-rw-r--r--support/texlab/crates/diagnostics/src/citations.rs91
-rw-r--r--support/texlab/crates/diagnostics/src/grammar.rs6
-rw-r--r--support/texlab/crates/diagnostics/src/grammar/bib.rs87
-rw-r--r--support/texlab/crates/diagnostics/src/grammar/tex.rs91
-rw-r--r--support/texlab/crates/diagnostics/src/labels.rs53
-rw-r--r--support/texlab/crates/diagnostics/src/lib.rs85
-rw-r--r--support/texlab/crates/diagnostics/src/manager.rs75
-rw-r--r--support/texlab/crates/diagnostics/src/tests.rs201
-rw-r--r--support/texlab/crates/diagnostics/src/types.rs65
-rw-r--r--support/texlab/crates/diagnostics/src/util.rs28
-rw-r--r--support/texlab/crates/highlights/src/lib.rs2
-rw-r--r--support/texlab/crates/highlights/src/tests.rs2
-rw-r--r--support/texlab/crates/hover/src/lib.rs2
-rw-r--r--support/texlab/crates/hover/src/tests.rs2
-rw-r--r--support/texlab/crates/inlay-hints/src/lib.rs6
-rw-r--r--support/texlab/crates/inlay-hints/src/tests.rs2
-rw-r--r--support/texlab/crates/links/src/include.rs2
-rw-r--r--support/texlab/crates/links/src/lib.rs2
-rw-r--r--support/texlab/crates/links/src/tests.rs2
-rw-r--r--support/texlab/crates/references/src/lib.rs15
-rw-r--r--support/texlab/crates/references/src/tests.rs16
-rw-r--r--support/texlab/crates/texlab/Cargo.toml2
-rw-r--r--support/texlab/crates/texlab/benches/bench_main.rs32
-rw-r--r--support/texlab/crates/texlab/src/features/completion.rs108
-rw-r--r--support/texlab/crates/texlab/src/features/definition.rs53
-rw-r--r--support/texlab/crates/texlab/src/features/folding.rs44
-rw-r--r--support/texlab/crates/texlab/src/features/formatting.rs7
-rw-r--r--support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs5
-rw-r--r--support/texlab/crates/texlab/src/features/formatting/latexindent.rs7
-rw-r--r--support/texlab/crates/texlab/src/features/highlight.rs24
-rw-r--r--support/texlab/crates/texlab/src/features/hover.rs45
-rw-r--r--support/texlab/crates/texlab/src/features/inlay_hint.rs69
-rw-r--r--support/texlab/crates/texlab/src/features/link.rs28
-rw-r--r--support/texlab/crates/texlab/src/features/reference.rs29
-rw-r--r--support/texlab/crates/texlab/src/features/rename.rs42
-rw-r--r--support/texlab/crates/texlab/src/features/symbols.rs115
-rw-r--r--support/texlab/crates/texlab/src/lib.rs4
-rw-r--r--support/texlab/crates/texlab/src/main.rs2
-rw-r--r--support/texlab/crates/texlab/src/server.rs342
-rw-r--r--support/texlab/crates/texlab/src/util.rs8
-rw-r--r--support/texlab/crates/texlab/src/util/capabilities.rs179
-rw-r--r--support/texlab/crates/texlab/src/util/client_flags.rs40
-rw-r--r--support/texlab/crates/texlab/src/util/diagnostics.rs201
-rw-r--r--support/texlab/crates/texlab/src/util/from_proto.rs223
-rw-r--r--support/texlab/crates/texlab/src/util/lsp_enums.rs36
-rw-r--r--support/texlab/crates/texlab/src/util/to_proto.rs459
-rw-r--r--support/texlab/texlab.14
-rw-r--r--support/texlab/texlab.pdfbin26384 -> 26470 bytes
57 files changed, 1572 insertions, 1590 deletions
diff --git a/support/texlab/CHANGELOG.md b/support/texlab/CHANGELOG.md
index 3a2771ebfd..928eb0c10b 100644
--- a/support/texlab/CHANGELOG.md
+++ b/support/texlab/CHANGELOG.md
@@ -5,6 +5,12 @@ 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).
+## [5.12.3] - 2024-01-27
+
+### Fixed
+
+- Fix completing includes in conjunction with `\graphicspath` ([#997](https://github.com/latex-lsp/texlab/issues/997))
+
## [5.12.2] - 2024-01-20
### Fixed
diff --git a/support/texlab/Cargo.lock b/support/texlab/Cargo.lock
index 8a130372d7..64669f9f36 100644
--- a/support/texlab/Cargo.lock
+++ b/support/texlab/Cargo.lock
@@ -443,9 +443,19 @@ dependencies = [
name = "diagnostics"
version = "0.0.0"
dependencies = [
+ "anyhow",
"base-db",
+ "dirs",
+ "encoding_rs",
+ "encoding_rs_io",
+ "expect-test",
"itertools 0.12.0",
"line-index",
+ "log",
+ "multimap",
+ "once_cell",
+ "parking_lot",
+ "regex",
"rowan",
"rustc-hash",
"syntax",
@@ -977,6 +987,15 @@ dependencies = [
]
[[package]]
+name = "multimap"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1a5d38b9b352dbd913288736af36af41c48d61b1a8cd34bcecd727561b7d511"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1496,7 +1515,7 @@ dependencies = [
[[package]]
name = "texlab"
-version = "5.12.2"
+version = "5.12.3"
dependencies = [
"anyhow",
"base-db",
diff --git a/support/texlab/crates/base-db/src/semantics/tex.rs b/support/texlab/crates/base-db/src/semantics/tex.rs
index c1e9f45bb9..659ec8c166 100644
--- a/support/texlab/crates/base-db/src/semantics/tex.rs
+++ b/support/texlab/crates/base-db/src/semantics/tex.rs
@@ -59,6 +59,8 @@ impl Semantics {
self.process_environment(environment);
} else if let Some(theorem_def) = latex::TheoremDefinition::cast(node.clone()) {
self.process_theorem_definition(theorem_def);
+ } else if let Some(graphics_path) = latex::GraphicsPath::cast(node.clone()) {
+ self.process_graphics_path(graphics_path);
}
}
@@ -268,6 +270,12 @@ impl Semantics {
heading,
});
}
+
+ fn process_graphics_path(&mut self, graphics_path: latex::GraphicsPath) {
+ for path in graphics_path.path_list().filter_map(|path| path.key()) {
+ self.graphics_paths.insert(path.to_string());
+ }
+ }
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
diff --git a/support/texlab/crates/completion/src/providers/include.rs b/support/texlab/crates/completion/src/providers/include.rs
index 2dd204cfdc..f8bb55dfeb 100644
--- a/support/texlab/crates/completion/src/providers/include.rs
+++ b/support/texlab/crates/completion/src/providers/include.rs
@@ -87,7 +87,7 @@ pub fn complete_includes<'a>(
if let Some(score) = builder.matcher.score(&name, segment_text) {
builder.items.push(CompletionItem::new_simple(
score,
- cursor.range,
+ segment_range,
CompletionItemData::File(name),
));
}
@@ -96,7 +96,7 @@ pub fn complete_includes<'a>(
if let Some(score) = builder.matcher.score(&name, segment_text) {
builder.items.push(CompletionItem::new_simple(
score,
- cursor.range,
+ segment_range,
CompletionItemData::Directory(name),
));
}
@@ -121,11 +121,12 @@ fn current_dir(
let path = workspace.current_dir(&parent.dir).to_file_path().ok()?;
let mut path = PathBuf::from(path.to_str()?.replace('\\', "/"));
- if !path_text.is_empty() {
- if let Some(graphics_path) = graphics_path {
- path.push(graphics_path);
- }
+ if let Some(graphics_path) = graphics_path {
+ path.push(graphics_path);
+ }
+
+ if !path_text.is_empty() {
path.push(path_text);
if !path_text.ends_with('/') {
path.pop();
diff --git a/support/texlab/crates/definition/src/lib.rs b/support/texlab/crates/definition/src/lib.rs
index 55319b7fe4..e35a0fdcc1 100644
--- a/support/texlab/crates/definition/src/lib.rs
+++ b/support/texlab/crates/definition/src/lib.rs
@@ -23,12 +23,12 @@ pub struct DefinitionResult<'a> {
}
#[derive(Debug)]
-struct DefinitionContext<'a> {
- params: DefinitionParams<'a>,
+struct DefinitionContext<'a, 'b> {
+ params: &'b DefinitionParams<'a>,
results: FxHashSet<DefinitionResult<'a>>,
}
-pub fn goto_definition(params: DefinitionParams) -> FxHashSet<DefinitionResult> {
+pub fn goto_definition<'a>(params: &DefinitionParams<'a>) -> FxHashSet<DefinitionResult<'a>> {
let mut context = DefinitionContext {
params,
results: FxHashSet::default(),
diff --git a/support/texlab/crates/definition/src/tests.rs b/support/texlab/crates/definition/src/tests.rs
index 8d81927d32..54b0e38f30 100644
--- a/support/texlab/crates/definition/src/tests.rs
+++ b/support/texlab/crates/definition/src/tests.rs
@@ -30,7 +30,7 @@ fn check(input: &str) {
}
}
- let actual = crate::goto_definition(DefinitionParams { feature, offset });
+ let actual = crate::goto_definition(&DefinitionParams { feature, offset });
assert_eq!(actual, expected);
}
diff --git a/support/texlab/crates/diagnostics/Cargo.toml b/support/texlab/crates/diagnostics/Cargo.toml
index 7de9c6042d..1fb4b2171d 100644
--- a/support/texlab/crates/diagnostics/Cargo.toml
+++ b/support/texlab/crates/diagnostics/Cargo.toml
@@ -7,15 +7,25 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
+anyhow = "1.0.75"
base-db = { path = "../base-db" }
+dirs = "5.0.1"
+encoding_rs = "0.8.33"
+encoding_rs_io = "0.1.7"
itertools = "0.12.0"
line-index = { path = "../line-index" }
+log = "0.4.20"
+multimap = "0.9.1"
+once_cell = "1.19.0"
+parking_lot = "0.12.1"
+regex = "1.10.2"
rowan = "0.15.15"
rustc-hash = "1.1.0"
syntax = { path = "../syntax" }
url = "2.5.0"
[dev-dependencies]
+expect-test = "1.4.1"
test-utils = { path = "../test-utils" }
[lib]
diff --git a/support/texlab/crates/diagnostics/src/build_log.rs b/support/texlab/crates/diagnostics/src/build_log.rs
index 4c53a35ff8..352e18d0d1 100644
--- a/support/texlab/crates/diagnostics/src/build_log.rs
+++ b/support/texlab/crates/diagnostics/src/build_log.rs
@@ -1,94 +1,54 @@
-use std::borrow::Cow;
-
use base_db::{Document, Workspace};
use line_index::LineCol;
+use multimap::MultiMap;
use rowan::{TextLen, TextRange, TextSize};
use rustc_hash::FxHashMap;
use syntax::BuildError;
use url::Url;
-use crate::{
- types::{Diagnostic, DiagnosticData},
- DiagnosticBuilder, DiagnosticSource,
-};
-
-#[derive(Debug, Default)]
-struct BuildLog {
- errors: FxHashMap<Url, Vec<Diagnostic>>,
-}
+use crate::types::Diagnostic;
-#[derive(Debug, Default)]
-pub struct BuildErrors {
- logs: FxHashMap<Url, BuildLog>,
-}
+pub fn update(
+ workspace: &Workspace,
+ log_document: &Document,
+ results: &mut FxHashMap<Url, MultiMap<Url, Diagnostic>>,
+) -> Option<()> {
+ let mut errors = MultiMap::default();
-impl DiagnosticSource for BuildErrors {
- fn update(&mut self, workspace: &Workspace, log_document: &Document) {
- let mut errors: FxHashMap<Url, Vec<Diagnostic>> = FxHashMap::default();
+ let data = log_document.data.as_log()?;
- let Some(data) = log_document.data.as_log() else {
- return;
- };
+ let parents = workspace.parents(log_document);
+ let root_document = parents.iter().next()?;
- let parents = workspace.parents(log_document);
- let Some(root_document) = parents.iter().next() else {
- return;
- };
+ let base_path = root_document
+ .path
+ .as_deref()
+ .and_then(|path| path.parent())?;
- let Some(base_path) = root_document.path.as_deref().and_then(|path| path.parent()) else {
- return;
+ for error in &data.errors {
+ let full_path = base_path.join(&error.relative_path);
+ let Ok(full_path_uri) = Url::from_file_path(&full_path) else {
+ continue;
};
- for error in &data.errors {
- let full_path = base_path.join(&error.relative_path);
- let Ok(full_path_uri) = Url::from_file_path(&full_path) else {
- continue;
- };
+ let tex_document = workspace.lookup(&full_path_uri).unwrap_or(root_document);
- let tex_document = workspace.lookup(&full_path_uri).unwrap_or(root_document);
+ let range = find_range_of_hint(tex_document, error).unwrap_or_else(|| {
+ let line = error.line.unwrap_or(0);
+ let offset = tex_document
+ .line_index
+ .offset(LineCol { line, col: 0 })
+ .unwrap_or(TextSize::from(0));
- let range = find_range_of_hint(tex_document, error).unwrap_or_else(|| {
- let line = error.line.unwrap_or(0);
- let offset = tex_document
- .line_index
- .offset(LineCol { line, col: 0 })
- .unwrap_or(TextSize::from(0));
+ TextRange::empty(offset)
+ });
- TextRange::empty(offset)
- });
-
- let diagnostic = Diagnostic {
- range,
- data: DiagnosticData::Build(error.clone()),
- };
-
- errors
- .entry(tex_document.uri.clone())
- .or_default()
- .push(diagnostic);
- }
-
- self.logs
- .insert(log_document.uri.clone(), BuildLog { errors });
+ let diagnostic = Diagnostic::Build(range, error.clone());
+ errors.insert(tex_document.uri.clone(), diagnostic);
}
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- self.logs.retain(|uri, _| workspace.lookup(uri).is_some());
-
- for document in workspace.iter() {
- let Some(log) = self.logs.get(&document.uri) else {
- continue;
- };
-
- for (uri, errors) in &log.errors {
- builder.push_many(uri, errors.iter().map(Cow::Borrowed));
- }
- }
- }
+ results.insert(log_document.uri.clone(), errors);
+ Some(())
}
fn find_range_of_hint(document: &Document, error: &BuildError) -> Option<TextRange> {
diff --git a/support/texlab/crates/texlab/src/util/chktex.rs b/support/texlab/crates/diagnostics/src/chktex.rs
index 6166108707..a342f78349 100644
--- a/support/texlab/crates/texlab/src/util/chktex.rs
+++ b/support/texlab/crates/diagnostics/src/chktex.rs
@@ -5,13 +5,13 @@ use std::{
};
use base_db::{Document, Workspace};
-use distro::Language;
use encoding_rs_io::DecodeReaderBytesBuilder;
-use lsp_types::{Diagnostic, NumberOrString};
-use lsp_types::{DiagnosticSeverity, Position, Range};
+use line_index::LineCol;
use once_cell::sync::Lazy;
use regex::Regex;
+use crate::{types::Diagnostic, ChktexError, ChktexSeverity};
+
#[derive(Debug)]
pub struct Command {
text: String,
@@ -21,9 +21,7 @@ pub struct Command {
impl Command {
pub fn new(workspace: &Workspace, document: &Document) -> Option<Self> {
- if document.language != Language::Tex {
- return None;
- }
+ document.data.as_tex()?;
let parent = workspace
.parents(document)
@@ -31,7 +29,7 @@ impl Command {
.next()
.unwrap_or(document);
- if parent.uri.scheme() != "file" {
+ if parent.path.is_none() {
log::warn!("Calling ChkTeX on non-local files is not supported yet.");
return None;
}
@@ -78,30 +76,31 @@ impl Command {
let character = captures[2].parse::<u32>().unwrap() - 1;
let digit = captures[3].parse::<u32>().unwrap();
let kind = &captures[4];
- let code = &captures[5];
+ let code = String::from(&captures[5]);
let message = captures[6].into();
- let range = Range::new(
- Position::new(line, character),
- Position::new(line, character + digit),
- );
+ let start = LineCol {
+ line,
+ col: character,
+ };
+
+ let end = LineCol {
+ line,
+ col: character + digit,
+ };
let severity = match kind {
- "Message" => DiagnosticSeverity::INFORMATION,
- "Warning" => DiagnosticSeverity::WARNING,
- _ => DiagnosticSeverity::ERROR,
+ "Message" => ChktexSeverity::Message,
+ "Warning" => ChktexSeverity::Warning,
+ _ => ChktexSeverity::Error,
};
- diagnostics.push(Diagnostic {
- range,
- severity: Some(severity),
- code: Some(NumberOrString::String(code.into())),
+ diagnostics.push(Diagnostic::Chktex(ChktexError {
+ start,
+ end,
message,
- code_description: None,
- source: Some(String::from("ChkTeX")),
- related_information: None,
- tags: None,
- data: None,
- });
+ severity,
+ code,
+ }));
}
diagnostics
diff --git a/support/texlab/crates/diagnostics/src/citations.rs b/support/texlab/crates/diagnostics/src/citations.rs
index 0b750aaadb..403d2f5bfc 100644
--- a/support/texlab/crates/diagnostics/src/citations.rs
+++ b/support/texlab/crates/diagnostics/src/citations.rs
@@ -1,46 +1,22 @@
-use std::borrow::Cow;
-
use base_db::{
semantics::{bib::Entry, tex::Citation},
util::queries::{self, Object},
Document, Project, Workspace,
};
+use multimap::MultiMap;
use rustc_hash::FxHashSet;
+use url::Url;
-use crate::{
- types::{BibError, Diagnostic, DiagnosticData, TexError},
- DiagnosticBuilder, DiagnosticSource,
-};
+use crate::types::{BibError, Diagnostic, TexError};
const MAX_UNUSED_ENTRIES: usize = 1000;
-#[derive(Default)]
-pub struct CitationErrors;
-
-impl DiagnosticSource for CitationErrors {
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- for document in workspace.iter() {
- let project = workspace.project(document);
- detect_undefined_citations(&project, document, builder);
- detect_unused_entries(&project, document, builder);
- }
-
- detect_duplicate_entries(workspace, builder);
- }
-}
-
-fn detect_undefined_citations<'db>(
- project: &Project<'db>,
- document: &'db Document,
- builder: &mut DiagnosticBuilder<'db>,
-) {
- let Some(data) = document.data.as_tex() else {
- return;
- };
+pub fn detect_undefined_citations<'a>(
+ project: &Project<'a>,
+ document: &'a Document,
+ results: &mut MultiMap<Url, Diagnostic>,
+) -> Option<()> {
+ let data = document.data.as_tex()?;
let entries: FxHashSet<&str> = Entry::find_all(project)
.map(|(_, entry)| entry.name_text())
@@ -49,28 +25,24 @@ fn detect_undefined_citations<'db>(
for citation in &data.semantics.citations {
let name = citation.name_text();
if name != "*" && !entries.contains(name) {
- let diagnostic = Diagnostic {
- range: citation.name.range,
- data: DiagnosticData::Tex(TexError::UndefinedCitation),
- };
-
- builder.push(&document.uri, Cow::Owned(diagnostic));
+ let diagnostic = Diagnostic::Tex(citation.name.range, TexError::UndefinedCitation);
+ results.insert(document.uri.clone(), diagnostic);
}
}
+
+ Some(())
}
-fn detect_unused_entries<'db>(
- project: &Project<'db>,
- document: &'db Document,
- builder: &mut DiagnosticBuilder<'db>,
-) {
- let Some(data) = document.data.as_bib() else {
- return;
- };
+pub fn detect_unused_entries<'a>(
+ project: &Project<'a>,
+ document: &'a Document,
+ results: &mut MultiMap<Url, Diagnostic>,
+) -> Option<()> {
+ let data = document.data.as_bib()?;
// If this is a huge bibliography, then don't bother checking for unused entries.
if data.semantics.entries.len() > MAX_UNUSED_ENTRIES {
- return;
+ return None;
}
let citations: FxHashSet<&str> = Citation::find_all(project)
@@ -79,17 +51,18 @@ fn detect_unused_entries<'db>(
for entry in &data.semantics.entries {
if !citations.contains(entry.name.text.as_str()) {
- let diagnostic = Diagnostic {
- range: entry.name.range,
- data: DiagnosticData::Bib(BibError::UnusedEntry),
- };
-
- builder.push(&document.uri, Cow::Owned(diagnostic));
+ let diagnostic = Diagnostic::Bib(entry.name.range, BibError::UnusedEntry);
+ results.insert(document.uri.clone(), diagnostic);
}
}
+
+ Some(())
}
-fn detect_duplicate_entries<'db>(workspace: &'db Workspace, builder: &mut DiagnosticBuilder<'db>) {
+pub fn detect_duplicate_entries<'a>(
+ workspace: &'a Workspace,
+ results: &mut MultiMap<Url, Diagnostic>,
+) {
for conflict in queries::Conflict::find_all::<Entry>(workspace) {
let others = conflict
.rest
@@ -97,11 +70,7 @@ fn detect_duplicate_entries<'db>(workspace: &'db Workspace, builder: &mut Diagno
.map(|location| (location.document.uri.clone(), location.range))
.collect();
- let diagnostic = Diagnostic {
- range: conflict.main.range,
- data: DiagnosticData::Bib(BibError::DuplicateEntry(others)),
- };
-
- builder.push(&conflict.main.document.uri, Cow::Owned(diagnostic));
+ let diagnostic = Diagnostic::Bib(conflict.main.range, BibError::DuplicateEntry(others));
+ results.insert(conflict.main.document.uri.clone(), diagnostic);
}
}
diff --git a/support/texlab/crates/diagnostics/src/grammar.rs b/support/texlab/crates/diagnostics/src/grammar.rs
index 1a9e3cecda..df05ee6d14 100644
--- a/support/texlab/crates/diagnostics/src/grammar.rs
+++ b/support/texlab/crates/diagnostics/src/grammar.rs
@@ -1,4 +1,2 @@
-mod bib;
-mod tex;
-
-pub use self::{bib::BibSyntaxErrors, tex::TexSyntaxErrors};
+pub mod bib;
+pub mod tex;
diff --git a/support/texlab/crates/diagnostics/src/grammar/bib.rs b/support/texlab/crates/diagnostics/src/grammar/bib.rs
index e101e68f2a..494cdbe4b1 100644
--- a/support/texlab/crates/diagnostics/src/grammar/bib.rs
+++ b/support/texlab/crates/diagnostics/src/grammar/bib.rs
@@ -1,48 +1,35 @@
-use base_db::{Document, DocumentData, Workspace};
+use base_db::{BibDocumentData, Document};
+use multimap::MultiMap;
use rowan::{ast::AstNode, TextRange};
use syntax::bibtex::{self, HasDelims, HasEq, HasName, HasType, HasValue};
+use url::Url;
-use crate::{
- types::{BibError, DiagnosticData},
- util::SimpleDiagnosticSource,
- Diagnostic, DiagnosticBuilder, DiagnosticSource,
-};
+use crate::types::{BibError, Diagnostic};
-#[derive(Default)]
-pub struct BibSyntaxErrors(SimpleDiagnosticSource);
+pub fn update(document: &Document, results: &mut MultiMap<Url, Diagnostic>) -> Option<()> {
+ let data = document.data.as_bib()?;
+ let mut analyzer = Analyzer {
+ data,
+ diagnostics: Vec::new(),
+ };
-impl DiagnosticSource for BibSyntaxErrors {
- fn update(&mut self, _workspace: &Workspace, document: &Document) {
- let mut analyzer = Analyzer {
- document,
- diagnostics: Vec::new(),
- };
+ analyzer.analyze_root();
- analyzer.analyze_root();
- self.0
- .errors
- .insert(document.uri.clone(), analyzer.diagnostics);
- }
+ *results
+ .entry(document.uri.clone())
+ .or_insert_vec(Vec::new()) = analyzer.diagnostics;
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- self.0.publish(workspace, builder);
- }
+ Some(())
}
struct Analyzer<'a> {
- document: &'a Document,
+ data: &'a BibDocumentData,
diagnostics: Vec<Diagnostic>,
}
impl<'a> Analyzer<'a> {
fn analyze_root(&mut self) {
- let DocumentData::Bib(data) = &self.document.data else { return };
-
- for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() {
+ for node in self.data.root_node().descendants() {
if let Some(entry) = bibtex::Entry::cast(node.clone()) {
self.analyze_entry(entry);
} else if let Some(field) = bibtex::Field::cast(node.clone()) {
@@ -54,50 +41,50 @@ impl<'a> Analyzer<'a> {
fn analyze_entry(&mut self, entry: bibtex::Entry) {
if entry.left_delim_token().is_none() {
let offset = entry.type_token().unwrap().text_range().end();
- self.diagnostics.push(Diagnostic {
- range: TextRange::empty(offset),
- data: DiagnosticData::Bib(BibError::ExpectingLCurly),
- });
+ self.diagnostics.push(Diagnostic::Bib(
+ TextRange::empty(offset),
+ BibError::ExpectingLCurly,
+ ));
return;
}
if entry.name_token().is_none() {
let offset = entry.left_delim_token().unwrap().text_range().end();
- self.diagnostics.push(Diagnostic {
- range: TextRange::empty(offset),
- data: DiagnosticData::Bib(BibError::ExpectingKey),
- });
+ self.diagnostics.push(Diagnostic::Bib(
+ TextRange::empty(offset),
+ BibError::ExpectingKey,
+ ));
return;
}
if entry.right_delim_token().is_none() {
let offset = entry.syntax().text_range().end();
- self.diagnostics.push(Diagnostic {
- range: TextRange::empty(offset),
- data: DiagnosticData::Bib(BibError::ExpectingRCurly),
- });
+ self.diagnostics.push(Diagnostic::Bib(
+ TextRange::empty(offset),
+ BibError::ExpectingRCurly,
+ ));
}
}
fn analyze_field(&mut self, field: bibtex::Field) {
if field.eq_token().is_none() {
let offset = field.name_token().unwrap().text_range().end();
- self.diagnostics.push(Diagnostic {
- range: TextRange::empty(offset),
- data: DiagnosticData::Bib(BibError::ExpectingEq),
- });
+ self.diagnostics.push(Diagnostic::Bib(
+ TextRange::empty(offset),
+ BibError::ExpectingEq,
+ ));
return;
}
if field.value().is_none() {
let offset = field.eq_token().unwrap().text_range().end();
- self.diagnostics.push(Diagnostic {
- range: TextRange::empty(offset),
- data: DiagnosticData::Bib(BibError::ExpectingFieldValue),
- });
+ self.diagnostics.push(Diagnostic::Bib(
+ TextRange::empty(offset),
+ BibError::ExpectingFieldValue,
+ ));
}
}
}
diff --git a/support/texlab/crates/diagnostics/src/grammar/tex.rs b/support/texlab/crates/diagnostics/src/grammar/tex.rs
index b61ae4b020..fb2fa5b5fd 100644
--- a/support/texlab/crates/diagnostics/src/grammar/tex.rs
+++ b/support/texlab/crates/diagnostics/src/grammar/tex.rs
@@ -1,56 +1,47 @@
-use base_db::{Config, Document, DocumentData, Workspace};
+use base_db::{Config, Document, TexDocumentData};
+use multimap::MultiMap;
use rowan::{ast::AstNode, NodeOrToken, TextRange};
use syntax::latex;
-
-use crate::{
- types::{DiagnosticData, TexError},
- util::SimpleDiagnosticSource,
- Diagnostic, DiagnosticBuilder, DiagnosticSource,
-};
-
-#[derive(Default)]
-pub struct TexSyntaxErrors(SimpleDiagnosticSource);
-
-impl DiagnosticSource for TexSyntaxErrors {
- fn update(&mut self, workspace: &Workspace, document: &Document) {
- let mut analyzer = Analyzer {
- document,
- config: workspace.config(),
- diagnostics: Vec::new(),
- };
-
- analyzer.analyze_root();
- self.0
- .errors
- .insert(document.uri.clone(), analyzer.diagnostics);
+use url::Url;
+
+use crate::types::{Diagnostic, TexError};
+
+pub fn update(
+ document: &Document,
+ config: &Config,
+ results: &mut MultiMap<Url, Diagnostic>,
+) -> Option<()> {
+ let data = document.data.as_tex()?;
+ if !document.uri.as_str().ends_with(".tex") {
+ return None;
}
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- self.0.publish(workspace, builder);
- }
+ let mut analyzer = Analyzer {
+ data,
+ config,
+ diagnostics: Vec::new(),
+ };
+
+ analyzer.analyze_root();
+
+ *results
+ .entry(document.uri.clone())
+ .or_insert_vec(Vec::new()) = analyzer.diagnostics;
+
+ Some(())
}
struct Analyzer<'a> {
- document: &'a Document,
+ data: &'a TexDocumentData,
config: &'a Config,
diagnostics: Vec<Diagnostic>,
}
impl<'a> Analyzer<'a> {
fn analyze_root(&mut self) {
- if !self.document.uri.as_str().ends_with(".tex") {
- return;
- }
-
- let DocumentData::Tex(data) = &self.document.data else { return };
-
let verbatim_envs = &self.config.syntax.verbatim_environments;
- let mut traversal = latex::SyntaxNode::new_root(data.green.clone()).preorder();
+ let mut traversal = self.data.root_node().preorder();
while let Some(event) = traversal.next() {
match event {
rowan::WalkEvent::Enter(node) => {
@@ -82,10 +73,10 @@ impl<'a> Analyzer<'a> {
let begin = environment.begin()?.name()?.key()?;
let end = environment.end()?.name()?.key()?;
if begin != end {
- self.diagnostics.push(Diagnostic {
- range: latex::small_range(&begin),
- data: DiagnosticData::Tex(TexError::MismatchedEnvironment),
- });
+ self.diagnostics.push(Diagnostic::Tex(
+ latex::small_range(&begin),
+ TexError::MismatchedEnvironment,
+ ));
}
Some(())
@@ -108,10 +99,10 @@ impl<'a> Analyzer<'a> {
.filter_map(NodeOrToken::into_token)
.any(|token| token.kind() == latex::R_CURLY)
{
- self.diagnostics.push(Diagnostic {
- range: TextRange::empty(node.text_range().end()),
- data: DiagnosticData::Tex(TexError::ExpectingRCurly),
- });
+ self.diagnostics.push(Diagnostic::Tex(
+ TextRange::empty(node.text_range().end()),
+ TexError::ExpectingRCurly,
+ ));
}
Some(())
@@ -119,10 +110,10 @@ impl<'a> Analyzer<'a> {
fn analyze_curly_braces(&mut self, node: latex::SyntaxNode) -> Option<()> {
if node.kind() == latex::ERROR && node.first_token()?.text() == "}" {
- self.diagnostics.push(Diagnostic {
- range: node.text_range(),
- data: DiagnosticData::Tex(TexError::UnexpectedRCurly),
- });
+ self.diagnostics.push(Diagnostic::Tex(
+ node.text_range(),
+ TexError::UnexpectedRCurly,
+ ));
Some(())
} else {
diff --git a/support/texlab/crates/diagnostics/src/labels.rs b/support/texlab/crates/diagnostics/src/labels.rs
index 03df664a15..92f9072918 100644
--- a/support/texlab/crates/diagnostics/src/labels.rs
+++ b/support/texlab/crates/diagnostics/src/labels.rs
@@ -1,35 +1,18 @@
-use std::borrow::Cow;
-
use base_db::{
semantics::tex::{Label, LabelKind},
util::queries,
DocumentData, Workspace,
};
use itertools::Itertools;
+use multimap::MultiMap;
use rustc_hash::FxHashSet;
+use url::Url;
-use crate::{
- types::{DiagnosticData, TexError},
- Diagnostic, DiagnosticBuilder, DiagnosticSource,
-};
-
-#[derive(Default)]
-pub struct LabelErrors;
-
-impl DiagnosticSource for LabelErrors {
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- detect_undefined_and_unused_labels(workspace, builder);
- detect_duplicate_labels(workspace, builder);
- }
-}
+use crate::types::{Diagnostic, TexError};
-fn detect_undefined_and_unused_labels<'db>(
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
+pub fn detect_undefined_and_unused_labels(
+ workspace: &Workspace,
+ results: &mut MultiMap<Url, Diagnostic>,
) {
let graphs: Vec<_> = workspace
.iter()
@@ -61,25 +44,19 @@ fn detect_undefined_and_unused_labels<'db>(
for label in &data.semantics.labels {
if label.kind != LabelKind::Definition && !label_defs.contains(&label.name.text) {
- let diagnostic = Diagnostic {
- range: label.name.range,
- data: DiagnosticData::Tex(TexError::UndefinedLabel),
- };
- builder.push(&document.uri, Cow::Owned(diagnostic));
+ let diagnostic = Diagnostic::Tex(label.name.range, TexError::UndefinedLabel);
+ results.insert(document.uri.clone(), diagnostic);
}
if label.kind == LabelKind::Definition && !label_refs.contains(&label.name.text) {
- let diagnostic = Diagnostic {
- range: label.name.range,
- data: DiagnosticData::Tex(TexError::UnusedLabel),
- };
- builder.push(&document.uri, Cow::Owned(diagnostic));
+ let diagnostic = Diagnostic::Tex(label.name.range, TexError::UnusedLabel);
+ results.insert(document.uri.clone(), diagnostic);
}
}
}
}
-fn detect_duplicate_labels<'db>(workspace: &'db Workspace, builder: &mut DiagnosticBuilder<'db>) {
+pub fn detect_duplicate_labels(workspace: &Workspace, results: &mut MultiMap<Url, Diagnostic>) {
for conflict in queries::Conflict::find_all::<Label>(workspace) {
let others = conflict
.rest
@@ -87,11 +64,7 @@ fn detect_duplicate_labels<'db>(workspace: &'db Workspace, builder: &mut Diagnos
.map(|location| (location.document.uri.clone(), location.range))
.collect();
- let diagnostic = Diagnostic {
- range: conflict.main.range,
- data: DiagnosticData::Tex(TexError::DuplicateLabel(others)),
- };
-
- builder.push(&conflict.main.document.uri, Cow::Owned(diagnostic));
+ let diagnostic = Diagnostic::Tex(conflict.main.range, TexError::DuplicateLabel(others));
+ results.insert(conflict.main.document.uri.clone(), diagnostic);
}
}
diff --git a/support/texlab/crates/diagnostics/src/lib.rs b/support/texlab/crates/diagnostics/src/lib.rs
index d0be31a0ba..e5baf9ebdb 100644
--- a/support/texlab/crates/diagnostics/src/lib.rs
+++ b/support/texlab/crates/diagnostics/src/lib.rs
@@ -1,88 +1,13 @@
mod build_log;
+pub mod chktex;
mod citations;
mod grammar;
mod labels;
-pub mod types;
-pub(crate) mod util;
+mod manager;
+mod types;
-use std::borrow::Cow;
-
-use base_db::{Document, Workspace};
-use build_log::BuildErrors;
-use citations::CitationErrors;
-use grammar::{BibSyntaxErrors, TexSyntaxErrors};
-use labels::LabelErrors;
-use rustc_hash::FxHashMap;
-use types::Diagnostic;
-use url::Url;
-
-#[derive(Debug, PartialEq, Eq, Clone, Default)]
-pub struct DiagnosticBuilder<'db> {
- inner: FxHashMap<&'db Url, Vec<Cow<'db, Diagnostic>>>,
-}
-
-impl<'db> DiagnosticBuilder<'db> {
- pub fn push(&mut self, uri: &'db Url, diagnostic: Cow<'db, Diagnostic>) {
- self.inner.entry(uri).or_default().push(diagnostic);
- }
-
- pub fn push_many(
- &mut self,
- uri: &'db Url,
- diagnostics: impl Iterator<Item = Cow<'db, Diagnostic>>,
- ) {
- self.inner.entry(uri).or_default().extend(diagnostics);
- }
-
- pub fn iter(&self) -> impl Iterator<Item = (&'db Url, impl Iterator<Item = &Diagnostic>)> {
- self.inner
- .iter()
- .map(|(uri, diagnostics)| (*uri, diagnostics.iter().map(|diag| diag.as_ref())))
- }
-}
-
-pub trait DiagnosticSource {
- #[allow(unused_variables)]
- fn update(&mut self, workspace: &Workspace, document: &Document) {}
-
- fn publish<'db>(&'db mut self, workspace: &'db Workspace, builder: &mut DiagnosticBuilder<'db>);
-}
-
-pub struct DiagnosticManager {
- sources: Vec<Box<dyn DiagnosticSource>>,
-}
-
-impl Default for DiagnosticManager {
- fn default() -> Self {
- let sources: Vec<Box<dyn DiagnosticSource>> = vec![
- Box::<TexSyntaxErrors>::default(),
- Box::<BibSyntaxErrors>::default(),
- Box::<BuildErrors>::default(),
- Box::<LabelErrors>::default(),
- Box::<CitationErrors>::default(),
- ];
-
- Self { sources }
- }
-}
-
-impl DiagnosticSource for DiagnosticManager {
- fn update(&mut self, workspace: &Workspace, document: &Document) {
- for source in &mut self.sources {
- source.update(workspace, document);
- }
- }
-
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- for source in &mut self.sources {
- source.publish(workspace, builder);
- }
- }
-}
+pub use manager::Manager;
+pub use types::*;
#[cfg(test)]
mod tests;
diff --git a/support/texlab/crates/diagnostics/src/manager.rs b/support/texlab/crates/diagnostics/src/manager.rs
new file mode 100644
index 0000000000..1ce5e194b8
--- /dev/null
+++ b/support/texlab/crates/diagnostics/src/manager.rs
@@ -0,0 +1,75 @@
+use base_db::{util::filter_regex_patterns, Document, Owner, Workspace};
+use multimap::MultiMap;
+use rustc_hash::FxHashMap;
+use url::Url;
+
+use crate::types::Diagnostic;
+
+/// Manages all diagnostics for a workspace.
+#[derive(Debug, Default)]
+pub struct Manager {
+ grammar: MultiMap<Url, Diagnostic>,
+ chktex: FxHashMap<Url, Vec<Diagnostic>>,
+ build_log: FxHashMap<Url, MultiMap<Url, Diagnostic>>,
+}
+
+impl Manager {
+ /// Updates the syntax-based diagnostics for the given document.
+ pub fn update_syntax(&mut self, workspace: &Workspace, document: &Document) {
+ self.grammar.remove(&document.uri);
+ super::grammar::tex::update(document, workspace.config(), &mut self.grammar);
+ super::grammar::bib::update(document, &mut self.grammar);
+
+ self.build_log.remove(&document.uri);
+ super::build_log::update(workspace, document, &mut self.build_log);
+ }
+
+ /// Updates the ChkTeX diagnostics for the given document.
+ pub fn update_chktex(&mut self, uri: Url, diagnostics: Vec<Diagnostic>) {
+ self.chktex.insert(uri, diagnostics);
+ }
+
+ /// Returns all filtered diagnostics for the given workspace.
+ pub fn get(&self, workspace: &Workspace) -> MultiMap<Url, Diagnostic> {
+ let mut results = MultiMap::default();
+ for (uri, diagnostics) in &self.grammar {
+ results.insert_many_from_slice(uri.clone(), diagnostics);
+ }
+
+ for (uri, diagnostics) in self.build_log.values().flatten() {
+ results.insert_many_from_slice(uri.clone(), diagnostics);
+ }
+
+ for (uri, diagnostics) in &self.chktex {
+ if workspace
+ .lookup(uri)
+ .map_or(false, |document| document.owner == Owner::Client)
+ {
+ results.insert_many_from_slice(uri.clone(), diagnostics);
+ }
+ }
+
+ for document in workspace.iter() {
+ let project = workspace.project(document);
+ super::citations::detect_undefined_citations(&project, document, &mut results);
+ super::citations::detect_unused_entries(&project, document, &mut results);
+ }
+
+ super::citations::detect_duplicate_entries(workspace, &mut results);
+ super::labels::detect_duplicate_labels(workspace, &mut results);
+ super::labels::detect_undefined_and_unused_labels(workspace, &mut results);
+
+ let config = &workspace.config().diagnostics;
+ for (_, diagnostics) in &mut results {
+ diagnostics.retain(|diagnostic| {
+ filter_regex_patterns(
+ &diagnostic.message(),
+ &config.allowed_patterns,
+ &config.ignored_patterns,
+ )
+ });
+ }
+
+ results
+ }
+}
diff --git a/support/texlab/crates/diagnostics/src/tests.rs b/support/texlab/crates/diagnostics/src/tests.rs
index 539e3ba765..b7c6004d19 100644
--- a/support/texlab/crates/diagnostics/src/tests.rs
+++ b/support/texlab/crates/diagnostics/src/tests.rs
@@ -1,37 +1,23 @@
-use std::borrow::Cow;
+use expect_test::{expect, Expect};
+use itertools::Itertools;
-use crate::{
- types::{BibError, Diagnostic, DiagnosticData, TexError},
- DiagnosticBuilder, DiagnosticManager, DiagnosticSource,
-};
-
-fn check(input: &str, expected_data: &[DiagnosticData]) {
+fn check(input: &str, expect: Expect) {
let fixture = test_utils::fixture::Fixture::parse(input);
- let mut manager = DiagnosticManager::default();
-
- let mut expected = DiagnosticBuilder::default();
- let mut expected_data = expected_data.iter();
- for document in &fixture.documents {
- let diagnostics = document.ranges.iter().copied().map(|range| {
- let data = expected_data.next().unwrap().clone();
- Cow::Owned(Diagnostic { range, data })
- });
-
- expected.push_many(&document.uri, diagnostics);
- }
+ let mut manager = crate::Manager::default();
for document in fixture.workspace.iter() {
- manager.update(&fixture.workspace, &document);
+ manager.update_syntax(&fixture.workspace, &document);
}
- let mut actual = DiagnosticBuilder::default();
- manager.publish(&fixture.workspace, &mut actual);
-
- for diagnostics in actual.inner.values_mut() {
- diagnostics.sort_by_key(|diag| (diag.range.start(), diag.range.len()));
- }
+ let results = manager.get(&fixture.workspace);
+ let results = results
+ .iter_all()
+ .filter(|(_, diags)| !diags.is_empty())
+ .sorted_by(|(uri1, _), (uri2, _)| uri1.cmp(&uri2))
+ .map(|(uri, diags)| (uri.as_str(), diags))
+ .collect_vec();
- assert_eq!(actual, expected);
+ expect.assert_debug_eq(&results);
}
#[test]
@@ -42,7 +28,19 @@ fn test_bib_entry_missing_l_delim() {
@article
!
"#,
- &[DiagnosticData::Bib(BibError::ExpectingLCurly)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.bib",
+ [
+ Bib(
+ 8..8,
+ ExpectingLCurly,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -58,7 +56,19 @@ fn test_bib_entry_missing_r_delim() {
\bibliography{main}
\cite{foo}
"#,
- &[DiagnosticData::Bib(BibError::ExpectingRCurly)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.bib",
+ [
+ Bib(
+ 14..14,
+ ExpectingRCurly,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -69,7 +79,19 @@ fn test_bib_entry_missing_name() {
%! main.bib
@article{
!"#,
- &[DiagnosticData::Bib(BibError::ExpectingKey)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.bib",
+ [
+ Bib(
+ 9..9,
+ ExpectingKey,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -87,7 +109,19 @@ fn test_bib_field_missing_eq() {
\bibliography{main}
\cite{foo}
"#,
- &[DiagnosticData::Bib(BibError::ExpectingEq)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.bib",
+ [
+ Bib(
+ 23..23,
+ ExpectingEq,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -105,7 +139,19 @@ fn test_bib_field_missing_value() {
\bibliography{main}
\cite{foo}
"#,
- &[DiagnosticData::Bib(BibError::ExpectingFieldValue)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.bib",
+ [
+ Bib(
+ 25..25,
+ ExpectingFieldValue,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -119,10 +165,23 @@ fn test_tex_unmatched_braces() {
{
!
"#,
- &[
- DiagnosticData::Tex(TexError::UnexpectedRCurly),
- DiagnosticData::Tex(TexError::ExpectingRCurly),
- ],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.tex",
+ [
+ Tex(
+ 0..1,
+ UnexpectedRCurly,
+ ),
+ Tex(
+ 4..4,
+ ExpectingRCurly,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -135,7 +194,19 @@ fn test_tex_environment_mismatched() {
^^^
\end{bar}
"#,
- &[DiagnosticData::Tex(TexError::MismatchedEnvironment)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.tex",
+ [
+ Tex(
+ 7..10,
+ MismatchedEnvironment,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -148,7 +219,19 @@ fn test_label_unused() {
^^^
\label{bar}\ref{bar}
"#,
- &[DiagnosticData::Tex(TexError::UnusedLabel)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.tex",
+ [
+ Tex(
+ 7..10,
+ UnusedLabel,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -160,7 +243,19 @@ fn test_label_undefined() {
\ref{foo}
^^^
"#,
- &[DiagnosticData::Tex(TexError::UndefinedLabel)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.tex",
+ [
+ Tex(
+ 5..8,
+ UndefinedLabel,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -172,7 +267,19 @@ fn test_citation_undefined() {
\cite{foo}
^^^
"#,
- &[DiagnosticData::Tex(TexError::UndefinedCitation)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.tex",
+ [
+ Tex(
+ 6..9,
+ UndefinedCitation,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
@@ -184,6 +291,18 @@ fn test_citation_unused() {
@article{foo,}
^^^
"#,
- &[DiagnosticData::Bib(BibError::UnusedEntry)],
+ expect![[r#"
+ [
+ (
+ "file:///texlab/main.bib",
+ [
+ Bib(
+ 9..12,
+ UnusedEntry,
+ ),
+ ],
+ ),
+ ]
+ "#]],
)
}
diff --git a/support/texlab/crates/diagnostics/src/types.rs b/support/texlab/crates/diagnostics/src/types.rs
index a443245b6f..9cbbf8936f 100644
--- a/support/texlab/crates/diagnostics/src/types.rs
+++ b/support/texlab/crates/diagnostics/src/types.rs
@@ -1,21 +1,9 @@
+use line_index::LineCol;
use rowan::TextRange;
use syntax::BuildError;
use url::Url;
#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct Diagnostic {
- pub range: TextRange,
- pub data: DiagnosticData,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum DiagnosticData {
- Tex(TexError),
- Bib(BibError),
- Build(BuildError),
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TexError {
UnexpectedRCurly,
ExpectingRCurly,
@@ -36,3 +24,54 @@ pub enum BibError {
UnusedEntry,
DuplicateEntry(Vec<(Url, TextRange)>),
}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct ChktexError {
+ pub start: LineCol,
+ pub end: LineCol,
+ pub message: String,
+ pub severity: ChktexSeverity,
+ pub code: String,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum ChktexSeverity {
+ Error,
+ Warning,
+ Message,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum Diagnostic {
+ Tex(TextRange, TexError),
+ Bib(TextRange, BibError),
+ Build(TextRange, BuildError),
+ Chktex(ChktexError),
+}
+
+impl Diagnostic {
+ pub fn message(&self) -> &str {
+ match self {
+ Diagnostic::Tex(_, error) => match error {
+ TexError::UnexpectedRCurly => "Unexpected \"}\"",
+ TexError::ExpectingRCurly => "Expecting a curly bracket: \"}\"",
+ TexError::MismatchedEnvironment => "Mismatched environment",
+ TexError::UnusedLabel => "Unused label",
+ TexError::UndefinedLabel => "Undefined reference",
+ TexError::UndefinedCitation => "Undefined reference",
+ TexError::DuplicateLabel(_) => "Duplicate label",
+ },
+ Diagnostic::Bib(_, error) => match error {
+ BibError::ExpectingLCurly => "Expecting a curly bracket: \"{\"",
+ BibError::ExpectingKey => "Expecting a key",
+ BibError::ExpectingRCurly => "Expecting a curly bracket: \"}\"",
+ BibError::ExpectingEq => "Expecting an equality sign: \"=\"",
+ BibError::ExpectingFieldValue => "Expecting a field value",
+ BibError::UnusedEntry => "Unused entry",
+ BibError::DuplicateEntry(_) => "Duplicate entry key",
+ },
+ Diagnostic::Build(_, error) => &error.message,
+ Diagnostic::Chktex(error) => &error.message,
+ }
+ }
+}
diff --git a/support/texlab/crates/diagnostics/src/util.rs b/support/texlab/crates/diagnostics/src/util.rs
deleted file mode 100644
index fd34125dbe..0000000000
--- a/support/texlab/crates/diagnostics/src/util.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::borrow::Cow;
-
-use base_db::Workspace;
-use rustc_hash::FxHashMap;
-use url::Url;
-
-use crate::{Diagnostic, DiagnosticBuilder, DiagnosticSource};
-
-#[derive(Default)]
-pub struct SimpleDiagnosticSource {
- pub errors: FxHashMap<Url, Vec<Diagnostic>>,
-}
-
-impl DiagnosticSource for SimpleDiagnosticSource {
- fn publish<'db>(
- &'db mut self,
- workspace: &'db Workspace,
- builder: &mut DiagnosticBuilder<'db>,
- ) {
- self.errors.retain(|uri, _| workspace.lookup(uri).is_some());
-
- for document in workspace.iter() {
- if let Some(diagnostics) = self.errors.get(&document.uri) {
- builder.push_many(&document.uri, diagnostics.iter().map(Cow::Borrowed));
- }
- }
- }
-}
diff --git a/support/texlab/crates/highlights/src/lib.rs b/support/texlab/crates/highlights/src/lib.rs
index 1845da3907..009dd20807 100644
--- a/support/texlab/crates/highlights/src/lib.rs
+++ b/support/texlab/crates/highlights/src/lib.rs
@@ -21,7 +21,7 @@ pub struct HighlightParams<'a> {
pub offset: TextSize,
}
-pub fn find_all(params: HighlightParams) -> Vec<Highlight> {
+pub fn find_all(params: &HighlightParams) -> Vec<Highlight> {
let mut results = Vec::new();
label::find_highlights(&params, &mut results);
results
diff --git a/support/texlab/crates/highlights/src/tests.rs b/support/texlab/crates/highlights/src/tests.rs
index 74c3cc0c18..ef63a3137b 100644
--- a/support/texlab/crates/highlights/src/tests.rs
+++ b/support/texlab/crates/highlights/src/tests.rs
@@ -10,7 +10,7 @@ fn check(input: &str, expected_kinds: &[HighlightKind]) {
.map(|location| location.range)
.collect::<Vec<_>>();
- let results = crate::find_all(HighlightParams { feature, offset });
+ let results = crate::find_all(&HighlightParams { feature, offset });
let actual_ranges = results
.iter()
diff --git a/support/texlab/crates/hover/src/lib.rs b/support/texlab/crates/hover/src/lib.rs
index bd16be3282..2d22d50b0b 100644
--- a/support/texlab/crates/hover/src/lib.rs
+++ b/support/texlab/crates/hover/src/lib.rs
@@ -34,7 +34,7 @@ pub enum HoverData<'db> {
StringRef(String),
}
-pub fn find(params: HoverParams) -> Option<Hover> {
+pub fn find<'a>(params: &HoverParams<'a>) -> Option<Hover<'a>> {
citation::find_hover(&params)
.or_else(|| package::find_hover(&params))
.or_else(|| entry_type::find_hover(&params))
diff --git a/support/texlab/crates/hover/src/tests.rs b/support/texlab/crates/hover/src/tests.rs
index bbe33fa2db..e6377bb034 100644
--- a/support/texlab/crates/hover/src/tests.rs
+++ b/support/texlab/crates/hover/src/tests.rs
@@ -6,7 +6,7 @@ fn check(input: &str, expect: Expect) {
let fixture = test_utils::fixture::Fixture::parse(input);
let (feature, offset) = fixture.make_params().unwrap();
let params = HoverParams { feature, offset };
- let data = crate::find(params).map(|hover| {
+ let data = crate::find(&params).map(|hover| {
assert_eq!(fixture.documents[0].ranges[0], hover.range);
hover.data
});
diff --git a/support/texlab/crates/inlay-hints/src/lib.rs b/support/texlab/crates/inlay-hints/src/lib.rs
index e3c80751f0..946f77c1c4 100644
--- a/support/texlab/crates/inlay-hints/src/lib.rs
+++ b/support/texlab/crates/inlay-hints/src/lib.rs
@@ -20,7 +20,7 @@ pub enum InlayHintData<'a> {
LabelReference(RenderedLabel<'a>),
}
-pub fn find_all<'a>(params: InlayHintParams<'a>) -> Option<Vec<InlayHint>> {
+pub fn find_all<'a>(params: &InlayHintParams<'a>) -> Option<Vec<InlayHint<'a>>> {
let mut builder = InlayHintBuilder {
params,
hints: Vec::new(),
@@ -30,8 +30,8 @@ pub fn find_all<'a>(params: InlayHintParams<'a>) -> Option<Vec<InlayHint>> {
Some(builder.hints)
}
-struct InlayHintBuilder<'a> {
- params: InlayHintParams<'a>,
+struct InlayHintBuilder<'a, 'b> {
+ params: &'b InlayHintParams<'a>,
hints: Vec<InlayHint<'a>>,
}
diff --git a/support/texlab/crates/inlay-hints/src/tests.rs b/support/texlab/crates/inlay-hints/src/tests.rs
index 5b8a43d614..f8c4d619b1 100644
--- a/support/texlab/crates/inlay-hints/src/tests.rs
+++ b/support/texlab/crates/inlay-hints/src/tests.rs
@@ -7,7 +7,7 @@ fn check(input: &str, expect: Expect) {
let (feature, _) = fixture.make_params().unwrap();
let range = TextRange::new(0.into(), feature.document.text.text_len());
let params = crate::InlayHintParams { range, feature };
- let actual = crate::find_all(params).unwrap_or_default();
+ let actual = crate::find_all(&params).unwrap_or_default();
let expected_offsets = fixture.locations().map(|location| location.range.start());
for (hint, offset) in actual.iter().zip(expected_offsets) {
diff --git a/support/texlab/crates/links/src/include.rs b/support/texlab/crates/links/src/include.rs
index 374e0089df..1392b16eaa 100644
--- a/support/texlab/crates/links/src/include.rs
+++ b/support/texlab/crates/links/src/include.rs
@@ -1,7 +1,7 @@
use base_db::{DocumentLocation, FeatureParams};
pub(super) fn find_links<'a>(
- params: FeatureParams<'a>,
+ params: &FeatureParams<'a>,
results: &mut Vec<DocumentLocation<'a>>,
) -> Option<()> {
let document = params.document;
diff --git a/support/texlab/crates/links/src/lib.rs b/support/texlab/crates/links/src/lib.rs
index 67e48cf930..48f5e09fc0 100644
--- a/support/texlab/crates/links/src/lib.rs
+++ b/support/texlab/crates/links/src/lib.rs
@@ -2,7 +2,7 @@ use base_db::{DocumentLocation, FeatureParams};
mod include;
-pub fn find_links(params: FeatureParams) -> Vec<DocumentLocation> {
+pub fn find_links<'a>(params: &FeatureParams<'a>) -> Vec<DocumentLocation<'a>> {
let mut results = Vec::new();
include::find_links(params, &mut results);
results
diff --git a/support/texlab/crates/links/src/tests.rs b/support/texlab/crates/links/src/tests.rs
index d083b33be6..6837bc96b7 100644
--- a/support/texlab/crates/links/src/tests.rs
+++ b/support/texlab/crates/links/src/tests.rs
@@ -3,7 +3,7 @@ use expect_test::{expect, Expect};
fn check(input: &str, expect: Expect) {
let fixture = test_utils::fixture::Fixture::parse(input);
let (params, _) = fixture.make_params().unwrap();
- let links = crate::find_links(params);
+ let links = crate::find_links(&params);
let actual_ranges = links.iter().map(|link| link.range).collect::<Vec<_>>();
diff --git a/support/texlab/crates/references/src/lib.rs b/support/texlab/crates/references/src/lib.rs
index c223ad8eba..23dbd7e0ae 100644
--- a/support/texlab/crates/references/src/lib.rs
+++ b/support/texlab/crates/references/src/lib.rs
@@ -21,15 +21,16 @@ pub enum ReferenceKind {
pub struct ReferenceParams<'a> {
pub feature: FeatureParams<'a>,
pub offset: TextSize,
+ pub include_declaration: bool,
}
#[derive(Debug)]
-struct ReferenceContext<'a> {
- params: ReferenceParams<'a>,
+struct ReferenceContext<'a, 'b> {
+ params: &'b ReferenceParams<'a>,
results: Vec<Reference<'a>>,
}
-pub fn find_all(params: ReferenceParams) -> Vec<Reference<'_>> {
+pub fn find_all<'a>(params: &ReferenceParams<'a>) -> Vec<DocumentLocation<'a>> {
let mut context = ReferenceContext {
params,
results: Vec::new(),
@@ -38,7 +39,13 @@ pub fn find_all(params: ReferenceParams) -> Vec<Reference<'_>> {
entry::find_all(&mut context);
label::find_all(&mut context);
string_def::find_all(&mut context);
- context.results
+
+ context
+ .results
+ .into_iter()
+ .filter(|r| r.kind == ReferenceKind::Reference || params.include_declaration)
+ .map(|reference| reference.location)
+ .collect()
}
#[cfg(test)]
diff --git a/support/texlab/crates/references/src/tests.rs b/support/texlab/crates/references/src/tests.rs
index 3a3e7008a0..a21a3ce551 100644
--- a/support/texlab/crates/references/src/tests.rs
+++ b/support/texlab/crates/references/src/tests.rs
@@ -1,17 +1,19 @@
use std::collections::HashSet;
-use crate::{ReferenceKind, ReferenceParams};
+use crate::ReferenceParams;
-fn check(fixture: &str, include_def: bool) {
+fn check(fixture: &str, include_declaration: bool) {
let fixture = test_utils::fixture::Fixture::parse(fixture);
let (feature, offset) = fixture.make_params().unwrap();
let expected = fixture.locations().collect::<HashSet<_>>();
- let actual = crate::find_all(ReferenceParams { feature, offset })
- .into_iter()
- .filter(|reference| reference.kind == ReferenceKind::Reference || include_def)
- .map(|reference| reference.location)
- .collect::<HashSet<_>>();
+ let actual = crate::find_all(&ReferenceParams {
+ feature,
+ offset,
+ include_declaration,
+ })
+ .into_iter()
+ .collect::<HashSet<_>>();
assert_eq!(actual, expected);
}
diff --git a/support/texlab/crates/texlab/Cargo.toml b/support/texlab/crates/texlab/Cargo.toml
index 0162fe499a..53b63ded3e 100644
--- a/support/texlab/crates/texlab/Cargo.toml
+++ b/support/texlab/crates/texlab/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "texlab"
description = "LaTeX Language Server"
-version = "5.12.2"
+version = "5.12.3"
license.workspace = true
readme = "README.md"
authors.workspace = true
diff --git a/support/texlab/crates/texlab/benches/bench_main.rs b/support/texlab/crates/texlab/benches/bench_main.rs
index 0f54a2c2d1..daeb5409ed 100644
--- a/support/texlab/crates/texlab/benches/bench_main.rs
+++ b/support/texlab/crates/texlab/benches/bench_main.rs
@@ -1,8 +1,4 @@
-use base_db::{Owner, Workspace};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
-use distro::Language;
-use line_index::LineCol;
-use lsp_types::{ClientCapabilities, CompletionParams, Position, TextDocumentPositionParams, Url};
use parser::{parse_latex, SyntaxConfig};
const CODE: &str = include_str!("../../../texlab.tex");
@@ -12,34 +8,6 @@ fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("LaTeX/Parser", |b| {
b.iter(|| parse_latex(black_box(CODE), &config));
});
-
- c.bench_function("LaTeX/Completion/Command", |b| {
- let uri = Url::parse("http://example.com/texlab.tex").unwrap();
- let text = CODE.to_string();
- let mut workspace = Workspace::default();
- workspace.open(
- uri.clone(),
- text,
- Language::Tex,
- Owner::Client,
- LineCol { line: 0, col: 0 },
- );
-
- let client_capabilities = ClientCapabilities::default();
- let params = CompletionParams {
- context: None,
- text_document_position: TextDocumentPositionParams::new(
- lsp_types::TextDocumentIdentifier { uri: uri.clone() },
- Position::new(0, 1),
- ),
- work_done_progress_params: Default::default(),
- partial_result_params: Default::default(),
- };
-
- b.iter(|| {
- texlab::features::completion::complete(&workspace, &params, &client_capabilities, None)
- });
- });
}
criterion_group!(benches, criterion_benchmark);
diff --git a/support/texlab/crates/texlab/src/features/completion.rs b/support/texlab/crates/texlab/src/features/completion.rs
index 8e5fa2adbb..5d32588970 100644
--- a/support/texlab/crates/texlab/src/features/completion.rs
+++ b/support/texlab/crates/texlab/src/features/completion.rs
@@ -1,73 +1,77 @@
-use base_db::{util::RenderedObject, Document, FeatureParams, Workspace};
-use completion::{
- ArgumentData, CompletionItem, CompletionItemData, CompletionParams, EntryTypeData,
- FieldTypeData,
-};
+use base_db::{util::RenderedObject, Workspace};
+use completion::{ArgumentData, CompletionItem, CompletionItemData, EntryTypeData, FieldTypeData};
use line_index::LineIndex;
-use lsp_types::{ClientCapabilities, ClientInfo, CompletionList};
+use rowan::ast::AstNode;
use serde::{Deserialize, Serialize};
+use syntax::bibtex;
-use crate::util::{
- capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt, lsp_enums::Structure,
-};
+use crate::util::{from_proto, line_index_ext::LineIndexExt, lsp_enums::Structure, ClientFlags};
pub fn complete(
workspace: &Workspace,
- params: &lsp_types::CompletionParams,
- client_capabilities: &ClientCapabilities,
- client_info: Option<&ClientInfo>,
-) -> Option<CompletionList> {
- let document = workspace.lookup(&params.text_document_position.text_document.uri)?;
- let feature = FeatureParams::new(workspace, document);
- let offset = document
- .line_index
- .offset_lsp(params.text_document_position.position)?;
-
- let params = CompletionParams { feature, offset };
+ params: lsp_types::CompletionParams,
+ client_flags: &ClientFlags,
+) -> Option<lsp_types::CompletionList> {
+ let params = from_proto::completion_params(workspace, params)?;
let result = completion::complete(&params);
- let mut list = CompletionList::default();
- let item_builder = ItemBuilder::new(document, client_capabilities);
- let always_incomplete = client_info.map_or(false, |info| info.name == "Visual Studio Code");
- list.is_incomplete = always_incomplete || result.items.len() >= completion::LIMIT;
- list.items = result
+ let item_builder = ItemBuilder {
+ line_index: &params.feature.document.line_index,
+ client_flags,
+ };
+
+ let is_incomplete =
+ client_flags.completion_always_incomplete || result.items.len() >= completion::LIMIT;
+
+ let items = result
.items
.into_iter()
.enumerate()
.filter_map(|(i, item)| item_builder.convert(item, i))
.collect();
- Some(list)
+ Some(lsp_types::CompletionList {
+ is_incomplete,
+ items,
+ })
+}
+
+pub fn resolve(workspace: &Workspace, item: &mut lsp_types::CompletionItem) -> Option<()> {
+ let data = from_proto::completion_resolve_info(item)?;
+ match data {
+ ResolveInfo::Package | ResolveInfo::DocumentClass => {
+ let metadata = completion_data::DATABASE.meta(&item.label)?;
+ let value = metadata.description.as_deref()?.into();
+ item.documentation = Some(lsp_types::Documentation::MarkupContent(
+ lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::PlainText,
+ value,
+ },
+ ));
+ }
+ ResolveInfo::Citation { uri, key } => {
+ let data = workspace.lookup(&uri)?.data.as_bib()?;
+ let root = bibtex::Root::cast(data.root_node())?;
+ let entry = root.find_entry(&key)?;
+ let value = citeproc::render(&entry)?;
+ item.documentation = Some(lsp_types::Documentation::MarkupContent(
+ lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::Markdown,
+ value,
+ },
+ ));
+ }
+ }
+
+ Some(())
}
struct ItemBuilder<'a> {
line_index: &'a LineIndex,
- item_kinds: &'a [lsp_types::CompletionItemKind],
- supports_snippets: bool,
- supports_images: bool,
+ client_flags: &'a ClientFlags,
}
impl<'a> ItemBuilder<'a> {
- pub fn new(document: &'a Document, client_capabilities: &'a ClientCapabilities) -> Self {
- let line_index = &document.line_index;
- let item_kinds = client_capabilities
- .text_document
- .as_ref()
- .and_then(|cap| cap.completion.as_ref())
- .and_then(|cap| cap.completion_item_kind.as_ref())
- .and_then(|cap| cap.value_set.as_deref())
- .unwrap_or_default();
-
- let supports_snippets = client_capabilities.has_snippet_support();
- let supports_images = client_capabilities.has_completion_markdown_support();
- Self {
- line_index,
- item_kinds,
- supports_snippets,
- supports_images,
- }
- }
-
pub fn convert(&self, item: CompletionItem, index: usize) -> Option<lsp_types::CompletionItem> {
let mut result = lsp_types::CompletionItem::default();
let range = self.line_index.line_col_lsp_range(item.range)?;
@@ -125,7 +129,7 @@ impl<'a> ItemBuilder<'a> {
if result
.kind
- .is_some_and(|kind| !self.item_kinds.contains(&kind))
+ .is_some_and(|kind| !self.client_flags.completion_kinds.contains(&kind))
{
result.kind = Some(lsp_types::CompletionItemKind::TEXT);
}
@@ -166,7 +170,7 @@ impl<'a> ItemBuilder<'a> {
result: &mut lsp_types::CompletionItem,
range: lsp_types::Range,
) {
- if self.supports_snippets {
+ if self.client_flags.completion_snippets {
result.kind = Some(Structure::Snippet.completion_kind());
result.text_edit =
Some(lsp_types::TextEdit::new(range, "begin{$1}\n\t$0\n\\end{$1}".into()).into());
@@ -386,7 +390,7 @@ impl<'a> ItemBuilder<'a> {
}
fn inline_image(&self, name: &str, base64: &str) -> Option<lsp_types::Documentation> {
- if self.supports_images {
+ if self.client_flags.completion_markdown {
let kind = lsp_types::MarkupKind::Markdown;
let value = format!("![{name}](data:image/png;base64,{base64}|width=48,height=48)");
let content = lsp_types::MarkupContent { kind, value };
diff --git a/support/texlab/crates/texlab/src/features/definition.rs b/support/texlab/crates/texlab/src/features/definition.rs
index ec7f08c296..098505079c 100644
--- a/support/texlab/crates/texlab/src/features/definition.rs
+++ b/support/texlab/crates/texlab/src/features/definition.rs
@@ -1,49 +1,16 @@
-use base_db::{FeatureParams, Workspace};
-use definition::DefinitionParams;
-use lsp_types::{GotoDefinitionResponse, LocationLink, Position, Url};
+use base_db::Workspace;
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto};
pub fn goto_definition(
workspace: &Workspace,
- uri: &Url,
- position: Position,
-) -> Option<GotoDefinitionResponse> {
- let document = workspace.lookup(uri)?;
- let offset = document.line_index.offset_lsp(position)?;
- let feature = FeatureParams::new(workspace, document);
- let params = DefinitionParams { feature, offset };
+ params: lsp_types::GotoDefinitionParams,
+) -> Option<lsp_types::GotoDefinitionResponse> {
+ let params = from_proto::definition_params(workspace, params)?;
+ let links = definition::goto_definition(&params)
+ .into_iter()
+ .filter_map(|result| to_proto::location_link(result, &params.feature.document.line_index))
+ .collect();
- let mut links = Vec::new();
- for result in definition::goto_definition(params) {
- if let Some(link) = convert_link(document, result) {
- links.push(link);
- }
- }
-
- Some(GotoDefinitionResponse::Link(links))
-}
-
-fn convert_link(
- document: &base_db::Document,
- result: definition::DefinitionResult<'_>,
-) -> Option<LocationLink> {
- let origin_selection_range = Some(
- document
- .line_index
- .line_col_lsp_range(result.origin_selection_range)?,
- );
-
- let target_line_index = &result.target.line_index;
- let target_uri = result.target.uri.clone();
- let target_range = target_line_index.line_col_lsp_range(result.target_range)?;
- let target_selection_range =
- target_line_index.line_col_lsp_range(result.target_selection_range)?;
- let value = LocationLink {
- origin_selection_range,
- target_uri,
- target_range,
- target_selection_range,
- };
- Some(value)
+ Some(lsp_types::GotoDefinitionResponse::Link(links))
}
diff --git a/support/texlab/crates/texlab/src/features/folding.rs b/support/texlab/crates/texlab/src/features/folding.rs
index 6bdfc3a309..b19fa8a4da 100644
--- a/support/texlab/crates/texlab/src/features/folding.rs
+++ b/support/texlab/crates/texlab/src/features/folding.rs
@@ -1,46 +1,20 @@
use base_db::Workspace;
-use folding::FoldingRangeKind;
-use lsp_types::{ClientCapabilities, Url};
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto, ClientFlags};
pub fn find_all(
workspace: &Workspace,
- uri: &Url,
- capabilities: &ClientCapabilities,
+ params: lsp_types::FoldingRangeParams,
+ client_flags: &ClientFlags,
) -> Option<Vec<serde_json::Value>> {
- let custom_kinds = capabilities
- .text_document
- .as_ref()
- .and_then(|cap| cap.folding_range.as_ref())
- .and_then(|cap| cap.folding_range_kind.as_ref())
- .and_then(|cap| cap.value_set.as_ref())
- .is_some();
+ let params = from_proto::feature_params(workspace, params.text_document)?;
- let document = workspace.lookup(uri)?;
- let foldings = folding::find_all(document)
+ let foldings = folding::find_all(&params.document)
.into_iter()
.filter_map(|folding| {
- let range = document.line_index.line_col_lsp_range(folding.range)?;
+ to_proto::folding_range(folding, &params.document.line_index, client_flags)
+ })
+ .collect();
- let kind = if custom_kinds {
- Some(match folding.kind {
- FoldingRangeKind::Section => "section",
- FoldingRangeKind::Environment => "environment",
- FoldingRangeKind::Entry => "entry",
- })
- } else {
- None
- };
-
- Some(serde_json::json!({
- "startLine": range.start.line,
- "startCharacter": range.start.character,
- "endLine": range.end.line,
- "endCharacter": range.end.character,
- "kind": kind,
- }))
- });
-
- Some(foldings.collect())
+ Some(foldings)
}
diff --git a/support/texlab/crates/texlab/src/features/formatting.rs b/support/texlab/crates/texlab/src/features/formatting.rs
index 9073b04aa3..3fcd66e39b 100644
--- a/support/texlab/crates/texlab/src/features/formatting.rs
+++ b/support/texlab/crates/texlab/src/features/formatting.rs
@@ -3,15 +3,14 @@ mod latexindent;
use base_db::{Formatter, Workspace};
use distro::Language;
-use lsp_types::{FormattingOptions, TextEdit, Url};
use self::{bibtex_internal::format_bibtex_internal, latexindent::format_with_latexindent};
pub fn format_source_code(
workspace: &Workspace,
- uri: &Url,
- options: &FormattingOptions,
-) -> Option<Vec<TextEdit>> {
+ uri: &lsp_types::Url,
+ options: &lsp_types::FormattingOptions,
+) -> Option<Vec<lsp_types::TextEdit>> {
let document = workspace.lookup(uri)?;
match document.language {
Language::Tex => match workspace.config().formatting.tex_formatter {
diff --git a/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs b/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs
index 8d5894c256..3a441a5292 100644
--- a/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs
+++ b/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs
@@ -1,5 +1,4 @@
use base_db::{Document, Workspace};
-use lsp_types::{FormattingOptions, TextEdit};
use rowan::TextLen;
use crate::util::line_index_ext::LineIndexExt;
@@ -7,8 +6,8 @@ use crate::util::line_index_ext::LineIndexExt;
pub fn format_bibtex_internal(
workspace: &Workspace,
document: &Document,
- options: &FormattingOptions,
-) -> Option<Vec<TextEdit>> {
+ options: &lsp_types::FormattingOptions,
+) -> Option<Vec<lsp_types::TextEdit>> {
let data = document.data.as_bib()?;
let options = bibfmt::Options {
insert_spaces: options.insert_spaces,
diff --git a/support/texlab/crates/texlab/src/features/formatting/latexindent.rs b/support/texlab/crates/texlab/src/features/formatting/latexindent.rs
index 510bd80207..061c360cf5 100644
--- a/support/texlab/crates/texlab/src/features/formatting/latexindent.rs
+++ b/support/texlab/crates/texlab/src/features/formatting/latexindent.rs
@@ -5,7 +5,6 @@ use std::{
use base_db::{Document, LatexIndentConfig, Workspace};
use distro::Language;
-use lsp_types::{Position, TextEdit};
use rowan::TextLen;
use tempfile::tempdir;
@@ -14,7 +13,7 @@ use crate::util::line_index_ext::LineIndexExt;
pub fn format_with_latexindent(
workspace: &Workspace,
document: &Document,
-) -> Option<Vec<TextEdit>> {
+) -> Option<Vec<lsp_types::TextEdit>> {
let config = workspace.config();
let target_dir = tempdir().ok()?;
let source_dir = workspace.current_dir(&document.dir).to_file_path().ok()?;
@@ -51,9 +50,9 @@ pub fn format_with_latexindent(
None
} else {
let line_index = &document.line_index;
- let start = Position::new(0, 0);
+ let start = lsp_types::Position::new(0, 0);
let end = line_index.line_col_lsp(old_text.text_len())?;
- Some(vec![TextEdit {
+ Some(vec![lsp_types::TextEdit {
range: lsp_types::Range::new(start, end),
new_text,
}])
diff --git a/support/texlab/crates/texlab/src/features/highlight.rs b/support/texlab/crates/texlab/src/features/highlight.rs
index 10c5d95fb1..4706e3964e 100644
--- a/support/texlab/crates/texlab/src/features/highlight.rs
+++ b/support/texlab/crates/texlab/src/features/highlight.rs
@@ -1,27 +1,15 @@
-use base_db::{FeatureParams, Workspace};
-use highlights::{HighlightKind, HighlightParams};
+use base_db::Workspace;
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto};
pub fn find_all(
workspace: &Workspace,
- params: &lsp_types::DocumentHighlightParams,
+ params: lsp_types::DocumentHighlightParams,
) -> Option<Vec<lsp_types::DocumentHighlight>> {
- let uri = &params.text_document_position_params.text_document.uri;
- let document = workspace.lookup(uri)?;
- let position = params.text_document_position_params.position;
- let offset = document.line_index.offset_lsp(position)?;
- let feature = FeatureParams::new(workspace, document);
- let params = HighlightParams { feature, offset };
- let results = highlights::find_all(params);
+ let params = from_proto::highlight_params(workspace, params)?;
+ let results = highlights::find_all(&params);
let results = results.into_iter().filter_map(|result| {
- let range = document.line_index.line_col_lsp_range(result.range)?;
- let kind = Some(match result.kind {
- HighlightKind::Write => lsp_types::DocumentHighlightKind::WRITE,
- HighlightKind::Read => lsp_types::DocumentHighlightKind::READ,
- });
-
- Some(lsp_types::DocumentHighlight { range, kind })
+ to_proto::document_highlight(result, &params.feature.document.line_index)
});
Some(results.collect())
diff --git a/support/texlab/crates/texlab/src/features/hover.rs b/support/texlab/crates/texlab/src/features/hover.rs
index 1aac645488..725f9ae936 100644
--- a/support/texlab/crates/texlab/src/features/hover.rs
+++ b/support/texlab/crates/texlab/src/features/hover.rs
@@ -1,44 +1,9 @@
-use base_db::{FeatureParams, Workspace};
-use hover::{HoverData, HoverParams};
+use base_db::Workspace;
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto};
pub fn find(workspace: &Workspace, params: lsp_types::HoverParams) -> Option<lsp_types::Hover> {
- let uri_and_pos = &params.text_document_position_params;
- let document = workspace.lookup(&uri_and_pos.text_document.uri)?;
- let feature = FeatureParams::new(workspace, document);
- let offset = document.line_index.offset_lsp(uri_and_pos.position)?;
- let hover = ::hover::find(HoverParams { feature, offset })?;
-
- let contents = match hover.data {
- HoverData::Citation(text) => lsp_types::MarkupContent {
- kind: lsp_types::MarkupKind::Markdown,
- value: text,
- },
- HoverData::Package(description) => lsp_types::MarkupContent {
- kind: lsp_types::MarkupKind::PlainText,
- value: description.into(),
- },
- HoverData::EntryType(type_) => lsp_types::MarkupContent {
- kind: lsp_types::MarkupKind::Markdown,
- value: type_.documentation?.into(),
- },
- HoverData::FieldType(type_) => lsp_types::MarkupContent {
- kind: lsp_types::MarkupKind::Markdown,
- value: type_.documentation.into(),
- },
- HoverData::Label(label) => lsp_types::MarkupContent {
- kind: lsp_types::MarkupKind::PlainText,
- value: label.reference(),
- },
- HoverData::StringRef(text) => lsp_types::MarkupContent {
- kind: lsp_types::MarkupKind::PlainText,
- value: text,
- },
- };
-
- Some(lsp_types::Hover {
- contents: lsp_types::HoverContents::Markup(contents),
- range: document.line_index.line_col_lsp_range(hover.range),
- })
+ let params = from_proto::hover_params(workspace, params)?;
+ let hover = ::hover::find(&params)?;
+ to_proto::hover(hover, &params.feature.document.line_index)
}
diff --git a/support/texlab/crates/texlab/src/features/inlay_hint.rs b/support/texlab/crates/texlab/src/features/inlay_hint.rs
index 44ed0757a5..ddd8ff26d8 100644
--- a/support/texlab/crates/texlab/src/features/inlay_hint.rs
+++ b/support/texlab/crates/texlab/src/features/inlay_hint.rs
@@ -1,67 +1,16 @@
-use base_db::{util::RenderedObject, FeatureParams, Workspace};
-use inlay_hints::{InlayHintData, InlayHintParams};
+use base_db::Workspace;
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto};
pub fn find_all(
workspace: &Workspace,
- uri: &lsp_types::Url,
- range: lsp_types::Range,
+ params: lsp_types::InlayHintParams,
) -> Option<Vec<lsp_types::InlayHint>> {
- let document = workspace.lookup(uri)?;
- let line_index = &document.line_index;
- let range = line_index.offset_lsp_range(range)?;
+ let params = from_proto::inlay_hint_params(workspace, params)?;
+ let hints = inlay_hints::find_all(&params)?
+ .into_iter()
+ .filter_map(|hint| to_proto::inlay_hint(hint, &params.feature.document.line_index))
+ .collect();
- let feature = FeatureParams::new(workspace, document);
- let params = InlayHintParams { range, feature };
- let hints = inlay_hints::find_all(params)?;
- let hints = hints.into_iter().filter_map(|hint| {
- let position = line_index.line_col_lsp(hint.offset)?;
- Some(match hint.data {
- InlayHintData::LabelDefinition(label) => {
- let number = label.number?;
-
- let text = match &label.object {
- RenderedObject::Section { prefix, .. } => {
- format!("{} {}", prefix, number)
- }
- RenderedObject::Float { kind, .. } => {
- format!("{} {}", kind.as_str(), number)
- }
- RenderedObject::Theorem { kind, .. } => {
- format!("{} {}", kind, number)
- }
- RenderedObject::Equation => format!("Equation ({})", number),
- RenderedObject::EnumItem => format!("Item {}", number),
- };
-
- 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,
- }
- }
- InlayHintData::LabelReference(label) => {
- let text = label.reference();
-
- 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,
- }
- }
- })
- });
-
- Some(hints.collect())
+ Some(hints)
}
diff --git a/support/texlab/crates/texlab/src/features/link.rs b/support/texlab/crates/texlab/src/features/link.rs
index b821dcf6e1..5b4de5f705 100644
--- a/support/texlab/crates/texlab/src/features/link.rs
+++ b/support/texlab/crates/texlab/src/features/link.rs
@@ -1,20 +1,16 @@
-use base_db::{FeatureParams, Workspace};
-use lsp_types::{DocumentLink, Url};
+use base_db::Workspace;
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto};
-pub fn find_all(workspace: &Workspace, uri: &Url) -> Option<Vec<DocumentLink>> {
- let document = workspace.lookup(uri)?;
+pub fn find_all(
+ workspace: &Workspace,
+ params: lsp_types::DocumentLinkParams,
+) -> Option<Vec<lsp_types::DocumentLink>> {
+ let params = from_proto::feature_params(workspace, params.text_document)?;
+ let links = links::find_links(&params)
+ .into_iter()
+ .filter_map(|link| to_proto::document_link(link, &params.document.line_index))
+ .collect();
- let links = links::find_links(FeatureParams::new(workspace, document)).into_iter();
- let links = links.filter_map(|link| {
- Some(lsp_types::DocumentLink {
- data: None,
- tooltip: None,
- target: Some(link.document.uri.clone()),
- range: document.line_index.line_col_lsp_range(link.range)?,
- })
- });
-
- Some(links.collect())
+ Some(links)
}
diff --git a/support/texlab/crates/texlab/src/features/reference.rs b/support/texlab/crates/texlab/src/features/reference.rs
index 380471e6cf..823e794d08 100644
--- a/support/texlab/crates/texlab/src/features/reference.rs
+++ b/support/texlab/crates/texlab/src/features/reference.rs
@@ -1,34 +1,17 @@
-use base_db::{FeatureParams, Workspace};
-use references::{ReferenceKind, ReferenceParams};
+use base_db::Workspace;
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, to_proto};
pub fn find_all(
workspace: &Workspace,
params: lsp_types::ReferenceParams,
) -> Option<Vec<lsp_types::Location>> {
- let uri_and_pos = params.text_document_position;
- let include_declaration = params.context.include_declaration;
+ let params = from_proto::reference_params(workspace, params)?;
- let document = workspace.lookup(&uri_and_pos.text_document.uri)?;
- let offset = document.line_index.offset_lsp(uri_and_pos.position)?;
-
- let feature = FeatureParams::new(workspace, document);
- let mut results = Vec::new();
- for result in references::find_all(ReferenceParams { feature, offset })
+ let results = references::find_all(&params)
.into_iter()
- .filter(|result| result.kind == ReferenceKind::Reference || include_declaration)
- {
- let document = result.location.document;
- let uri = document.uri.clone();
- if let Some(range) = document
- .line_index
- .line_col_lsp_range(result.location.range)
- {
- let location = lsp_types::Location::new(uri, range);
- results.push(location);
- }
- }
+ .filter_map(to_proto::location)
+ .collect();
Some(results)
}
diff --git a/support/texlab/crates/texlab/src/features/rename.rs b/support/texlab/crates/texlab/src/features/rename.rs
index 297547e7e5..b905a7105b 100644
--- a/support/texlab/crates/texlab/src/features/rename.rs
+++ b/support/texlab/crates/texlab/src/features/rename.rs
@@ -1,50 +1,22 @@
-use std::collections::HashMap;
+use base_db::Workspace;
-use base_db::{FeatureParams, Workspace};
-use rename::RenameParams;
-
-use crate::util::line_index_ext::LineIndexExt;
+use crate::util::{from_proto, line_index_ext::LineIndexExt, to_proto};
pub fn prepare_rename_all(
workspace: &Workspace,
- params: &lsp_types::TextDocumentPositionParams,
+ params: lsp_types::TextDocumentPositionParams,
) -> Option<lsp_types::Range> {
- let params = create_params(workspace, params)?;
+ let params = from_proto::rename_params(workspace, params)?;
let range = rename::prepare_rename(&params)?;
params.feature.document.line_index.line_col_lsp_range(range)
}
pub fn rename_all(
workspace: &Workspace,
- params: &lsp_types::RenameParams,
+ params: lsp_types::RenameParams,
) -> Option<lsp_types::WorkspaceEdit> {
let new_name = &params.new_name;
- let params = create_params(workspace, &params.text_document_position)?;
+ let params = from_proto::rename_params(workspace, params.text_document_position)?;
let result = rename::rename(params);
-
- let mut changes = HashMap::default();
- for (document, ranges) in result.changes {
- let mut edits = Vec::new();
- ranges
- .into_iter()
- .filter_map(|range| document.line_index.line_col_lsp_range(range))
- .for_each(|range| edits.push(lsp_types::TextEdit::new(range, new_name.clone())));
-
- changes.insert(document.uri.clone(), edits);
- }
-
- Some(lsp_types::WorkspaceEdit::new(changes))
-}
-
-fn create_params<'db>(
- workspace: &'db Workspace,
- params: &lsp_types::TextDocumentPositionParams,
-) -> Option<RenameParams<'db>> {
- let document = workspace.lookup(&params.text_document.uri)?;
- let inner = FeatureParams::new(workspace, document);
- let offset = document.line_index.offset_lsp(params.position)?;
- Some(RenameParams {
- feature: inner,
- offset,
- })
+ Some(to_proto::workspace_edit(result, &new_name))
}
diff --git a/support/texlab/crates/texlab/src/features/symbols.rs b/support/texlab/crates/texlab/src/features/symbols.rs
index 65e92a5b17..8e29eb9a9c 100644
--- a/support/texlab/crates/texlab/src/features/symbols.rs
+++ b/support/texlab/crates/texlab/src/features/symbols.rs
@@ -1,112 +1,27 @@
-use base_db::{data::BibtexEntryTypeCategory, Document, Workspace};
-use lsp_types::{
- ClientCapabilities, DocumentSymbol, DocumentSymbolResponse, Location, WorkspaceSymbolResponse,
-};
+use base_db::Workspace;
-use crate::util::{capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt};
+use crate::util::{from_proto, to_proto, ClientFlags};
pub fn document_symbols(
workspace: &Workspace,
- document: &Document,
- capabilities: &ClientCapabilities,
-) -> DocumentSymbolResponse {
- let symbols = symbols::document_symbols(workspace, document);
- if capabilities.has_hierarchical_document_symbol_support() {
- let results = symbols
- .into_iter()
- .filter_map(|symbol| convert_to_nested_symbol(symbol, document))
- .collect();
-
- DocumentSymbolResponse::Nested(results)
- } else {
- let mut results = Vec::new();
- for symbol in symbols {
- convert_to_flat_symbols(symbol, document, &mut results);
- }
-
- DocumentSymbolResponse::Flat(results)
- }
+ params: lsp_types::DocumentSymbolParams,
+ client_flags: &ClientFlags,
+) -> Option<lsp_types::DocumentSymbolResponse> {
+ let params = from_proto::feature_params(workspace, params.text_document)?;
+ let symbols = symbols::document_symbols(workspace, params.document);
+ Some(to_proto::document_symbol_response(
+ params.document,
+ symbols,
+ client_flags,
+ ))
}
-pub fn workspace_symbols(workspace: &Workspace, query: &str) -> WorkspaceSymbolResponse {
+pub fn workspace_symbols(workspace: &Workspace, query: &str) -> lsp_types::WorkspaceSymbolResponse {
let symbols = symbols::workspace_symbols(workspace, query);
let mut results = Vec::new();
for symbols::SymbolLocation { symbol, document } in symbols {
- convert_to_flat_symbols(symbol, document, &mut results);
- }
-
- WorkspaceSymbolResponse::Flat(results)
-}
-
-fn convert_to_nested_symbol(
- symbol: symbols::Symbol,
- document: &Document,
-) -> Option<DocumentSymbol> {
- let children = symbol
- .children
- .into_iter()
- .filter_map(|child| convert_to_nested_symbol(child, document))
- .collect();
-
- #[allow(deprecated)]
- Some(DocumentSymbol {
- name: symbol.name,
- detail: symbol.label.map(|label| label.text),
- kind: convert_symbol_kind(symbol.kind),
- deprecated: Some(false),
- range: document.line_index.line_col_lsp_range(symbol.full_range)?,
- selection_range: document
- .line_index
- .line_col_lsp_range(symbol.selection_range)?,
- children: Some(children),
- tags: None,
- })
-}
-
-fn convert_to_flat_symbols(
- symbol: symbols::Symbol,
- document: &Document,
- results: &mut Vec<lsp_types::SymbolInformation>,
-) {
- let Some(range) = document.line_index.line_col_lsp_range(symbol.full_range) else {
- return;
- };
-
- #[allow(deprecated)]
- results.push(lsp_types::SymbolInformation {
- name: symbol.name,
- kind: convert_symbol_kind(symbol.kind),
- deprecated: Some(false),
- location: Location::new(document.uri.clone(), range),
- tags: None,
- container_name: None,
- });
-
- for child in symbol.children {
- convert_to_flat_symbols(child, document, results);
+ to_proto::symbol_information(symbol, document, &mut results);
}
-}
-fn convert_symbol_kind(value: symbols::SymbolKind) -> lsp_types::SymbolKind {
- match value {
- symbols::SymbolKind::Section => lsp_types::SymbolKind::MODULE,
- symbols::SymbolKind::Figure => lsp_types::SymbolKind::METHOD,
- symbols::SymbolKind::Algorithm => lsp_types::SymbolKind::METHOD,
- symbols::SymbolKind::Table => lsp_types::SymbolKind::METHOD,
- symbols::SymbolKind::Listing => lsp_types::SymbolKind::METHOD,
- symbols::SymbolKind::Enumeration => lsp_types::SymbolKind::ENUM,
- symbols::SymbolKind::EnumerationItem => lsp_types::SymbolKind::ENUM_MEMBER,
- symbols::SymbolKind::Theorem => lsp_types::SymbolKind::VARIABLE,
- symbols::SymbolKind::Equation => lsp_types::SymbolKind::CONSTANT,
- symbols::SymbolKind::Entry(category) => match category {
- BibtexEntryTypeCategory::Misc => lsp_types::SymbolKind::INTERFACE,
- BibtexEntryTypeCategory::String => lsp_types::SymbolKind::STRING,
- BibtexEntryTypeCategory::Article => lsp_types::SymbolKind::EVENT,
- BibtexEntryTypeCategory::Thesis => lsp_types::SymbolKind::OBJECT,
- BibtexEntryTypeCategory::Book => lsp_types::SymbolKind::STRUCT,
- BibtexEntryTypeCategory::Part => lsp_types::SymbolKind::OPERATOR,
- BibtexEntryTypeCategory::Collection => lsp_types::SymbolKind::TYPE_PARAMETER,
- },
- symbols::SymbolKind::Field => lsp_types::SymbolKind::FIELD,
- }
+ lsp_types::WorkspaceSymbolResponse::Flat(results)
}
diff --git a/support/texlab/crates/texlab/src/lib.rs b/support/texlab/crates/texlab/src/lib.rs
index 19ef5ba8a9..50bddc3185 100644
--- a/support/texlab/crates/texlab/src/lib.rs
+++ b/support/texlab/crates/texlab/src/lib.rs
@@ -1,6 +1,6 @@
mod client;
-pub mod features;
+pub(crate) mod features;
mod server;
-pub mod util;
+pub(crate) mod util;
pub use self::{client::LspClient, server::Server};
diff --git a/support/texlab/crates/texlab/src/main.rs b/support/texlab/crates/texlab/src/main.rs
index d588a568bc..08977ae96a 100644
--- a/support/texlab/crates/texlab/src/main.rs
+++ b/support/texlab/crates/texlab/src/main.rs
@@ -28,7 +28,7 @@ fn main() -> Result<()> {
setup_logger(opts);
let (connection, threads) = Connection::stdio();
- Server::new(connection).run()?;
+ Server::exec(connection)?;
threads.join()?;
Ok(())
diff --git a/support/texlab/crates/texlab/src/server.rs b/support/texlab/crates/texlab/src/server.rs
index f06984d596..0fb186f688 100644
--- a/support/texlab/crates/texlab/src/server.rs
+++ b/support/texlab/crates/texlab/src/server.rs
@@ -14,7 +14,6 @@ use anyhow::Result;
use base_db::{Config, Owner, Workspace};
use commands::{BuildCommand, CleanCommand, CleanTarget, ForwardSearch};
use crossbeam_channel::{Receiver, Sender};
-use diagnostics::{DiagnosticManager, DiagnosticSource};
use distro::{Distro, Language};
use line_index::LineCol;
use lsp_server::{Connection, ErrorCode, Message, RequestId};
@@ -22,22 +21,17 @@ use lsp_types::{notification::*, request::*, *};
use notify::event::ModifyKind;
use notify_debouncer_full::{DebouncedEvent, Debouncer, FileIdMap};
use parking_lot::{Mutex, RwLock};
-use rowan::ast::AstNode;
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashSet;
use serde::{de::DeserializeOwned, Serialize};
-use syntax::bibtex;
use threadpool::ThreadPool;
use crate::{
client::LspClient,
features::{
- completion::{self, ResolveInfo},
- definition, folding, formatting, highlight, hover, inlay_hint, link, reference, rename,
- symbols,
- },
- util::{
- self, capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt, normalize_uri,
+ completion, definition, folding, formatting, highlight, hover, inlay_hint, link, reference,
+ rename, symbols,
},
+ util::{from_proto, line_index_ext::LineIndexExt, normalize_uri, to_proto, ClientFlags},
};
use self::{
@@ -55,7 +49,7 @@ enum InternalMessage {
SetOptions(Options),
FileEvent(Vec<DebouncedEvent>),
Diagnostics,
- ChktexResult(Url, Vec<lsp_types::Diagnostic>),
+ ChktexFinished(Url, Vec<diagnostics::Diagnostic>),
ForwardSearch(Url, Option<Position>),
}
@@ -65,70 +59,68 @@ pub struct Server {
internal_rx: Receiver<InternalMessage>,
workspace: Arc<RwLock<Workspace>>,
client: LspClient,
- client_capabilities: Arc<ClientCapabilities>,
- client_info: Option<Arc<ClientInfo>>,
- diagnostic_manager: DiagnosticManager,
- chktex_diagnostics: FxHashMap<Url, Vec<Diagnostic>>,
+ client_flags: Arc<ClientFlags>,
+ diagnostic_manager: diagnostics::Manager,
watcher: FileWatcher,
pool: ThreadPool,
pending_builds: Arc<Mutex<FxHashSet<u32>>>,
}
impl Server {
- pub fn new(connection: Connection) -> Self {
+ pub fn exec(connection: Connection) -> Result<()> {
let client = LspClient::new(connection.sender.clone());
let (internal_tx, internal_rx) = crossbeam_channel::unbounded();
let watcher = FileWatcher::new(internal_tx.clone()).expect("init file watcher");
- Self {
+ let mut workspace = Workspace::default();
+
+ let (id, params) = connection.initialize_start()?;
+ let params: InitializeParams = serde_json::from_value(params)?;
+
+ let workspace_folders = params
+ .workspace_folders
+ .unwrap_or_default()
+ .into_iter()
+ .filter(|folder| folder.uri.scheme() == "file")
+ .flat_map(|folder| folder.uri.to_file_path())
+ .collect();
+
+ workspace.set_folders(workspace_folders);
+
+ let result = InitializeResult {
+ capabilities: Self::capabilities(),
+ server_info: Some(ServerInfo {
+ name: "TexLab".to_owned(),
+ version: Some(env!("CARGO_PKG_VERSION").to_owned()),
+ }),
+ };
+
+ connection.initialize_finish(id, serde_json::to_value(result)?)?;
+
+ let server = Self {
connection: Arc::new(connection),
internal_tx,
internal_rx,
- workspace: Default::default(),
+ workspace: Arc::new(RwLock::new(workspace)),
client,
- client_capabilities: Default::default(),
- client_info: Default::default(),
- chktex_diagnostics: Default::default(),
- diagnostic_manager: DiagnosticManager::default(),
+ client_flags: Arc::new(from_proto::client_flags(
+ params.capabilities,
+ params.client_info,
+ )),
+ diagnostic_manager: diagnostics::Manager::default(),
watcher,
pool: threadpool::Builder::new().build(),
pending_builds: Default::default(),
- }
- }
+ };
- fn run_query<R, Q>(&self, id: RequestId, query: Q)
- where
- R: Serialize,
- Q: FnOnce(&Workspace) -> R + Send + 'static,
- {
- let client = self.client.clone();
- let workspace = Arc::clone(&self.workspace);
- self.pool.execute(move || {
- let response = lsp_server::Response::new_ok(id, query(&workspace.read()));
- client.send_response(response).unwrap();
- });
- }
+ let options = serde_json::from_value(params.initialization_options.unwrap_or_default())
+ .unwrap_or_default();
- fn run_fallible<R, Q>(&self, id: RequestId, query: Q)
- where
- R: Serialize,
- Q: FnOnce() -> Result<R> + Send + 'static,
- {
- let client = self.client.clone();
- self.pool.execute(move || match query() {
- Ok(result) => {
- let response = lsp_server::Response::new_ok(id, result);
- client.send_response(response).unwrap();
- }
- Err(why) => {
- client
- .send_error(id, ErrorCode::InternalError, why.to_string())
- .unwrap();
- }
- });
+ server.run(options)?;
+ Ok(())
}
- fn capabilities(&self) -> ServerCapabilities {
+ fn capabilities() -> ServerCapabilities {
ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
@@ -185,57 +177,40 @@ impl Server {
}
}
- fn initialize(&mut self) -> Result<()> {
- let (id, params) = self.connection.initialize_start()?;
- let params: InitializeParams = serde_json::from_value(params)?;
-
- self.client_capabilities = Arc::new(params.capabilities);
- self.client_info = params.client_info.map(Arc::new);
-
- let workspace_folders = params
- .workspace_folders
- .unwrap_or_default()
- .into_iter()
- .filter(|folder| folder.uri.scheme() == "file")
- .flat_map(|folder| folder.uri.to_file_path())
- .collect();
-
- self.workspace.write().set_folders(workspace_folders);
-
- let result = InitializeResult {
- capabilities: self.capabilities(),
- server_info: Some(ServerInfo {
- name: "TexLab".to_owned(),
- version: Some(env!("CARGO_PKG_VERSION").to_owned()),
- }),
- };
- self.connection
- .initialize_finish(id, serde_json::to_value(result)?)?;
-
- let StartupOptions { skip_distro } =
- serde_json::from_value(params.initialization_options.unwrap_or_default())
- .unwrap_or_default();
-
- if !skip_distro {
- let sender = self.internal_tx.clone();
- self.pool.execute(move || {
- let distro = Distro::detect().unwrap_or_else(|why| {
- log::warn!("Unable to load distro files: {}", why);
- Distro::default()
- });
-
- log::info!("Detected distribution: {:?}", distro.kind);
- sender.send(InternalMessage::SetDistro(distro)).unwrap();
- });
- }
+ fn run_query<R, Q>(&self, id: RequestId, query: Q)
+ where
+ R: Serialize,
+ Q: FnOnce(&Workspace) -> R + Send + 'static,
+ {
+ let client = self.client.clone();
+ let workspace = Arc::clone(&self.workspace);
+ self.pool.execute(move || {
+ let response = lsp_server::Response::new_ok(id, query(&workspace.read()));
+ client.send_response(response).unwrap();
+ });
+ }
- self.register_configuration();
- self.pull_options();
- Ok(())
+ fn run_fallible<R, Q>(&self, id: RequestId, query: Q)
+ where
+ R: Serialize,
+ Q: FnOnce() -> Result<R> + Send + 'static,
+ {
+ let client = self.client.clone();
+ self.pool.execute(move || match query() {
+ Ok(result) => {
+ let response = lsp_server::Response::new_ok(id, result);
+ client.send_response(response).unwrap();
+ }
+ Err(why) => {
+ client
+ .send_error(id, ErrorCode::InternalError, why.to_string())
+ .unwrap();
+ }
+ });
}
fn register_configuration(&mut self) {
- if self.client_capabilities.has_push_configuration_support() {
+ if self.client_flags.configuration_push {
let registration = Registration {
id: "pull-config".to_string(),
method: DidChangeConfiguration::METHOD.to_string(),
@@ -269,7 +244,7 @@ impl Server {
.iter()
.filter_map(|path| workspace.lookup_path(path))
{
- self.diagnostic_manager.update(&workspace, document);
+ self.diagnostic_manager.update_syntax(&workspace, document);
}
drop(workspace);
@@ -279,23 +254,16 @@ impl Server {
fn publish_diagnostics(&mut self) -> Result<()> {
let workspace = self.workspace.read();
- let mut all_diagnostics =
- util::diagnostics::collect(&workspace, &mut self.diagnostic_manager);
-
- for (uri, diagnostics) in &self.chktex_diagnostics {
- let Some(document) = workspace.lookup(uri) else {
- continue;
- };
- let Some(existing) = all_diagnostics.get_mut(document) else {
+ for (uri, diagnostics) in self.diagnostic_manager.get(&workspace) {
+ let Some(document) = workspace.lookup(&uri) else {
continue;
};
- existing.extend(diagnostics.iter().cloned());
- }
- util::diagnostics::filter(&mut all_diagnostics, &workspace);
+ let diagnostics = diagnostics
+ .into_iter()
+ .filter_map(|diagnostic| to_proto::diagnostic(&workspace, document, &diagnostic))
+ .collect();
- for (document, diagnostics) in all_diagnostics {
- let uri = document.uri.clone();
let version = None;
let params = PublishDiagnosticsParams {
uri,
@@ -320,7 +288,7 @@ impl Server {
}
fn pull_options(&mut self) {
- if !self.client_capabilities.has_pull_configuration_support() {
+ if !self.client_flags.configuration_pull {
return;
}
@@ -364,7 +332,7 @@ impl Server {
}
fn did_change_configuration(&mut self, params: DidChangeConfigurationParams) -> Result<()> {
- if self.client_capabilities.has_pull_configuration_support() {
+ if self.client_flags.configuration_pull {
self.pull_options();
} else {
let options = self.client.parse_options(params.settings)?;
@@ -392,7 +360,7 @@ impl Server {
let workspace = self.workspace.read();
self.diagnostic_manager
- .update(&workspace, workspace.lookup(&uri).unwrap());
+ .update_syntax(&workspace, workspace.lookup(&uri).unwrap());
if workspace.config().diagnostics.chktex.on_open {
drop(workspace);
@@ -435,7 +403,7 @@ impl Server {
}
self.diagnostic_manager
- .update(&workspace, workspace.lookup(&uri).unwrap());
+ .update_syntax(&workspace, workspace.lookup(&uri).unwrap());
drop(workspace);
self.update_workspace();
@@ -478,45 +446,38 @@ impl Server {
Ok(())
}
- fn run_chktex(&mut self, uri: &Url) {
+ fn run_chktex(&mut self, uri: &Url) -> Option<()> {
let workspace = self.workspace.read();
- let Some(document) = workspace.lookup(uri) else {
- return;
- };
- let Some(command) = util::chktex::Command::new(&workspace, document) else {
- return;
- };
+
+ let document = workspace.lookup(uri)?;
+ let command = diagnostics::chktex::Command::new(&workspace, document)?;
let sender = self.internal_tx.clone();
let uri = document.uri.clone();
self.pool.execute(move || {
let diagnostics = command.run().unwrap_or_default();
sender
- .send(InternalMessage::ChktexResult(uri, diagnostics))
+ .send(InternalMessage::ChktexFinished(uri, diagnostics))
.unwrap();
});
+
+ Some(())
}
- fn document_link(&self, id: RequestId, params: DocumentLinkParams) -> Result<()> {
- let mut uri = params.text_document.uri;
- normalize_uri(&mut uri);
+ fn document_link(&self, id: RequestId, mut params: DocumentLinkParams) -> Result<()> {
+ normalize_uri(&mut params.text_document.uri);
self.run_query(id, move |workspace| {
- link::find_all(workspace, &uri).unwrap_or_default()
+ link::find_all(workspace, params).unwrap_or_default()
});
Ok(())
}
- fn document_symbols(&self, id: RequestId, params: DocumentSymbolParams) -> Result<()> {
- let mut uri = params.text_document.uri;
- normalize_uri(&mut uri);
+ fn document_symbols(&self, id: RequestId, mut params: DocumentSymbolParams) -> Result<()> {
+ normalize_uri(&mut params.text_document.uri);
- let capabilities = Arc::clone(&self.client_capabilities);
+ let client_flags = Arc::clone(&self.client_flags);
self.run_query(id, move |workspace| {
- let Some(document) = workspace.lookup(&uri) else {
- return DocumentSymbolResponse::Flat(vec![]);
- };
-
- symbols::document_symbols(workspace, document, &capabilities)
+ symbols::document_symbols(workspace, params, &client_flags)
});
Ok(())
@@ -533,11 +494,10 @@ impl Server {
fn completion(&self, id: RequestId, mut params: CompletionParams) -> Result<()> {
normalize_uri(&mut params.text_document_position.text_document.uri);
let position = params.text_document_position.position;
- let client_capabilities = Arc::clone(&self.client_capabilities);
- let client_info = self.client_info.clone();
+ let client_flags = Arc::clone(&self.client_flags);
self.update_cursor(&params.text_document_position.text_document.uri, position);
self.run_query(id, move |db| {
- completion::complete(db, &params, &client_capabilities, client_info.as_deref())
+ completion::complete(db, params, &client_flags)
});
Ok(())
@@ -555,54 +515,20 @@ impl Server {
fn completion_resolve(&self, id: RequestId, mut item: CompletionItem) -> Result<()> {
self.run_query(id, move |workspace| {
- match item
- .data
- .clone()
- .map(|data| serde_json::from_value(data).unwrap())
- {
- Some(ResolveInfo::Package | ResolveInfo::DocumentClass) => {
- item.documentation = completion_data::DATABASE
- .meta(&item.label)
- .and_then(|meta| meta.description.as_deref())
- .map(|value| {
- Documentation::MarkupContent(MarkupContent {
- kind: MarkupKind::PlainText,
- value: value.into(),
- })
- });
- }
- Some(ResolveInfo::Citation { uri, key }) => {
- if let Some(data) = workspace
- .lookup(&uri)
- .and_then(|document| document.data.as_bib())
- {
- item.documentation = bibtex::Root::cast(data.root_node())
- .and_then(|root| root.find_entry(&key))
- .and_then(|entry| citeproc::render(&entry))
- .map(|value| {
- Documentation::MarkupContent(MarkupContent {
- kind: MarkupKind::Markdown,
- value,
- })
- });
- }
- }
- None => {}
- };
-
+ completion::resolve(workspace, &mut item);
item
});
Ok(())
}
- fn folding_range(&self, id: RequestId, params: FoldingRangeParams) -> Result<()> {
- let mut uri = params.text_document.uri;
- normalize_uri(&mut uri);
- let client_capabilities = Arc::clone(&self.client_capabilities);
+ fn folding_range(&self, id: RequestId, mut params: FoldingRangeParams) -> Result<()> {
+ normalize_uri(&mut params.text_document.uri);
+ let client_flags = Arc::clone(&self.client_flags);
self.run_query(id, move |db| {
- folding::find_all(db, &uri, &client_capabilities).unwrap_or_default()
+ folding::find_all(db, params, &client_flags).unwrap_or_default()
});
+
Ok(())
}
@@ -623,33 +549,28 @@ impl Server {
Ok(())
}
- fn goto_definition(&self, id: RequestId, params: GotoDefinitionParams) -> Result<()> {
- let mut uri = params.text_document_position_params.text_document.uri;
- normalize_uri(&mut uri);
- let position = params.text_document_position_params.position;
- self.run_query(id, move |db| {
- definition::goto_definition(db, &uri, position)
- });
-
+ fn goto_definition(&self, id: RequestId, mut params: GotoDefinitionParams) -> Result<()> {
+ normalize_uri(&mut params.text_document_position_params.text_document.uri);
+ self.run_query(id, move |db| definition::goto_definition(db, params));
Ok(())
}
fn prepare_rename(&self, id: RequestId, mut params: TextDocumentPositionParams) -> Result<()> {
normalize_uri(&mut params.text_document.uri);
- self.run_query(id, move |db| rename::prepare_rename_all(db, &params));
+ self.run_query(id, move |db| rename::prepare_rename_all(db, params));
Ok(())
}
fn rename(&self, id: RequestId, mut params: RenameParams) -> Result<()> {
normalize_uri(&mut params.text_document_position.text_document.uri);
- self.run_query(id, move |db| rename::rename_all(db, &params));
+ self.run_query(id, move |db| rename::rename_all(db, params));
Ok(())
}
fn document_highlight(&self, id: RequestId, mut params: DocumentHighlightParams) -> Result<()> {
normalize_uri(&mut params.text_document_position_params.text_document.uri);
self.run_query(id, move |db| {
- highlight::find_all(db, &params).unwrap_or_default()
+ highlight::find_all(db, params).unwrap_or_default()
});
Ok(())
@@ -716,11 +637,10 @@ impl Server {
Ok(())
}
- fn inlay_hints(&self, id: RequestId, params: InlayHintParams) -> Result<()> {
- let mut uri = params.text_document.uri;
- normalize_uri(&mut uri);
+ fn inlay_hints(&self, id: RequestId, mut params: InlayHintParams) -> Result<()> {
+ normalize_uri(&mut params.text_document.uri);
self.run_query(id, move |db| {
- inlay_hint::find_all(db, &uri, params.range).unwrap_or_default()
+ inlay_hint::find_all(db, params).unwrap_or_default()
});
Ok(())
}
@@ -757,7 +677,7 @@ impl Server {
let command = BuildCommand::new(&workspace, &uri);
let internal = self.internal_tx.clone();
- let progress = self.client_capabilities.has_work_done_progress_support();
+ let progress = self.client_flags.progress;
let pending_builds = Arc::clone(&self.pending_builds);
self.pool.execute(move || {
@@ -896,7 +816,7 @@ impl Server {
changed |= workspace.load(&path, language, Owner::Server).is_ok();
if let Some(document) = workspace.lookup_path(&path) {
- self.diagnostic_manager.update(&workspace, document);
+ self.diagnostic_manager.update_syntax(&workspace, document);
}
}
}
@@ -1111,8 +1031,8 @@ impl Server {
InternalMessage::Diagnostics => {
self.publish_diagnostics()?;
}
- InternalMessage::ChktexResult(uri, diagnostics) => {
- self.chktex_diagnostics.insert(uri, diagnostics);
+ InternalMessage::ChktexFinished(uri, diagnostics) => {
+ self.diagnostic_manager.update_chktex(uri, diagnostics);
self.publish_diagnostics()?;
}
InternalMessage::ForwardSearch(uri, position) => {
@@ -1124,8 +1044,22 @@ impl Server {
}
}
- pub fn run(mut self) -> Result<()> {
- self.initialize()?;
+ pub fn run(mut self, options: StartupOptions) -> Result<()> {
+ if !options.skip_distro {
+ let sender = self.internal_tx.clone();
+ self.pool.execute(move || {
+ let distro = Distro::detect().unwrap_or_else(|why| {
+ log::warn!("Unable to load distro files: {}", why);
+ Distro::default()
+ });
+
+ log::info!("Detected distribution: {:?}", distro.kind);
+ sender.send(InternalMessage::SetDistro(distro)).unwrap();
+ });
+ }
+
+ self.register_configuration();
+ self.pull_options();
self.process_messages()?;
self.pool.join();
Ok(())
diff --git a/support/texlab/crates/texlab/src/util.rs b/support/texlab/crates/texlab/src/util.rs
index ecc7b850d7..c40ba8b51a 100644
--- a/support/texlab/crates/texlab/src/util.rs
+++ b/support/texlab/crates/texlab/src/util.rs
@@ -1,8 +1,10 @@
-pub mod capabilities;
-pub mod chktex;
-pub mod diagnostics;
+mod client_flags;
+pub mod from_proto;
pub mod line_index_ext;
pub mod lsp_enums;
+pub mod to_proto;
+
+pub use self::client_flags::ClientFlags;
pub fn normalize_uri(uri: &mut lsp_types::Url) {
if let Some(mut segments) = uri.path_segments() {
diff --git a/support/texlab/crates/texlab/src/util/capabilities.rs b/support/texlab/crates/texlab/src/util/capabilities.rs
deleted file mode 100644
index 987c0b48dd..0000000000
--- a/support/texlab/crates/texlab/src/util/capabilities.rs
+++ /dev/null
@@ -1,179 +0,0 @@
-use lsp_types::{ClientCapabilities, MarkupKind};
-
-pub trait ClientCapabilitiesExt {
- fn has_definition_link_support(&self) -> bool;
-
- fn has_hierarchical_document_symbol_support(&self) -> bool;
-
- fn has_work_done_progress_support(&self) -> bool;
-
- fn has_completion_markdown_support(&self) -> bool;
-
- fn has_hover_markdown_support(&self) -> bool;
-
- fn has_pull_configuration_support(&self) -> bool;
-
- fn has_push_configuration_support(&self) -> bool;
-
- fn has_file_watching_support(&self) -> bool;
-
- fn has_snippet_support(&self) -> bool;
-}
-
-impl ClientCapabilitiesExt for ClientCapabilities {
- fn has_definition_link_support(&self) -> bool {
- self.text_document
- .as_ref()
- .and_then(|cap| cap.definition.as_ref())
- .and_then(|cap| cap.link_support)
- == Some(true)
- }
-
- fn has_hierarchical_document_symbol_support(&self) -> bool {
- self.text_document
- .as_ref()
- .and_then(|cap| cap.document_symbol.as_ref())
- .and_then(|cap| cap.hierarchical_document_symbol_support)
- == Some(true)
- }
-
- fn has_work_done_progress_support(&self) -> bool {
- self.window.as_ref().and_then(|cap| cap.work_done_progress) == Some(true)
- }
-
- fn has_completion_markdown_support(&self) -> bool {
- self.text_document
- .as_ref()
- .and_then(|cap| cap.completion.as_ref())
- .and_then(|cap| cap.completion_item.as_ref())
- .and_then(|cap| cap.documentation_format.as_ref())
- .map_or(false, |formats| formats.contains(&MarkupKind::Markdown))
- }
-
- fn has_hover_markdown_support(&self) -> bool {
- self.text_document
- .as_ref()
- .and_then(|cap| cap.hover.as_ref())
- .and_then(|cap| cap.content_format.as_ref())
- .map_or(false, |formats| formats.contains(&MarkupKind::Markdown))
- }
-
- fn has_pull_configuration_support(&self) -> bool {
- self.workspace.as_ref().and_then(|cap| cap.configuration) == Some(true)
- }
-
- fn has_push_configuration_support(&self) -> bool {
- self.workspace
- .as_ref()
- .and_then(|cap| cap.did_change_configuration)
- .and_then(|cap| cap.dynamic_registration)
- == Some(true)
- }
-
- fn has_file_watching_support(&self) -> bool {
- self.workspace
- .as_ref()
- .and_then(|cap| cap.did_change_watched_files)
- .and_then(|cap| cap.dynamic_registration)
- == Some(true)
- }
-
- fn has_snippet_support(&self) -> bool {
- self.text_document
- .as_ref()
- .and_then(|cap| cap.completion.as_ref())
- .and_then(|cap| cap.completion_item.as_ref())
- .and_then(|cap| cap.snippet_support)
- == Some(true)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use lsp_types::{
- DocumentSymbolClientCapabilities, GotoCapability, HoverClientCapabilities,
- TextDocumentClientCapabilities, WindowClientCapabilities,
- };
-
- #[test]
- fn test_has_definition_link_support_true() {
- let capabilities = ClientCapabilities {
- text_document: Some(TextDocumentClientCapabilities {
- definition: Some(GotoCapability {
- link_support: Some(true),
- ..GotoCapability::default()
- }),
- ..TextDocumentClientCapabilities::default()
- }),
- ..ClientCapabilities::default()
- };
- assert!(capabilities.has_definition_link_support());
- }
-
- #[test]
- fn test_has_definition_link_support_false() {
- let capabilities = ClientCapabilities::default();
- assert!(!capabilities.has_definition_link_support());
- }
-
- #[test]
- fn test_has_hierarchical_document_symbol_support_true() {
- let capabilities = ClientCapabilities {
- text_document: Some(TextDocumentClientCapabilities {
- document_symbol: Some(DocumentSymbolClientCapabilities {
- hierarchical_document_symbol_support: Some(true),
- ..DocumentSymbolClientCapabilities::default()
- }),
- ..TextDocumentClientCapabilities::default()
- }),
- ..ClientCapabilities::default()
- };
- assert!(capabilities.has_hierarchical_document_symbol_support());
- }
-
- #[test]
- fn test_has_hierarchical_document_symbol_support_false() {
- let capabilities = ClientCapabilities::default();
- assert!(!capabilities.has_hierarchical_document_symbol_support());
- }
-
- #[test]
- fn test_has_work_done_progress_support_true() {
- let capabilities = ClientCapabilities {
- window: Some(WindowClientCapabilities {
- work_done_progress: Some(true),
- ..WindowClientCapabilities::default()
- }),
- ..ClientCapabilities::default()
- };
- assert!(capabilities.has_work_done_progress_support());
- }
-
- #[test]
- fn test_has_work_done_progress_support_false() {
- let capabilities = ClientCapabilities::default();
- assert!(!capabilities.has_work_done_progress_support());
- }
-
- #[test]
- fn test_has_hover_markdown_support_true() {
- let capabilities = ClientCapabilities {
- text_document: Some(TextDocumentClientCapabilities {
- hover: Some(HoverClientCapabilities {
- content_format: Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]),
- ..HoverClientCapabilities::default()
- }),
- ..TextDocumentClientCapabilities::default()
- }),
- ..ClientCapabilities::default()
- };
- assert!(capabilities.has_hover_markdown_support());
- }
-
- #[test]
- fn test_has_hover_markdown_support_false() {
- let capabilities = ClientCapabilities::default();
- assert!(!capabilities.has_hover_markdown_support());
- }
-}
diff --git a/support/texlab/crates/texlab/src/util/client_flags.rs b/support/texlab/crates/texlab/src/util/client_flags.rs
new file mode 100644
index 0000000000..e5813e4f02
--- /dev/null
+++ b/support/texlab/crates/texlab/src/util/client_flags.rs
@@ -0,0 +1,40 @@
+/// Contains information about the client's capabilities.
+/// This is used to determine which features the server can use.
+#[derive(Debug, Clone)]
+pub struct ClientFlags {
+ /// If `true`, the server can return `DocumentSymbol` instead of `SymbolInformation`.
+ pub hierarchical_document_symbols: bool,
+
+ /// If `true`, the server can include markdown in completion items.
+ /// This is used to include images via base64 encoding.
+ pub completion_markdown: bool,
+
+ /// If `true`, the server can include snippets like `\begin{...}` in completion items.
+ pub completion_snippets: bool,
+
+ /// The completion kinds supported by the client. Unsupported kinds will be replaced by `TEXT`.
+ pub completion_kinds: Vec<lsp_types::CompletionItemKind>,
+
+ /// If `true`, the server will always mark the completion list as incomplete.
+ /// This is used as a workaround for VSCode where the client-side filtering messes with `filterText`.
+ /// If not set, then fuzzy citation completion will not work.
+ pub completion_always_incomplete: bool,
+
+ /// If `true`, the server can include markdown in hover responses.
+ pub hover_markdown: bool,
+
+ /// If `true`, the server can pull the configuration from the client.
+ pub configuration_pull: bool,
+
+ /// If `true`, the client notifies the server when the configuration changes.
+ pub configuration_push: bool,
+
+ /// If `true`, the client can return `LocationLink` instead of `Location`.
+ pub definition_link: bool,
+
+ /// If `true`, the server can return custom kinds like `section`.
+ pub folding_custom_kinds: bool,
+
+ /// If `true`, the server can report progress using `WorkDoneProgress`.
+ pub progress: bool,
+}
diff --git a/support/texlab/crates/texlab/src/util/diagnostics.rs b/support/texlab/crates/texlab/src/util/diagnostics.rs
deleted file mode 100644
index 0ef24d065b..0000000000
--- a/support/texlab/crates/texlab/src/util/diagnostics.rs
+++ /dev/null
@@ -1,201 +0,0 @@
-use base_db::{util::filter_regex_patterns, Document, Workspace};
-use diagnostics::{
- types::{BibError, Diagnostic, DiagnosticData, TexError},
- DiagnosticBuilder, DiagnosticSource,
-};
-use rowan::TextRange;
-use rustc_hash::FxHashMap;
-use syntax::BuildErrorLevel;
-
-use super::line_index_ext::LineIndexExt;
-
-pub fn collect<'db>(
- workspace: &'db Workspace,
- source: &mut dyn DiagnosticSource,
-) -> FxHashMap<&'db Document, Vec<lsp_types::Diagnostic>> {
- let mut builder = DiagnosticBuilder::default();
- source.publish(workspace, &mut builder);
- builder
- .iter()
- .filter_map(|(uri, diags)| workspace.lookup(uri).map(|document| (document, diags)))
- .map(|(document, diags)| {
- let diags = diags
- .into_iter()
- .filter_map(|diag| create_diagnostic(workspace, document, diag))
- .collect::<Vec<_>>();
-
- (document, diags)
- })
- .collect()
-}
-
-fn create_diagnostic(
- workspace: &Workspace,
- document: &Document,
- diagnostic: &Diagnostic,
-) -> Option<lsp_types::Diagnostic> {
- let range = document.line_index.line_col_lsp_range(diagnostic.range)?;
-
- let severity = match &diagnostic.data {
- DiagnosticData::Tex(error) => match error {
- TexError::UnexpectedRCurly => lsp_types::DiagnosticSeverity::ERROR,
- TexError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR,
- TexError::MismatchedEnvironment => lsp_types::DiagnosticSeverity::ERROR,
- TexError::UnusedLabel => lsp_types::DiagnosticSeverity::HINT,
- TexError::UndefinedLabel => lsp_types::DiagnosticSeverity::ERROR,
- TexError::UndefinedCitation => lsp_types::DiagnosticSeverity::ERROR,
- TexError::DuplicateLabel(_) => lsp_types::DiagnosticSeverity::ERROR,
- },
- DiagnosticData::Bib(error) => match error {
- BibError::ExpectingLCurly => lsp_types::DiagnosticSeverity::ERROR,
- BibError::ExpectingKey => lsp_types::DiagnosticSeverity::ERROR,
- BibError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR,
- BibError::ExpectingEq => lsp_types::DiagnosticSeverity::ERROR,
- BibError::ExpectingFieldValue => lsp_types::DiagnosticSeverity::ERROR,
- BibError::UnusedEntry => lsp_types::DiagnosticSeverity::HINT,
- BibError::DuplicateEntry(_) => lsp_types::DiagnosticSeverity::ERROR,
- },
- DiagnosticData::Build(error) => match error.level {
- BuildErrorLevel::Error => lsp_types::DiagnosticSeverity::ERROR,
- BuildErrorLevel::Warning => lsp_types::DiagnosticSeverity::WARNING,
- },
- };
-
- let code = match &diagnostic.data {
- DiagnosticData::Tex(error) => match error {
- TexError::UnexpectedRCurly => Some(1),
- TexError::ExpectingRCurly => Some(2),
- TexError::MismatchedEnvironment => Some(3),
- TexError::UnusedLabel => Some(9),
- TexError::UndefinedLabel => Some(10),
- TexError::UndefinedCitation => Some(11),
- TexError::DuplicateLabel(_) => Some(14),
- },
- DiagnosticData::Bib(error) => match error {
- BibError::ExpectingLCurly => Some(4),
- BibError::ExpectingKey => Some(5),
- BibError::ExpectingRCurly => Some(6),
- BibError::ExpectingEq => Some(7),
- BibError::ExpectingFieldValue => Some(8),
- BibError::UnusedEntry => Some(12),
- BibError::DuplicateEntry(_) => Some(13),
- },
- DiagnosticData::Build(_) => None,
- };
-
- let source = match &diagnostic.data {
- DiagnosticData::Tex(_) | DiagnosticData::Bib(_) => "texlab",
- DiagnosticData::Build(_) => "latex",
- };
-
- let message = String::from(match &diagnostic.data {
- DiagnosticData::Tex(error) => match error {
- TexError::UnexpectedRCurly => "Unexpected \"}\"",
- TexError::ExpectingRCurly => "Expecting a curly bracket: \"}\"",
- TexError::MismatchedEnvironment => "Mismatched environment",
- TexError::UnusedLabel => "Unused label",
- TexError::UndefinedLabel => "Undefined reference",
- TexError::UndefinedCitation => "Undefined reference",
- TexError::DuplicateLabel(_) => "Duplicate label",
- },
- DiagnosticData::Bib(error) => match error {
- BibError::ExpectingLCurly => "Expecting a curly bracket: \"{\"",
- BibError::ExpectingKey => "Expecting a key",
- BibError::ExpectingRCurly => "Expecting a curly bracket: \"}\"",
- BibError::ExpectingEq => "Expecting an equality sign: \"=\"",
- BibError::ExpectingFieldValue => "Expecting a field value",
- BibError::UnusedEntry => "Unused entry",
- BibError::DuplicateEntry(_) => "Duplicate entry key",
- },
- DiagnosticData::Build(error) => &error.message,
- });
-
- let tags = match &diagnostic.data {
- DiagnosticData::Tex(error) => match error {
- TexError::UnexpectedRCurly => None,
- TexError::ExpectingRCurly => None,
- TexError::MismatchedEnvironment => None,
- TexError::UnusedLabel => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]),
- TexError::UndefinedLabel => None,
- TexError::UndefinedCitation => None,
- TexError::DuplicateLabel(_) => None,
- },
- DiagnosticData::Bib(error) => match error {
- BibError::ExpectingLCurly => None,
- BibError::ExpectingKey => None,
- BibError::ExpectingRCurly => None,
- BibError::ExpectingEq => None,
- BibError::ExpectingFieldValue => None,
- BibError::UnusedEntry => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]),
- BibError::DuplicateEntry(_) => None,
- },
- DiagnosticData::Build(_) => None,
- };
-
- let related_information = match &diagnostic.data {
- DiagnosticData::Tex(error) => match error {
- TexError::UnexpectedRCurly => None,
- TexError::ExpectingRCurly => None,
- TexError::MismatchedEnvironment => None,
- TexError::UnusedLabel => None,
- TexError::UndefinedLabel => None,
- TexError::UndefinedCitation => None,
- TexError::DuplicateLabel(others) => make_conflict_info(workspace, others, "label"),
- },
- DiagnosticData::Bib(error) => match error {
- BibError::ExpectingLCurly => None,
- BibError::ExpectingKey => None,
- BibError::ExpectingRCurly => None,
- BibError::ExpectingEq => None,
- BibError::ExpectingFieldValue => None,
- BibError::UnusedEntry => None,
- BibError::DuplicateEntry(others) => make_conflict_info(workspace, others, "entry"),
- },
- DiagnosticData::Build(_) => None,
- };
-
- Some(lsp_types::Diagnostic {
- severity: Some(severity),
- code: code.map(lsp_types::NumberOrString::Number),
- source: Some(String::from(source)),
- tags,
- related_information,
- ..lsp_types::Diagnostic::new_simple(range, message)
- })
-}
-
-fn make_conflict_info(
- workspace: &Workspace,
- locations: &Vec<(lsp_types::Url, TextRange)>,
- object: &str,
-) -> Option<Vec<lsp_types::DiagnosticRelatedInformation>> {
- let mut items = Vec::new();
- for (uri, range) in locations {
- let range = workspace
- .lookup(uri)?
- .line_index
- .line_col_lsp_range(*range)?;
-
- let message = format!("conflicting {object} defined here");
- let location = lsp_types::Location::new(uri.clone(), range);
- items.push(lsp_types::DiagnosticRelatedInformation { location, message });
- }
-
- Some(items)
-}
-
-pub fn filter(
- all_diagnostics: &mut FxHashMap<&Document, Vec<lsp_types::Diagnostic>>,
- workspace: &Workspace,
-) {
- let config = &workspace.config().diagnostics;
- for diagnostics in all_diagnostics.values_mut() {
- diagnostics.retain(|diagnostic| {
- filter_regex_patterns(
- &diagnostic.message,
- &config.allowed_patterns,
- &config.ignored_patterns,
- )
- });
- }
-}
diff --git a/support/texlab/crates/texlab/src/util/from_proto.rs b/support/texlab/crates/texlab/src/util/from_proto.rs
new file mode 100644
index 0000000000..e05d317dfe
--- /dev/null
+++ b/support/texlab/crates/texlab/src/util/from_proto.rs
@@ -0,0 +1,223 @@
+use base_db::{FeatureParams, Workspace};
+use completion::CompletionParams;
+use definition::DefinitionParams;
+use highlights::HighlightParams;
+use hover::HoverParams;
+use inlay_hints::InlayHintParams;
+use references::ReferenceParams;
+use rename::RenameParams;
+use rowan::TextSize;
+
+use crate::features::completion::ResolveInfo;
+
+use super::{line_index_ext::LineIndexExt, ClientFlags};
+
+pub fn client_flags(
+ capabilities: lsp_types::ClientCapabilities,
+ info: Option<lsp_types::ClientInfo>,
+) -> ClientFlags {
+ let hierarchical_document_symbols = capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.document_symbol.as_ref())
+ .and_then(|cap| cap.hierarchical_document_symbol_support)
+ .unwrap_or(false);
+
+ let completion_markdown = capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.completion.as_ref())
+ .and_then(|cap| cap.completion_item.as_ref())
+ .and_then(|cap| cap.documentation_format.as_ref())
+ .map_or(false, |formats| {
+ formats.contains(&lsp_types::MarkupKind::Markdown)
+ });
+
+ let completion_snippets = capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.completion.as_ref())
+ .and_then(|cap| cap.completion_item.as_ref())
+ .and_then(|cap| cap.snippet_support)
+ .unwrap_or(false);
+
+ let completion_kinds = capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.completion.as_ref())
+ .and_then(|cap| cap.completion_item_kind.as_ref())
+ .and_then(|cap| cap.value_set.clone())
+ .unwrap_or_default();
+
+ let completion_always_incomplete = info.map_or(false, |info| info.name == "Visual Studio Code");
+
+ 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, |formats| {
+ formats.contains(&lsp_types::MarkupKind::Markdown)
+ });
+
+ let configuration_pull = capabilities
+ .workspace
+ .as_ref()
+ .and_then(|cap| cap.configuration)
+ .unwrap_or(false);
+
+ let configuration_push = capabilities
+ .workspace
+ .as_ref()
+ .and_then(|cap| cap.did_change_configuration)
+ .and_then(|cap| cap.dynamic_registration)
+ .unwrap_or(false);
+
+ let definition_link = capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.definition)
+ .and_then(|cap| cap.link_support)
+ .unwrap_or(false);
+
+ let folding_custom_kinds = capabilities
+ .text_document
+ .as_ref()
+ .and_then(|cap| cap.folding_range.as_ref())
+ .and_then(|cap| cap.folding_range_kind.as_ref())
+ .and_then(|cap| cap.value_set.as_ref())
+ .is_some();
+
+ let progress = capabilities
+ .window
+ .as_ref()
+ .and_then(|cap| cap.work_done_progress)
+ .unwrap_or(false);
+
+ ClientFlags {
+ hierarchical_document_symbols,
+ completion_markdown,
+ completion_snippets,
+ completion_kinds,
+ completion_always_incomplete,
+ hover_markdown,
+ configuration_pull,
+ configuration_push,
+ definition_link,
+ folding_custom_kinds,
+ progress,
+ }
+}
+
+pub fn rename_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::TextDocumentPositionParams,
+) -> Option<RenameParams<'a>> {
+ let (feature, offset) =
+ feature_params_offset(workspace, params.text_document, params.position)?;
+
+ Some(RenameParams { feature, offset })
+}
+
+pub fn hover_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::HoverParams,
+) -> Option<HoverParams<'a>> {
+ let (feature, offset) = feature_params_offset(
+ workspace,
+ params.text_document_position_params.text_document,
+ params.text_document_position_params.position,
+ )?;
+
+ Some(HoverParams { feature, offset })
+}
+
+pub fn inlay_hint_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::InlayHintParams,
+) -> Option<InlayHintParams> {
+ let feature = feature_params(workspace, params.text_document)?;
+ let range = feature.document.line_index.offset_lsp_range(params.range)?;
+ Some(InlayHintParams { feature, range })
+}
+
+pub fn highlight_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::DocumentHighlightParams,
+) -> Option<HighlightParams<'a>> {
+ let (feature, offset) = feature_params_offset(
+ workspace,
+ params.text_document_position_params.text_document,
+ params.text_document_position_params.position,
+ )?;
+
+ Some(HighlightParams { feature, offset })
+}
+
+pub fn definition_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::GotoDefinitionParams,
+) -> Option<DefinitionParams<'a>> {
+ let (feature, offset) = feature_params_offset(
+ workspace,
+ params.text_document_position_params.text_document,
+ params.text_document_position_params.position,
+ )?;
+
+ Some(DefinitionParams { feature, offset })
+}
+
+pub fn completion_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::CompletionParams,
+) -> Option<CompletionParams<'a>> {
+ let (feature, offset) = feature_params_offset(
+ workspace,
+ params.text_document_position.text_document,
+ params.text_document_position.position,
+ )?;
+
+ Some(CompletionParams { feature, offset })
+}
+
+pub fn reference_params<'a>(
+ workspace: &'a Workspace,
+ params: lsp_types::ReferenceParams,
+) -> Option<ReferenceParams<'a>> {
+ let (feature, offset) = feature_params_offset(
+ workspace,
+ params.text_document_position.text_document,
+ params.text_document_position.position,
+ )?;
+
+ let include_declaration = params.context.include_declaration;
+ Some(ReferenceParams {
+ feature,
+ offset,
+ include_declaration,
+ })
+}
+
+pub fn feature_params<'a>(
+ workspace: &'a Workspace,
+ text_document: lsp_types::TextDocumentIdentifier,
+) -> Option<FeatureParams<'a>> {
+ let document = workspace.lookup(&text_document.uri)?;
+ Some(FeatureParams::new(workspace, document))
+}
+
+pub fn feature_params_offset<'a>(
+ workspace: &'a Workspace,
+ text_document: lsp_types::TextDocumentIdentifier,
+ position: lsp_types::Position,
+) -> Option<(FeatureParams<'a>, TextSize)> {
+ let feature = feature_params(workspace, text_document)?;
+ let offset = feature.document.line_index.offset_lsp(position)?;
+ Some((feature, offset))
+}
+
+pub fn completion_resolve_info(item: &mut lsp_types::CompletionItem) -> Option<ResolveInfo> {
+ item.data
+ .take()
+ .and_then(|data| serde_json::from_value(data).ok())
+}
diff --git a/support/texlab/crates/texlab/src/util/lsp_enums.rs b/support/texlab/crates/texlab/src/util/lsp_enums.rs
index e79297af8a..352b392868 100644
--- a/support/texlab/crates/texlab/src/util/lsp_enums.rs
+++ b/support/texlab/crates/texlab/src/util/lsp_enums.rs
@@ -1,5 +1,5 @@
use base_db::data::BibtexEntryTypeCategory;
-use lsp_types::{CompletionItemKind, SymbolKind};
+use lsp_types::CompletionItemKind;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Structure {
@@ -14,7 +14,6 @@ pub enum Structure {
Label,
Folder,
File,
- PgfLibrary,
TikzLibrary,
Color,
ColorModel,
@@ -40,7 +39,6 @@ impl Structure {
Self::Label => CompletionItemKind::CONSTRUCTOR,
Self::Folder => CompletionItemKind::FOLDER,
Self::File => CompletionItemKind::FILE,
- Self::PgfLibrary => CompletionItemKind::PROPERTY,
Self::TikzLibrary => CompletionItemKind::PROPERTY,
Self::Color => CompletionItemKind::COLOR,
Self::ColorModel => CompletionItemKind::COLOR,
@@ -58,36 +56,4 @@ impl Structure {
Self::GlossaryEntry => CompletionItemKind::KEYWORD,
}
}
-
- pub fn symbol_kind(self) -> SymbolKind {
- match self {
- Self::Command => SymbolKind::FUNCTION,
- Self::Snippet => unimplemented!(),
- Self::Environment => SymbolKind::ENUM,
- Self::Section => SymbolKind::MODULE,
- Self::Float => SymbolKind::METHOD,
- Self::Theorem => SymbolKind::VARIABLE,
- Self::Equation => SymbolKind::CONSTANT,
- Self::Item => SymbolKind::ENUM_MEMBER,
- Self::Label => SymbolKind::CONSTRUCTOR,
- Self::Folder => SymbolKind::NAMESPACE,
- Self::File => SymbolKind::FILE,
- Self::PgfLibrary => SymbolKind::PROPERTY,
- Self::TikzLibrary => SymbolKind::PROPERTY,
- Self::Color => unimplemented!(),
- Self::ColorModel => unimplemented!(),
- Self::Package => SymbolKind::CLASS,
- Self::Class => SymbolKind::CLASS,
- Self::Entry(BibtexEntryTypeCategory::Misc) => SymbolKind::INTERFACE,
- Self::Entry(BibtexEntryTypeCategory::String) => SymbolKind::STRING,
- Self::Entry(BibtexEntryTypeCategory::Article) => SymbolKind::EVENT,
- Self::Entry(BibtexEntryTypeCategory::Book) => SymbolKind::STRUCT,
- Self::Entry(BibtexEntryTypeCategory::Collection) => SymbolKind::TYPE_PARAMETER,
- Self::Entry(BibtexEntryTypeCategory::Part) => SymbolKind::OPERATOR,
- Self::Entry(BibtexEntryTypeCategory::Thesis) => SymbolKind::OBJECT,
- Self::Field => SymbolKind::FIELD,
- Self::Argument => SymbolKind::NUMBER,
- Self::GlossaryEntry => unimplemented!(),
- }
- }
}
diff --git a/support/texlab/crates/texlab/src/util/to_proto.rs b/support/texlab/crates/texlab/src/util/to_proto.rs
new file mode 100644
index 0000000000..f5cf2edf8b
--- /dev/null
+++ b/support/texlab/crates/texlab/src/util/to_proto.rs
@@ -0,0 +1,459 @@
+use std::collections::HashMap;
+
+use base_db::{
+ data::BibtexEntryTypeCategory, util::RenderedObject, Document, DocumentLocation, Workspace,
+};
+use definition::DefinitionResult;
+use diagnostics::{BibError, ChktexSeverity, Diagnostic, TexError};
+use folding::{FoldingRange, FoldingRangeKind};
+use highlights::{Highlight, HighlightKind};
+use hover::{Hover, HoverData};
+use inlay_hints::{InlayHint, InlayHintData};
+use line_index::LineIndex;
+use lsp_types::NumberOrString;
+use rename::RenameResult;
+use rowan::TextRange;
+use syntax::BuildErrorLevel;
+
+use super::{line_index_ext::LineIndexExt, ClientFlags};
+
+pub fn diagnostic(
+ workspace: &Workspace,
+ document: &Document,
+ diagnostic: &Diagnostic,
+) -> Option<lsp_types::Diagnostic> {
+ let range = match diagnostic {
+ Diagnostic::Tex(range, _) | Diagnostic::Bib(range, _) | Diagnostic::Build(range, _) => {
+ document.line_index.line_col_lsp_range(*range)?
+ }
+ Diagnostic::Chktex(range) => {
+ let start = lsp_types::Position::new(range.start.line, range.start.col);
+ let end = lsp_types::Position::new(range.end.line, range.end.col);
+ lsp_types::Range::new(start, end)
+ }
+ };
+
+ let severity = match diagnostic {
+ Diagnostic::Tex(_, error) => match error {
+ TexError::UnexpectedRCurly => lsp_types::DiagnosticSeverity::ERROR,
+ TexError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR,
+ TexError::MismatchedEnvironment => lsp_types::DiagnosticSeverity::ERROR,
+ TexError::UnusedLabel => lsp_types::DiagnosticSeverity::HINT,
+ TexError::UndefinedLabel => lsp_types::DiagnosticSeverity::ERROR,
+ TexError::UndefinedCitation => lsp_types::DiagnosticSeverity::ERROR,
+ TexError::DuplicateLabel(_) => lsp_types::DiagnosticSeverity::ERROR,
+ },
+ Diagnostic::Bib(_, error) => match error {
+ BibError::ExpectingLCurly => lsp_types::DiagnosticSeverity::ERROR,
+ BibError::ExpectingKey => lsp_types::DiagnosticSeverity::ERROR,
+ BibError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR,
+ BibError::ExpectingEq => lsp_types::DiagnosticSeverity::ERROR,
+ BibError::ExpectingFieldValue => lsp_types::DiagnosticSeverity::ERROR,
+ BibError::UnusedEntry => lsp_types::DiagnosticSeverity::HINT,
+ BibError::DuplicateEntry(_) => lsp_types::DiagnosticSeverity::ERROR,
+ },
+ Diagnostic::Build(_, error) => match error.level {
+ BuildErrorLevel::Error => lsp_types::DiagnosticSeverity::ERROR,
+ BuildErrorLevel::Warning => lsp_types::DiagnosticSeverity::WARNING,
+ },
+ Diagnostic::Chktex(error) => match error.severity {
+ ChktexSeverity::Message => lsp_types::DiagnosticSeverity::HINT,
+ ChktexSeverity::Warning => lsp_types::DiagnosticSeverity::WARNING,
+ ChktexSeverity::Error => lsp_types::DiagnosticSeverity::ERROR,
+ },
+ };
+
+ let code: Option<NumberOrString> = match &diagnostic {
+ Diagnostic::Tex(_, error) => match error {
+ TexError::UnexpectedRCurly => Some(NumberOrString::Number(1)),
+ TexError::ExpectingRCurly => Some(NumberOrString::Number(2)),
+ TexError::MismatchedEnvironment => Some(NumberOrString::Number(3)),
+ TexError::UnusedLabel => Some(NumberOrString::Number(9)),
+ TexError::UndefinedLabel => Some(NumberOrString::Number(10)),
+ TexError::UndefinedCitation => Some(NumberOrString::Number(11)),
+ TexError::DuplicateLabel(_) => Some(NumberOrString::Number(14)),
+ },
+ Diagnostic::Bib(_, error) => match error {
+ BibError::ExpectingLCurly => Some(NumberOrString::Number(4)),
+ BibError::ExpectingKey => Some(NumberOrString::Number(5)),
+ BibError::ExpectingRCurly => Some(NumberOrString::Number(6)),
+ BibError::ExpectingEq => Some(NumberOrString::Number(7)),
+ BibError::ExpectingFieldValue => Some(NumberOrString::Number(8)),
+ BibError::UnusedEntry => Some(NumberOrString::Number(12)),
+ BibError::DuplicateEntry(_) => Some(NumberOrString::Number(13)),
+ },
+ Diagnostic::Build(_, _) => None,
+ Diagnostic::Chktex(error) => Some(NumberOrString::String(error.code.clone())),
+ };
+
+ let source = match &diagnostic {
+ Diagnostic::Tex(_, _) | Diagnostic::Bib(_, _) => "texlab",
+ Diagnostic::Build(_, _) => "latex",
+ Diagnostic::Chktex(_) => "ChkTeX",
+ };
+
+ let message = String::from(match &diagnostic {
+ Diagnostic::Tex(_, error) => match error {
+ TexError::UnexpectedRCurly => "Unexpected \"}\"",
+ TexError::ExpectingRCurly => "Expecting a curly bracket: \"}\"",
+ TexError::MismatchedEnvironment => "Mismatched environment",
+ TexError::UnusedLabel => "Unused label",
+ TexError::UndefinedLabel => "Undefined reference",
+ TexError::UndefinedCitation => "Undefined reference",
+ TexError::DuplicateLabel(_) => "Duplicate label",
+ },
+ Diagnostic::Bib(_, error) => match error {
+ BibError::ExpectingLCurly => "Expecting a curly bracket: \"{\"",
+ BibError::ExpectingKey => "Expecting a key",
+ BibError::ExpectingRCurly => "Expecting a curly bracket: \"}\"",
+ BibError::ExpectingEq => "Expecting an equality sign: \"=\"",
+ BibError::ExpectingFieldValue => "Expecting a field value",
+ BibError::UnusedEntry => "Unused entry",
+ BibError::DuplicateEntry(_) => "Duplicate entry key",
+ },
+ Diagnostic::Build(_, error) => &error.message,
+ Diagnostic::Chktex(error) => &error.message,
+ });
+
+ let tags = match &diagnostic {
+ Diagnostic::Tex(_, error) => match error {
+ TexError::UnexpectedRCurly => None,
+ TexError::ExpectingRCurly => None,
+ TexError::MismatchedEnvironment => None,
+ TexError::UnusedLabel => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]),
+ TexError::UndefinedLabel => None,
+ TexError::UndefinedCitation => None,
+ TexError::DuplicateLabel(_) => None,
+ },
+ Diagnostic::Bib(_, error) => match error {
+ BibError::ExpectingLCurly => None,
+ BibError::ExpectingKey => None,
+ BibError::ExpectingRCurly => None,
+ BibError::ExpectingEq => None,
+ BibError::ExpectingFieldValue => None,
+ BibError::UnusedEntry => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]),
+ BibError::DuplicateEntry(_) => None,
+ },
+ Diagnostic::Build(_, _) => None,
+ Diagnostic::Chktex(_) => None,
+ };
+
+ fn make_conflict_info(
+ workspace: &Workspace,
+ locations: &Vec<(lsp_types::Url, TextRange)>,
+ object: &str,
+ ) -> Option<Vec<lsp_types::DiagnosticRelatedInformation>> {
+ let mut items = Vec::new();
+ for (uri, range) in locations {
+ let range = workspace
+ .lookup(uri)?
+ .line_index
+ .line_col_lsp_range(*range)?;
+
+ let message = format!("conflicting {object} defined here");
+ let location = lsp_types::Location::new(uri.clone(), range);
+ items.push(lsp_types::DiagnosticRelatedInformation { location, message });
+ }
+
+ Some(items)
+ }
+
+ let related_information = match &diagnostic {
+ Diagnostic::Tex(_, error) => match error {
+ TexError::UnexpectedRCurly => None,
+ TexError::ExpectingRCurly => None,
+ TexError::MismatchedEnvironment => None,
+ TexError::UnusedLabel => None,
+ TexError::UndefinedLabel => None,
+ TexError::UndefinedCitation => None,
+ TexError::DuplicateLabel(others) => make_conflict_info(workspace, others, "label"),
+ },
+ Diagnostic::Bib(_, error) => match error {
+ BibError::ExpectingLCurly => None,
+ BibError::ExpectingKey => None,
+ BibError::ExpectingRCurly => None,
+ BibError::ExpectingEq => None,
+ BibError::ExpectingFieldValue => None,
+ BibError::UnusedEntry => None,
+ BibError::DuplicateEntry(others) => make_conflict_info(workspace, others, "entry"),
+ },
+ Diagnostic::Build(_, _) => None,
+ Diagnostic::Chktex(_) => None,
+ };
+
+ Some(lsp_types::Diagnostic {
+ severity: Some(severity),
+ code,
+ source: Some(String::from(source)),
+ tags,
+ related_information,
+ ..lsp_types::Diagnostic::new_simple(range, message)
+ })
+}
+
+pub fn inlay_hint(hint: InlayHint, line_index: &LineIndex) -> Option<lsp_types::InlayHint> {
+ let position = line_index.line_col_lsp(hint.offset)?;
+ Some(match hint.data {
+ InlayHintData::LabelDefinition(label) => {
+ let number = label.number?;
+
+ let text = match &label.object {
+ RenderedObject::Section { prefix, .. } => {
+ format!("{} {}", prefix, number)
+ }
+ RenderedObject::Float { kind, .. } => {
+ format!("{} {}", kind.as_str(), number)
+ }
+ RenderedObject::Theorem { kind, .. } => {
+ format!("{} {}", kind, number)
+ }
+ RenderedObject::Equation => format!("Equation ({})", number),
+ RenderedObject::EnumItem => format!("Item {}", number),
+ };
+
+ 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,
+ }
+ }
+ InlayHintData::LabelReference(label) => {
+ let text = label.reference();
+
+ 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,
+ }
+ }
+ })
+}
+
+pub fn document_link(
+ link: DocumentLocation,
+ line_index: &LineIndex,
+) -> Option<lsp_types::DocumentLink> {
+ Some(lsp_types::DocumentLink {
+ data: None,
+ tooltip: None,
+ target: Some(link.document.uri.clone()),
+ range: line_index.line_col_lsp_range(link.range)?,
+ })
+}
+
+pub fn folding_range(
+ folding: FoldingRange,
+ line_index: &LineIndex,
+ client_flags: &ClientFlags,
+) -> Option<serde_json::Value> {
+ let range = line_index.line_col_lsp_range(folding.range)?;
+
+ let kind = if client_flags.folding_custom_kinds {
+ Some(match folding.kind {
+ FoldingRangeKind::Section => "section",
+ FoldingRangeKind::Environment => "environment",
+ FoldingRangeKind::Entry => "entry",
+ })
+ } else {
+ None
+ };
+
+ Some(serde_json::json!({
+ "startLine": range.start.line,
+ "startCharacter": range.start.character,
+ "endLine": range.end.line,
+ "endCharacter": range.end.character,
+ "kind": kind,
+ }))
+}
+
+pub fn location_link(
+ result: DefinitionResult,
+ line_index: &LineIndex,
+) -> Option<lsp_types::LocationLink> {
+ let origin_selection_range = line_index.line_col_lsp_range(result.origin_selection_range);
+
+ let target_line_index = &result.target.line_index;
+ let target_uri = result.target.uri.clone();
+ let target_range = target_line_index.line_col_lsp_range(result.target_range)?;
+ let target_selection_range =
+ target_line_index.line_col_lsp_range(result.target_selection_range)?;
+
+ Some(lsp_types::LocationLink {
+ origin_selection_range,
+ target_uri,
+ target_range,
+ target_selection_range,
+ })
+}
+
+pub fn document_symbol(
+ symbol: symbols::Symbol,
+ line_index: &LineIndex,
+) -> Option<lsp_types::DocumentSymbol> {
+ let children = symbol
+ .children
+ .into_iter()
+ .filter_map(|child| document_symbol(child, line_index))
+ .collect();
+
+ #[allow(deprecated)]
+ Some(lsp_types::DocumentSymbol {
+ name: symbol.name,
+ detail: symbol.label.map(|label| label.text),
+ kind: symbol_kind(symbol.kind),
+ deprecated: Some(false),
+ range: line_index.line_col_lsp_range(symbol.full_range)?,
+ selection_range: line_index.line_col_lsp_range(symbol.selection_range)?,
+ children: Some(children),
+ tags: None,
+ })
+}
+
+pub fn symbol_information(
+ symbol: symbols::Symbol,
+ document: &Document,
+ results: &mut Vec<lsp_types::SymbolInformation>,
+) -> Option<()> {
+ let range = document.line_index.line_col_lsp_range(symbol.full_range)?;
+
+ #[allow(deprecated)]
+ results.push(lsp_types::SymbolInformation {
+ name: symbol.name,
+ kind: symbol_kind(symbol.kind),
+ deprecated: Some(false),
+ location: lsp_types::Location::new(document.uri.clone(), range),
+ tags: None,
+ container_name: None,
+ });
+
+ for child in symbol.children {
+ symbol_information(child, document, results);
+ }
+
+ Some(())
+}
+
+pub fn symbol_kind(value: symbols::SymbolKind) -> lsp_types::SymbolKind {
+ match value {
+ symbols::SymbolKind::Section => lsp_types::SymbolKind::MODULE,
+ symbols::SymbolKind::Figure => lsp_types::SymbolKind::METHOD,
+ symbols::SymbolKind::Algorithm => lsp_types::SymbolKind::METHOD,
+ symbols::SymbolKind::Table => lsp_types::SymbolKind::METHOD,
+ symbols::SymbolKind::Listing => lsp_types::SymbolKind::METHOD,
+ symbols::SymbolKind::Enumeration => lsp_types::SymbolKind::ENUM,
+ symbols::SymbolKind::EnumerationItem => lsp_types::SymbolKind::ENUM_MEMBER,
+ symbols::SymbolKind::Theorem => lsp_types::SymbolKind::VARIABLE,
+ symbols::SymbolKind::Equation => lsp_types::SymbolKind::CONSTANT,
+ symbols::SymbolKind::Entry(category) => match category {
+ BibtexEntryTypeCategory::Misc => lsp_types::SymbolKind::INTERFACE,
+ BibtexEntryTypeCategory::String => lsp_types::SymbolKind::STRING,
+ BibtexEntryTypeCategory::Article => lsp_types::SymbolKind::EVENT,
+ BibtexEntryTypeCategory::Thesis => lsp_types::SymbolKind::OBJECT,
+ BibtexEntryTypeCategory::Book => lsp_types::SymbolKind::STRUCT,
+ BibtexEntryTypeCategory::Part => lsp_types::SymbolKind::OPERATOR,
+ BibtexEntryTypeCategory::Collection => lsp_types::SymbolKind::TYPE_PARAMETER,
+ },
+ symbols::SymbolKind::Field => lsp_types::SymbolKind::FIELD,
+ }
+}
+
+pub fn document_symbol_response(
+ document: &Document,
+ symbols: Vec<symbols::Symbol>,
+ client_flags: &ClientFlags,
+) -> lsp_types::DocumentSymbolResponse {
+ if client_flags.hierarchical_document_symbols {
+ let results = symbols
+ .into_iter()
+ .filter_map(|symbol| document_symbol(symbol, &document.line_index))
+ .collect();
+
+ lsp_types::DocumentSymbolResponse::Nested(results)
+ } else {
+ let mut results = Vec::new();
+ for symbol in symbols {
+ symbol_information(symbol, document, &mut results);
+ }
+
+ lsp_types::DocumentSymbolResponse::Flat(results)
+ }
+}
+
+pub fn workspace_edit(result: RenameResult, new_name: &str) -> lsp_types::WorkspaceEdit {
+ let mut changes = HashMap::default();
+ for (document, ranges) in result.changes {
+ let mut edits = Vec::new();
+ ranges
+ .into_iter()
+ .filter_map(|range| document.line_index.line_col_lsp_range(range))
+ .for_each(|range| edits.push(lsp_types::TextEdit::new(range, new_name.into())));
+
+ changes.insert(document.uri.clone(), edits);
+ }
+
+ lsp_types::WorkspaceEdit::new(changes)
+}
+
+pub fn location(location: DocumentLocation) -> Option<lsp_types::Location> {
+ let document = location.document;
+ let range = document.line_index.line_col_lsp_range(location.range)?;
+ Some(lsp_types::Location::new(document.uri.clone(), range))
+}
+
+pub fn document_highlight(
+ highlight: Highlight,
+ line_index: &LineIndex,
+) -> Option<lsp_types::DocumentHighlight> {
+ let range = line_index.line_col_lsp_range(highlight.range)?;
+ let kind = Some(match highlight.kind {
+ HighlightKind::Write => lsp_types::DocumentHighlightKind::WRITE,
+ HighlightKind::Read => lsp_types::DocumentHighlightKind::READ,
+ });
+
+ Some(lsp_types::DocumentHighlight { range, kind })
+}
+
+pub fn hover(hover: Hover, line_index: &LineIndex) -> Option<lsp_types::Hover> {
+ let contents = match hover.data {
+ HoverData::Citation(text) => lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::Markdown,
+ value: text,
+ },
+ HoverData::Package(description) => lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::PlainText,
+ value: description.into(),
+ },
+ HoverData::EntryType(type_) => lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::Markdown,
+ value: type_.documentation?.into(),
+ },
+ HoverData::FieldType(type_) => lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::Markdown,
+ value: type_.documentation.into(),
+ },
+ HoverData::Label(label) => lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::PlainText,
+ value: label.reference(),
+ },
+ HoverData::StringRef(text) => lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::PlainText,
+ value: text,
+ },
+ };
+
+ Some(lsp_types::Hover {
+ contents: lsp_types::HoverContents::Markup(contents),
+ range: line_index.line_col_lsp_range(hover.range),
+ })
+}
diff --git a/support/texlab/texlab.1 b/support/texlab/texlab.1
index 97e9fa5a96..ea5ac6ec05 100644
--- a/support/texlab/texlab.1
+++ b/support/texlab/texlab.1
@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
-.TH TEXLAB "1" "January 2024" "texlab 5.12.2" "User Commands"
+.TH TEXLAB "1" "January 2024" "texlab 5.12.3" "User Commands"
.SH NAME
-texlab \- manual page for texlab 5.12.2
+texlab \- manual page for texlab 5.12.3
.SH SYNOPSIS
.B texlab
[\fI\,OPTIONS\/\fR]
diff --git a/support/texlab/texlab.pdf b/support/texlab/texlab.pdf
index 0ba7e1348e..6e2ddb1b35 100644
--- a/support/texlab/texlab.pdf
+++ b/support/texlab/texlab.pdf
Binary files differ