mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 06:41:14 +00:00
319 lines
12 KiB
Rust
319 lines
12 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
use crate::Cli;
|
|
use by_address::ByAddress;
|
|
use i_slint_compiler::{
|
|
expression_tree::Expression,
|
|
langtype::Type,
|
|
lookup::{LookupCtx, LookupObject, LookupResult},
|
|
namedreference::NamedReference,
|
|
object_tree::ElementRc,
|
|
parser::{SyntaxKind, SyntaxNode},
|
|
};
|
|
use std::{
|
|
cell::RefCell,
|
|
collections::{HashMap, HashSet},
|
|
io::Write,
|
|
rc::Rc,
|
|
};
|
|
|
|
#[derive(Clone, Default)]
|
|
pub struct LookupChangeState {
|
|
/// the lookup change pass will map that property reference to another
|
|
property_mappings: HashMap<NamedReference, String>,
|
|
|
|
/// What needs to be added before the closing brace of the component
|
|
extra_component_stuff: Rc<RefCell<Vec<u8>>>,
|
|
|
|
/// Replace `self.` with that id
|
|
replace_self: Option<String>,
|
|
|
|
/// Elements that should get an id
|
|
elements_id: HashMap<ByAddress<ElementRc>, String>,
|
|
}
|
|
|
|
pub(crate) fn fold_node(
|
|
node: &SyntaxNode,
|
|
file: &mut impl Write,
|
|
state: &mut crate::State,
|
|
args: &Cli,
|
|
) -> std::io::Result<bool> {
|
|
let kind = node.kind();
|
|
if kind == SyntaxKind::QualifiedName
|
|
&& node.parent().map_or(false, |n| n.kind() == SyntaxKind::Expression)
|
|
{
|
|
return fully_qualify_property_access(node, file, state);
|
|
} else if kind == SyntaxKind::Element
|
|
&& node.parent().map_or(false, |n| n.kind() == SyntaxKind::Component)
|
|
{
|
|
return move_properties_to_root(node, state, file, args);
|
|
} else if kind == SyntaxKind::Element {
|
|
if let Some(new_id) = state
|
|
.current_elem
|
|
.as_ref()
|
|
.and_then(|e| state.lookup_change.elements_id.get(&ByAddress(e.clone())))
|
|
{
|
|
write!(file, "{new_id} := ")?;
|
|
}
|
|
} else if matches!(
|
|
kind,
|
|
SyntaxKind::Binding | SyntaxKind::TwoWayBinding | SyntaxKind::CallbackConnection
|
|
) && !state.lookup_change.property_mappings.is_empty()
|
|
&& node.parent().map_or(false, |n| n.kind() == SyntaxKind::Element)
|
|
{
|
|
if let Some(el) = &state.current_elem {
|
|
let prop_name = i_slint_compiler::parser::normalize_identifier(
|
|
node.child_token(SyntaxKind::Identifier).unwrap().text(),
|
|
);
|
|
let nr = NamedReference::new(el, &prop_name);
|
|
if let Some(new_name) = state.lookup_change.property_mappings.get(&nr).cloned() {
|
|
state.lookup_change.replace_self = Some(
|
|
state
|
|
.lookup_change
|
|
.elements_id
|
|
.get(&ByAddress(el.clone()))
|
|
.map_or_else(|| el.borrow().id.clone(), |s| s.clone()),
|
|
);
|
|
for n in node.children_with_tokens() {
|
|
let extra = state.lookup_change.extra_component_stuff.clone();
|
|
if n.kind() == SyntaxKind::Identifier {
|
|
write!(&mut *extra.borrow_mut(), "{new_name}")?;
|
|
} else {
|
|
crate::visit_node_or_token(n, &mut *extra.borrow_mut(), state, args)?;
|
|
}
|
|
}
|
|
state.lookup_change.replace_self = None;
|
|
return Ok(true);
|
|
}
|
|
}
|
|
} else if matches!(kind, SyntaxKind::PropertyDeclaration | SyntaxKind::CallbackDeclaration) {
|
|
if let Some(el) = &state.current_elem {
|
|
let prop_name = i_slint_compiler::parser::normalize_identifier(
|
|
&node.child_node(SyntaxKind::DeclaredIdentifier).unwrap().text().to_string(),
|
|
);
|
|
let nr = NamedReference::new(el, &prop_name);
|
|
if state.lookup_change.property_mappings.contains_key(&nr) {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
Ok(false)
|
|
}
|
|
|
|
/// Make sure that a qualified name is fully qualified with `self.`.
|
|
/// Also rename the property in `state.lookup_change.property_mappings`
|
|
fn fully_qualify_property_access(
|
|
node: &SyntaxNode,
|
|
file: &mut impl Write,
|
|
state: &mut crate::State,
|
|
) -> std::io::Result<bool> {
|
|
let mut it = node
|
|
.children_with_tokens()
|
|
.filter(|n| n.kind() == SyntaxKind::Identifier)
|
|
.filter_map(|n| n.into_token());
|
|
let first = match it.next() {
|
|
Some(first) => first,
|
|
None => return Ok(false),
|
|
};
|
|
let first_str = i_slint_compiler::parser::normalize_identifier(first.text());
|
|
with_lookup_ctx(state, |ctx| -> std::io::Result<bool> {
|
|
ctx.current_token = Some(first.clone().into());
|
|
let global_lookup = i_slint_compiler::lookup::global_lookup();
|
|
match global_lookup.lookup(ctx, &first_str) {
|
|
Some(LookupResult::Expression {
|
|
expression: Expression::PropertyReference(nr) | Expression::CallbackReference(nr),
|
|
..
|
|
}) => {
|
|
if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) {
|
|
write!(file, "root.{new_name} ")?;
|
|
Ok(true)
|
|
} else {
|
|
let element = nr.element();
|
|
if state
|
|
.current_component
|
|
.as_ref()
|
|
.map_or(false, |c| Rc::ptr_eq(&element, &c.root_element))
|
|
{
|
|
// We could decide to also prefix all root properties with root
|
|
//write!(file, "root.")?;
|
|
} else if state.current_elem.as_ref().map_or(false, |e| Rc::ptr_eq(&element, e))
|
|
{
|
|
if let Some(replace_self) = &state.lookup_change.replace_self {
|
|
write!(file, "{replace_self}.")?;
|
|
} else {
|
|
write!(file, "self.")?;
|
|
}
|
|
}
|
|
Ok(false)
|
|
}
|
|
}
|
|
Some(LookupResult::Expression {
|
|
expression: Expression::ElementReference(el), ..
|
|
}) => {
|
|
let second = match it.next() {
|
|
Some(second) => second,
|
|
None => return Ok(false),
|
|
};
|
|
let prop_name = i_slint_compiler::parser::normalize_identifier(second.text());
|
|
let nr = NamedReference::new(&el.upgrade().unwrap(), &prop_name);
|
|
if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) {
|
|
write!(file, "root.{new_name} ")?;
|
|
Ok(true)
|
|
} else if let Some(replace_self) = &state.lookup_change.replace_self {
|
|
if first_str == "self" || first_str == "parent" {
|
|
if first_str == "self" {
|
|
write!(file, "{replace_self}.{second} ")?;
|
|
} else {
|
|
let replace_parent = state
|
|
.lookup_change
|
|
.elements_id
|
|
.get(&ByAddress(nr.element()))
|
|
.map_or_else(|| nr.element().borrow().id.clone(), |s| s.clone());
|
|
write!(file, "{replace_parent}.{second} ")?;
|
|
}
|
|
for t in it {
|
|
write!(file, ".{} ", t.text())?;
|
|
}
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
_ => Ok(false),
|
|
}
|
|
})
|
|
.unwrap_or(Ok(false))
|
|
}
|
|
|
|
/// Move the properties from state.lookup_change.property_mappings to the root
|
|
fn move_properties_to_root(
|
|
node: &SyntaxNode,
|
|
state: &mut crate::State,
|
|
file: &mut impl Write,
|
|
args: &Cli,
|
|
) -> std::io::Result<bool> {
|
|
if state.lookup_change.property_mappings.is_empty() {
|
|
return Ok(false);
|
|
}
|
|
let mut seen_brace = false;
|
|
for c in node.children_with_tokens() {
|
|
let k = c.kind();
|
|
if k == SyntaxKind::LBrace {
|
|
seen_brace = true;
|
|
} else if seen_brace && k != SyntaxKind::Whitespace {
|
|
let property_mappings = state.lookup_change.property_mappings.clone();
|
|
for (nr, prop) in property_mappings.iter() {
|
|
let decl =
|
|
nr.element().borrow().property_declarations.get(nr.name()).unwrap().clone();
|
|
let n2: SyntaxNode = decl.node.clone().unwrap().either(|p| p.into(), |c| c.into());
|
|
let old_current_element =
|
|
std::mem::replace(&mut state.current_elem, Some(nr.element()));
|
|
state.lookup_change.replace_self = Some(
|
|
state
|
|
.lookup_change
|
|
.elements_id
|
|
.get(&ByAddress(nr.element()))
|
|
.map_or_else(|| nr.element().borrow().id.clone(), |s| s.clone()),
|
|
);
|
|
for c2 in n2.children_with_tokens() {
|
|
if c2.kind() == SyntaxKind::DeclaredIdentifier {
|
|
write!(file, " {prop} ")?;
|
|
} else {
|
|
crate::visit_node_or_token(c2, file, state, args)?;
|
|
}
|
|
}
|
|
state.lookup_change.replace_self = None;
|
|
write!(file, "\n ")?;
|
|
state.current_elem = old_current_element;
|
|
}
|
|
seen_brace = false;
|
|
} else if k == SyntaxKind::RBrace {
|
|
file.write_all(
|
|
&**std::mem::take(&mut state.lookup_change.extra_component_stuff).borrow(),
|
|
)?;
|
|
}
|
|
crate::visit_node_or_token(c, file, state, args)?;
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
fn with_lookup_ctx<R>(state: &crate::State, f: impl FnOnce(&mut LookupCtx) -> R) -> Option<R> {
|
|
let mut build_diagnostics = Default::default();
|
|
let tr = &state.current_doc.as_ref()?.local_registry;
|
|
let mut lookup_context = LookupCtx::empty_context(tr, &mut build_diagnostics);
|
|
|
|
let ty = state
|
|
.current_elem
|
|
.as_ref()
|
|
.zip(state.property_name.as_ref())
|
|
.map_or(Type::Invalid, |(e, n)| e.borrow().lookup_property(&n).property_type);
|
|
|
|
let scope = state
|
|
.current_component
|
|
.as_ref()
|
|
.map(|c| c.root_element.clone())
|
|
.into_iter()
|
|
.chain(state.current_elem.clone().into_iter())
|
|
.collect::<Vec<_>>();
|
|
|
|
lookup_context.property_name = state.property_name.as_ref().map(String::as_str);
|
|
lookup_context.property_type = ty;
|
|
lookup_context.component_scope = &scope;
|
|
Some(f(&mut lookup_context))
|
|
}
|
|
|
|
pub(crate) fn collect_movable_properties(state: &mut crate::State) {
|
|
pub fn collect_movable_properties_recursive(vec: &mut Vec<NamedReference>, elem: &ElementRc) {
|
|
for c in &elem.borrow().children {
|
|
if c.borrow().repeated.is_some() {
|
|
continue;
|
|
}
|
|
vec.extend(
|
|
c.borrow()
|
|
.property_declarations
|
|
.iter()
|
|
.map(|(name, _)| NamedReference::new(c, &name)),
|
|
);
|
|
collect_movable_properties_recursive(vec, &c);
|
|
}
|
|
}
|
|
if let Some(c) = &state.current_component {
|
|
let mut props_to_move = Vec::new();
|
|
collect_movable_properties_recursive(&mut props_to_move, &c.root_element);
|
|
let mut seen = HashSet::new();
|
|
state.lookup_change.property_mappings = props_to_move
|
|
.into_iter()
|
|
.map(|nr| {
|
|
let element = nr.element();
|
|
ensure_element_has_id(&element, &mut state.lookup_change.elements_id);
|
|
if let Some(parent) = i_slint_compiler::object_tree::find_parent_element(&element) {
|
|
ensure_element_has_id(&parent, &mut state.lookup_change.elements_id);
|
|
}
|
|
let mut name = format!("_{}_{}", element.borrow().id, nr.name());
|
|
while !seen.insert(name.clone())
|
|
|| c.root_element.borrow().lookup_property(&name).is_valid()
|
|
{
|
|
name = format!("_{name}");
|
|
}
|
|
(nr, name)
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
fn ensure_element_has_id(
|
|
element: &ElementRc,
|
|
elements_id: &mut HashMap<ByAddress<ElementRc>, String>,
|
|
) {
|
|
static ID_GEN: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
|
if element.borrow().id.is_empty() {
|
|
elements_id.entry(ByAddress(element.clone())).or_insert_with(|| {
|
|
format!("_{}", ID_GEN.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
|
|
});
|
|
}
|
|
}
|