Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a58c137
Extracted preview protocol out of the LSP, so it can be used by other…
anlumo Mar 23, 2026
9653744
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 2, 2026
8f4eae0
Moved lsp-types dependency to the workspace Cargo.toml.
anlumo Apr 3, 2026
285d286
Moved preview-protocol to internal and renamed it to i-slint-preview-…
anlumo Apr 3, 2026
0d3546f
Added a readme to i-slint-preview-protocol
anlumo Apr 3, 2026
326d856
Internal crate references need both a path and a version.
anlumo Apr 3, 2026
7512669
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 3, 2026
5965b20
Publish i-slint-preview-protocol
anlumo Apr 3, 2026
05be418
Insert readme file in preview-protocol crate documentation.
anlumo Apr 13, 2026
8666fda
Fixed compile errors
anlumo Apr 15, 2026
faa0ea4
Fixed versioned reference to i-slint-preview-protocol
anlumo Apr 17, 2026
f89ff65
Simplified LspToPreview by always using the SwitchableLspToPreview ty…
anlumo Apr 17, 2026
e8a4295
Added a websocket client for previews to the LSP.
anlumo Apr 17, 2026
ac5242f
Fixed sending messages from the LSP's websocket to the LSP.
anlumo Mar 26, 2026
afe1e6c
Compile fixes for tests.
anlumo Mar 26, 2026
e2201fd
Fixed LSP wasm compilation.
anlumo Mar 26, 2026
0f30138
Fixed a test function.
anlumo Mar 27, 2026
05c7748
Fixed LSP startup/shutdown panicing.
anlumo Mar 27, 2026
fdaac00
Accidentally reversed the logic of wasm/non-wasm preview
anlumo Mar 30, 2026
08e8b4f
Updated remote viewer client in LSP to the new internal LSP API.
anlumo Apr 1, 2026
8114d21
Added old code for the remote viewer from the first attempt.
anlumo Apr 1, 2026
3a06993
Implemented connection/disconnection of remote viewers in the vscode …
anlumo Apr 15, 2026
5093648
Send quit command on preview shutdown.
anlumo Apr 15, 2026
fd79262
Removed unnecessary debug messages.
anlumo Apr 15, 2026
8afbeaa
Fixed showing a slint file in the remote preview, also added error lo…
anlumo Apr 15, 2026
40a76e7
Show connection state in the remote viewer's UI.
anlumo Apr 15, 2026
5815af7
Implemented disconnection events in the remote viewer. This required …
anlumo Apr 15, 2026
aedd8ef
Removed old disabled code.
anlumo Apr 15, 2026
2cbc8b1
Fixed error output in the remote viewer.
anlumo Apr 15, 2026
91b1809
Integrated remote viewer as an option in the old command line viewer.
anlumo Apr 16, 2026
4e114a7
Fixed lockfile.
anlumo Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
716 changes: 695 additions & 21 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ members = [
'internal/core',
'internal/core-macros',
'internal/interpreter',
'internal/preview-protocol',
'tests/doctests',
'tests/driver/cpp',
'tests/driver/driverlib',
Expand All @@ -79,6 +80,7 @@ members = [
'tools/tr-extractor',
"ui-libraries/material/examples/gallery",
'xtask',
'tools/remote-viewer',
]

default-members = [
Expand Down Expand Up @@ -200,6 +202,9 @@ windows-core = { version = "0.62.0" }
lyon_path = { version = "1.0", default-features = false }
objc2 = { version = "0.6.3" }

# Not updated because of https://github.com/gluon-lang/lsp-types/issues/284
lsp-types = "0.95.0"

[profile.release]
lto = true
panic = "abort"
Expand Down
18 changes: 17 additions & 1 deletion editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@
"category": "Slint",
"icon": "$(preview)"
},
{
"command": "slint.connectRemotePreview",
"title": "Connect to Remote Preview",
"category": "Slint",
"icon": "$(preview)"
},
{
"command": "slint.disconnectRemotePreview",
"title": "Disconnect Remote Preview",
"category": "Slint",
"icon": "$(debug-disconnect)"
},
{
"command": "slint.reload",
"title": "Restart server",
Expand All @@ -107,6 +119,10 @@
"command": "slint.showPreview",
"when": "editorLangId == slint"
},
{
"command": "slint.connectRemotePreview",
"when": "editorLangId == slint"
},
{
"command": "slint.reload"
},
Expand Down Expand Up @@ -253,4 +269,4 @@
"typescript": "catalog:",
"vscode-tmgrammar-test": "0.1.3"
}
}
}
82 changes: 82 additions & 0 deletions editors/vscode/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,49 @@ export class ClientHandle {

const client = new ClientHandle();

export type RemoteViewerInfo = {
id: string;

label: string;
detail: string;

value: {
addresses: string[];
port: number;
};

timer?: NodeJS.Timeout;
};
export const remote_viewers = new Map<string, RemoteViewerInfo>();

export let remoteViewerStatusBarItem: vscode.StatusBarItem | undefined;
export function updateRemoteViewerStatusBarItem(newItem: vscode.StatusBarItem) {
remoteViewerStatusBarItem = newItem;
}
export enum RemoteViewerStatusBarItemState {
disconnected,
connecting,
connected,
}
export function setRemoteViewerStatusBarItemState(state: RemoteViewerStatusBarItemState) {
if (remoteViewerStatusBarItem) {
switch (state) {
case RemoteViewerStatusBarItemState.disconnected:
remoteViewerStatusBarItem.text = `$(vm) Slint Remote Preview`;
remoteViewerStatusBarItem.command = 'slint.selectRemotePreview';
break;
case RemoteViewerStatusBarItemState.connecting:
remoteViewerStatusBarItem.text = `$(vm-connect) Slint Remote Preview`;
remoteViewerStatusBarItem.command = 'slint.disconnectRemotePreview';
break;
case RemoteViewerStatusBarItemState.connected:
remoteViewerStatusBarItem.text = `$(vm-active) Slint Remote Preview`;
remoteViewerStatusBarItem.command = 'slint.disconnectRemotePreview';
break;
}
}
}

// LSP related:

// Set up our middleware. It is used to redirect/forward to the WASM preview
Expand Down Expand Up @@ -149,6 +192,41 @@ export function activate(

client.add_updater((cl) => {
wasm_preview.initClientForPreview(context, cl);
cl?.onNotification("slint/remote_viewer_discovered", async (params) => {
vscode.window.showInformationMessage(`Received update for remote viewers: ${JSON.stringify(params)}`);
cl.outputChannel.appendLine(`Received update for remote viewers: ${JSON.stringify(params)}`);
const old_entry = remote_viewers.get(params.host);
if (old_entry) {
clearTimeout(old_entry.timer);
}
const remote_viewer_entry = {
id: params.host,

label: params.host,
detail: params.addresses.join(', '),

value: params,
timer: setTimeout(() => {
remote_viewers.delete(params.host);
}, 60000),
};
remote_viewers.set(params.host, remote_viewer_entry);
});
cl?.onNotification("slint/remote_viewer_connection_state", async (params) => {
switch (params.state) {
case "connected":
vscode.window.showInformationMessage(`Remote viewer connected: ${params.address}:${params.port}, remoteViewerStatusBarItem ${remoteViewerStatusBarItem ? 'available' : 'undefined'}`);
cl.outputChannel.appendLine(`Remote viewer connected: ${params.address}:${params.port}`);
setRemoteViewerStatusBarItemState(RemoteViewerStatusBarItemState.connected);
break;
case "disconnected":
vscode.window.showInformationMessage(`Remote viewer disconnected: ${params.address}:${params.port}`);
cl.outputChannel.appendLine(`Remote viewer disconnected: ${params.address}:${params.port}`);
setRemoteViewerStatusBarItemState(RemoteViewerStatusBarItemState.disconnected);
break;
}
// TODO
});
});

vscode.workspace.onDidChangeConfiguration(async (ev) => {
Expand Down Expand Up @@ -236,6 +314,10 @@ export function deactivate(): Thenable<void> | undefined {
if (!client.client) {
return undefined;
}
for (const viewer of remote_viewers.values()) {
clearTimeout(viewer.timer);
}
remote_viewers.clear();
return client.stop();
}

Expand Down
87 changes: 87 additions & 0 deletions editors/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as vscode from "vscode";
import { SlintTelemetrySender } from "./telemetry";
import * as common from "./common";
import { NotificationType } from "vscode-languageclient";
import * as lsp_commands from "./lsp_commands";

import {
LanguageClient,
Expand Down Expand Up @@ -189,6 +190,7 @@ function startClient(
const devBuild = serverModule.includes("/target/debug/");
if (devBuild) {
options.env["RUST_BACKTRACE"] = "1";
options.env["RUST_LOG"] = "debug";
}

options.env["SLINT_LSP_PANIC_LOG_DIR"] = lsp_panic_log_dir(context).fsPath;
Expand Down Expand Up @@ -234,6 +236,8 @@ function startClient(
handleTelemetryEvent(params.type, context.globalState);
},
);

common.setRemoteViewerStatusBarItemState(common.RemoteViewerStatusBarItemState.disconnected);
});

const cl = new LanguageClient(
Expand Down Expand Up @@ -319,6 +323,8 @@ export function activate(context: vscode.ExtensionContext) {
return;
}

setupRemotePreview(context);

const custom_lsp = !serverModule.startsWith(
path.join(context.extensionPath, "bin"),
);
Expand Down Expand Up @@ -406,3 +412,84 @@ function startTelemetryTimer(
}
}
}

const remotePreviewConnectionStringMatcher = /^(?:\[(?<ipv6>[0-9A-Fa-f:.]+)\]|(?<host>[^:\s\[\]]+)):(?<port>\d{1,5})$/;

function setupRemotePreview(context: vscode.ExtensionContext) {
const remoteViewerStatusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right, 100
);

common.updateRemoteViewerStatusBarItem(remoteViewerStatusBarItem);
common.setRemoteViewerStatusBarItemState(common.RemoteViewerStatusBarItemState.disconnected);
remoteViewerStatusBarItem.show();

context.subscriptions.push(
vscode.commands.registerCommand("slint.selectRemotePreview", () => {
const picker = vscode.window.createQuickPick<vscode.QuickPickItem & Partial<common.RemoteViewerInfo>>();
picker.title = "Select a remote preview";
picker.placeholder = "Manual entry (e.g. 127.0.1:1234)";
picker.ignoreFocusOut = true;

const updateItems = () => {
const typed = picker.value.trim();
const items: (vscode.QuickPickItem & Partial<common.RemoteViewerInfo>)[] = [];

remotePreviewConnectionStringMatcher.lastIndex = 0;
const match = remotePreviewConnectionStringMatcher.exec(typed);

if (match) {
items.push({
id: 'ENTER',
label: `Use typed value: ${typed}`,
detail: "",
alwaysShow: true,
value: {
addresses: [match.groups?.ipv6 ?? match.groups?.host ?? ""],
port: parseInt(match.groups?.port ?? "1234"),
},
});
items.push({ id: "sep", label: "", kind: vscode.QuickPickItemKind.Separator });
}

items.push(...common.remote_viewers.values());

picker.items = items;
};

const connect = async (item: common.RemoteViewerInfo) => {
lsp_commands.connectRemotePreview(item.value.addresses, item.value.port);
common.setRemoteViewerStatusBarItemState(common.RemoteViewerStatusBarItemState.connecting);

picker.hide();
};

// picker.onDidAccept(() => {
// const picked = picker.activeItems[0] ?? picker.selectedItems[0];
// if (picked) {
// connect(picked as common.RemoteViewerInfo);
// }
// });

picker.onDidChangeSelection((items) => {
const picked = items[0];
if (picked) {
connect(picked as common.RemoteViewerInfo);
}
});

picker.onDidHide(() => {
picker.dispose();
});

updateItems();
picker.onDidChangeValue(updateItems);

picker.show();
}),
remoteViewerStatusBarItem,
vscode.commands.registerCommand("slint.disconnectRemotePreview", () => {
lsp_commands.disconnectRemotePreview();
}),
);
}
8 changes: 8 additions & 0 deletions editors/vscode/src/lsp_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ import * as vscode from "vscode";
export function showPreview(url: LspURI, component: string): Thenable<unknown> {
return vscode.commands.executeCommand("slint/showPreview", url, component);
}

export function connectRemotePreview(addresses: string[], port: number): Thenable<unknown> {
return vscode.commands.executeCommand("slint/connectRemotePreview", addresses, port);
}

export function disconnectRemotePreview(): Thenable<unknown> {
return vscode.commands.executeCommand("slint/disconnectRemotePreview");
}
16 changes: 12 additions & 4 deletions editors/vscode/src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
{
"extends": "../tsconfig.default.json",
"compilerOptions": {
"lib": ["es2021", "webworker"],
"types": ["vscode"]
"target": "ES2021",
"lib": [
"es2021",
"webworker"
],
"types": [
"vscode"
]
},
"include": ["./*.ts", "./*.d.ts"]
}
"include": [
"./*.ts"
]
}
2 changes: 1 addition & 1 deletion editors/vscode/src/wasm_preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function update_configuration() {
if (language_client) {
send_to_lsp({
PreviewTypeChanged: {
is_external: previewPanel !== null || use_wasm_preview(),
target: (previewPanel !== null || use_wasm_preview()) ? 'embedded-wasm' : 'child-process',
},
});
}
Expand Down
20 changes: 20 additions & 0 deletions internal/preview-protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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

[package]
name = "i-slint-preview-protocol"
description.workspace = true
authors.workspace = true
documentation.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[dependencies]
lsp-types.workspace = true
serde.workspace = true
serde_json.workspace = true
9 changes: 9 additions & 0 deletions internal/preview-protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

# The Slint Preview Protocol

**NOTE**: This library is an **internal** crate of the [Slint project](https://slint.dev).
This crate should **not be used directly** by applications using Slint.
You should use the `slint` crate instead.

**WARNING**: This crate does not follow the semver convention for versioning and can
only be used with `version = "=x.y.z"` in Cargo.toml.
17 changes: 17 additions & 0 deletions internal/preview-protocol/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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

#![doc = include_str!("../README.md")]

mod lsp_to_preview;
mod preview_to_lsp;
mod versioned_url;

pub use lsp_to_preview::{LspToPreviewMessage, PreviewComponent, PreviewConfig};
pub use preview_to_lsp::{PreviewTarget, PreviewToLspMessage};
pub use versioned_url::VersionedUrl;

pub use lsp_types;

pub type SourceFileVersion = Option<i32>;
pub const SERVICE_TYPE: &str = "_slint-preview._tcp.local.";
Loading
Loading