mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-02 21:03:00 +00:00
261 lines
9.5 KiB
Rust
261 lines
9.5 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// 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, Type};
|
|
use i_slint_compiler::object_tree::ElementRc;
|
|
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<Hover> {
|
|
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) => from_property_in_element(&nr.element(), nr.name())?,
|
|
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) => from_property_in_type(&el, &name)?,
|
|
};
|
|
|
|
Some(Hover { contents: HoverContents::Markup(contents), range: None })
|
|
}
|
|
|
|
fn from_property_in_element(element: &ElementRc, name: &str) -> Option<MarkupContent> {
|
|
if let Some(decl) = element.borrow().property_declarations.get(name) {
|
|
return property_tooltip(&decl.property_type, name, decl.pure.unwrap_or(false));
|
|
}
|
|
from_property_in_type(&element.borrow().base_type, name)
|
|
}
|
|
|
|
fn from_property_in_type(base: &ElementType, name: &str) -> Option<MarkupContent> {
|
|
match base {
|
|
ElementType::Component(c) => from_property_in_element(&c.root_element, name),
|
|
ElementType::Builtin(b) => {
|
|
let resolved_name = b.native_class.lookup_alias(name).unwrap_or(name);
|
|
let info = b.properties.get(resolved_name)?;
|
|
property_tooltip(&info.ty, name, false)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn property_tooltip(ty: &Type, name: &str, pure: bool) -> Option<MarkupContent> {
|
|
let pure = if pure { "pure " } else { "" };
|
|
if let Type::Callback(callback) = ty {
|
|
let sig = signature_from_function_ty(&callback);
|
|
Some(from_slint_code(&format!("{pure}callback {name}{sig}")))
|
|
} else if let Type::Function(function) = &ty {
|
|
let sig = signature_from_function_ty(&function);
|
|
Some(from_slint_code(&format!("{pure}function {name}{sig}")))
|
|
} else if ty.is_property_type() {
|
|
Some(from_slint_code(&format!("property <{ty}> {name}")))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn signature_from_function_ty(f: &i_slint_compiler::langtype::Function) -> String {
|
|
let ret = if matches!(f.return_type, Type::Void) {
|
|
String::new()
|
|
} else {
|
|
format!(" -> {}", f.return_type)
|
|
};
|
|
let args = f
|
|
.args
|
|
.iter()
|
|
.zip(f.arg_names.iter().chain(std::iter::repeat(&Default::default())))
|
|
.filter(|(x, _)| *x != &Type::ElementReference)
|
|
.map(|(ty, name)| if !name.is_empty() { format!("{name}: {ty}") } else { ty.to_string() })
|
|
.join(", ");
|
|
format!("({args}){ret}")
|
|
}
|
|
|
|
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```"),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use i_slint_compiler::parser::TextSize;
|
|
|
|
#[test]
|
|
fn test_tooltip() {
|
|
let source = r#"
|
|
import { StandardTableView } from "std-widgets.slint";
|
|
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 <string> hello;
|
|
callback xyz(string, int);
|
|
pure callback www;
|
|
}
|
|
enum Eee { E1, E2, E3 }
|
|
export component Test {
|
|
property <string> root-prop;
|
|
function fn_loc() -> int { 42 }
|
|
the-ta := TA {
|
|
property<int> 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 <Eee> e: Eee.E2;
|
|
pointer-event(aaa) => {}
|
|
}
|
|
Rectangle {
|
|
background: red;
|
|
border-color: self.background;
|
|
}
|
|
StandardTableView {
|
|
row-pointer-event => { }
|
|
}
|
|
}"#;
|
|
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: TextSize| {
|
|
crate::language::token_at_offset(
|
|
&doc,
|
|
TextSize::new(
|
|
source.find(needle).unwrap_or_else(|| panic!("'{needle}' not found")) as u32,
|
|
) + offset,
|
|
)
|
|
.unwrap()
|
|
};
|
|
|
|
#[track_caller]
|
|
fn assert_tooltip(h: Option<Hover>, 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.into())),
|
|
"```slint\nproperty <string> hello\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("Glob.hello_world", 8.into())),
|
|
"```slint\nproperty <{ a: int,b: float,}> hello-world\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("self.enabled", 5.into())),
|
|
"```slint\nproperty <bool> enabled\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 10.into())),
|
|
"```slint\nproperty <int> local-prop\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("root-prop.to-float", 1.into())),
|
|
"```slint\nproperty <string> root-prop\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("background: red", 0.into())),
|
|
"```slint\nproperty <brush> background\n```",
|
|
);
|
|
// callbacks
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("self.www", 5.into())),
|
|
"```slint\npure callback www()\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("xyz(abc", 0.into())),
|
|
"```slint\ncallback xyz(string, int)\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("Glob.cb(", 6.into())),
|
|
"```slint\ncallback cb(string, int) -> [int]\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("row-pointer-event", 0.into())),
|
|
// Fixme: this uses LogicalPoint instead of Point because of implementation details
|
|
"```slint\ncallback row-pointer-event(row-index: int, event: PointerEvent, mouse-position: LogicalPosition)\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("pointer-event", 5.into())),
|
|
"```slint\ncallback pointer-event(event: PointerEvent)\n```",
|
|
);
|
|
// functions
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 1.into())),
|
|
"```slint\npure function fn-glob(abc: int)\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("root.fn_loc", 8.into())),
|
|
"```slint\nfunction fn-loc() -> int\n```",
|
|
);
|
|
// elements
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("self.enabled", 0.into())),
|
|
"```slint\nthe-ta := TA { /*...*/ }\n```",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("self.background", 0.into())),
|
|
"```slint\nRectangle { /*...*/ }\n```",
|
|
);
|
|
// global
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("hello: Glob", 8.into())),
|
|
"```slint\nglobal Glob\n```",
|
|
);
|
|
|
|
//components
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("Rectangle {", 8.into())),
|
|
"Rectangle (builtin)",
|
|
);
|
|
assert_tooltip(
|
|
get_tooltip(&mut dc, find_tk("the-ta := TA {", 11.into())),
|
|
"```slint\ncomponent TA\n```",
|
|
);
|
|
|
|
// enums
|
|
assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 0.into())), "enum Eee");
|
|
assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 5.into())), "```slint\nEee.E2\n```");
|
|
}
|
|
}
|