Skip to content

Commit 7ada390

Browse files
authored
Slintpad: allow to load libraries with ?lib=foo=https://... (#9441)
Also allow `https://` URL to be mapped directly for libraries (without loading them in a editor tab)
1 parent 07c2f25 commit 7ada390

File tree

5 files changed

+90
-29
lines changed

5 files changed

+90
-29
lines changed

internal/compiler/tests/syntax/imports/library.slint

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import { NotFound } from "@not-found/foo.slint";
88

99
import { NotFound2 } from "@test-lib/lib.slint";
1010
// ^error{No exported type called 'NotFound2' found in "@test-lib/lib.slint"}
11-
import { NotFound3 } from "@test-lib/not-existing.slint";
12-
// ^error{Cannot find requested import "@test-lib/not-existing.slint" in the library search path}
1311

1412
import { NotFound4 } from "@notexist";
1513
// ^error{Cannot find requested import "@notexist" in the library search path}

internal/compiler/typeloader.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use smol_str::{SmolStr, ToSmolStr};
55
use std::cell::RefCell;
66
use std::collections::{HashMap, HashSet};
7+
use std::io::ErrorKind;
78
use std::path::{Path, PathBuf};
89
use std::rc::{Rc, Weak};
910

@@ -931,10 +932,7 @@ impl TypeLoader {
931932
}
932933
self.all_documents.dependencies.remove(path);
933934
if self.all_documents.currently_loading.contains_key(path) {
934-
Err(std::io::Error::new(
935-
std::io::ErrorKind::InvalidInput,
936-
format!("{path:?} is still loading"),
937-
))
935+
Err(std::io::Error::new(ErrorKind::InvalidInput, format!("{path:?} is still loading")))
938936
} else {
939937
Ok(())
940938
}
@@ -1205,11 +1203,13 @@ impl TypeLoader {
12051203
) -> Option<PathBuf> {
12061204
let mut borrowed_state = state.borrow_mut();
12071205

1206+
let mut resolved = false;
12081207
let (path_canon, builtin) = match borrowed_state
12091208
.tl
12101209
.resolve_import_path(import_token.as_ref(), file_to_import)
12111210
{
12121211
Some(x) => {
1212+
resolved = true;
12131213
if let Some(file_name) = x.0.file_name().and_then(|f| f.to_str()) {
12141214
let len = file_to_import.len();
12151215
if !file_to_import.ends_with(file_name)
@@ -1325,7 +1325,10 @@ impl TypeLoader {
13251325
Some(&path_canon),
13261326
state.borrow_mut().diag,
13271327
)),
1328-
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
1328+
Err(err)
1329+
if !resolved
1330+
&& matches!(err.kind(), ErrorKind::NotFound | ErrorKind::NotADirectory) =>
1331+
{
13291332
state.borrow_mut().diag.push_error(
13301333
if file_to_import.starts_with('@') {
13311334
format!(
@@ -1582,6 +1585,7 @@ impl TypeLoader {
15821585
};
15831586
crate::fileaccess::load_file(path.as_path())
15841587
.map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1588+
.or_else(|| Some((path, None)))
15851589
})
15861590
}
15871591

@@ -2118,12 +2122,27 @@ import { E } from "@unknown/lib.slint";
21182122
assert!(build_diagnostics.has_errors());
21192123
let diags = build_diagnostics.to_string_vec();
21202124
assert_eq!(diags.len(), 5);
2121-
assert!(diags[0].starts_with(&format!(
2122-
"HELLO:3: Error reading requested import \"{}\": ",
2123-
test_source_path.to_string_lossy()
2124-
)));
2125-
assert_eq!(&diags[1], "HELLO:4: Cannot find requested import \"@libdir/unknown.slint\" in the library search path");
2126-
assert_eq!(&diags[2], "HELLO:5: Cannot find requested import \"@libfile.slint/unknown.slint\" in the library search path");
2125+
assert_starts_with(
2126+
&diags[0],
2127+
&format!(
2128+
"HELLO:3: Error reading requested import \"{}\": ",
2129+
test_source_path.to_string_lossy()
2130+
),
2131+
);
2132+
assert_starts_with(
2133+
&diags[1],
2134+
&format!(
2135+
"HELLO:4: Error reading requested import \"{}\": ",
2136+
test_source_path.join("unknown.slint").to_string_lossy(),
2137+
),
2138+
);
2139+
assert_starts_with(
2140+
&diags[2],
2141+
&format!(
2142+
"HELLO:5: Error reading requested import \"{}\": ",
2143+
test_source_path.join("lib.slint").join("unknown.slint").to_string_lossy()
2144+
),
2145+
);
21272146
assert_eq!(
21282147
&diags[3],
21292148
"HELLO:6: Cannot find requested import \"@unknown\" in the library search path"
@@ -2132,6 +2151,11 @@ import { E } from "@unknown/lib.slint";
21322151
&diags[4],
21332152
"HELLO:7: Cannot find requested import \"@unknown/lib.slint\" in the library search path"
21342153
);
2154+
2155+
#[track_caller]
2156+
fn assert_starts_with(actual: &str, start: &str) {
2157+
assert!(actual.starts_with(start), "{actual:?} does not start with {start:?}");
2158+
}
21352159
}
21362160

21372161
#[test]

tools/slintpad/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,10 @@ The `preview.html` page contains only the preview and the code must be given via
5555
- https://slint.dev/editor/?snippet=_+%3A%3D+Text+%7B+text%3A+%22Hello+Slint%22%3B+%7D?style=fluent-dark
5656
- https://slint.dev/editor/preview.html?snippet=_+%3A%3D+Text+%7B+text%3A+%22Hello+Slint%22%3B+%7D?style=material
5757

58-
- `?gz=` Like `?snippet=` but compressed with gzip and base64 encoded.
58+
- `?gz=` Like `?snippet=` but compressed with gzip and base64 encoded.
59+
60+
- `?lib=lib_name=<url>` query argument, followed by the name of the library and the URL to load it from.
61+
62+
Example: The to use the `@material` library:
63+
64+
- https://slint.dev/editor/?lib=material=https://raw.githubusercontent.com/slint-ui/material-components/refs/heads/master/material.slint

tools/slintpad/src/editor_widget.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -613,24 +613,28 @@ export class EditorWidget extends Widget {
613613
}
614614

615615
protected async handle_lsp_url_request(url: string): Promise<string> {
616-
if (this.#url_mapper === null) {
617-
return Promise.resolve("Error: Can not resolve URL.");
618-
}
616+
if (url.startsWith("slintpad:/")) {
617+
if (this.#url_mapper === null) {
618+
return Promise.resolve("Error: Can not resolve URL.");
619+
}
619620

620-
const internal_uri = monaco.Uri.parse(url);
621-
const uri = this.#url_mapper.from_internal(internal_uri);
621+
const internal_uri = monaco.Uri.parse(url);
622+
const uri = this.#url_mapper.from_internal(internal_uri);
622623

623-
if (uri === null) {
624-
return Promise.resolve("Error: Can not map URL.");
625-
}
624+
if (uri === null) {
625+
return Promise.resolve("Error: Can not map URL.");
626+
}
626627

627-
return (
628-
await this.safely_open_editor_with_url_content(
629-
uri,
630-
internal_uri,
631-
false,
632-
)
633-
)[1];
628+
return (
629+
await this.safely_open_editor_with_url_content(
630+
uri,
631+
internal_uri,
632+
false,
633+
)
634+
)[1];
635+
}
636+
const r = await fetch(url);
637+
return await r.text();
634638
}
635639

636640
private async safely_open_editor_with_url_content(

tools/slintpad/src/lsp.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ export type ShowDocumentCallback = (
4040
_position: LspPosition,
4141
) => boolean;
4242

43+
function configuration_from_url(): Map<string, any>[] {
44+
var obj = new Map<string, any>();
45+
const params = new URLSearchParams(window.location.search);
46+
const include_paths = params.getAll("include");
47+
if (include_paths.length > 0) {
48+
obj.set("includePaths", include_paths);
49+
}
50+
const library_paths = params.getAll("lib");
51+
if (library_paths.length > 0) {
52+
const lib_map = new Map<string, string>();
53+
for (const lib of library_paths) {
54+
const [name, url] = lib.split("=");
55+
lib_map.set(name, url);
56+
}
57+
obj.set("libraryPaths", lib_map);
58+
}
59+
return [obj];
60+
}
61+
4362
function createLanguageClient(
4463
transports: MessageTransports,
4564
): MonacoLanguageClient {
@@ -53,6 +72,16 @@ function createLanguageClient(
5372
error: () => ({ action: ErrorAction.Continue }),
5473
closed: () => ({ action: CloseAction.DoNotRestart }),
5574
},
75+
middleware: {
76+
workspace: {
77+
configuration: (params, token, next) => {
78+
if (params.items[0].section === "slint") {
79+
return configuration_from_url();
80+
}
81+
return next(params, token);
82+
},
83+
},
84+
},
5685
},
5786
// create a language client connection to the server running in the web worker
5887
connectionProvider: {

0 commit comments

Comments
 (0)