diff --git a/tools/lsp/language.rs b/tools/lsp/language.rs index e664230b3f6..d7f43a70475 100644 --- a/tools/lsp/language.rs +++ b/tools/lsp/language.rs @@ -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; @@ -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; @@ -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()]), @@ -262,18 +265,16 @@ pub fn register_request_handlers(rh: &mut RequestHandler) { }); Ok(result) }); - rh.register::(|_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::) + rh.register::(|params, ctx| async move { + let document_cache = &mut ctx.document_cache.borrow_mut(); + let result = token_descr( + document_cache, + ¶ms.text_document_position_params.text_document.uri, + ¶ms.text_document_position_params.position, + ) + .and_then(|(token, _)| hover::get_tooltip(document_cache, token)); + + Ok(result) }); rh.register::(|params, ctx| async move { let document_cache = &mut ctx.document_cache.borrow_mut(); diff --git a/tools/lsp/language/goto.rs b/tools/lsp/language/goto.rs index f1a391a648f..7a3634708d6 100644 --- a/tools/lsp/language/goto.rs +++ b/tools/lsp/language/goto.rs @@ -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 { - 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 { - 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 { @@ -216,7 +68,7 @@ fn goto_type(ty: &Type) -> Option { } fn goto_node(node: &SyntaxNode) -> Option { - 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, @@ -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); @@ -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); diff --git a/tools/lsp/language/hover.rs b/tools/lsp/language/hover.rs new file mode 100644 index 00000000000..a4a19cc8e82 --- /dev/null +++ b/tools/lsp/language/hover.rs @@ -0,0 +1,211 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +use super::token_info::TokenInfo; +use crate::common::DocumentCache; +use i_slint_compiler::langtype::{ElementType, PropertyLookupResult, Type}; +use i_slint_compiler::parser::SyntaxToken; +use itertools::Itertools as _; +use lsp_types::{Hover, HoverContents, MarkupContent}; + +pub fn get_tooltip(document_cache: &mut DocumentCache, token: SyntaxToken) -> Option { + let token_info = crate::language::token_info::token_info(document_cache, token)?; + let contents = match token_info { + TokenInfo::Type(ty) => from_plain_text(ty.to_string()), + TokenInfo::ElementType(e) => match e { + ElementType::Component(c) => { + if c.is_global() { + from_slint_code(&format!("global {}", c.id)) + } else { + from_slint_code(&format!("component {}", c.id)) + } + } + ElementType::Builtin(b) => from_plain_text(format!("{} (builtin)", b.name)), + _ => return None, + }, + TokenInfo::ElementRc(e) => { + let e = e.borrow(); + let component = &e.enclosing_component.upgrade().unwrap(); + if component.is_global() { + from_slint_code(&format!("global {}", component.id)) + } else if e.id.is_empty() { + from_slint_code(&format!("{} {{ /*...*/ }}", e.base_type)) + } else { + from_slint_code(&format!("{} := {} {{ /*...*/ }}", e.id, e.base_type)) + } + } + TokenInfo::NamedReference(nr) => { + let prop_info = nr.element().borrow().lookup_property(nr.name()); + from_prop_result(prop_info)? + } + TokenInfo::EnumerationValue(v) => from_slint_code(&format!("{}.{}", v.enumeration.name, v)), + TokenInfo::FileName(_) => return None, + // Todo: this can happen when there is some syntax error + TokenInfo::LocalProperty(_) | TokenInfo::LocalCallback(_) => return None, + TokenInfo::IncompleteNamedReference(el, name) => { + let prop_info = el.lookup_property(&name); + from_prop_result(prop_info)? + } + }; + + Some(Hover { contents: HoverContents::Markup(contents), range: None }) +} + +fn from_prop_result(prop_info: PropertyLookupResult) -> Option { + let pure = if prop_info.declared_pure.is_some_and(|x| x) { "pure " } else { "" }; + if let Type::Callback { return_type, args } = &prop_info.property_type { + let ret = return_type.as_ref().map(|x| format!(" -> {}", x)).unwrap_or_default(); + let args = args.iter().map(|x| x.to_string()).join(", "); + Some(from_slint_code(&format!("{pure}callback {}({args}){ret}", prop_info.resolved_name))) + } else if let Type::Function { return_type, args } = &prop_info.property_type { + let ret = if matches!(**return_type, Type::Void) { + String::new() + } else { + format!(" -> {return_type}") + }; + let args = args.iter().map(|x| x.to_string()).join(", "); + Some(from_slint_code(&format!("{pure}function {}({args}){ret}", prop_info.resolved_name))) + } else if prop_info.property_type.is_property_type() { + Some(from_slint_code(&format!( + "property <{}> {}", + prop_info.property_type, prop_info.resolved_name + ))) + } else { + None + } +} + +fn from_plain_text(value: String) -> MarkupContent { + MarkupContent { kind: lsp_types::MarkupKind::PlainText, value } +} + +fn from_slint_code(value: &str) -> MarkupContent { + MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: format!("```slint\n{value}\n```"), + } +} + +#[test] +fn test_tooltip() { + let source = r#" +global Glob { + in-out property <{a:int,b:float}> hello_world; + callback cb(string, int) -> [int]; + public pure function fn_glob(abc: int) {} +} +component TA inherits TouchArea { + in property hello; + callback xyz(string, int); + pure callback www; +} +enum Eee { E1, E2, E3 } +export component Test { + property root-prop; + function fn_loc() -> int { 42 } + the-ta := TA { + property local-prop: root-prop.to-float(); + hello: Glob.hello_world.a; + xyz(abc, def) => { + self.www(); + self.enabled = false; + Glob.fn_glob(local-prop); + Glob.cb("xxx", 45); + root.fn_loc(); + } + property e: Eee.E2; + } + Rectangle { + background: red; + border-color: self.background; + } +}"#; + let (mut dc, uri, _) = crate::language::test::loaded_document_cache(source.into()); + let doc = dc.get_document(&uri).unwrap().node.clone().unwrap(); + + let find_tk = |needle: &str, offset: i32| { + crate::language::token_at_offset( + &doc, + (source.find(needle).unwrap_or_else(|| panic!("'{needle}' not found")) as u32) + .saturating_add_signed(offset), + ) + .unwrap() + }; + + #[track_caller] + fn assert_tooltip(h: Option, str: &str) { + match h.unwrap().contents { + HoverContents::Markup(m) => assert_eq!(m.value, str), + x => panic!("Found {x:?} ({str})"), + } + } + + // properties + assert_tooltip( + get_tooltip(&mut dc, find_tk("hello: Glob", 0)), + "```slint\nproperty hello\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("Glob.hello_world", 8)), + "```slint\nproperty <{ a: int,b: float,}> hello-world\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.enabled", 5)), + "```slint\nproperty enabled\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 10)), + "```slint\nproperty local-prop\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("root-prop.to-float", 1)), + "```slint\nproperty root-prop\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("background: red", 0)), + "```slint\nproperty background\n```", + ); + // callbacks + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.www", 5)), + "```slint\npure callback www()\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("xyz(abc", 0)), + "```slint\ncallback xyz(string, int)\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("Glob.cb(", 6)), + "```slint\ncallback cb(string, int) -> [int]\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 1)), + "```slint\npure function fn-glob(int)\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("root.fn_loc", 8)), + "```slint\nfunction fn-loc() -> int\n```", + ); + // elements + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.enabled", 0)), + "```slint\nthe-ta := TA { /*...*/ }\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.background", 0)), + "```slint\nRectangle { /*...*/ }\n```", + ); + // global + assert_tooltip(get_tooltip(&mut dc, find_tk("hello: Glob", 8)), "```slint\nglobal Glob\n```"); + + //components + assert_tooltip(get_tooltip(&mut dc, find_tk("Rectangle {", 8)), "Rectangle (builtin)"); + assert_tooltip( + get_tooltip(&mut dc, find_tk("the-ta := TA {", 11)), + "```slint\ncomponent TA\n```", + ); + + // enums + assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 0)), "enum Eee"); + assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 5)), "```slint\nEee.E2\n```"); +} diff --git a/tools/lsp/language/token_info.rs b/tools/lsp/language/token_info.rs new file mode 100644 index 00000000000..2aefb66d4bd --- /dev/null +++ b/tools/lsp/language/token_info.rs @@ -0,0 +1,181 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +use crate::common::DocumentCache; +use i_slint_compiler::diagnostics::Spanned; +use i_slint_compiler::expression_tree::Expression; +use i_slint_compiler::langtype::{ElementType, EnumerationValue, Type}; +use i_slint_compiler::lookup::{LookupObject, LookupResult}; +use i_slint_compiler::namedreference::NamedReference; +use i_slint_compiler::object_tree::ElementRc; +use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxToken}; +use i_slint_compiler::pathutils::clean_path; +use std::path::Path; + +pub enum TokenInfo { + Type(Type), + ElementType(ElementType), + ElementRc(ElementRc), + NamedReference(NamedReference), + EnumerationValue(EnumerationValue), + FileName(std::path::PathBuf), + LocalProperty(syntax_nodes::PropertyDeclaration), + LocalCallback(syntax_nodes::CallbackDeclaration), + IncompleteNamedReference(ElementType, String), +} + +pub fn token_info(document_cache: &mut DocumentCache, token: SyntaxToken) -> Option { + 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)?; + Some(TokenInfo::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)?; + Some(TokenInfo::ElementType( + doc.local_registry.lookup_element(&qual.to_string()).ok()?, + )) + } + SyntaxKind::Expression => { + if token.kind() != SyntaxKind::Identifier { + return None; + } + let lr = crate::util::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) + })?; + match lr? { + LookupResult::Expression { + expression: Expression::ElementReference(e), + .. + } => Some(TokenInfo::ElementRc(e.upgrade()?)), + LookupResult::Expression { + expression: + Expression::CallbackReference(nr, _) + | Expression::PropertyReference(nr) + | Expression::FunctionReference(nr, _), + .. + } => Some(TokenInfo::NamedReference(nr)), + LookupResult::Expression { + expression: Expression::EnumerationValue(v), + .. + } => Some(TokenInfo::EnumerationValue(v)), + LookupResult::Enumeration(e) => Some(TokenInfo::Type(Type::Enumeration(e))), + _ => return None, + } + } + _ => 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 Some(TokenInfo::ElementType( + doc.local_registry.lookup_element(&imp_name.internal_name).ok()?, + )); + } 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) => { + Some(TokenInfo::ElementType(ElementType::Component(c))) + } + itertools::Either::Right(ty) => Some(TokenInfo::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); + return Some(TokenInfo::FileName(import_file)); + } 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 Some(TokenInfo::LocalProperty(p)); + } + return find_property_declaration_in_base(document_cache, element, &prop_name); + } 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 Some(TokenInfo::LocalProperty(p)); + } + return find_property_declaration_in_base(document_cache, element, &prop_name); + } 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 Some(TokenInfo::LocalCallback(p)); + } + return find_property_declaration_in_base(document_cache, element, &prop_name); + } + 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 { + 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 element_type = crate::util::lookup_current_element_type((*element).clone(), tr)?; + Some(TokenInfo::IncompleteNamedReference(element_type, prop_name.to_string())) +}