mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-22 00:02:40 +00:00
live-preview: Handle known components using our local DocumentCache
This commit is contained in:
parent
6c034372a1
commit
d657ee65af
9 changed files with 124 additions and 117 deletions
|
@ -68,7 +68,7 @@ preview-lense = []
|
|||
## to provide an implementation of the external preview API when building for WASM)
|
||||
preview-api = ["preview-external"]
|
||||
## Build in the actual code to act as a preview for slint files.
|
||||
preview-engine = ["dep:slint", "dep:slint-interpreter", "dep:i-slint-core", "dep:i-slint-backend-selector", "dep:image", "dep:slint-build", "dep:spin_on", "dep:i-slint-common"]
|
||||
preview-engine = ["dep:slint", "dep:slint-interpreter", "dep:i-slint-core", "dep:i-slint-backend-selector", "dep:image", "dep:slint-build", "dep:i-slint-common"]
|
||||
## Build in the actual code to act as a preview for slint files. Does nothing in WASM!
|
||||
preview-builtin = ["preview-engine"]
|
||||
## Support the external preview optionally used by e.g. the VSCode plugin
|
||||
|
@ -93,7 +93,6 @@ i-slint-common = { workspace = true, optional = true }
|
|||
i-slint-core = { workspace = true, features = ["std"], optional = true }
|
||||
slint = { workspace = true, features = ["compat-1-2"], optional = true }
|
||||
slint-interpreter = { workspace = true, features = ["compat-1-2", "highlight", "internal"], optional = true }
|
||||
spin_on = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
clap = { workspace = true }
|
||||
|
@ -114,6 +113,7 @@ wasm-bindgen-futures = "0.4.30"
|
|||
|
||||
[dev-dependencies]
|
||||
i-slint-backend-testing = { path = "../../internal/backends/testing" }
|
||||
spin_on = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = { workspace = true, features = ["default"], optional = true }
|
||||
|
|
|
@ -30,8 +30,7 @@ use crate::wasm_prelude::*;
|
|||
|
||||
pub fn uri_to_file(uri: &Url) -> Option<PathBuf> {
|
||||
if uri.scheme() == "builtin" {
|
||||
let path = String::from("builtin:") + uri.path();
|
||||
Some(PathBuf::from(path))
|
||||
Some(PathBuf::from(uri.to_string()))
|
||||
} else {
|
||||
let path = uri.to_file_path().ok()?;
|
||||
let cleaned_path = i_slint_compiler::pathutils::clean_path(&path);
|
||||
|
@ -41,15 +40,7 @@ pub fn uri_to_file(uri: &Url) -> Option<PathBuf> {
|
|||
|
||||
pub fn file_to_uri(path: &Path) -> Option<Url> {
|
||||
if path.starts_with("builtin:/") {
|
||||
let p_str = path.to_string_lossy();
|
||||
let p_str = if &p_str[9..11] == "///" {
|
||||
p_str.to_string()
|
||||
} else {
|
||||
let mut r = p_str.to_string();
|
||||
r.insert_str(8, "//");
|
||||
r
|
||||
};
|
||||
Url::parse(&p_str).ok()
|
||||
Url::parse(path.to_str()?).ok()
|
||||
} else {
|
||||
Url::from_file_path(path).ok()
|
||||
}
|
||||
|
@ -155,8 +146,7 @@ impl DocumentCache {
|
|||
content: String,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) -> Result<()> {
|
||||
let path =
|
||||
uri_to_file(url).ok_or::<Error>(String::from("Failed to convert path").into())?;
|
||||
let path = uri_to_file(url).ok_or("Failed to convert path")?;
|
||||
self.0.load_file(&path, version, &path, content, false, diag).await;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -531,7 +521,6 @@ pub enum LspToPreviewMessage {
|
|||
SetConfiguration { config: PreviewConfig },
|
||||
ShowPreview(PreviewComponent),
|
||||
HighlightFromEditor { url: Option<Url>, offset: u32 },
|
||||
KnownComponents { url: Option<VersionedUrl>, components: Vec<ComponentInformation> },
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -704,4 +693,20 @@ mod tests {
|
|||
|
||||
assert!(Url::from_file_path(&builtin_path).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uri_conversion_of_slashed_builtins() {
|
||||
let builtin_path1 = PathBuf::from("builtin:/fluent/button.slint");
|
||||
let builtin_path3 = PathBuf::from("builtin:///fluent/button.slint");
|
||||
|
||||
let url1 = file_to_uri(&builtin_path1).unwrap();
|
||||
let url3 = file_to_uri(&builtin_path3).unwrap();
|
||||
assert_ne!(url1, url3);
|
||||
|
||||
let back_conversion1 = uri_to_file(&url1).unwrap();
|
||||
let back_conversion3 = uri_to_file(&url3).unwrap();
|
||||
assert_eq!(back_conversion1, back_conversion3);
|
||||
|
||||
assert_eq!(back_conversion1, builtin_path1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
// cSpell: ignore descr rfind unindented
|
||||
|
||||
use crate::common::{ComponentInformation, DocumentCache, Position, PropertyChange};
|
||||
#[cfg(feature = "preview-engine")]
|
||||
use i_slint_compiler::langtype::{DefaultSizeBinding, ElementType};
|
||||
#[cfg(feature = "preview-engine")]
|
||||
use lsp_types::Url;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::wasm_prelude::UrlWasm;
|
||||
|
||||
#[cfg(feature = "preview-engine")]
|
||||
fn builtin_component_info(name: &str, fills_parent: bool) -> ComponentInformation {
|
||||
let (category, is_layout) = match name {
|
||||
"GridLayout" | "HorizontalLayout" | "VerticalLayout" => ("Layout", true),
|
||||
|
@ -98,6 +98,7 @@ fn exported_project_component_info(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "preview-engine")]
|
||||
fn file_local_component_info(name: &str, position: Position) -> ComponentInformation {
|
||||
ComponentInformation {
|
||||
name: name.to_string(),
|
||||
|
@ -113,6 +114,7 @@ fn file_local_component_info(name: &str, position: Position) -> ComponentInforma
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "preview-engine")]
|
||||
pub fn builtin_components(document_cache: &DocumentCache, result: &mut Vec<ComponentInformation>) {
|
||||
let registry = document_cache.global_type_registry();
|
||||
result.extend(registry.all_elements().iter().filter_map(|(name, ty)| match ty {
|
||||
|
@ -171,6 +173,7 @@ pub fn all_exported_components(
|
|||
result.dedup_by(|a, b| a.name == b.name);
|
||||
}
|
||||
|
||||
#[cfg(feature = "preview-engine")]
|
||||
pub fn file_local_components(
|
||||
document_cache: &DocumentCache,
|
||||
url: &Url,
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
// 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
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::wasm_prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::common;
|
||||
|
|
|
@ -11,7 +11,7 @@ mod semantic_tokens;
|
|||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
||||
use crate::common::{self, component_catalog, DocumentCache, Result};
|
||||
use crate::common::{self, DocumentCache, Result};
|
||||
use crate::util;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -93,6 +93,7 @@ pub struct Context {
|
|||
pub server_notifier: crate::ServerNotifier,
|
||||
pub init_param: InitializeParams,
|
||||
/// The last component for which the user clicked "show preview"
|
||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
||||
pub to_show: RefCell<Option<common::PreviewComponent>>,
|
||||
}
|
||||
|
||||
|
@ -423,9 +424,6 @@ pub fn show_preview_command(params: &[serde_json::Value], ctx: &Rc<Context>) ->
|
|||
ctx.to_show.replace(Some(c.clone()));
|
||||
ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::ShowPreview(c));
|
||||
|
||||
// Update known Components
|
||||
report_known_components(document_cache, ctx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -681,28 +679,6 @@ pub(crate) async fn reload_document_impl(
|
|||
lsp_diags
|
||||
}
|
||||
|
||||
fn report_known_components(document_cache: &mut DocumentCache, ctx: &Rc<Context>) {
|
||||
let mut components = Vec::new();
|
||||
component_catalog::builtin_components(document_cache, &mut components);
|
||||
component_catalog::all_exported_components(
|
||||
&document_cache,
|
||||
&mut |ci| !ci.is_global,
|
||||
&mut components,
|
||||
);
|
||||
|
||||
components.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
let url = ctx.to_show.borrow().as_ref().map(|pc| {
|
||||
let url = pc.url.clone();
|
||||
let version = document_cache.document_version(&url);
|
||||
component_catalog::file_local_components(document_cache, &url, &mut components);
|
||||
common::VersionedUrl::new(url, version)
|
||||
});
|
||||
|
||||
ctx.server_notifier
|
||||
.send_message_to_preview(common::LspToPreviewMessage::KnownComponents { url, components });
|
||||
}
|
||||
|
||||
pub async fn reload_document(
|
||||
ctx: &Rc<Context>,
|
||||
content: String,
|
||||
|
@ -720,9 +696,6 @@ pub async fn reload_document(
|
|||
)?;
|
||||
}
|
||||
|
||||
// Tell Preview about the Components:
|
||||
report_known_components(document_cache, ctx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// cSpell: ignore rfind
|
||||
|
||||
use super::component_catalog::all_exported_components;
|
||||
use crate::common::component_catalog::all_exported_components;
|
||||
use crate::common::{self, DocumentCache};
|
||||
use crate::util::{lookup_current_element_type, map_position, with_lookup_ctx};
|
||||
|
||||
|
|
|
@ -12,9 +12,6 @@ use i_slint_compiler::parser::{syntax_nodes, Language, SyntaxKind};
|
|||
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::wasm_prelude::*;
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq)]
|
||||
pub(crate) struct DefinitionInformation {
|
||||
property_definition_range: lsp_types::Range,
|
||||
|
|
|
@ -310,6 +310,7 @@ fn main_loop(connection: Connection, init_param: InitializeParams, cli_args: Cli
|
|||
preview_config: RefCell::new(Default::default()),
|
||||
server_notifier,
|
||||
init_param,
|
||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
||||
to_show: Default::default(),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// 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 crate::common::{self, ComponentInformation, ElementRcNode, PreviewComponent, PreviewConfig};
|
||||
use crate::common::{
|
||||
self, component_catalog, ComponentInformation, ElementRcNode, PreviewComponent, PreviewConfig,
|
||||
};
|
||||
use crate::lsp_ext::Health;
|
||||
use crate::preview::element_selection::ElementSelection;
|
||||
use crate::util;
|
||||
|
@ -74,34 +76,77 @@ struct PreviewState {
|
|||
}
|
||||
thread_local! {static PREVIEW_STATE: std::cell::RefCell<PreviewState> = Default::default();}
|
||||
|
||||
struct DummyWaker();
|
||||
|
||||
impl std::task::Wake for DummyWaker {
|
||||
fn wake(self: std::sync::Arc<Self>) {}
|
||||
}
|
||||
|
||||
pub fn poll_once<F: std::future::Future>(future: F) -> Option<F::Output> {
|
||||
let waker = std::sync::Arc::new(DummyWaker()).into();
|
||||
let mut ctx = std::task::Context::from_waker(&waker);
|
||||
|
||||
let future = std::pin::pin!(future);
|
||||
|
||||
match future.poll(&mut ctx) {
|
||||
std::task::Poll::Ready(result) => Some(result),
|
||||
std::task::Poll::Pending => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl PreviewState {
|
||||
fn refresh_document_cache(
|
||||
&mut self,
|
||||
url: &lsp_types::Url,
|
||||
url: &Url,
|
||||
version: SourceFileVersion,
|
||||
source_code: String,
|
||||
) {
|
||||
let Some(dc) = self.document_cache.as_mut() else {
|
||||
let Some(document_cache) = self.document_cache.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diag = BuildDiagnostics::default();
|
||||
let _ = spin_on::spin_on(dc.load_url(url, version, source_code, &mut diag)); // ignore url conversion errors
|
||||
let _ = poll_once(document_cache.load_url(url, version, source_code, &mut diag)); // ignore url conversion errors
|
||||
|
||||
eprintln!("Updated Document Cache in Live Preview: has_error: {}", diag.has_error());
|
||||
let mut components = Vec::new();
|
||||
component_catalog::builtin_components(document_cache, &mut components);
|
||||
component_catalog::all_exported_components(
|
||||
&document_cache,
|
||||
&mut |ci| !ci.is_global,
|
||||
&mut components,
|
||||
);
|
||||
|
||||
components.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
self.known_components = components;
|
||||
|
||||
if let Some(ui) = &self.ui {
|
||||
ui::ui_set_known_components(ui, &self.known_components)
|
||||
}
|
||||
}
|
||||
|
||||
fn recreate_document_cache(&mut self, config: &PreviewConfig, style: String) {
|
||||
fn recreate_document_cache(
|
||||
&mut self,
|
||||
config: &PreviewConfig,
|
||||
style: String,
|
||||
preview_url: &Url,
|
||||
) {
|
||||
let style = if style.is_empty() { None } else { Some(style) };
|
||||
|
||||
if let Some(dc) = &self.document_cache {
|
||||
let cc = dc.compiler_configuration();
|
||||
if cc.style == style
|
||||
&& cc.include_paths == config.include_paths
|
||||
&& cc.library_paths == config.library_paths
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
let mut compiler_config = i_slint_compiler::CompilerConfiguration::new(
|
||||
i_slint_compiler::generator::OutputFormat::Interpreter,
|
||||
);
|
||||
|
||||
if style.is_empty() {
|
||||
compiler_config.style = None;
|
||||
} else {
|
||||
compiler_config.style = Some(style);
|
||||
}
|
||||
|
||||
compiler_config.style = style;
|
||||
compiler_config.include_paths = config.include_paths.clone();
|
||||
compiler_config.library_paths = config.library_paths.clone();
|
||||
compiler_config.open_import_fallback = Some(Rc::new(|path| {
|
||||
|
@ -110,31 +155,43 @@ impl PreviewState {
|
|||
}));
|
||||
|
||||
self.document_cache = Some(common::DocumentCache::new(compiler_config));
|
||||
|
||||
if let Some((version, contents)) = CONTENT_CACHE
|
||||
.get_or_init(Default::default)
|
||||
.lock()
|
||||
.unwrap()
|
||||
.source_code
|
||||
.get(&preview_url)
|
||||
.cloned()
|
||||
{
|
||||
self.refresh_document_cache(preview_url, version, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_contents(url: &common::VersionedUrl, content: String) {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
let old = cache.source_code.insert(url.url().clone(), (*url.version(), content.clone()));
|
||||
if cache.dependency.contains(url.url()) {
|
||||
if let Some((old_version, old)) = old {
|
||||
if content == old && old_version == *url.version() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((old_version, old)) = old {
|
||||
if content == old && old_version == *url.version() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let fu = url.clone();
|
||||
let fc = content.clone();
|
||||
let fu = url.clone();
|
||||
let fc = content.clone();
|
||||
|
||||
let _ = i_slint_core::api::invoke_from_event_loop(move || {
|
||||
let url = fu;
|
||||
let content = fc;
|
||||
let _ = i_slint_core::api::invoke_from_event_loop(move || {
|
||||
let url = fu;
|
||||
let content = fc;
|
||||
|
||||
PREVIEW_STATE.with(move |ps| {
|
||||
ps.borrow_mut().refresh_document_cache(url.url(), url.version().clone(), content)
|
||||
})
|
||||
});
|
||||
PREVIEW_STATE.with(move |ps| {
|
||||
ps.borrow_mut().refresh_document_cache(url.url(), url.version().clone(), content)
|
||||
})
|
||||
});
|
||||
|
||||
if cache.dependency.contains(url.url()) {
|
||||
let ui_is_visible = cache.ui_is_visible;
|
||||
let Some(current) = cache.current.clone() else {
|
||||
return;
|
||||
|
@ -400,12 +457,6 @@ fn change_style() {
|
|||
return;
|
||||
};
|
||||
|
||||
let config = cache.config.clone();
|
||||
let style = get_current_style();
|
||||
let _ = i_slint_core::api::invoke_from_event_loop(move || {
|
||||
PREVIEW_STATE.with(|ps| ps.borrow_mut().recreate_document_cache(&config, style))
|
||||
});
|
||||
|
||||
drop(cache);
|
||||
|
||||
if ui_is_visible {
|
||||
|
@ -440,11 +491,6 @@ pub fn config_changed(config: PreviewConfig) {
|
|||
|
||||
drop(cache);
|
||||
|
||||
let style = get_current_style();
|
||||
let _ = i_slint_core::api::invoke_from_event_loop(move || {
|
||||
PREVIEW_STATE.with(|ps| ps.borrow_mut().recreate_document_cache(&config, style))
|
||||
});
|
||||
|
||||
if ui_is_visible {
|
||||
if let Some(hide_ui) = hide_ui {
|
||||
set_show_preview_ui(!hide_ui);
|
||||
|
@ -612,10 +658,16 @@ async fn reload_preview_impl(
|
|||
style: String,
|
||||
config: PreviewConfig,
|
||||
) {
|
||||
let preview_url = preview_component.url.clone();
|
||||
|
||||
let component = PreviewComponent { style: String::new(), ..preview_component };
|
||||
|
||||
start_parsing();
|
||||
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
preview_state.borrow_mut().recreate_document_cache(&config, style.clone(), &preview_url);
|
||||
});
|
||||
|
||||
let path = component.url.to_file_path().unwrap_or(PathBuf::from(&component.url.to_string()));
|
||||
let source = {
|
||||
let (_, from_cache) = get_url_from_cache(&component.url).unwrap_or_default();
|
||||
|
@ -699,24 +751,6 @@ pub fn highlight(url: Option<Url>, offset: u32) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn known_components(
|
||||
_url: &Option<common::VersionedUrl>,
|
||||
mut components: Vec<ComponentInformation>,
|
||||
) {
|
||||
components.sort_unstable_by_key(|ci| ci.name.clone());
|
||||
|
||||
run_in_ui_thread(move || async move {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let mut preview_state = preview_state.borrow_mut();
|
||||
preview_state.known_components = components;
|
||||
|
||||
if let Some(ui) = &preview_state.ui {
|
||||
ui::ui_set_known_components(ui, &preview_state.known_components)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_component_info(component_type: &str) -> Option<ComponentInformation> {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
|
@ -730,15 +764,15 @@ pub fn get_component_info(component_type: &str) -> Option<ComponentInformation>
|
|||
|
||||
fn convert_diagnostics(
|
||||
diagnostics: &[slint_interpreter::Diagnostic],
|
||||
) -> HashMap<lsp_types::Url, Vec<lsp_types::Diagnostic>> {
|
||||
let mut result: HashMap<lsp_types::Url, Vec<lsp_types::Diagnostic>> = Default::default();
|
||||
) -> HashMap<Url, Vec<lsp_types::Diagnostic>> {
|
||||
let mut result: HashMap<Url, Vec<lsp_types::Diagnostic>> = Default::default();
|
||||
for d in diagnostics {
|
||||
if d.source_file().map_or(true, |f| !i_slint_compiler::pathutils::is_absolute(f)) {
|
||||
continue;
|
||||
}
|
||||
let uri = lsp_types::Url::from_file_path(d.source_file().unwrap())
|
||||
let uri = Url::from_file_path(d.source_file().unwrap())
|
||||
.ok()
|
||||
.unwrap_or_else(|| lsp_types::Url::parse("file:/unknown").unwrap());
|
||||
.unwrap_or_else(|| Url::parse("file:/unknown").unwrap());
|
||||
result.entry(uri).or_default().push(crate::util::to_lsp_diag(d));
|
||||
}
|
||||
result
|
||||
|
@ -954,9 +988,6 @@ pub fn lsp_to_preview_message(
|
|||
M::HighlightFromEditor { url, offset } => {
|
||||
highlight(url, offset);
|
||||
}
|
||||
M::KnownComponents { url, components } => {
|
||||
known_components(&url, components);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue