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 { 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 = 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> { 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 { 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 { 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 { 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 { 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 { 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, ) -> 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, 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 { 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 { 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 { 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), }) }