mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 14:51:15 +00:00
LSP: Move code around
No behavior should change in this patch! Move all the code directly related to the LSP into a `language` module, with `server_loop.rs` becoming `language.rs`, managing that module. All the preview related code is moved into `preview`, with `preview.rs` basically forwarding to `native.rs` and `wasm.rs`. Code accessed from both `language` and `preview` stayed where it was.
This commit is contained in:
parent
65f9e6f1eb
commit
4dda627d14
12 changed files with 456 additions and 454 deletions
|
@ -3,11 +3,18 @@
|
|||
|
||||
// cSpell: ignore descr rfind
|
||||
|
||||
mod completion;
|
||||
mod goto;
|
||||
mod properties;
|
||||
mod semantic_tokens;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use crate::common::PreviewApi;
|
||||
use crate::util::{map_node, map_range, map_token};
|
||||
use crate::util::{map_node, map_range, map_token, to_lsp_diag};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::wasm_prelude::*;
|
||||
use crate::{completion, goto, semantic_tokens, util};
|
||||
|
||||
use i_slint_compiler::object_tree::ElementRc;
|
||||
use i_slint_compiler::parser::{syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken};
|
||||
|
@ -432,8 +439,6 @@ pub fn query_properties_command(
|
|||
) -> Result<serde_json::Value, Error> {
|
||||
let document_cache = &mut ctx.document_cache.borrow_mut();
|
||||
|
||||
use crate::properties;
|
||||
|
||||
let text_document_uri = serde_json::from_value::<lsp_types::TextDocumentIdentifier>(
|
||||
params.get(0).ok_or("No text document provided")?.clone(),
|
||||
)?
|
||||
|
@ -468,8 +473,6 @@ pub async fn set_binding_command(
|
|||
params: &[serde_json::Value],
|
||||
ctx: &Rc<Context>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
use crate::properties;
|
||||
|
||||
let text_document = serde_json::from_value::<lsp_types::OptionalVersionedTextDocumentIdentifier>(
|
||||
params.get(0).ok_or("No text document provided")?.clone(),
|
||||
)?;
|
||||
|
@ -561,8 +564,6 @@ pub async fn remove_binding_command(
|
|||
params: &[serde_json::Value],
|
||||
ctx: &Rc<Context>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
use crate::properties;
|
||||
|
||||
let text_document = serde_json::from_value::<lsp_types::OptionalVersionedTextDocumentIdentifier>(
|
||||
params.get(0).ok_or("No text document provided")?.clone(),
|
||||
)?;
|
||||
|
@ -679,7 +680,7 @@ pub(crate) async fn reload_document_impl(
|
|||
continue;
|
||||
}
|
||||
let uri = Url::from_file_path(d.source_file().unwrap()).unwrap();
|
||||
lsp_diags.entry(uri).or_default().push(util::to_lsp_diag(&d));
|
||||
lsp_diags.entry(uri).or_default().push(to_lsp_diag(&d));
|
||||
}
|
||||
|
||||
lsp_diags
|
||||
|
@ -1113,7 +1114,7 @@ pub async fn load_configuration(ctx: &Context) -> Result<(), Error> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test::{complex_document_cache, loaded_document_cache};
|
||||
use test::{complex_document_cache, loaded_document_cache};
|
||||
|
||||
#[test]
|
||||
fn test_reload_document_invalid_contents() {
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
// cSpell: ignore rfind
|
||||
|
||||
use crate::server_loop::DocumentCache;
|
||||
use crate::util::{lookup_current_element_type, map_position};
|
||||
use super::DocumentCache;
|
||||
use crate::util::{lookup_current_element_type, map_position, with_lookup_ctx};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::wasm_prelude::*;
|
||||
use i_slint_compiler::diagnostics::Spanned;
|
||||
|
@ -131,7 +132,7 @@ pub(crate) fn completion_at(
|
|||
} else if let Some(n) = syntax_nodes::Binding::new(node.clone()) {
|
||||
if let Some(colon) = n.child_token(SyntaxKind::Colon) {
|
||||
if offset >= colon.text_range().end().into() {
|
||||
return crate::util::with_lookup_ctx(document_cache, node, |ctx| {
|
||||
return with_lookup_ctx(document_cache, node, |ctx| {
|
||||
resolve_expression_scope(ctx).map(Into::into)
|
||||
})?;
|
||||
}
|
||||
|
@ -218,7 +219,7 @@ pub(crate) fn completion_at(
|
|||
);
|
||||
}
|
||||
|
||||
return crate::util::with_lookup_ctx(document_cache, node, |ctx| {
|
||||
return with_lookup_ctx(document_cache, node, |ctx| {
|
||||
resolve_expression_scope(ctx).map(Into::into)
|
||||
})?;
|
||||
} else if let Some(q) = syntax_nodes::QualifiedName::new(node.clone()) {
|
||||
|
@ -260,7 +261,7 @@ pub(crate) fn completion_at(
|
|||
return resolve_type_scope(token, document_cache).map(Into::into);
|
||||
}
|
||||
SyntaxKind::Expression => {
|
||||
return crate::util::with_lookup_ctx(document_cache, node, |ctx| {
|
||||
return with_lookup_ctx(document_cache, node, |ctx| {
|
||||
let it = q.children_with_tokens().filter_map(|t| t.into_token());
|
||||
let mut it = it.skip_while(|t| {
|
||||
t.kind() != SyntaxKind::Identifier && t.token != token.token
|
||||
|
@ -679,15 +680,16 @@ fn add_components_to_import(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Given a source text containing the unicode emoji `🔺`, the emoji will be removed and then an autocompletion request will be done as if the cursor was there
|
||||
fn get_completions(file: &str) -> Option<Vec<CompletionItem>> {
|
||||
const CURSOR_EMOJI: char = '🔺';
|
||||
let offset = file.find(CURSOR_EMOJI).unwrap() as u32;
|
||||
let source = file.replace(CURSOR_EMOJI, "");
|
||||
let (mut dc, uri, _) = crate::test::loaded_document_cache(source);
|
||||
let (mut dc, uri, _) = crate::language::test::loaded_document_cache(source);
|
||||
|
||||
let doc = dc.documents.get_document(&uri.to_file_path().unwrap()).unwrap();
|
||||
let token = crate::server_loop::token_at_offset(doc.node.as_ref().unwrap(), offset)?;
|
||||
let token = crate::language::token_at_offset(doc.node.as_ref().unwrap(), offset)?;
|
||||
|
||||
completion_at(&mut dc, token, offset, None)
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use crate::util::map_node_and_url;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::server_loop::DocumentCache;
|
||||
use super::DocumentCache;
|
||||
use crate::util::{lookup_current_element_type, map_node_and_url, with_lookup_ctx};
|
||||
|
||||
use i_slint_compiler::diagnostics::Spanned;
|
||||
use i_slint_compiler::expression_tree::Expression;
|
||||
|
@ -14,6 +11,7 @@ use i_slint_compiler::lookup::{LookupObject, LookupResult};
|
|||
use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxNode, SyntaxToken};
|
||||
|
||||
use lsp_types::{GotoDefinitionResponse, LocationLink, Range};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn goto_definition(
|
||||
document_cache: &mut DocumentCache,
|
||||
|
@ -47,7 +45,7 @@ pub fn goto_definition(
|
|||
if token.kind() != SyntaxKind::Identifier {
|
||||
return None;
|
||||
}
|
||||
let lr = crate::util::with_lookup_ctx(document_cache, node, |ctx| {
|
||||
let lr = with_lookup_ctx(document_cache, node, |ctx| {
|
||||
let mut it = n
|
||||
.children_with_tokens()
|
||||
.filter_map(|t| t.into_token())
|
||||
|
@ -191,7 +189,7 @@ fn find_property_declaration_in_base(
|
|||
.map(|doc| &doc.local_registry)
|
||||
.unwrap_or(&global_tr);
|
||||
|
||||
let mut element_type = crate::util::lookup_current_element_type((*element).clone(), tr)?;
|
||||
let mut element_type = lookup_current_element_type((*element).clone(), tr)?;
|
||||
while let ElementType::Component(com) = element_type {
|
||||
if let Some(p) = com.root_element.borrow().property_declarations.get(prop_name) {
|
||||
return p.node.clone();
|
|
@ -1,10 +1,13 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use crate::server_loop::DocumentCache;
|
||||
use super::DocumentCache;
|
||||
use crate::util::{
|
||||
map_node, map_node_and_url, map_position, map_range, to_lsp_diag, with_property_lookup_ctx,
|
||||
ExpressionContextInfo,
|
||||
};
|
||||
use crate::Error;
|
||||
|
||||
use crate::util::{map_node, map_node_and_url, map_position, map_range, ExpressionContextInfo};
|
||||
use i_slint_compiler::diagnostics::{BuildDiagnostics, Spanned};
|
||||
use i_slint_compiler::langtype::{ElementType, Type};
|
||||
use i_slint_compiler::object_tree::{Element, ElementRc, PropertyDeclaration, PropertyVisibility};
|
||||
|
@ -492,9 +495,7 @@ fn set_binding_on_existing_property(
|
|||
.flatten();
|
||||
|
||||
Ok((
|
||||
SetBindingResponse {
|
||||
diagnostics: diag.iter().map(crate::util::to_lsp_diag).collect::<Vec<_>>(),
|
||||
},
|
||||
SetBindingResponse { diagnostics: diag.iter().map(to_lsp_diag).collect::<Vec<_>>() },
|
||||
workspace_edit,
|
||||
))
|
||||
}
|
||||
|
@ -613,9 +614,7 @@ fn set_binding_on_known_property(
|
|||
.flatten();
|
||||
|
||||
Ok((
|
||||
SetBindingResponse {
|
||||
diagnostics: diag.iter().map(crate::util::to_lsp_diag).collect::<Vec<_>>(),
|
||||
},
|
||||
SetBindingResponse { diagnostics: diag.iter().map(to_lsp_diag).collect::<Vec<_>>() },
|
||||
workspace_edit,
|
||||
))
|
||||
}
|
||||
|
@ -661,7 +660,7 @@ pub(crate) fn set_binding(
|
|||
if let Some(node) = element.node.as_ref() {
|
||||
let expr_context_info =
|
||||
ExpressionContextInfo::new(node.clone(), property_name.to_string(), false);
|
||||
crate::util::with_property_lookup_ctx(document_cache, &expr_context_info, |ctx| {
|
||||
with_property_lookup_ctx(document_cache, &expr_context_info, |ctx| {
|
||||
let expression =
|
||||
i_slint_compiler::expression_tree::Expression::from_binding_expression_node(
|
||||
expression_node,
|
||||
|
@ -688,7 +687,7 @@ pub(crate) fn set_binding(
|
|||
);
|
||||
return Ok((
|
||||
SetBindingResponse {
|
||||
diagnostics: diag.iter().map(crate::util::to_lsp_diag).collect::<Vec<_>>(),
|
||||
diagnostics: diag.iter().map(to_lsp_diag).collect::<Vec<_>>(),
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
@ -808,9 +807,8 @@ pub(crate) fn remove_binding(
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::server_loop;
|
||||
|
||||
use crate::test::{complex_document_cache, loaded_document_cache};
|
||||
use crate::language;
|
||||
use crate::language::test::{complex_document_cache, loaded_document_cache};
|
||||
|
||||
fn find_property<'a>(
|
||||
properties: &'a [PropertyInformation],
|
||||
|
@ -826,7 +824,7 @@ mod tests {
|
|||
url: &lsp_types::Url,
|
||||
) -> Option<(ElementRc, Vec<PropertyInformation>)> {
|
||||
let element =
|
||||
server_loop::element_at_position(dc, url, &lsp_types::Position { line, character })?;
|
||||
language::element_at_position(dc, url, &lsp_types::Position { line, character })?;
|
||||
Some((element.clone(), get_properties(&element)))
|
||||
}
|
||||
|
||||
|
@ -874,8 +872,7 @@ mod tests {
|
|||
fn test_element_information() {
|
||||
let (mut dc, url, _) = complex_document_cache();
|
||||
let element =
|
||||
server_loop::element_at_position(&mut dc, &url, &lsp_types::Position::new(33, 4))
|
||||
.unwrap();
|
||||
language::element_at_position(&mut dc, &url, &lsp_types::Position::new(33, 4)).unwrap();
|
||||
|
||||
let result = get_element_information(&element);
|
||||
|
|
@ -8,7 +8,7 @@ use lsp_types::{
|
|||
SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens, SemanticTokensResult,
|
||||
};
|
||||
|
||||
use crate::server_loop::DocumentCache;
|
||||
use super::DocumentCache;
|
||||
|
||||
/// Give all the used types/modifier a number in an indexed array
|
||||
macro_rules! declare_legend {
|
|
@ -7,7 +7,7 @@ use lsp_types::{Diagnostic, Url};
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::server_loop::{reload_document_impl, DocumentCache};
|
||||
use crate::language::{reload_document_impl, DocumentCache};
|
||||
|
||||
/// Create an empty `DocumentCache`
|
||||
pub fn empty_document_cache() -> DocumentCache {
|
|
@ -4,25 +4,20 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
mod common;
|
||||
mod completion;
|
||||
mod goto;
|
||||
mod lsp_ext;
|
||||
mod language;
|
||||
pub mod lsp_ext;
|
||||
#[cfg(feature = "preview")]
|
||||
mod preview;
|
||||
mod properties;
|
||||
mod semantic_tokens;
|
||||
mod server_loop;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod util;
|
||||
pub mod util;
|
||||
|
||||
use common::PreviewApi;
|
||||
use language::*;
|
||||
|
||||
use i_slint_compiler::CompilerConfiguration;
|
||||
use lsp_types::notification::{
|
||||
DidChangeConfiguration, DidChangeTextDocument, DidOpenTextDocument, Notification,
|
||||
};
|
||||
use lsp_types::{DidChangeTextDocumentParams, DidOpenTextDocumentParams, InitializeParams};
|
||||
use server_loop::*;
|
||||
|
||||
use clap::Parser;
|
||||
use lsp_server::{Connection, ErrorCode, Message, RequestId, Response};
|
||||
|
@ -230,7 +225,7 @@ pub fn run_lsp_server() -> Result<(), Error> {
|
|||
|
||||
let init_param: InitializeParams = serde_json::from_value(params).unwrap();
|
||||
let initialize_result =
|
||||
serde_json::to_value(server_loop::server_initialize_result(&init_param.capabilities))?;
|
||||
serde_json::to_value(language::server_initialize_result(&init_param.capabilities))?;
|
||||
connection.initialize_finish(id, initialize_result)?;
|
||||
|
||||
main_loop(connection, init_param)?;
|
||||
|
|
|
@ -1,390 +1,12 @@
|
|||
// 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 condvar
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
|
||||
use crate::common::{PostLoadBehavior, PreviewComponent};
|
||||
use crate::lsp_ext::{Health, ServerStatusNotification, ServerStatusParams};
|
||||
use lsp_types::notification::Notification;
|
||||
use once_cell::sync::Lazy;
|
||||
use slint_interpreter::ComponentHandle;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Condvar, Mutex};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum RequestedGuiEventLoopState {
|
||||
/// The UI event loop hasn't been started yet because no preview has been requested
|
||||
Uninitialized,
|
||||
/// The LSP thread requested the UI loop to start because a preview was requested,
|
||||
/// But the loop hasn't been started yet
|
||||
StartLoop,
|
||||
/// The Loop is now started so the LSP thread can start posting events
|
||||
LoopStated,
|
||||
/// The LSP thread requested the application to be terminated
|
||||
QuitLoop,
|
||||
}
|
||||
|
||||
static GUI_EVENT_LOOP_NOTIFIER: Lazy<Condvar> = Lazy::new(Condvar::new);
|
||||
static GUI_EVENT_LOOP_STATE_REQUEST: Lazy<Mutex<RequestedGuiEventLoopState>> =
|
||||
Lazy::new(|| Mutex::new(RequestedGuiEventLoopState::Uninitialized));
|
||||
|
||||
fn run_in_ui_thread<F: Future<Output = ()> + 'static>(
|
||||
create_future: impl Send + FnOnce() -> F + 'static,
|
||||
) {
|
||||
// Wake up the main thread to start the event loop, if possible
|
||||
{
|
||||
let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
if *state_request == RequestedGuiEventLoopState::Uninitialized {
|
||||
*state_request = RequestedGuiEventLoopState::StartLoop;
|
||||
GUI_EVENT_LOOP_NOTIFIER.notify_one();
|
||||
}
|
||||
// We don't want to call post_event before the loop is properly initialized
|
||||
while *state_request == RequestedGuiEventLoopState::StartLoop {
|
||||
state_request = GUI_EVENT_LOOP_NOTIFIER.wait(state_request).unwrap();
|
||||
}
|
||||
}
|
||||
i_slint_core::api::invoke_from_event_loop(move || {
|
||||
i_slint_core::future::spawn_local(create_future()).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn start_ui_event_loop() {
|
||||
{
|
||||
let mut state_requested = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
|
||||
while *state_requested == RequestedGuiEventLoopState::Uninitialized {
|
||||
state_requested = GUI_EVENT_LOOP_NOTIFIER.wait(state_requested).unwrap();
|
||||
}
|
||||
|
||||
if *state_requested == RequestedGuiEventLoopState::QuitLoop {
|
||||
return;
|
||||
}
|
||||
|
||||
if *state_requested == RequestedGuiEventLoopState::StartLoop {
|
||||
// make sure the backend is initialized
|
||||
i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
|
||||
// Send an event so that once the loop is started, we notify the LSP thread that it can send more events
|
||||
i_slint_core::api::invoke_from_event_loop(|| {
|
||||
let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
if *state_request == RequestedGuiEventLoopState::StartLoop {
|
||||
*state_request = RequestedGuiEventLoopState::LoopStated;
|
||||
GUI_EVENT_LOOP_NOTIFIER.notify_one();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
i_slint_backend_selector::with_platform(|b| {
|
||||
b.set_event_loop_quit_on_last_window_closed(false);
|
||||
b.run_event_loop()
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn quit_ui_event_loop() {
|
||||
// Wake up the main thread, in case it wasn't woken up earlier. If it wasn't, then don't request
|
||||
// a start of the event loop.
|
||||
{
|
||||
let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
*state_request = RequestedGuiEventLoopState::QuitLoop;
|
||||
GUI_EVENT_LOOP_NOTIFIER.notify_one();
|
||||
}
|
||||
|
||||
let _ = i_slint_core::api::quit_event_loop();
|
||||
|
||||
// Make sure then sender channel gets dropped
|
||||
if let Some(cache) = CONTENT_CACHE.get() {
|
||||
let mut cache = cache.lock().unwrap();
|
||||
cache.sender = None;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load_preview(
|
||||
sender: crate::ServerNotifier,
|
||||
component: PreviewComponent,
|
||||
post_load_behavior: PostLoadBehavior,
|
||||
) {
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static PENDING_EVENTS: AtomicU32 = AtomicU32::new(0);
|
||||
if PENDING_EVENTS.load(Ordering::SeqCst) > 0 {
|
||||
return;
|
||||
}
|
||||
PENDING_EVENTS.fetch_add(1, Ordering::SeqCst);
|
||||
run_in_ui_thread(move || async move {
|
||||
PENDING_EVENTS.fetch_sub(1, Ordering::SeqCst);
|
||||
reload_preview(sender, component, post_load_behavior).await
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContentCache {
|
||||
source_code: HashMap<PathBuf, String>,
|
||||
dependency: HashSet<PathBuf>,
|
||||
current: PreviewComponent,
|
||||
sender: Option<crate::ServerNotifier>,
|
||||
highlight: Option<(PathBuf, u32)>,
|
||||
design_mode: bool,
|
||||
}
|
||||
|
||||
static CONTENT_CACHE: once_cell::sync::OnceCell<Mutex<ContentCache>> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
|
||||
pub fn set_contents(path: &Path, content: String) {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.source_code.insert(path.to_owned(), content);
|
||||
if cache.dependency.contains(path) {
|
||||
let current = cache.current.clone();
|
||||
let sender = cache.sender.clone();
|
||||
drop(cache);
|
||||
if let Some(sender) = sender {
|
||||
load_preview(sender, current, PostLoadBehavior::DoNothing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_changed(style: &str, include_paths: &[PathBuf]) {
|
||||
if let Some(cache) = CONTENT_CACHE.get() {
|
||||
let mut cache = cache.lock().unwrap();
|
||||
let style = style.to_string();
|
||||
if cache.current.style != style || cache.current.include_paths != include_paths {
|
||||
cache.current.style = style;
|
||||
cache.current.include_paths = include_paths.to_vec();
|
||||
let current = cache.current.clone();
|
||||
let sender = cache.sender.clone();
|
||||
drop(cache);
|
||||
if let Some(sender) = sender {
|
||||
load_preview(sender, current, PostLoadBehavior::DoNothing);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// If the file is in the cache, returns it.
|
||||
/// In any was, register it as a dependency
|
||||
fn get_file_from_cache(path: PathBuf) -> Option<String> {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
let r = cache.source_code.get(&path).cloned();
|
||||
cache.dependency.insert(path);
|
||||
r
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PreviewState {
|
||||
handle: Option<slint_interpreter::ComponentInstance>,
|
||||
}
|
||||
thread_local! {static PREVIEW_STATE: std::cell::RefCell<PreviewState> = Default::default();}
|
||||
|
||||
pub fn design_mode() -> bool {
|
||||
let cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.design_mode
|
||||
}
|
||||
|
||||
pub fn set_design_mode(sender: crate::ServerNotifier, enable: bool) {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.design_mode = enable;
|
||||
|
||||
configure_design_mode(enable, &sender);
|
||||
send_notification(
|
||||
&sender,
|
||||
if enable { "Design mode enabled." } else { "Design mode disabled." },
|
||||
Health::Ok,
|
||||
);
|
||||
}
|
||||
|
||||
fn show_document_request_from_element_callback(
|
||||
file: &str,
|
||||
start_line: u32,
|
||||
start_column: u32,
|
||||
_end_line: u32,
|
||||
_end_column: u32,
|
||||
) -> Option<lsp_types::ShowDocumentParams> {
|
||||
use lsp_types::{Position, Range, ShowDocumentParams, Url};
|
||||
|
||||
let start_pos = Position::new(start_line - 1, start_column);
|
||||
// let end_pos = Position::new(end_line - 1, end_column);
|
||||
// Place the cursor at the start of the range and do not mark up the entire range!
|
||||
let selection = Some(Range::new(start_pos, start_pos));
|
||||
|
||||
Url::from_file_path(file).ok().map(|uri| ShowDocumentParams {
|
||||
uri,
|
||||
external: Some(false),
|
||||
take_focus: Some(true),
|
||||
selection,
|
||||
})
|
||||
}
|
||||
|
||||
fn configure_design_mode(enabled: bool, sender: &crate::ServerNotifier) {
|
||||
let sender = sender.clone();
|
||||
run_in_ui_thread(move || async move {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
if let Some(handle) = &preview_state.handle {
|
||||
handle.set_design_mode(enabled);
|
||||
|
||||
handle.on_element_selected(Box::new(
|
||||
move |file: &str,
|
||||
start_line: u32,
|
||||
start_column: u32,
|
||||
end_line: u32,
|
||||
end_column: u32| {
|
||||
let Some(params) = show_document_request_from_element_callback(
|
||||
file,
|
||||
start_line,
|
||||
start_column - 1,
|
||||
end_line,
|
||||
end_column - 1,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let Ok(fut) =
|
||||
sender.send_request::<lsp_types::request::ShowDocument>(params)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
i_slint_core::future::spawn_local(fut).unwrap();
|
||||
},
|
||||
));
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async fn reload_preview(
|
||||
sender: crate::ServerNotifier,
|
||||
preview_component: PreviewComponent,
|
||||
post_load_behavior: PostLoadBehavior,
|
||||
) {
|
||||
send_notification(&sender, "Loading Preview…", Health::Ok);
|
||||
|
||||
let design_mode;
|
||||
|
||||
{
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.dependency.clear();
|
||||
cache.current = preview_component.clone();
|
||||
design_mode = cache.design_mode;
|
||||
}
|
||||
|
||||
let mut builder = slint_interpreter::ComponentCompiler::default();
|
||||
|
||||
if !preview_component.style.is_empty() {
|
||||
builder.set_style(preview_component.style);
|
||||
}
|
||||
builder.set_include_paths(preview_component.include_paths);
|
||||
|
||||
builder.set_file_loader(|path| {
|
||||
let path = path.to_owned();
|
||||
Box::pin(async move { get_file_from_cache(path).map(Result::Ok) })
|
||||
});
|
||||
|
||||
let compiled = if let Some(mut from_cache) = get_file_from_cache(preview_component.path.clone())
|
||||
{
|
||||
if let Some(component) = &preview_component.component {
|
||||
from_cache =
|
||||
format!("{}\nexport component _Preview inherits {} {{ }}\n", from_cache, component);
|
||||
}
|
||||
builder.build_from_source(from_cache, preview_component.path).await
|
||||
} else {
|
||||
builder.build_from_path(preview_component.path).await
|
||||
};
|
||||
|
||||
notify_diagnostics(builder.diagnostics(), &sender);
|
||||
|
||||
if let Some(compiled) = compiled {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let mut preview_state = preview_state.borrow_mut();
|
||||
let handle = if let Some(handle) = preview_state.handle.take() {
|
||||
let window = handle.window();
|
||||
let handle = compiled.create_with_existing_window(window).unwrap();
|
||||
match post_load_behavior {
|
||||
PostLoadBehavior::ShowAfterLoad => handle.show().unwrap(),
|
||||
PostLoadBehavior::DoNothing => {}
|
||||
}
|
||||
handle
|
||||
} else {
|
||||
let handle = compiled.create().unwrap();
|
||||
handle.show().unwrap();
|
||||
handle
|
||||
};
|
||||
if let Some((path, offset)) =
|
||||
CONTENT_CACHE.get().and_then(|c| c.lock().unwrap().highlight.clone())
|
||||
{
|
||||
handle.highlight(path, offset);
|
||||
}
|
||||
preview_state.handle = Some(handle);
|
||||
});
|
||||
send_notification(&sender, "Preview Loaded", Health::Ok);
|
||||
} else {
|
||||
send_notification(&sender, "Preview not updated", Health::Error);
|
||||
}
|
||||
|
||||
configure_design_mode(design_mode, &sender);
|
||||
|
||||
CONTENT_CACHE.get_or_init(Default::default).lock().unwrap().sender.replace(sender);
|
||||
}
|
||||
|
||||
fn notify_diagnostics(
|
||||
diagnostics: &[slint_interpreter::Diagnostic],
|
||||
sender: &crate::ServerNotifier,
|
||||
) -> Option<()> {
|
||||
let mut lsp_diags: HashMap<lsp_types::Url, Vec<lsp_types::Diagnostic>> = Default::default();
|
||||
for d in diagnostics {
|
||||
if d.source_file().map_or(true, |f| f.is_relative()) {
|
||||
continue;
|
||||
}
|
||||
let uri = lsp_types::Url::from_file_path(d.source_file().unwrap()).unwrap();
|
||||
lsp_diags.entry(uri).or_default().push(crate::util::to_lsp_diag(d));
|
||||
}
|
||||
|
||||
for (uri, diagnostics) in lsp_diags {
|
||||
sender
|
||||
.send_notification(
|
||||
"textDocument/publishDiagnostics".into(),
|
||||
lsp_types::PublishDiagnosticsParams { uri, diagnostics, version: None },
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn send_notification(sender: &crate::ServerNotifier, arg: &str, health: Health) {
|
||||
sender
|
||||
.send_notification(
|
||||
ServerStatusNotification::METHOD.into(),
|
||||
ServerStatusParams { health, quiescent: false, message: Some(arg.into()) },
|
||||
)
|
||||
.unwrap_or_else(|e| eprintln!("Error sending notification: {:?}", e));
|
||||
}
|
||||
|
||||
/// Highlight the element pointed at the offset in the path.
|
||||
/// When path is None, remove the highlight.
|
||||
pub fn highlight(path: Option<PathBuf>, offset: u32) {
|
||||
let highlight = path.map(|x| (x, offset));
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
|
||||
if cache.highlight == highlight {
|
||||
return;
|
||||
}
|
||||
cache.highlight = highlight;
|
||||
|
||||
if cache.highlight.as_ref().map_or(true, |(path, _)| cache.dependency.contains(path)) {
|
||||
run_in_ui_thread(move || async move {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
if let (Some(cache), Some(handle)) =
|
||||
(CONTENT_CACHE.get(), preview_state.handle.as_ref())
|
||||
{
|
||||
if let Some((path, offset)) = cache.lock().unwrap().highlight.clone() {
|
||||
handle.highlight(path, offset);
|
||||
} else {
|
||||
handle.highlight(PathBuf::default(), 0);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod native;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use native::*;
|
||||
|
|
391
tools/lsp/preview/native.rs
Normal file
391
tools/lsp/preview/native.rs
Normal file
|
@ -0,0 +1,391 @@
|
|||
// 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 condvar
|
||||
|
||||
use crate::common::{PostLoadBehavior, PreviewComponent};
|
||||
use crate::lsp_ext::{Health, ServerStatusNotification, ServerStatusParams};
|
||||
|
||||
use lsp_types::notification::Notification;
|
||||
use once_cell::sync::Lazy;
|
||||
use slint_interpreter::ComponentHandle;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Condvar, Mutex};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum RequestedGuiEventLoopState {
|
||||
/// The UI event loop hasn't been started yet because no preview has been requested
|
||||
Uninitialized,
|
||||
/// The LSP thread requested the UI loop to start because a preview was requested,
|
||||
/// But the loop hasn't been started yet
|
||||
StartLoop,
|
||||
/// The Loop is now started so the LSP thread can start posting events
|
||||
LoopStated,
|
||||
/// The LSP thread requested the application to be terminated
|
||||
QuitLoop,
|
||||
}
|
||||
|
||||
static GUI_EVENT_LOOP_NOTIFIER: Lazy<Condvar> = Lazy::new(Condvar::new);
|
||||
static GUI_EVENT_LOOP_STATE_REQUEST: Lazy<Mutex<RequestedGuiEventLoopState>> =
|
||||
Lazy::new(|| Mutex::new(RequestedGuiEventLoopState::Uninitialized));
|
||||
|
||||
fn run_in_ui_thread<F: Future<Output = ()> + 'static>(
|
||||
create_future: impl Send + FnOnce() -> F + 'static,
|
||||
) {
|
||||
// Wake up the main thread to start the event loop, if possible
|
||||
{
|
||||
let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
if *state_request == RequestedGuiEventLoopState::Uninitialized {
|
||||
*state_request = RequestedGuiEventLoopState::StartLoop;
|
||||
GUI_EVENT_LOOP_NOTIFIER.notify_one();
|
||||
}
|
||||
// We don't want to call post_event before the loop is properly initialized
|
||||
while *state_request == RequestedGuiEventLoopState::StartLoop {
|
||||
state_request = GUI_EVENT_LOOP_NOTIFIER.wait(state_request).unwrap();
|
||||
}
|
||||
}
|
||||
i_slint_core::api::invoke_from_event_loop(move || {
|
||||
i_slint_core::future::spawn_local(create_future()).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn start_ui_event_loop() {
|
||||
{
|
||||
let mut state_requested = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
|
||||
while *state_requested == RequestedGuiEventLoopState::Uninitialized {
|
||||
state_requested = GUI_EVENT_LOOP_NOTIFIER.wait(state_requested).unwrap();
|
||||
}
|
||||
|
||||
if *state_requested == RequestedGuiEventLoopState::QuitLoop {
|
||||
return;
|
||||
}
|
||||
|
||||
if *state_requested == RequestedGuiEventLoopState::StartLoop {
|
||||
// make sure the backend is initialized
|
||||
i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
|
||||
// Send an event so that once the loop is started, we notify the LSP thread that it can send more events
|
||||
i_slint_core::api::invoke_from_event_loop(|| {
|
||||
let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
if *state_request == RequestedGuiEventLoopState::StartLoop {
|
||||
*state_request = RequestedGuiEventLoopState::LoopStated;
|
||||
GUI_EVENT_LOOP_NOTIFIER.notify_one();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
i_slint_backend_selector::with_platform(|b| {
|
||||
b.set_event_loop_quit_on_last_window_closed(false);
|
||||
b.run_event_loop()
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn quit_ui_event_loop() {
|
||||
// Wake up the main thread, in case it wasn't woken up earlier. If it wasn't, then don't request
|
||||
// a start of the event loop.
|
||||
{
|
||||
let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
|
||||
*state_request = RequestedGuiEventLoopState::QuitLoop;
|
||||
GUI_EVENT_LOOP_NOTIFIER.notify_one();
|
||||
}
|
||||
|
||||
let _ = i_slint_core::api::quit_event_loop();
|
||||
|
||||
// Make sure then sender channel gets dropped
|
||||
if let Some(cache) = CONTENT_CACHE.get() {
|
||||
let mut cache = cache.lock().unwrap();
|
||||
cache.sender = None;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load_preview(
|
||||
sender: crate::ServerNotifier,
|
||||
component: PreviewComponent,
|
||||
post_load_behavior: PostLoadBehavior,
|
||||
) {
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
static PENDING_EVENTS: AtomicU32 = AtomicU32::new(0);
|
||||
if PENDING_EVENTS.load(Ordering::SeqCst) > 0 {
|
||||
return;
|
||||
}
|
||||
PENDING_EVENTS.fetch_add(1, Ordering::SeqCst);
|
||||
run_in_ui_thread(move || async move {
|
||||
PENDING_EVENTS.fetch_sub(1, Ordering::SeqCst);
|
||||
reload_preview(sender, component, post_load_behavior).await
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContentCache {
|
||||
source_code: HashMap<PathBuf, String>,
|
||||
dependency: HashSet<PathBuf>,
|
||||
current: PreviewComponent,
|
||||
sender: Option<crate::ServerNotifier>,
|
||||
highlight: Option<(PathBuf, u32)>,
|
||||
design_mode: bool,
|
||||
}
|
||||
|
||||
static CONTENT_CACHE: once_cell::sync::OnceCell<Mutex<ContentCache>> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
|
||||
pub fn set_contents(path: &Path, content: String) {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.source_code.insert(path.to_owned(), content);
|
||||
if cache.dependency.contains(path) {
|
||||
let current = cache.current.clone();
|
||||
let sender = cache.sender.clone();
|
||||
drop(cache);
|
||||
if let Some(sender) = sender {
|
||||
load_preview(sender, current, PostLoadBehavior::DoNothing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_changed(style: &str, include_paths: &[PathBuf]) {
|
||||
if let Some(cache) = CONTENT_CACHE.get() {
|
||||
let mut cache = cache.lock().unwrap();
|
||||
let style = style.to_string();
|
||||
if cache.current.style != style || cache.current.include_paths != include_paths {
|
||||
cache.current.style = style;
|
||||
cache.current.include_paths = include_paths.to_vec();
|
||||
let current = cache.current.clone();
|
||||
let sender = cache.sender.clone();
|
||||
drop(cache);
|
||||
if let Some(sender) = sender {
|
||||
load_preview(sender, current, PostLoadBehavior::DoNothing);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// If the file is in the cache, returns it.
|
||||
/// In any was, register it as a dependency
|
||||
fn get_file_from_cache(path: PathBuf) -> Option<String> {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
let r = cache.source_code.get(&path).cloned();
|
||||
cache.dependency.insert(path);
|
||||
r
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PreviewState {
|
||||
handle: Option<slint_interpreter::ComponentInstance>,
|
||||
}
|
||||
thread_local! {static PREVIEW_STATE: std::cell::RefCell<PreviewState> = Default::default();}
|
||||
|
||||
pub fn design_mode() -> bool {
|
||||
let cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.design_mode
|
||||
}
|
||||
|
||||
pub fn set_design_mode(sender: crate::ServerNotifier, enable: bool) {
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.design_mode = enable;
|
||||
|
||||
configure_design_mode(enable, &sender);
|
||||
send_notification(
|
||||
&sender,
|
||||
if enable { "Design mode enabled." } else { "Design mode disabled." },
|
||||
Health::Ok,
|
||||
);
|
||||
}
|
||||
|
||||
fn show_document_request_from_element_callback(
|
||||
file: &str,
|
||||
start_line: u32,
|
||||
start_column: u32,
|
||||
_end_line: u32,
|
||||
_end_column: u32,
|
||||
) -> Option<lsp_types::ShowDocumentParams> {
|
||||
use lsp_types::{Position, Range, ShowDocumentParams, Url};
|
||||
|
||||
let start_pos = Position::new(start_line - 1, start_column);
|
||||
// let end_pos = Position::new(end_line - 1, end_column);
|
||||
// Place the cursor at the start of the range and do not mark up the entire range!
|
||||
let selection = Some(Range::new(start_pos, start_pos));
|
||||
|
||||
Url::from_file_path(file).ok().map(|uri| ShowDocumentParams {
|
||||
uri,
|
||||
external: Some(false),
|
||||
take_focus: Some(true),
|
||||
selection,
|
||||
})
|
||||
}
|
||||
|
||||
fn configure_design_mode(enabled: bool, sender: &crate::ServerNotifier) {
|
||||
let sender = sender.clone();
|
||||
run_in_ui_thread(move || async move {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
if let Some(handle) = &preview_state.handle {
|
||||
handle.set_design_mode(enabled);
|
||||
|
||||
handle.on_element_selected(Box::new(
|
||||
move |file: &str,
|
||||
start_line: u32,
|
||||
start_column: u32,
|
||||
end_line: u32,
|
||||
end_column: u32| {
|
||||
let Some(params) = show_document_request_from_element_callback(
|
||||
file,
|
||||
start_line,
|
||||
start_column - 1,
|
||||
end_line,
|
||||
end_column - 1,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let Ok(fut) =
|
||||
sender.send_request::<lsp_types::request::ShowDocument>(params)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
i_slint_core::future::spawn_local(fut).unwrap();
|
||||
},
|
||||
));
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async fn reload_preview(
|
||||
sender: crate::ServerNotifier,
|
||||
preview_component: PreviewComponent,
|
||||
post_load_behavior: PostLoadBehavior,
|
||||
) {
|
||||
send_notification(&sender, "Loading Preview…", Health::Ok);
|
||||
|
||||
let design_mode;
|
||||
|
||||
{
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
cache.dependency.clear();
|
||||
cache.current = preview_component.clone();
|
||||
design_mode = cache.design_mode;
|
||||
}
|
||||
|
||||
let mut builder = slint_interpreter::ComponentCompiler::default();
|
||||
|
||||
if !preview_component.style.is_empty() {
|
||||
builder.set_style(preview_component.style);
|
||||
}
|
||||
builder.set_include_paths(preview_component.include_paths);
|
||||
|
||||
builder.set_file_loader(|path| {
|
||||
let path = path.to_owned();
|
||||
Box::pin(async move { get_file_from_cache(path).map(Result::Ok) })
|
||||
});
|
||||
|
||||
let compiled = if let Some(mut from_cache) = get_file_from_cache(preview_component.path.clone())
|
||||
{
|
||||
if let Some(component) = &preview_component.component {
|
||||
from_cache =
|
||||
format!("{}\nexport component _Preview inherits {} {{ }}\n", from_cache, component);
|
||||
}
|
||||
builder.build_from_source(from_cache, preview_component.path).await
|
||||
} else {
|
||||
builder.build_from_path(preview_component.path).await
|
||||
};
|
||||
|
||||
notify_diagnostics(builder.diagnostics(), &sender);
|
||||
|
||||
if let Some(compiled) = compiled {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let mut preview_state = preview_state.borrow_mut();
|
||||
let handle = if let Some(handle) = preview_state.handle.take() {
|
||||
let window = handle.window();
|
||||
let handle = compiled.create_with_existing_window(window).unwrap();
|
||||
match post_load_behavior {
|
||||
PostLoadBehavior::ShowAfterLoad => handle.show().unwrap(),
|
||||
PostLoadBehavior::DoNothing => {}
|
||||
}
|
||||
handle
|
||||
} else {
|
||||
let handle = compiled.create().unwrap();
|
||||
handle.show().unwrap();
|
||||
handle
|
||||
};
|
||||
if let Some((path, offset)) =
|
||||
CONTENT_CACHE.get().and_then(|c| c.lock().unwrap().highlight.clone())
|
||||
{
|
||||
handle.highlight(path, offset);
|
||||
}
|
||||
preview_state.handle = Some(handle);
|
||||
});
|
||||
send_notification(&sender, "Preview Loaded", Health::Ok);
|
||||
} else {
|
||||
send_notification(&sender, "Preview not updated", Health::Error);
|
||||
}
|
||||
|
||||
configure_design_mode(design_mode, &sender);
|
||||
|
||||
CONTENT_CACHE.get_or_init(Default::default).lock().unwrap().sender.replace(sender);
|
||||
}
|
||||
|
||||
fn notify_diagnostics(
|
||||
diagnostics: &[slint_interpreter::Diagnostic],
|
||||
sender: &crate::ServerNotifier,
|
||||
) -> Option<()> {
|
||||
let mut lsp_diags: HashMap<lsp_types::Url, Vec<lsp_types::Diagnostic>> = Default::default();
|
||||
for d in diagnostics {
|
||||
if d.source_file().map_or(true, |f| f.is_relative()) {
|
||||
continue;
|
||||
}
|
||||
let uri = lsp_types::Url::from_file_path(d.source_file().unwrap()).unwrap();
|
||||
lsp_diags.entry(uri).or_default().push(crate::util::to_lsp_diag(d));
|
||||
}
|
||||
|
||||
for (uri, diagnostics) in lsp_diags {
|
||||
sender
|
||||
.send_notification(
|
||||
"textDocument/publishDiagnostics".into(),
|
||||
lsp_types::PublishDiagnosticsParams { uri, diagnostics, version: None },
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn send_notification(sender: &crate::ServerNotifier, arg: &str, health: Health) {
|
||||
sender
|
||||
.send_notification(
|
||||
ServerStatusNotification::METHOD.into(),
|
||||
ServerStatusParams { health, quiescent: false, message: Some(arg.into()) },
|
||||
)
|
||||
.unwrap_or_else(|e| eprintln!("Error sending notification: {:?}", e));
|
||||
}
|
||||
|
||||
/// Highlight the element pointed at the offset in the path.
|
||||
/// When path is None, remove the highlight.
|
||||
pub fn highlight(path: Option<PathBuf>, offset: u32) {
|
||||
let highlight = path.map(|x| (x, offset));
|
||||
let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
|
||||
if cache.highlight == highlight {
|
||||
return;
|
||||
}
|
||||
cache.highlight = highlight;
|
||||
|
||||
if cache.highlight.as_ref().map_or(true, |(path, _)| cache.dependency.contains(path)) {
|
||||
run_in_ui_thread(move || async move {
|
||||
PREVIEW_STATE.with(|preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
if let (Some(cache), Some(handle)) =
|
||||
(CONTENT_CACHE.get(), preview_state.handle.as_ref())
|
||||
{
|
||||
if let Some((path, offset)) = cache.lock().unwrap().highlight.clone() {
|
||||
handle.highlight(path, offset);
|
||||
} else {
|
||||
handle.highlight(PathBuf::default(), 0);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use crate::server_loop::DocumentCache;
|
||||
use super::DocumentCache;
|
||||
|
||||
use i_slint_compiler::diagnostics::{DiagnosticLevel, SourceFile, Spanned};
|
||||
use i_slint_compiler::langtype::{ElementType, Type};
|
||||
|
|
|
@ -4,21 +4,17 @@
|
|||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
mod common;
|
||||
mod completion;
|
||||
mod goto;
|
||||
mod lsp_ext;
|
||||
mod properties;
|
||||
mod semantic_tokens;
|
||||
mod server_loop;
|
||||
mod util;
|
||||
mod language;
|
||||
pub mod lsp_ext;
|
||||
#[cfg(feature = "preview")]
|
||||
mod wasm_preview;
|
||||
mod preview;
|
||||
pub mod util;
|
||||
|
||||
use common::PreviewApi;
|
||||
use i_slint_compiler::CompilerConfiguration;
|
||||
use js_sys::Function;
|
||||
pub use language::{Context, DocumentCache, Error, RequestHandler};
|
||||
use serde::Serialize;
|
||||
pub use server_loop::{Context, DocumentCache, Error, RequestHandler};
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::io::ErrorKind;
|
||||
|
@ -229,7 +225,7 @@ pub fn create(
|
|||
let reentry_guard = Rc::new(RefCell::new(ReentryGuard::default()));
|
||||
|
||||
let mut rh = RequestHandler::default();
|
||||
server_loop::register_request_handlers(&mut rh);
|
||||
language::register_request_handlers(&mut rh);
|
||||
|
||||
Ok(SlintServer {
|
||||
ctx: Rc::new(Context {
|
||||
|
@ -247,7 +243,7 @@ pub fn create(
|
|||
impl SlintServer {
|
||||
#[wasm_bindgen]
|
||||
pub fn server_initialize_result(&self, cap: JsValue) -> Result<JsValue, JsError> {
|
||||
Ok(to_value(&server_loop::server_initialize_result(&serde_wasm_bindgen::from_value(cap)?))?)
|
||||
Ok(to_value(&language::server_initialize_result(&serde_wasm_bindgen::from_value(cap)?))?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -257,7 +253,7 @@ impl SlintServer {
|
|||
wasm_bindgen_futures::future_to_promise(async move {
|
||||
let _lock = ReentryGuard::lock(guard).await;
|
||||
let uri: lsp_types::Url = serde_wasm_bindgen::from_value(uri)?;
|
||||
server_loop::reload_document(
|
||||
language::reload_document(
|
||||
&ctx,
|
||||
content,
|
||||
uri.clone(),
|
||||
|
@ -272,7 +268,7 @@ impl SlintServer {
|
|||
|
||||
/* #[wasm_bindgen]
|
||||
pub fn show_preview(&self, params: JsValue) -> Result<(), JsError> {
|
||||
server_loop::show_preview_command(
|
||||
language::show_preview_command(
|
||||
&serde_wasm_bindgen::from_value(params)?,
|
||||
&ServerNotifier,
|
||||
&mut self.0.borrow_mut(),
|
||||
|
@ -296,7 +292,7 @@ impl SlintServer {
|
|||
pub async fn reload_config(&self) -> Result<(), JsError> {
|
||||
let guard = self.reentry_guard.clone();
|
||||
let _lock = ReentryGuard::lock(guard).await;
|
||||
server_loop::load_configuration(&self.ctx).await.map_err(|e| JsError::new(&e.to_string()))
|
||||
language::load_configuration(&self.ctx).await.map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue