Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

LSP: hover (tooltip) support #5969

Merged
merged 3 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions tools/lsp/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
pub mod completion;
mod formatting;
mod goto;
mod hover;
mod semantic_tokens;
#[cfg(test)]
pub mod test;
pub mod token_info;

use crate::common::{self, DocumentCache};
use crate::util;
Expand All @@ -28,10 +30,10 @@ use lsp_types::request::{
use lsp_types::{
ClientCapabilities, CodeActionOrCommand, CodeActionProviderCapability, CodeLens,
CodeLensOptions, Color, ColorInformation, ColorPresentation, Command, CompletionOptions,
DocumentSymbol, DocumentSymbolResponse, Hover, InitializeParams, InitializeResult, OneOf,
Position, PrepareRenameResponse, PublishDiagnosticsParams, RenameOptions,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities,
ServerInfo, TextDocumentSyncCapability, TextEdit, Url, WorkDoneProgressOptions,
DocumentSymbol, DocumentSymbolResponse, InitializeParams, InitializeResult, OneOf, Position,
PrepareRenameResponse, PublishDiagnosticsParams, RenameOptions, SemanticTokensFullOptions,
SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities, ServerInfo,
TextDocumentSyncCapability, TextEdit, Url, WorkDoneProgressOptions,
};
use std::cell::RefCell;
use std::collections::HashMap;
Expand Down Expand Up @@ -170,6 +172,7 @@ impl RequestHandler {
pub fn server_initialize_result(client_cap: &ClientCapabilities) -> InitializeResult {
InitializeResult {
capabilities: ServerCapabilities {
hover_provider: Some(true.into()),
completion_provider: Some(CompletionOptions {
resolve_provider: None,
trigger_characters: Some(vec![".".to_owned()]),
Expand Down Expand Up @@ -262,18 +265,16 @@ pub fn register_request_handlers(rh: &mut RequestHandler) {
});
Ok(result)
});
rh.register::<HoverRequest, _>(|_params, _ctx| async move {
/*let result =
token_descr(document_cache, params.text_document_position_params).map(|x| Hover {
contents: lsp_types::HoverContents::Scalar(MarkedString::from_language_code(
"text".into(),
format!("{:?}", x.token),
)),
range: None,
});
let resp = Response::new_ok(id, result);
connection.sender.send(Message::Response(resp))?;*/
Ok(None::<Hover>)
rh.register::<HoverRequest, _>(|params, ctx| async move {
let document_cache = &mut ctx.document_cache.borrow_mut();
let result = token_descr(
document_cache,
&params.text_document_position_params.text_document.uri,
&params.text_document_position_params.position,
)
.and_then(|(token, _)| hover::get_tooltip(document_cache, token));

Ok(result)
});
rh.register::<CodeActionRequest, _>(|params, ctx| async move {
let document_cache = &mut ctx.document_cache.borrow_mut();
Expand Down
238 changes: 45 additions & 193 deletions tools/lsp/language/goto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,209 +2,61 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

use super::DocumentCache;
use crate::util::{lookup_current_element_type, node_to_url_and_lsp_range, with_lookup_ctx};

use i_slint_compiler::diagnostics::Spanned;
use i_slint_compiler::expression_tree::Expression;
use crate::language::token_info::{token_info, TokenInfo};
use i_slint_compiler::langtype::{ElementType, Type};
use i_slint_compiler::lookup::{LookupObject, LookupResult};
use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxNode, SyntaxToken};
use i_slint_compiler::pathutils::clean_path;

use i_slint_compiler::parser::{SyntaxNode, SyntaxToken};
use lsp_types::{GotoDefinitionResponse, LocationLink, Range};
use std::path::Path;

pub fn goto_definition(
document_cache: &mut DocumentCache,
token: SyntaxToken,
) -> Option<GotoDefinitionResponse> {
let mut node = token.parent();
loop {
if let Some(n) = syntax_nodes::QualifiedName::new(node.clone()) {
let parent = n.parent()?;
return match parent.kind() {
SyntaxKind::Type => {
let qual = i_slint_compiler::object_tree::QualifiedTypeName::from_node(n);
let doc = document_cache.get_document_for_source_file(&node.source_file)?;
goto_type(&doc.local_registry.lookup_qualified(&qual.members))
}
SyntaxKind::Element => {
let qual = i_slint_compiler::object_tree::QualifiedTypeName::from_node(n);
let doc = document_cache.get_document_for_source_file(&node.source_file)?;
match doc.local_registry.lookup_element(&qual.to_string()) {
Ok(ElementType::Component(c)) => {
goto_node(&c.root_element.borrow().debug.first()?.node)
}
_ => None,
}
}
SyntaxKind::Expression => {
if token.kind() != SyntaxKind::Identifier {
return None;
}
let lr = with_lookup_ctx(document_cache, node, |ctx| {
let mut it = n
.children_with_tokens()
.filter_map(|t| t.into_token())
.filter(|t| t.kind() == SyntaxKind::Identifier);
let mut cur_tok = it.next()?;
let first_str =
i_slint_compiler::parser::normalize_identifier(cur_tok.text());
let global = i_slint_compiler::lookup::global_lookup();
let mut expr_it = global.lookup(ctx, &first_str)?;
while cur_tok.token != token.token {
cur_tok = it.next()?;
let str =
i_slint_compiler::parser::normalize_identifier(cur_tok.text());
expr_it = expr_it.lookup(ctx, &str)?;
}
Some(expr_it)
})?;
let gn = match lr? {
LookupResult::Expression {
expression: Expression::ElementReference(e),
..
} => e.upgrade()?.borrow().debug.first()?.node.clone().into(),
LookupResult::Expression {
expression:
Expression::CallbackReference(nr, _)
| Expression::PropertyReference(nr)
| Expression::FunctionReference(nr, _),
..
} => {
let mut el = nr.element();
loop {
if let Some(x) = el.borrow().property_declarations.get(nr.name()) {
break x.node.clone()?;
}
let base = el.borrow().base_type.clone();
if let ElementType::Component(c) = base {
el = c.root_element.clone();
} else {
return None;
}
}
}
LookupResult::Expression {
expression: Expression::EnumerationValue(v),
..
} => {
// FIXME: this goes to the enum definition instead of the value definition.
v.enumeration.node.clone()?.into()
}
LookupResult::Enumeration(e) => e.node.clone()?.into(),
_ => return None,
};
goto_node(&gn)
}
_ => None,
};
} else if let Some(n) = syntax_nodes::ImportIdentifier::new(node.clone()) {
let doc = document_cache.get_document_for_source_file(&node.source_file)?;
let imp_name = i_slint_compiler::typeloader::ImportedName::from_node(n);
return match doc.local_registry.lookup_element(&imp_name.internal_name) {
Ok(ElementType::Component(c)) => {
goto_node(&c.root_element.borrow().debug.first()?.node)
let token_info = token_info(document_cache, token)?;
match token_info {
TokenInfo::Type(ty) => goto_type(&ty),
TokenInfo::ElementType(el) => {
if let ElementType::Component(c) = el {
goto_node(&c.root_element.borrow().debug.first()?.node)
} else {
None
}
}
TokenInfo::ElementRc(el) => goto_node(&el.borrow().debug.first()?.node),
TokenInfo::NamedReference(nr) => {
let mut el = nr.element();
loop {
if let Some(x) = el.borrow().property_declarations.get(nr.name()) {
return goto_node(x.node.as_ref()?);
}
_ => None,
};
} else if let Some(n) = syntax_nodes::ExportSpecifier::new(node.clone()) {
let doc = document_cache.get_document_for_source_file(&node.source_file)?;
let (_, exp) = i_slint_compiler::object_tree::ExportedName::from_export_specifier(&n);
return match doc.exports.find(exp.as_str())? {
itertools::Either::Left(c) => {
goto_node(&c.root_element.borrow().debug.first()?.node)
let base = el.borrow().base_type.clone();
if let ElementType::Component(c) = base {
el = c.root_element.clone();
} else {
return None;
}
itertools::Either::Right(ty) => goto_type(&ty),
};
} else if matches!(node.kind(), SyntaxKind::ImportSpecifier | SyntaxKind::ExportModule) {
let import_file = node
.source_file
.path()
.parent()
.unwrap_or_else(|| Path::new("/"))
.join(node.child_text(SyntaxKind::StringLiteral)?.trim_matches('\"'));
let import_file = clean_path(&import_file);
let doc = document_cache.get_document_by_path(&import_file)?;
let doc_node = doc.node.clone()?;
return goto_node(&doc_node);
} else if syntax_nodes::BindingExpression::new(node.clone()).is_some() {
// don't fallback to the Binding
return None;
} else if let Some(n) = syntax_nodes::Binding::new(node.clone()) {
if token.kind() != SyntaxKind::Identifier {
return None;
}
let prop_name = i_slint_compiler::parser::normalize_identifier(token.text());
let element = syntax_nodes::Element::new(n.parent()?)?;
if let Some(p) = element.PropertyDeclaration().find_map(|p| {
(i_slint_compiler::parser::identifier_text(&p.DeclaredIdentifier())? == prop_name)
.then_some(p)
}) {
return goto_node(&p);
}
let n = find_property_declaration_in_base(document_cache, element, &prop_name)?;
return goto_node(&n);
} else if let Some(n) = syntax_nodes::TwoWayBinding::new(node.clone()) {
if token.kind() != SyntaxKind::Identifier {
return None;
}
let prop_name = i_slint_compiler::parser::normalize_identifier(token.text());
if prop_name != i_slint_compiler::parser::identifier_text(&n)? {
return None;
}
let element = syntax_nodes::Element::new(n.parent()?)?;
if let Some(p) = element.PropertyDeclaration().find_map(|p| {
(i_slint_compiler::parser::identifier_text(&p.DeclaredIdentifier())? == prop_name)
.then_some(p)
}) {
return goto_node(&p);
}
let n = find_property_declaration_in_base(document_cache, element, &prop_name)?;
return goto_node(&n);
} else if let Some(n) = syntax_nodes::CallbackConnection::new(node.clone()) {
if token.kind() != SyntaxKind::Identifier {
return None;
}
let prop_name = i_slint_compiler::parser::normalize_identifier(token.text());
if prop_name != i_slint_compiler::parser::identifier_text(&n)? {
return None;
}
let element = syntax_nodes::Element::new(n.parent()?)?;
if let Some(p) = element.CallbackDeclaration().find_map(|p| {
(i_slint_compiler::parser::identifier_text(&p.DeclaredIdentifier())? == prop_name)
.then_some(p)
}) {
return goto_node(&p);
}
let n = find_property_declaration_in_base(document_cache, element, &prop_name)?;
return goto_node(&n);
}
node = node.parent()?;
}
}

/// Try to lookup the property `prop_name` in the base of the given Element
fn find_property_declaration_in_base(
document_cache: &DocumentCache,
element: syntax_nodes::Element,
prop_name: &str,
) -> Option<SyntaxNode> {
let global_tr = document_cache.global_type_registry();
let tr = element
.source_file()
.and_then(|sf| document_cache.get_document_for_source_file(sf))
.map(|doc| &doc.local_registry)
.unwrap_or(&global_tr);

let mut element_type = lookup_current_element_type((*element).clone(), tr)?;
while let ElementType::Component(com) = element_type {
if let Some(p) = com.root_element.borrow().property_declarations.get(prop_name) {
return p.node.clone();
TokenInfo::EnumerationValue(v) => {
// FIXME: this goes to the enum definition instead of the value definition.
goto_node(&*v.enumeration.node.as_ref()?)
}
TokenInfo::FileName(f) => {
let doc = document_cache.get_document_by_path(&f)?;
let doc_node = doc.node.clone()?;
goto_node(&doc_node)
}
TokenInfo::LocalProperty(x) => goto_node(&x),
TokenInfo::LocalCallback(x) => goto_node(&x),
TokenInfo::IncompleteNamedReference(mut element_type, prop_name) => {
while let ElementType::Component(com) = element_type {
if let Some(p) = com.root_element.borrow().property_declarations.get(&prop_name) {
return goto_node(p.node.as_ref()?);
}
element_type = com.root_element.borrow().base_type.clone();
}
None
}
element_type = com.root_element.borrow().base_type.clone();
}
None
}

fn goto_type(ty: &Type) -> Option<GotoDefinitionResponse> {
Expand All @@ -216,7 +68,7 @@ fn goto_type(ty: &Type) -> Option<GotoDefinitionResponse> {
}

fn goto_node(node: &SyntaxNode) -> Option<GotoDefinitionResponse> {
let (target_uri, range) = node_to_url_and_lsp_range(node)?;
let (target_uri, range) = crate::util::node_to_url_and_lsp_range(node)?;
let range = Range::new(range.start, range.start); // Shrink range to a position:-)
Some(GotoDefinitionResponse::Link(vec![LocationLink {
origin_selection_range: None,
Expand Down Expand Up @@ -366,7 +218,7 @@ fn test_goto_definition_multi_files() {
let offset = source2.find("Hello } from ").unwrap() as u32;
// check the string literal
let token = crate::language::token_at_offset(&doc2, offset + 20).unwrap();
assert_eq!(token.kind(), SyntaxKind::StringLiteral);
assert_eq!(token.kind(), i_slint_compiler::parser::SyntaxKind::StringLiteral);
let def = goto_definition(&mut dc, token).unwrap();
let link = first_link(&def);
assert_eq!(link.target_uri, url1);
Expand All @@ -382,7 +234,7 @@ fn test_goto_definition_multi_files() {
let offset = source2.find("Another as A } from ").unwrap() as u32;
// check the string literal
let token = crate::language::token_at_offset(&doc2, offset + 25).unwrap();
assert_eq!(token.kind(), SyntaxKind::StringLiteral);
assert_eq!(token.kind(), i_slint_compiler::parser::SyntaxKind::StringLiteral);
let def = goto_definition(&mut dc, token).unwrap();
let link = first_link(&def);
assert_eq!(link.target_uri, url1);
Expand Down
Loading
Loading