live-preview: Handle known components using our local DocumentCache

This commit is contained in:
Tobias Hunger 2024-06-06 16:21:39 +02:00 committed by Tobias Hunger
parent 6c034372a1
commit d657ee65af
9 changed files with 124 additions and 117 deletions

View file

@ -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 }

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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;

View file

@ -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(())
}

View file

@ -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};

View file

@ -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,

View file

@ -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(),
});

View file

@ -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,13 +155,24 @@ 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;
@ -135,6 +191,7 @@ pub fn set_contents(url: &common::VersionedUrl, content: String) {
})
});
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);
}
}
}