lsp: Add option to show/hide preview ui

... and use that option in VSCode configuration.
This commit is contained in:
Tobias Hunger 2023-11-22 19:13:02 +01:00 committed by Tobias Hunger
parent b19cbba7ad
commit 548f10be2b
11 changed files with 177 additions and 66 deletions

View file

@ -94,6 +94,10 @@
},
"description": "The command line arguments passed to the Slint LSP server"
},
"slint.preview.show_ui": {
"type": "boolean",
"description": "Show the toolbar of the preview"
},
"slint.preview.style": {
"type": "string",
"description": "The default style to be used for the preview (eg: 'fluent', 'material', or 'native')"

View file

@ -19,6 +19,7 @@ pub trait PreviewApi {
fn load_preview(&self, component: PreviewComponent);
fn config_changed(
&self,
show_preview_ui: bool,
style: &str,
include_paths: &[PathBuf],
library_paths: &HashMap<String, PathBuf>,
@ -27,6 +28,12 @@ pub trait PreviewApi {
/// What is the current component to preview?
fn current_component(&self) -> Option<PreviewComponent>;
/// Return the currently configured `show_preview_ui` value.
///
/// This is not ideal, but we can not access the configuration without
/// a round trip into the editor.
fn show_preview_ui(&self) -> bool;
}
/// The Component to preview
@ -57,6 +64,7 @@ pub enum LspToPreviewMessage {
contents: String,
},
SetConfiguration {
show_preview_ui: bool,
style: String,
include_paths: Vec<String>,
library_paths: Vec<(String, String)>,

View file

@ -90,6 +90,7 @@ pub fn request_state(ctx: &std::rc::Rc<Context>) {
}
let style = documents.compiler_config.style.clone().unwrap_or_default();
ctx.preview.config_changed(
ctx.preview.show_preview_ui(),
&style,
&documents.compiler_config.include_paths,
&documents.compiler_config.library_paths,
@ -1221,6 +1222,7 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> {
.await?;
let document_cache = &mut ctx.document_cache.borrow_mut();
let mut show_preview_ui = false;
for v in r {
if let Some(o) = v.as_object() {
if let Some(ip) = o.get("includePaths").and_then(|v| v.as_array()) {
@ -1244,6 +1246,10 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> {
document_cache.documents.compiler_config.style = Some(style.into());
}
}
show_preview_ui = o
.get("preview")
.and_then(|v| v.as_object()?.get("show_ui")?.as_bool())
.unwrap_or_default();
}
}
@ -1254,6 +1260,7 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> {
let cc = &document_cache.documents.compiler_config;
let empty_string = String::new();
ctx.preview.config_changed(
show_preview_ui,
cc.style.as_ref().unwrap_or(&empty_string),
&cc.include_paths,
&cc.library_paths,

View file

@ -37,6 +37,7 @@ struct Previewer {
#[allow(unused)]
server_notifier: ServerNotifier,
use_external_previewer: RefCell<bool>,
show_preview_ui: RefCell<bool>,
to_show: RefCell<Option<common::PreviewComponent>>,
}
@ -103,15 +104,18 @@ impl PreviewApi for Previewer {
fn config_changed(
&self,
_show_preview_ui: bool,
_style: &str,
_include_paths: &[PathBuf],
_library_paths: &HashMap<String, PathBuf>,
) {
*self.show_preview_ui.borrow_mut() = _show_preview_ui;
if *self.use_external_previewer.borrow() {
#[cfg(feature = "preview-external")]
let _ = self.server_notifier.send_notification(
"slint/lsp_to_preview".to_string(),
crate::common::LspToPreviewMessage::SetConfiguration {
show_preview_ui: _show_preview_ui,
style: _style.to_string(),
include_paths: _include_paths
.iter()
@ -125,7 +129,7 @@ impl PreviewApi for Previewer {
);
} else {
#[cfg(feature = "preview-builtin")]
preview::config_changed(_style, _include_paths, _library_paths);
preview::config_changed(_show_preview_ui, _style, _include_paths, _library_paths);
}
}
@ -152,6 +156,10 @@ impl PreviewApi for Previewer {
fn current_component(&self) -> Option<crate::common::PreviewComponent> {
self.to_show.borrow().clone()
}
fn show_preview_ui(&self) -> bool {
*self.show_preview_ui.borrow()
}
}
#[derive(Clone, clap::Parser)]
@ -176,6 +184,10 @@ struct Cli {
/// Start the preview in full screen mode
#[arg(long, action)]
fullscreen: bool,
/// Show the preview UI
#[arg(long, default_value = "true", action = clap::ArgAction::Set)]
show_preview_ui: bool,
}
enum OutgoingRequest {
@ -265,11 +277,13 @@ fn main() {
std::env::set_var("SLINT_FULLSCREEN", "1");
}
let show_preview_ui = args.show_preview_ui;
#[cfg(feature = "preview-engine")]
{
let lsp_thread = std::thread::Builder::new()
.name("LanguageServer".into())
.spawn(|| {
.spawn(move || {
/// Make sure we quit the event loop even if we panic
struct QuitEventLoop;
impl Drop for QuitEventLoop {
@ -279,7 +293,7 @@ fn main() {
}
let quit_ui_loop = QuitEventLoop;
let threads = match run_lsp_server() {
let threads = match run_lsp_server(show_preview_ui) {
Ok(threads) => threads,
Err(error) => {
eprintln!("Error running LSP server: {}", error);
@ -304,7 +318,7 @@ fn main() {
}
}
pub fn run_lsp_server() -> Result<IoThreads> {
pub fn run_lsp_server(show_preview_ui: bool) -> Result<IoThreads> {
let (connection, io_threads) = Connection::stdio();
let (id, params) = connection.initialize_start()?;
@ -313,12 +327,16 @@ pub fn run_lsp_server() -> Result<IoThreads> {
serde_json::to_value(language::server_initialize_result(&init_param.capabilities))?;
connection.initialize_finish(id, initialize_result)?;
main_loop(connection, init_param)?;
main_loop(connection, init_param, show_preview_ui)?;
Ok(io_threads)
}
fn main_loop(connection: Connection, init_param: InitializeParams) -> Result<()> {
fn main_loop(
connection: Connection,
init_param: InitializeParams,
show_preview_ui: bool,
) -> Result<()> {
let mut rh = RequestHandler::default();
register_request_handlers(&mut rh);
@ -336,6 +354,7 @@ fn main_loop(connection: Connection, init_param: InitializeParams) -> Result<()>
#[cfg(all(feature = "preview-builtin", feature = "preview-external"))]
use_external_previewer: RefCell::new(false), // prefer internal
to_show: RefCell::new(None),
show_preview_ui: RefCell::new(show_preview_ui),
});
let mut compiler_config =
CompilerConfiguration::new(i_slint_compiler::generator::OutputFormat::Interpreter);

View file

@ -49,6 +49,8 @@ struct ContentCache {
ui_is_visible: bool,
design_mode: bool,
default_style: String,
// Duplicate this information in case the UI is not up yet.
show_preview_ui: bool,
}
static CONTENT_CACHE: std::sync::OnceLock<Mutex<ContentCache>> = std::sync::OnceLock::new();
@ -106,6 +108,7 @@ pub fn finish_parsing(ok: bool) {
}
pub fn config_changed(
show_preview_ui: bool,
style: &str,
include_paths: &[PathBuf],
library_paths: &HashMap<String, PathBuf>,
@ -116,15 +119,20 @@ pub fn config_changed(
if cache.current.style != style
|| cache.current.include_paths != include_paths
|| cache.current.library_paths != *library_paths
|| cache.show_preview_ui != show_preview_ui
{
cache.current.include_paths = include_paths.to_vec();
cache.current.library_paths = library_paths.clone();
cache.default_style = style;
cache.show_preview_ui = show_preview_ui;
let current = cache.current.clone();
let ui_is_visible = cache.ui_is_visible;
let show_preview_ui = cache.show_preview_ui;
drop(cache);
if ui_is_visible && !current.path.as_os_str().is_empty() {
set_show_preview_ui(show_preview_ui);
load_preview(current);
}
}

View file

@ -122,7 +122,7 @@ pub fn open_ui(sender: &ServerNotifier) {
}
}
let default_style = {
let (default_style, show_preview_ui) = {
let mut cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
if cache.ui_is_visible {
return; // UI is already up!
@ -132,21 +132,23 @@ pub fn open_ui(sender: &ServerNotifier) {
let mut s = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap();
*s = Some(sender.clone());
cache.default_style.clone()
(cache.default_style.clone(), cache.show_preview_ui)
};
i_slint_core::api::invoke_from_event_loop(move || {
PREVIEW_STATE.with(|preview_state| {
let mut preview_state = preview_state.borrow_mut();
open_ui_impl(&mut preview_state, default_style);
open_ui_impl(&mut preview_state, default_style, show_preview_ui);
});
})
.unwrap();
}
fn open_ui_impl(preview_state: &mut PreviewState, default_style: String) {
fn open_ui_impl(preview_state: &mut PreviewState, default_style: String, show_preview_ui: bool) {
// TODO: Handle Error!
let ui = preview_state.ui.get_or_insert_with(|| super::ui::create_ui(default_style).unwrap());
let ui = preview_state
.ui
.get_or_insert_with(|| super::ui::create_ui(default_style, show_preview_ui).unwrap());
ui.on_show_document(|url, line, column| {
ask_editor_to_show_document(
url.as_str().to_string(),
@ -218,6 +220,17 @@ pub fn notify_diagnostics(diagnostics: &[slint_interpreter::Diagnostic]) -> Opti
Some(())
}
pub fn set_show_preview_ui(show_preview_ui: bool) {
run_in_ui_thread(move || async move {
PREVIEW_STATE.with(|preview_state| {
let preview_state = preview_state.borrow();
if let Some(ui) = &preview_state.ui {
ui.set_show_preview_ui(show_preview_ui)
}
})
});
}
pub fn set_current_style(style: String) {
PREVIEW_STATE.with(move |preview_state| {
let preview_state = preview_state.borrow_mut();
@ -312,15 +325,15 @@ pub fn configure_design_mode(enabled: bool) {
/// This runs `set_preview_factory` in the UI thread
pub fn update_preview_area(compiled: slint_interpreter::ComponentDefinition, design_mode: bool) {
let default_style = {
let (default_style, show_preview_ui) = {
let cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
cache.default_style.clone()
(cache.default_style.clone(), cache.show_preview_ui)
};
PREVIEW_STATE.with(|preview_state| {
let mut preview_state = preview_state.borrow_mut();
open_ui_impl(&mut preview_state, default_style);
open_ui_impl(&mut preview_state, default_style, show_preview_ui);
let shared_handle = preview_state.handle.clone();

View file

@ -11,12 +11,10 @@ use slint_interpreter::{DiagnosticLevel, PlatformError};
slint::include_modules!();
pub fn create_ui(style: String) -> Result<PreviewUi, PlatformError> {
pub fn create_ui(style: String, show_preview_ui: bool) -> Result<PreviewUi, PlatformError> {
let ui = PreviewUi::new()?;
if std::env::var("SLINT_FULLSCREEN").is_ok() {
ui.set_hide_toolbar(true);
}
ui.set_show_preview_ui(show_preview_ui);
// design mode:
ui.on_design_mode_changed(super::set_design_mode);

View file

@ -75,7 +75,7 @@ impl PreviewConnector {
reject_c.take().call1(&JsValue::UNDEFINED,
&JsValue::from("PreviewConnector already set up.")).unwrap_throw();
} else {
match super::ui::create_ui(style) {
match super::ui::create_ui(style, true) {
Ok(ui) => {
ui.on_show_document(|url, line, column| ask_editor_to_show_document(url.as_str().to_string(), line as u32, column as u32, line as u32, column as u32));
preview_state.borrow_mut().ui = Some(ui);
@ -125,11 +125,11 @@ impl PreviewConnector {
super::set_contents(&PathBuf::from(&path), contents);
Ok(())
}
M::SetConfiguration { style, include_paths, library_paths } => {
M::SetConfiguration { show_preview_ui, style, include_paths, library_paths } => {
let ip: Vec<PathBuf> = include_paths.iter().map(PathBuf::from).collect();
let lp: HashMap<String, PathBuf> =
library_paths.iter().map(|(n, p)| (n.clone(), PathBuf::from(p))).collect();
super::config_changed(&style, &ip, &lp);
super::config_changed(show_preview_ui, &style, &ip, &lp);
Ok(())
}
M::ShowPreview { path, component, style, include_paths, library_paths } => {
@ -259,6 +259,15 @@ pub fn send_message_to_lsp(message: crate::common::PreviewToLspMessage) {
})
}
pub fn set_show_preview_ui(show_preview_ui: bool) {
PREVIEW_STATE.with(move |preview_state| {
let preview_state = preview_state.borrow_mut();
if let Some(ui) = &preview_state.ui {
ui.set_show_preview_ui(show_preview_ui)
}
});
}
pub fn set_current_style(style: String) {
PREVIEW_STATE.with(move |preview_state| {
let preview_state = preview_state.borrow_mut();

View file

@ -0,0 +1,51 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
// cSpell: ignore Heade
import { ListView, VerticalBox } from "std-widgets.slint";
export struct Diagnostics {
level: string,
message: string,
url: string,
line: int,
column: int,
}
export component DiagnosticsOverlay {
in property<[Diagnostics]> diagnostics;
callback show-document(/* url */ string, /* line */ int, /* column */ int);
if (root.diagnostics.length != 0): Rectangle {
background: #fff;
VerticalBox {
Text {
color: #000;
text: "Compilation failed:";
}
ListView {
width: parent.width - 10px;
height: parent.height - 10px;
padding: 5px;
for diag in root.diagnostics: Rectangle {
TouchArea {
mouse-cursor: pointer;
clicked => { root.show_document(diag.url, diag.line, diag.column); }
Text {
width: 100%;
wrap: word-wrap;
color: #000;
text: diag.level + ": " + diag.message;
}
}
}
}
}
}
}

View file

@ -5,22 +5,17 @@
import { Button, ComboBox, HorizontalBox, ListView, ScrollView, VerticalBox } from "std-widgets.slint";
import { HeaderBar } from "header-bar.slint";
import { Diagnostics, DiagnosticsOverlay } from "diagnostics-overlay.slint";
export struct Diagnostics {
level: string,
message: string,
url: string,
line: int,
column: int,
}
export { Diagnostics }
export component PreviewUi inherits Window {
in property<[string]> known-styles;
in property<[Diagnostics]> diagnostics;
in property<string> status-text;
in property<component-factory> preview-area <=> i-preview-area-container.component-factory;
in property<component-factory> preview-area;
in-out property<string> current-style;
in property<bool> hide-toolbar;
in property<bool> show-preview-ui;
callback design-mode-changed(/* enabled */ bool);
callback style-changed();
@ -29,8 +24,24 @@ export component PreviewUi inherits Window {
title: "Slint Live-Preview";
icon: @image-url("assets/slint-logo-small-light.png");
VerticalLayout {
if !hide_toolbar: HeaderBar {
if (!show-preview-ui): VerticalLayout {
no-ui-drawing-rect := Rectangle {
/* background: checkerboard pattern; */
ComponentContainer {
component-factory <=> root.preview-area;
width: clamp(no-ui-drawing-rect.width, self.min-width, self.max-width);
height: clamp(no-ui-drawing-rect.height, self.min-height, self.max-height);
}
// Diagnostics overlay:
DiagnosticsOverlay {
diagnostics <=> root.diagnostics;
show-document(url, line, column) => { root.show-document(url, line, column); }
}
}
}
if (show-preview-ui): VerticalLayout {
HeaderBar {
vertical-stretch: 0.0;
height: self.preferred-height;
@ -64,43 +75,16 @@ export component PreviewUi inherits Window {
drawing-rect := Rectangle {
/* background: checkerboard pattern; */
i-preview-area-container := ComponentContainer {
width: max(self.min-width, min(self.max-width, drawing-rect.width));
height: max(self.min-height, min(self.max-height, drawing-rect.height));
ComponentContainer {
component-factory <=> root.preview-area;
width: clamp(drawing-rect.width, self.min-width, self.max-width);
height: clamp(drawing-rect.height, self.min-height, self.max-height);
}
// Diagnostics overlay:
if (root.diagnostics.length != 0): Rectangle {
background: #fff;
VerticalBox {
Text {
color: #000;
text: "Compilation failed:";
}
ListView {
width: parent.width - 10px;
height: parent.height - 10px;
padding: 5px;
for diag in root.diagnostics: Rectangle {
TouchArea {
mouse-cursor: pointer;
clicked => { root.show_document(diag.url, diag.line, diag.column); }
Text {
width: 100%;
wrap: word-wrap;
color: #000;
text: diag.level + ": " + diag.message;
}
}
}
}
}
DiagnosticsOverlay {
diagnostics <=> root.diagnostics;
show-document(url, line, column) => { root.show-document(url, line, column); }
}
}
}

View file

@ -46,6 +46,7 @@ pub mod wasm_prelude {
struct Previewer {
server_notifier: ServerNotifier,
to_show: RefCell<Option<common::PreviewComponent>>,
show_preview_ui: RefCell<bool>,
}
impl PreviewApi for Previewer {
@ -94,14 +95,18 @@ impl PreviewApi for Previewer {
fn config_changed(
&self,
show_preview_ui: bool,
style: &str,
include_paths: &[PathBuf],
library_paths: &HashMap<String, PathBuf>,
) {
*self.show_preview_ui.borrow_mut() = show_preview_ui;
#[cfg(feature = "preview-external")]
let _ = self.server_notifier.send_notification(
"slint/lsp_to_preview".to_string(),
crate::common::LspToPreviewMessage::SetConfiguration {
show_preview_ui,
style: style.to_string(),
include_paths: include_paths
.iter()
@ -129,6 +134,10 @@ impl PreviewApi for Previewer {
fn current_component(&self) -> Option<crate::common::PreviewComponent> {
self.to_show.borrow().clone()
}
fn show_preview_ui(&self) -> bool {
*self.show_preview_ui.borrow()
}
}
#[derive(Clone)]
@ -269,6 +278,7 @@ pub fn create(
let preview = Rc::new(Previewer {
server_notifier: server_notifier.clone(),
to_show: Default::default(),
show_preview_ui: RefCell::new(true),
});
let init_param = serde_wasm_bindgen::from_value(init_param)?;