Skip to content

Commit 9c52f08

Browse files
committed
Implemented a way for remote viewers to request files by path.
1 parent 20a5789 commit 9c52f08

17 files changed

Lines changed: 214 additions & 104 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/development/lsp-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ pub enum LspToPreviewMessage {
352352

353353
// Preview to LSP
354354
pub enum PreviewToLspMessage {
355-
RequestState { unused: bool },
355+
RequestState { paths: Vec<String> },
356356
UpdateElement { ... },
357357
SendWorkspaceEdit { ... },
358358
ShowDocument { ... },

editors/vscode/src/wasm_preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ function initPreviewPanel(
230230
map_url(panel.webview, message.url);
231231
return;
232232
case "preview_ready":
233-
send_to_lsp({ RequestState: { unused: true } });
233+
send_to_lsp({ RequestState: {} });
234234
return;
235235
case "slint/preview_to_lsp":
236236
send_to_lsp(message.params);

internal/compiler/passes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,12 @@ pub async fn run_passes(
243243
}
244244
});
245245

246+
embed_images::preload_images(doc, resource_preloader).await;
246247
embed_images::embed_images(
247248
doc,
248249
type_loader.compiler_config.embed_resources,
249250
type_loader.compiler_config.const_scale_factor.unwrap_or(1.),
250251
&type_loader.compiler_config.resource_url_mapper,
251-
resource_preloader,
252252
diag,
253253
)
254254
.await;

internal/compiler/passes/embed_images.rs

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::object_tree::*;
1010
use image::GenericImageView;
1111
use smol_str::SmolStr;
1212
use std::cell::RefCell;
13-
use std::collections::HashMap;
13+
use std::collections::{HashMap, HashSet};
1414
use std::future::Future;
1515
use std::pin::Pin;
1616
use std::rc::Rc;
@@ -20,31 +20,54 @@ use typed_index_collections::TiVec;
2020
pub trait ResourcePreloader {
2121
fn load<'a>(
2222
&self,
23-
urls: impl Iterator<Item = &'a str>,
24-
push: impl FnMut(/* url */ &'a str, /* extension */ String, /* data */ Arc<[u8]>),
23+
paths: impl Iterator<Item = &'a str>,
24+
push: Rc<impl Fn(/* path */ &'a str, /* extension */ String, /* data */ Arc<[u8]>)>,
2525
) -> impl Future<Output = ()>;
2626
}
2727

2828
impl ResourcePreloader for () {
2929
fn load<'a>(
3030
&self,
31-
_urls: impl Iterator<Item = &'a str>,
32-
_push: impl FnMut(
33-
/* url */ &'a str,
34-
/* extension */ String,
35-
/* data */ Arc<[u8]>,
36-
),
31+
_paths: impl Iterator<Item = &'a str>,
32+
_push: Rc<impl Fn(&'a str, String, Arc<[u8]>)>,
3733
) -> impl Future<Output = ()> {
3834
std::future::ready(())
3935
}
4036
}
4137

38+
pub async fn preload_images(doc: &Document, resource_preloader: impl ResourcePreloader) {
39+
let mut all_components = Vec::new();
40+
doc.visit_all_used_components(|c| all_components.push(c.clone()));
41+
42+
let mut urls = HashSet::<SmolStr>::new();
43+
for component in &all_components {
44+
visit_all_expressions(component, |e, _| {
45+
foreach_image_url_from_expression(e, &mut |path| {
46+
urls.insert(path.clone());
47+
})
48+
});
49+
}
50+
51+
let global_embedded_resources = &doc.embedded_file_resources;
52+
resource_preloader
53+
.load(
54+
urls.iter().map(SmolStr::as_str),
55+
Rc::new(|url: &str, extension, data: Arc<[u8]>| {
56+
let mut resources = global_embedded_resources.borrow_mut();
57+
resources.push(EmbeddedResources {
58+
path: Some(url.into()),
59+
kind: EmbeddedResourcesKind::DataUriPayload(data.to_vec(), extension),
60+
});
61+
}),
62+
)
63+
.await;
64+
}
65+
4266
pub async fn embed_images(
4367
doc: &Document,
4468
embed_files: EmbedResourcesKind,
4569
scale_factor: f32,
4670
resource_url_mapper: &Option<Rc<dyn Fn(&str) -> Pin<Box<dyn Future<Output = Option<String>>>>>>,
47-
resource_preloader: impl ResourcePreloader,
4871
diag: &mut BuildDiagnostics,
4972
) {
5073
if embed_files == EmbedResourcesKind::Nothing && resource_url_mapper.is_none() {
@@ -53,6 +76,11 @@ pub async fn embed_images(
5376

5477
let global_embedded_resources = &doc.embedded_file_resources;
5578
let mut path_to_id = HashMap::<SmolStr, EmbeddedResourcesIdx>::new();
79+
for (id, resource) in global_embedded_resources.borrow().iter_enumerated() {
80+
if let Some(path) = &resource.path {
81+
path_to_id.insert(path.clone(), id);
82+
}
83+
}
5684

5785
let mut all_components = Vec::new();
5886
doc.visit_all_used_components(|c| all_components.push(c.clone()));
@@ -65,7 +93,9 @@ pub async fn embed_images(
6593
// Collect URLs (sync!):
6694
for component in &all_components {
6795
visit_all_expressions(component, |e, _| {
68-
collect_image_urls_from_expression(e, &mut urls)
96+
foreach_image_url_from_expression(e, &mut |path| {
97+
urls.insert(path.clone(), None);
98+
})
6999
});
70100
}
71101

@@ -78,20 +108,6 @@ pub async fn embed_images(
78108
urls
79109
};
80110

81-
resource_preloader
82-
.load(
83-
mapped_urls.values().filter_map(|url| url.as_ref().map(SmolStr::as_str)),
84-
|url: &str, extension, data| {
85-
let mut resources = global_embedded_resources.borrow_mut();
86-
let id = resources.push_and_get_key(EmbeddedResources {
87-
path: Some(url.into()),
88-
kind: EmbeddedResourcesKind::DataUriPayload(data.to_vec(), extension),
89-
});
90-
path_to_id.insert(url.into(), id);
91-
},
92-
)
93-
.await;
94-
95111
// Use URLs (sync!):
96112
for component in &all_components {
97113
visit_all_expressions(component, |e, _| {
@@ -108,17 +124,14 @@ pub async fn embed_images(
108124
}
109125
}
110126

111-
fn collect_image_urls_from_expression(
112-
e: &Expression,
113-
urls: &mut HashMap<SmolStr, Option<SmolStr>>,
114-
) {
127+
fn foreach_image_url_from_expression(e: &Expression, f: &mut impl FnMut(&SmolStr)) {
115128
if let Expression::ImageReference { resource_ref, .. } = e
116129
&& let ImageReference::AbsolutePath(path) = resource_ref
117130
{
118-
urls.insert(path.clone(), None);
131+
f(path);
119132
};
120133

121-
e.visit(|e| collect_image_urls_from_expression(e, urls));
134+
e.visit(|e| foreach_image_url_from_expression(e, f));
122135
}
123136

124137
fn embed_images_from_expression(
@@ -133,6 +146,10 @@ fn embed_images_from_expression(
133146
if let Expression::ImageReference { resource_ref, source_location, nine_slice: _ } = e
134147
&& let ImageReference::AbsolutePath(path) = resource_ref
135148
{
149+
if path_to_id.contains_key(path) {
150+
return;
151+
}
152+
136153
if path.starts_with("data:") {
137154
let image_ref = embed_data_uri(
138155
global_embedded_resources,

internal/interpreter/tests.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ fn reuse_window() {
1818
let handle = {
1919
let mut compiler = Compiler::default();
2020
compiler.set_style("fluent".into());
21-
let result = spin_on::spin_on(compiler.build_from_source(code.into(), Default::default()));
21+
let result =
22+
spin_on::spin_on(compiler.build_from_source(code.into(), Default::default(), ()));
2223
assert!(!result.has_errors(), "{:?}", result.diagnostics().collect::<Vec<_>>());
2324
let definition = result.component("MainWindow").unwrap();
2425
let instance = definition.create().unwrap();
@@ -32,7 +33,8 @@ fn reuse_window() {
3233
let _handle2 = {
3334
let mut compiler = Compiler::default();
3435
compiler.set_style("fluent".into());
35-
let result = spin_on::spin_on(compiler.build_from_source(code.into(), Default::default()));
36+
let result =
37+
spin_on::spin_on(compiler.build_from_source(code.into(), Default::default(), ()));
3638
assert!(!result.has_errors(), "{:?}", result.diagnostics().collect::<Vec<_>>());
3739
let definition = result.component("MainWindow").unwrap();
3840
let instance = definition.create_with_existing_window(handle.window()).unwrap();
@@ -68,7 +70,7 @@ export component Clock {
6870
}
6971
"#;
7072
let compiler = Compiler::default();
71-
let result = spin_on::spin_on(compiler.build_from_source(code.into(), Default::default()));
73+
let result = spin_on::spin_on(compiler.build_from_source(code.into(), Default::default(), ()));
7274
assert!(!result.has_errors(), "{:?}", result.diagnostics().collect::<Vec<_>>());
7375
let definition = result.component("Clock").unwrap();
7476
let instance = definition.create().unwrap();

internal/preview-protocol/src/preview_to_lsp.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ pub enum PreviewToLspMessage {
2828
PreviewTypeChanged { target: PreviewTarget },
2929
/// Request all documents and configuration to be sent from the LSP to the
3030
/// Preview.
31-
RequestState { unused: bool },
31+
RequestState {
32+
#[serde(default)]
33+
files: Vec<Url>,
34+
},
3235
/// Pass a `WorkspaceEdit` on to the editor
3336
SendWorkspaceEdit { label: Option<String>, edit: lsp_types::WorkspaceEdit },
3437
/// Pass a `ShowMessage` notification on to the editor

tools/lsp/language.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use i_slint_compiler::parser::{
2323
NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, syntax_nodes,
2424
};
2525
use i_slint_compiler::{diagnostics::BuildDiagnostics, langtype::Type};
26+
use i_slint_preview_protocol::PreviewToLspMessage;
2627
use itertools::Itertools;
2728
use lsp_types::{
2829
ClientCapabilities, CodeActionOrCommand, CodeActionProviderCapability, CodeLens,
@@ -173,6 +174,7 @@ pub struct Context {
173174
/// Files to recompile after all other operations are done
174175
/// (i.e. recompilations triggered by updates to unopened files)
175176
pub pending_recompile: HashSet<lsp_types::Url>,
177+
pub preview_to_lsp_sender: tokio::sync::mpsc::UnboundedSender<PreviewToLspMessage>,
176178
}
177179

178180
/// An error from a LSP request
@@ -400,7 +402,7 @@ pub fn register_request_handlers(rh: &mut RequestHandler) {
400402
}
401403
POPULATE_COMMAND => {
402404
let future = populate_command(&params.arguments, ctx)?;
403-
tokio::task::spawn_local(async move {
405+
crate::common::spawn_local(async move {
404406
if let Err(err) = future.await {
405407
tracing::error!("Error executing populate command: {err}");
406408
}
@@ -653,22 +655,24 @@ pub fn connect_remote_preview_command(
653655
});
654656
let port = params.get(1).and_then(serde_json::Value::as_u64);
655657

656-
let to_preview = ctx.to_preview.clone();
657-
658658
if let Some(addresses) = addresses {
659659
if let Some(port) = port {
660660
use crate::preview::connector::remote::RemoteLspToPreview;
661661

662-
let _ = to_preview.set_preview_target(i_slint_preview_protocol::PreviewTarget::Remote);
663-
to_preview.with_preview_target::<RemoteLspToPreview, Result<Option<serde_json::Value>, LspError>>(
664-
move |remote| {
662+
let _ =
663+
ctx.to_preview.set_preview_target(i_slint_preview_protocol::PreviewTarget::Remote);
664+
ctx.to_preview.with_preview_target::<RemoteLspToPreview, Result<Option<serde_json::Value>, LspError>>(
665+
|remote| {
666+
let preview_to_lsp_sender = ctx.preview_to_lsp_sender.clone();
665667
let future = remote.connect(addresses, port as u16);
666-
tokio::task::spawn_local(async move {
668+
crate::common::spawn_local(async move {
667669
if let Err(err) = future.await {
668670
LspError {
669671
code: LspErrorCode::RequestFailed,
670672
message: format!("Failed to connect to remote preview: {err}"),
671673
};
674+
} else {
675+
let _ = preview_to_lsp_sender.send(PreviewToLspMessage::RequestState { files: Vec::new() });
672676
}
673677
});
674678
Ok(None)
@@ -692,7 +696,7 @@ pub fn disconnect_remote_preview_command(ctx: &Context) {
692696
let to_preview = ctx.to_preview.clone();
693697
tracing::debug!("disconnect_remote_preview_command");
694698
to_preview.with_preview_target::<crate::preview::connector::RemoteLspToPreview, _>(|remote| {
695-
tokio::task::spawn_local(remote.disconnect());
699+
crate::common::spawn_local(remote.disconnect());
696700
});
697701
}
698702

tools/lsp/language/goto.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ fn test_goto_definition_multi_files() {
203203
common::DummyLspToPreview::default(),
204204
)),
205205
pending_recompile: Default::default(),
206+
preview_to_lsp_sender: tokio::sync::mpsc::unbounded_channel().0,
206207
};
207208
let (extra_files, diag) = spin_on::spin_on(crate::language::load_document_impl(
208209
&mut ctx,

tools/lsp/language/test.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub fn mock_context() -> Context {
3535
open_urls: HashSet::new(),
3636
to_preview: Rc::new(SwitchableLspToPreview::with_one(common::DummyLspToPreview::default())),
3737
pending_recompile: Default::default(),
38+
preview_to_lsp_sender: tokio::sync::mpsc::unbounded_channel().0,
3839
}
3940
}
4041

@@ -80,6 +81,7 @@ pub fn loaded_document_cache_with_file_name(
8081
common::DummyLspToPreview::default(),
8182
)),
8283
pending_recompile: Default::default(),
84+
preview_to_lsp_sender: tokio::sync::mpsc::unbounded_channel().0,
8385
};
8486
let (extra_files, diag) =
8587
spin_on::spin_on(load_document_impl(&mut ctx, content, url.clone(), Some(42)));

0 commit comments

Comments
 (0)