mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-23 16:51:43 +00:00
live preview: Delete an element again
* Refactor the code a bit: Move element edit functions into the `element_edit` module below `language`
This commit is contained in:
parent
1f44bdad2f
commit
a0b862b096
9 changed files with 372 additions and 267 deletions
|
|
@ -196,13 +196,37 @@ impl PropertyChange {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub enum PreviewToLspMessage {
|
pub enum PreviewToLspMessage {
|
||||||
Status { message: String, health: crate::lsp_ext::Health },
|
Status {
|
||||||
Diagnostics { uri: Url, diagnostics: Vec<lsp_types::Diagnostic> },
|
message: String,
|
||||||
ShowDocument { file: Url, selection: lsp_types::Range },
|
health: crate::lsp_ext::Health,
|
||||||
PreviewTypeChanged { is_external: bool },
|
},
|
||||||
RequestState { unused: bool }, // send all documents!
|
Diagnostics {
|
||||||
AddComponent { label: Option<String>, component: ComponentAddition },
|
uri: Url,
|
||||||
UpdateElement { position: VersionedPosition, properties: Vec<PropertyChange> },
|
diagnostics: Vec<lsp_types::Diagnostic>,
|
||||||
|
},
|
||||||
|
ShowDocument {
|
||||||
|
file: Url,
|
||||||
|
selection: lsp_types::Range,
|
||||||
|
},
|
||||||
|
PreviewTypeChanged {
|
||||||
|
is_external: bool,
|
||||||
|
},
|
||||||
|
RequestState {
|
||||||
|
unused: bool,
|
||||||
|
}, // send all documents!
|
||||||
|
AddComponent {
|
||||||
|
label: Option<String>,
|
||||||
|
component: ComponentAddition,
|
||||||
|
},
|
||||||
|
UpdateElement {
|
||||||
|
label: Option<String>,
|
||||||
|
position: VersionedPosition,
|
||||||
|
properties: Vec<PropertyChange>,
|
||||||
|
},
|
||||||
|
RemoveElement {
|
||||||
|
label: Option<String>,
|
||||||
|
position: VersionedPosition,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information on the Element types available
|
/// Information on the Element types available
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
mod completion;
|
mod completion;
|
||||||
mod component_catalog;
|
mod component_catalog;
|
||||||
|
pub mod element_edit;
|
||||||
mod formatting;
|
mod formatting;
|
||||||
mod goto;
|
mod goto;
|
||||||
mod properties;
|
mod properties;
|
||||||
|
|
@ -12,13 +13,9 @@ mod semantic_tokens;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{self, Result};
|
||||||
create_workspace_edit, create_workspace_edit_from_source_file, LspToPreviewMessage,
|
use crate::util;
|
||||||
PreviewComponent, PreviewConfig, Result, VersionedUrl,
|
|
||||||
};
|
|
||||||
use crate::util::{
|
|
||||||
find_element_indent, lookup_current_element_type, map_node, map_range, map_token, to_lsp_diag,
|
|
||||||
};
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::wasm_prelude::*;
|
use crate::wasm_prelude::*;
|
||||||
use i_slint_compiler::object_tree::ElementRc;
|
use i_slint_compiler::object_tree::ElementRc;
|
||||||
|
|
@ -84,32 +81,6 @@ fn create_show_preview_command(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
|
||||||
pub fn notify_preview_about_text_edit(
|
|
||||||
server_notifier: &crate::ServerNotifier,
|
|
||||||
edit: &TextEdit,
|
|
||||||
source_file: &i_slint_compiler::diagnostics::SourceFile,
|
|
||||||
) {
|
|
||||||
let new_length = edit.new_text.len() as u32;
|
|
||||||
let (start_offset, end_offset) = {
|
|
||||||
let so =
|
|
||||||
source_file.offset(edit.range.start.line as usize, edit.range.start.character as usize);
|
|
||||||
let eo =
|
|
||||||
source_file.offset(edit.range.end.line as usize, edit.range.end.character as usize);
|
|
||||||
(std::cmp::min(so, eo) as u32, std::cmp::max(so, eo) as u32)
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(url) = Url::from_file_path(source_file.path()) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
server_notifier.send_message_to_preview(LspToPreviewMessage::AdjustSelection {
|
|
||||||
url: VersionedUrl::new(url, source_file.version()),
|
|
||||||
start_offset,
|
|
||||||
end_offset,
|
|
||||||
new_length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
||||||
pub fn request_state(ctx: &std::rc::Rc<Context>) {
|
pub fn request_state(ctx: &std::rc::Rc<Context>) {
|
||||||
use i_slint_compiler::diagnostics::Spanned;
|
use i_slint_compiler::diagnostics::Spanned;
|
||||||
|
|
@ -125,24 +96,24 @@ pub fn request_state(ctx: &std::rc::Rc<Context>) {
|
||||||
let Ok(url) = Url::from_file_path(p) else {
|
let Ok(url) = Url::from_file_path(p) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::SetContents {
|
ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::SetContents {
|
||||||
url: VersionedUrl::new(url, node.source_file().and_then(|sf| sf.version())),
|
url: common::VersionedUrl::new(url, node.source_file().and_then(|sf| sf.version())),
|
||||||
contents: node.text().to_string(),
|
contents: node.text().to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::SetConfiguration {
|
ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::SetConfiguration {
|
||||||
config: cache.preview_config.clone(),
|
config: cache.preview_config.clone(),
|
||||||
});
|
});
|
||||||
if let Some(c) = ctx.to_show.borrow().clone() {
|
if let Some(c) = ctx.to_show.borrow().clone() {
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::ShowPreview(c))
|
ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::ShowPreview(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cache of loaded documents
|
/// A cache of loaded documents
|
||||||
pub struct DocumentCache {
|
pub struct DocumentCache {
|
||||||
pub(crate) documents: TypeLoader,
|
pub(crate) documents: TypeLoader,
|
||||||
preview_config: PreviewConfig,
|
preview_config: common::PreviewConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentCache {
|
impl DocumentCache {
|
||||||
|
|
@ -164,7 +135,7 @@ pub struct Context {
|
||||||
pub server_notifier: crate::ServerNotifier,
|
pub server_notifier: crate::ServerNotifier,
|
||||||
pub init_param: InitializeParams,
|
pub init_param: InitializeParams,
|
||||||
/// The last component for which the user clicked "show preview"
|
/// The last component for which the user clicked "show preview"
|
||||||
pub to_show: RefCell<Option<PreviewComponent>>,
|
pub to_show: RefCell<Option<common::PreviewComponent>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -387,9 +358,9 @@ pub fn register_request_handlers(rh: &mut RequestHandler) {
|
||||||
if p.kind() == SyntaxKind::QualifiedName
|
if p.kind() == SyntaxKind::QualifiedName
|
||||||
&& p.parent().map_or(false, |n| n.kind() == SyntaxKind::Element)
|
&& p.parent().map_or(false, |n| n.kind() == SyntaxKind::Element)
|
||||||
{
|
{
|
||||||
if let Some(range) = map_node(&p) {
|
if let Some(range) = util::map_node(&p) {
|
||||||
ctx.server_notifier.send_message_to_preview(
|
ctx.server_notifier.send_message_to_preview(
|
||||||
LspToPreviewMessage::HighlightFromEditor { url: Some(uri), offset },
|
common::LspToPreviewMessage::HighlightFromEditor { url: Some(uri), offset },
|
||||||
);
|
);
|
||||||
return Ok(Some(vec![lsp_types::DocumentHighlight { range, kind: None }]));
|
return Ok(Some(vec![lsp_types::DocumentHighlight { range, kind: None }]));
|
||||||
}
|
}
|
||||||
|
|
@ -397,23 +368,22 @@ pub fn register_request_handlers(rh: &mut RequestHandler) {
|
||||||
|
|
||||||
if let Some(value) = find_element_id_for_highlight(&tk, &p) {
|
if let Some(value) = find_element_id_for_highlight(&tk, &p) {
|
||||||
ctx.server_notifier.send_message_to_preview(
|
ctx.server_notifier.send_message_to_preview(
|
||||||
LspToPreviewMessage::HighlightFromEditor { url: None, offset: 0 },
|
common::LspToPreviewMessage::HighlightFromEditor { url: None, offset: 0 },
|
||||||
);
|
);
|
||||||
return Ok(Some(
|
return Ok(Some(
|
||||||
value
|
value
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| lsp_types::DocumentHighlight {
|
.map(|r| lsp_types::DocumentHighlight {
|
||||||
range: map_range(&p.source_file, r),
|
range: util::map_range(&p.source_file, r),
|
||||||
kind: None,
|
kind: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::HighlightFromEditor {
|
ctx.server_notifier.send_message_to_preview(
|
||||||
url: None,
|
common::LspToPreviewMessage::HighlightFromEditor { url: None, offset: 0 },
|
||||||
offset: 0,
|
);
|
||||||
});
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
});
|
});
|
||||||
rh.register::<Rename, _>(|params, ctx| async move {
|
rh.register::<Rename, _>(|params, ctx| async move {
|
||||||
|
|
@ -428,11 +398,11 @@ pub fn register_request_handlers(rh: &mut RequestHandler) {
|
||||||
let edits: Vec<_> = value
|
let edits: Vec<_> = value
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| TextEdit {
|
.map(|r| TextEdit {
|
||||||
range: map_range(&p.source_file, r),
|
range: util::map_range(&p.source_file, r),
|
||||||
new_text: params.new_name.clone(),
|
new_text: params.new_name.clone(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return Ok(Some(create_workspace_edit(uri, version, edits)));
|
return Ok(Some(common::create_workspace_edit(uri, version, edits)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Err("This symbol cannot be renamed. (Only element id can be renamed at the moment)".into())
|
Err("This symbol cannot be renamed. (Only element id can be renamed at the moment)".into())
|
||||||
|
|
@ -442,7 +412,7 @@ pub fn register_request_handlers(rh: &mut RequestHandler) {
|
||||||
let uri = params.text_document.uri;
|
let uri = params.text_document.uri;
|
||||||
if let Some((tk, _off)) = token_descr(&mut document_cache, &uri, ¶ms.position) {
|
if let Some((tk, _off)) = token_descr(&mut document_cache, &uri, ¶ms.position) {
|
||||||
if find_element_id_for_highlight(&tk, &tk.parent()).is_some() {
|
if find_element_id_for_highlight(&tk, &tk.parent()).is_some() {
|
||||||
return Ok(map_token(&tk).map(PrepareRenameResponse::Range));
|
return Ok(util::map_token(&tk).map(PrepareRenameResponse::Range));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
@ -468,9 +438,13 @@ pub fn show_preview_command(params: &[serde_json::Value], ctx: &Rc<Context>) ->
|
||||||
let component =
|
let component =
|
||||||
params.get(1).and_then(|v| v.as_str()).filter(|v| !v.is_empty()).map(|v| v.to_string());
|
params.get(1).and_then(|v| v.as_str()).filter(|v| !v.is_empty()).map(|v| v.to_string());
|
||||||
|
|
||||||
let c = PreviewComponent { url, component, style: config.style.clone().unwrap_or_default() };
|
let c = common::PreviewComponent {
|
||||||
|
url,
|
||||||
|
component,
|
||||||
|
style: config.style.clone().unwrap_or_default(),
|
||||||
|
};
|
||||||
ctx.to_show.replace(Some(c.clone()));
|
ctx.to_show.replace(Some(c.clone()));
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::ShowPreview(c));
|
ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::ShowPreview(c));
|
||||||
|
|
||||||
// Update known Components
|
// Update known Components
|
||||||
report_known_components(document_cache, ctx);
|
report_known_components(document_cache, ctx);
|
||||||
|
|
@ -558,7 +532,7 @@ pub async fn set_binding_command(
|
||||||
format!("No element found at the given start position {:?}", &element_range.start)
|
format!("No element found at the given start position {:?}", &element_range.start)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let node_range = map_node(
|
let node_range = util::map_node(
|
||||||
&element
|
&element
|
||||||
.borrow()
|
.borrow()
|
||||||
.debug
|
.debug
|
||||||
|
|
@ -642,7 +616,7 @@ pub async fn remove_binding_command(
|
||||||
format!("No element found at the given start position {:?}", &element_range.start)
|
format!("No element found at the given start position {:?}", &element_range.start)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let node_range = map_node(
|
let node_range = util::map_node(
|
||||||
&element
|
&element
|
||||||
.borrow()
|
.borrow()
|
||||||
.debug
|
.debug
|
||||||
|
|
@ -704,8 +678,8 @@ pub(crate) async fn reload_document_impl(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ctx) = ctx {
|
if let Some(ctx) = ctx {
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::SetContents {
|
ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::SetContents {
|
||||||
url: VersionedUrl::new(url, version),
|
url: common::VersionedUrl::new(url, version),
|
||||||
contents: content.clone(),
|
contents: content.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -727,7 +701,7 @@ pub(crate) async fn reload_document_impl(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let uri = Url::from_file_path(d.source_file().unwrap()).unwrap();
|
let uri = Url::from_file_path(d.source_file().unwrap()).unwrap();
|
||||||
lsp_diags.entry(uri).or_default().push(to_lsp_diag(&d));
|
lsp_diags.entry(uri).or_default().push(util::to_lsp_diag(&d));
|
||||||
}
|
}
|
||||||
|
|
||||||
lsp_diags
|
lsp_diags
|
||||||
|
|
@ -751,11 +725,11 @@ fn report_known_components(document_cache: &mut DocumentCache, ctx: &Rc<Context>
|
||||||
|
|
||||||
component_catalog::file_local_components(document_cache, &file, &mut components);
|
component_catalog::file_local_components(document_cache, &file, &mut components);
|
||||||
|
|
||||||
VersionedUrl::new(url, version)
|
common::VersionedUrl::new(url, version)
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.server_notifier
|
ctx.server_notifier
|
||||||
.send_message_to_preview(LspToPreviewMessage::KnownComponents { url, components });
|
.send_message_to_preview(common::LspToPreviewMessage::KnownComponents { url, components });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reload_document(
|
pub async fn reload_document(
|
||||||
|
|
@ -907,14 +881,14 @@ fn get_code_actions(
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.kind() == SyntaxKind::StringLiteral && node.kind() == SyntaxKind::Expression {
|
if token.kind() == SyntaxKind::StringLiteral && node.kind() == SyntaxKind::Expression {
|
||||||
let r = map_range(&token.source_file, node.text_range());
|
let r = util::map_range(&token.source_file, node.text_range());
|
||||||
let edits = vec![
|
let edits = vec![
|
||||||
TextEdit::new(lsp_types::Range::new(r.start, r.start), "@tr(".into()),
|
TextEdit::new(lsp_types::Range::new(r.start, r.start), "@tr(".into()),
|
||||||
TextEdit::new(lsp_types::Range::new(r.end, r.end), ")".into()),
|
TextEdit::new(lsp_types::Range::new(r.end, r.end), ")".into()),
|
||||||
];
|
];
|
||||||
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
||||||
title: "Wrap in `@tr()`".into(),
|
title: "Wrap in `@tr()`".into(),
|
||||||
edit: create_workspace_edit_from_source_file(&token.source_file, edits),
|
edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
} else if token.kind() == SyntaxKind::Identifier
|
} else if token.kind() == SyntaxKind::Identifier
|
||||||
|
|
@ -928,7 +902,7 @@ fn get_code_actions(
|
||||||
.get_document(token.source_file.path())
|
.get_document(token.source_file.path())
|
||||||
.map(|doc| &doc.local_registry)
|
.map(|doc| &doc.local_registry)
|
||||||
.unwrap_or(&global_tr);
|
.unwrap_or(&global_tr);
|
||||||
lookup_current_element_type(node.clone(), tr).is_none()
|
util::lookup_current_element_type(node.clone(), tr).is_none()
|
||||||
};
|
};
|
||||||
if is_lookup_error {
|
if is_lookup_error {
|
||||||
// Couldn't lookup the element, there is probably an error. Suggest an edit
|
// Couldn't lookup the element, there is probably an error. Suggest an edit
|
||||||
|
|
@ -941,7 +915,7 @@ fn get_code_actions(
|
||||||
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
||||||
title: format!("Add import from \"{file}\""),
|
title: format!("Add import from \"{file}\""),
|
||||||
kind: Some(lsp_types::CodeActionKind::QUICKFIX),
|
kind: Some(lsp_types::CodeActionKind::QUICKFIX),
|
||||||
edit: create_workspace_edit_from_source_file(
|
edit: common::create_workspace_edit_from_source_file(
|
||||||
&token.source_file,
|
&token.source_file,
|
||||||
vec![edit],
|
vec![edit],
|
||||||
),
|
),
|
||||||
|
|
@ -952,9 +926,9 @@ fn get_code_actions(
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_experimental_client_capability(client_capabilities, "snippetTextEdit") {
|
if has_experimental_client_capability(client_capabilities, "snippetTextEdit") {
|
||||||
let r = map_range(&token.source_file, node.parent().unwrap().text_range());
|
let r = util::map_range(&token.source_file, node.parent().unwrap().text_range());
|
||||||
let element = element_at_position(document_cache, &uri, &r.start);
|
let element = element_at_position(document_cache, &uri, &r.start);
|
||||||
let element_indent = element.as_ref().and_then(find_element_indent);
|
let element_indent = element.as_ref().and_then(util::find_element_indent);
|
||||||
let indented_lines = node
|
let indented_lines = node
|
||||||
.parent()
|
.parent()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -976,7 +950,7 @@ fn get_code_actions(
|
||||||
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
||||||
title: "Wrap in element".into(),
|
title: "Wrap in element".into(),
|
||||||
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
||||||
edit: create_workspace_edit_from_source_file(&token.source_file, edits),
|
edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -1040,7 +1014,7 @@ fn get_code_actions(
|
||||||
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
||||||
title: "Remove element".into(),
|
title: "Remove element".into(),
|
||||||
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
||||||
edit: create_workspace_edit_from_source_file(&token.source_file, edits),
|
edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -1063,7 +1037,7 @@ fn get_code_actions(
|
||||||
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
||||||
title: "Repeat element".into(),
|
title: "Repeat element".into(),
|
||||||
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
||||||
edit: create_workspace_edit_from_source_file(&token.source_file, edits),
|
edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -1074,7 +1048,7 @@ fn get_code_actions(
|
||||||
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
|
||||||
title: "Make conditional".into(),
|
title: "Make conditional".into(),
|
||||||
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
kind: Some(lsp_types::CodeActionKind::REFACTOR),
|
||||||
edit: create_workspace_edit_from_source_file(&token.source_file, edits),
|
edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -1096,7 +1070,7 @@ fn get_document_color(
|
||||||
loop {
|
loop {
|
||||||
if token.kind() == SyntaxKind::ColorLiteral {
|
if token.kind() == SyntaxKind::ColorLiteral {
|
||||||
(|| -> Option<()> {
|
(|| -> Option<()> {
|
||||||
let range = map_token(&token)?;
|
let range = util::map_token(&token)?;
|
||||||
let col = i_slint_compiler::literals::parse_color_literal(token.text())?;
|
let col = i_slint_compiler::literals::parse_color_literal(token.text())?;
|
||||||
let shift = |s: u32| -> f32 { ((col >> s) & 0xff) as f32 / 255. };
|
let shift = |s: u32| -> f32 { ((col >> s) & 0xff) as f32 / 255. };
|
||||||
result.push(ColorInformation {
|
result.push(ColorInformation {
|
||||||
|
|
@ -1141,14 +1115,14 @@ fn get_document_symbols(
|
||||||
let root_element = c.root_element.borrow();
|
let root_element = c.root_element.borrow();
|
||||||
let element_node = &root_element.debug.first()?.0;
|
let element_node = &root_element.debug.first()?.0;
|
||||||
let component_node = syntax_nodes::Component::new(element_node.parent()?)?;
|
let component_node = syntax_nodes::Component::new(element_node.parent()?)?;
|
||||||
let selection_range = map_node(&component_node.DeclaredIdentifier())?;
|
let selection_range = util::map_node(&component_node.DeclaredIdentifier())?;
|
||||||
if c.id.is_empty() {
|
if c.id.is_empty() {
|
||||||
// Symbols with empty names are invalid
|
// Symbols with empty names are invalid
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(DocumentSymbol {
|
Some(DocumentSymbol {
|
||||||
range: map_node(&component_node)?,
|
range: util::map_node(&component_node)?,
|
||||||
selection_range,
|
selection_range,
|
||||||
name: c.id.clone(),
|
name: c.id.clone(),
|
||||||
kind: if c.is_global() {
|
kind: if c.is_global() {
|
||||||
|
|
@ -1164,16 +1138,18 @@ fn get_document_symbols(
|
||||||
|
|
||||||
r.extend(inner_types.iter().filter_map(|c| match c {
|
r.extend(inner_types.iter().filter_map(|c| match c {
|
||||||
Type::Struct { name: Some(name), node: Some(node), .. } => Some(DocumentSymbol {
|
Type::Struct { name: Some(name), node: Some(node), .. } => Some(DocumentSymbol {
|
||||||
range: map_node(node.parent().as_ref()?)?,
|
range: util::map_node(node.parent().as_ref()?)?,
|
||||||
selection_range: map_node(&node.parent()?.child_node(SyntaxKind::DeclaredIdentifier)?)?,
|
selection_range: util::map_node(
|
||||||
|
&node.parent()?.child_node(SyntaxKind::DeclaredIdentifier)?,
|
||||||
|
)?,
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
kind: lsp_types::SymbolKind::STRUCT,
|
kind: lsp_types::SymbolKind::STRUCT,
|
||||||
..ds.clone()
|
..ds.clone()
|
||||||
}),
|
}),
|
||||||
Type::Enumeration(enumeration) => enumeration.node.as_ref().and_then(|node| {
|
Type::Enumeration(enumeration) => enumeration.node.as_ref().and_then(|node| {
|
||||||
Some(DocumentSymbol {
|
Some(DocumentSymbol {
|
||||||
range: map_node(node)?,
|
range: util::map_node(node)?,
|
||||||
selection_range: map_node(&node.DeclaredIdentifier())?,
|
selection_range: util::map_node(&node.DeclaredIdentifier())?,
|
||||||
name: enumeration.name.clone(),
|
name: enumeration.name.clone(),
|
||||||
kind: lsp_types::SymbolKind::ENUM,
|
kind: lsp_types::SymbolKind::ENUM,
|
||||||
..ds.clone()
|
..ds.clone()
|
||||||
|
|
@ -1193,8 +1169,8 @@ fn get_document_symbols(
|
||||||
let sub_element_node = element_node.parent()?;
|
let sub_element_node = element_node.parent()?;
|
||||||
debug_assert_eq!(sub_element_node.kind(), SyntaxKind::SubElement);
|
debug_assert_eq!(sub_element_node.kind(), SyntaxKind::SubElement);
|
||||||
Some(DocumentSymbol {
|
Some(DocumentSymbol {
|
||||||
range: map_node(&sub_element_node)?,
|
range: util::map_node(&sub_element_node)?,
|
||||||
selection_range: map_node(element_node.QualifiedName().as_ref()?)?,
|
selection_range: util::map_node(element_node.QualifiedName().as_ref()?)?,
|
||||||
name: e.base_type.to_string(),
|
name: e.base_type.to_string(),
|
||||||
detail: (!e.id.is_empty()).then(|| e.id.clone()),
|
detail: (!e.id.is_empty()).then(|| e.id.clone()),
|
||||||
kind: lsp_types::SymbolKind::VARIABLE,
|
kind: lsp_types::SymbolKind::VARIABLE,
|
||||||
|
|
@ -1226,7 +1202,7 @@ fn get_code_lenses(
|
||||||
// Handle preview lens
|
// Handle preview lens
|
||||||
r.extend(inner_components.iter().filter(|c| !c.is_global()).filter_map(|c| {
|
r.extend(inner_components.iter().filter(|c| !c.is_global()).filter_map(|c| {
|
||||||
Some(CodeLens {
|
Some(CodeLens {
|
||||||
range: map_node(&c.root_element.borrow().debug.first()?.0)?,
|
range: util::map_node(&c.root_element.borrow().debug.first()?.0)?,
|
||||||
command: Some(create_show_preview_command(true, &text_document.uri, c.id.as_str())),
|
command: Some(create_show_preview_command(true, &text_document.uri, c.id.as_str())),
|
||||||
data: None,
|
data: None,
|
||||||
})
|
})
|
||||||
|
|
@ -1362,97 +1338,18 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> {
|
||||||
document_cache.documents.import_component("std-widgets.slint", "StyleMetrics", &mut diag).await;
|
document_cache.documents.import_component("std-widgets.slint", "StyleMetrics", &mut diag).await;
|
||||||
|
|
||||||
let cc = &document_cache.documents.compiler_config;
|
let cc = &document_cache.documents.compiler_config;
|
||||||
let config = PreviewConfig {
|
let config = common::PreviewConfig {
|
||||||
hide_ui,
|
hide_ui,
|
||||||
style: cc.style.clone().unwrap_or_default(),
|
style: cc.style.clone().unwrap_or_default(),
|
||||||
include_paths: cc.include_paths.clone(),
|
include_paths: cc.include_paths.clone(),
|
||||||
library_paths: cc.library_paths.clone(),
|
library_paths: cc.library_paths.clone(),
|
||||||
};
|
};
|
||||||
document_cache.preview_config = config.clone();
|
document_cache.preview_config = config.clone();
|
||||||
ctx.server_notifier.send_message_to_preview(LspToPreviewMessage::SetConfiguration { config });
|
ctx.server_notifier
|
||||||
|
.send_message_to_preview(common::LspToPreviewMessage::SetConfiguration { config });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
|
||||||
pub fn add_component(
|
|
||||||
ctx: &Context,
|
|
||||||
component: crate::common::ComponentAddition,
|
|
||||||
) -> Result<lsp_types::WorkspaceEdit> {
|
|
||||||
let document_url = component.insert_position.url();
|
|
||||||
let dc = ctx.document_cache.borrow();
|
|
||||||
let file = lsp_types::Url::to_file_path(document_url)
|
|
||||||
.map_err(|_| "Failed to convert URL to file path".to_string())?;
|
|
||||||
|
|
||||||
if &dc.document_version(document_url) != component.insert_position.version() {
|
|
||||||
return Err("Document version mismatch.".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc = dc
|
|
||||||
.documents
|
|
||||||
.get_document(&file)
|
|
||||||
.ok_or_else(|| "Document URL not found in cache".to_string())?;
|
|
||||||
let mut edits = Vec::with_capacity(2);
|
|
||||||
if let Some(edit) =
|
|
||||||
completion::create_import_edit(doc, &component.component_type, &component.import_path)
|
|
||||||
{
|
|
||||||
if let Some(sf) = doc.node.as_ref().map(|n| &n.source_file) {
|
|
||||||
notify_preview_about_text_edit(&ctx.server_notifier, &edit, sf);
|
|
||||||
}
|
|
||||||
edits.push(edit);
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_file = doc.node.as_ref().unwrap().source_file.clone();
|
|
||||||
|
|
||||||
let ip = crate::util::map_position(&source_file, component.insert_position.offset().into());
|
|
||||||
edits.push(TextEdit {
|
|
||||||
range: lsp_types::Range::new(ip.clone(), ip),
|
|
||||||
new_text: component.component_text,
|
|
||||||
});
|
|
||||||
|
|
||||||
create_workspace_edit_from_source_file(&source_file, edits)
|
|
||||||
.ok_or("Could not create workspace edit".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
|
||||||
pub fn update_element(
|
|
||||||
ctx: &Context,
|
|
||||||
position: crate::common::VersionedPosition,
|
|
||||||
properties: Vec<crate::common::PropertyChange>,
|
|
||||||
) -> Result<lsp_types::WorkspaceEdit> {
|
|
||||||
let mut document_cache = ctx.document_cache.borrow_mut();
|
|
||||||
let file = lsp_types::Url::to_file_path(position.url())
|
|
||||||
.map_err(|_| "Failed to convert URL to file path".to_string())?;
|
|
||||||
|
|
||||||
if &document_cache.document_version(position.url()) != position.version() {
|
|
||||||
return Err("Document version mismatch.".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc = document_cache
|
|
||||||
.documents
|
|
||||||
.get_document(&file)
|
|
||||||
.ok_or_else(|| "Document not found".to_string())?;
|
|
||||||
|
|
||||||
let source_file = doc
|
|
||||||
.node
|
|
||||||
.as_ref()
|
|
||||||
.map(|n| n.source_file.clone())
|
|
||||||
.ok_or_else(|| "Document had no node".to_string())?;
|
|
||||||
let element_position = crate::util::map_position(&source_file, position.offset().into());
|
|
||||||
|
|
||||||
let element = element_at_position(&mut document_cache, &position.url(), &element_position)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
format!("No element found at the given start position {:?}", &element_position)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (_, e) = crate::language::properties::set_bindings(
|
|
||||||
&mut document_cache,
|
|
||||||
position.url(),
|
|
||||||
&element,
|
|
||||||
&properties,
|
|
||||||
)?;
|
|
||||||
Ok(e.ok_or_else(|| "Failed to create workspace edit".to_string())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
135
tools/lsp/language/element_edit.rs
Normal file
135
tools/lsp/language/element_edit.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
#![cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
||||||
|
|
||||||
|
use crate::Context;
|
||||||
|
|
||||||
|
use crate::common::{self, Result};
|
||||||
|
use crate::language::{self, completion};
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use crate::wasm_prelude::*;
|
||||||
|
|
||||||
|
use i_slint_compiler::object_tree::ElementRc;
|
||||||
|
use lsp_types::{TextEdit, Url, WorkspaceEdit};
|
||||||
|
|
||||||
|
pub fn notify_preview_about_text_edit(
|
||||||
|
server_notifier: &crate::ServerNotifier,
|
||||||
|
edit: &TextEdit,
|
||||||
|
source_file: &i_slint_compiler::diagnostics::SourceFile,
|
||||||
|
) {
|
||||||
|
let new_length = edit.new_text.len() as u32;
|
||||||
|
let (start_offset, end_offset) = {
|
||||||
|
let so =
|
||||||
|
source_file.offset(edit.range.start.line as usize, edit.range.start.character as usize);
|
||||||
|
let eo =
|
||||||
|
source_file.offset(edit.range.end.line as usize, edit.range.end.character as usize);
|
||||||
|
(std::cmp::min(so, eo) as u32, std::cmp::max(so, eo) as u32)
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(url) = Url::from_file_path(source_file.path()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
server_notifier.send_message_to_preview(common::LspToPreviewMessage::AdjustSelection {
|
||||||
|
url: common::VersionedUrl::new(url, source_file.version()),
|
||||||
|
start_offset,
|
||||||
|
end_offset,
|
||||||
|
new_length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn element_at_source_code_position(
|
||||||
|
dc: &mut language::DocumentCache,
|
||||||
|
position: &common::VersionedPosition,
|
||||||
|
) -> Result<ElementRc> {
|
||||||
|
let file = Url::to_file_path(position.url())
|
||||||
|
.map_err(|_| "Failed to convert URL to file path".to_string())?;
|
||||||
|
|
||||||
|
if &dc.document_version(position.url()) != position.version() {
|
||||||
|
return Err("Document version mismatch.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc = dc.documents.get_document(&file).ok_or_else(|| "Document not found".to_string())?;
|
||||||
|
|
||||||
|
let source_file = doc
|
||||||
|
.node
|
||||||
|
.as_ref()
|
||||||
|
.map(|n| n.source_file.clone())
|
||||||
|
.ok_or_else(|| "Document had no node".to_string())?;
|
||||||
|
let element_position = util::map_position(&source_file, position.offset().into());
|
||||||
|
|
||||||
|
Ok(language::element_at_position(dc, &position.url(), &element_position).ok_or_else(|| {
|
||||||
|
format!("No element found at the given start position {:?}", &element_position)
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_component(ctx: &Context, component: common::ComponentAddition) -> Result<WorkspaceEdit> {
|
||||||
|
let document_url = component.insert_position.url();
|
||||||
|
let dc = ctx.document_cache.borrow();
|
||||||
|
let file = Url::to_file_path(document_url)
|
||||||
|
.map_err(|_| "Failed to convert URL to file path".to_string())?;
|
||||||
|
|
||||||
|
if &dc.document_version(document_url) != component.insert_position.version() {
|
||||||
|
return Err("Document version mismatch.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc = dc
|
||||||
|
.documents
|
||||||
|
.get_document(&file)
|
||||||
|
.ok_or_else(|| "Document URL not found in cache".to_string())?;
|
||||||
|
let mut edits = Vec::with_capacity(2);
|
||||||
|
if let Some(edit) =
|
||||||
|
completion::create_import_edit(doc, &component.component_type, &component.import_path)
|
||||||
|
{
|
||||||
|
if let Some(sf) = doc.node.as_ref().map(|n| &n.source_file) {
|
||||||
|
notify_preview_about_text_edit(&ctx.server_notifier, &edit, sf);
|
||||||
|
}
|
||||||
|
edits.push(edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_file = doc.node.as_ref().unwrap().source_file.clone();
|
||||||
|
|
||||||
|
let ip = util::map_position(&source_file, component.insert_position.offset().into());
|
||||||
|
edits.push(TextEdit {
|
||||||
|
range: lsp_types::Range::new(ip.clone(), ip),
|
||||||
|
new_text: component.component_text,
|
||||||
|
});
|
||||||
|
|
||||||
|
common::create_workspace_edit_from_source_file(&source_file, edits)
|
||||||
|
.ok_or("Could not create workspace edit".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_element(
|
||||||
|
ctx: &Context,
|
||||||
|
position: common::VersionedPosition,
|
||||||
|
properties: Vec<common::PropertyChange>,
|
||||||
|
) -> Result<WorkspaceEdit> {
|
||||||
|
let element = element_at_source_code_position(&mut ctx.document_cache.borrow_mut(), &position)?;
|
||||||
|
|
||||||
|
let (_, e) = language::properties::set_bindings(
|
||||||
|
&mut ctx.document_cache.borrow_mut(),
|
||||||
|
position.url(),
|
||||||
|
&element,
|
||||||
|
&properties,
|
||||||
|
)?;
|
||||||
|
Ok(e.ok_or_else(|| "Failed to create workspace edit".to_string())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_element(ctx: &Context, position: common::VersionedPosition) -> Result<WorkspaceEdit> {
|
||||||
|
let element = element_at_source_code_position(&mut ctx.document_cache.borrow_mut(), &position)?;
|
||||||
|
|
||||||
|
let e = element.borrow();
|
||||||
|
let Some(node) = e.debug.get(0).map(|(n, _)| n) else {
|
||||||
|
return Err("No node found".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(range) = util::map_node(node) else {
|
||||||
|
return Err("Could not map element node".into());
|
||||||
|
};
|
||||||
|
let edits = vec![TextEdit { range, new_text: String::new() }];
|
||||||
|
|
||||||
|
common::create_workspace_edit_from_source_file(&node.source_file, edits)
|
||||||
|
.ok_or("Could not create workspace edit".into())
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ pub mod lsp_ext;
|
||||||
mod preview;
|
mod preview;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
use common::{LspToPreviewMessage, Result, VersionedUrl};
|
use common::Result;
|
||||||
use language::*;
|
use language::*;
|
||||||
|
|
||||||
use i_slint_compiler::CompilerConfiguration;
|
use i_slint_compiler::CompilerConfiguration;
|
||||||
|
|
@ -138,7 +138,7 @@ impl ServerNotifier {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_message_to_preview(&self, message: LspToPreviewMessage) {
|
pub fn send_message_to_preview(&self, message: common::LspToPreviewMessage) {
|
||||||
if self.use_external_preview.get() {
|
if self.use_external_preview.get() {
|
||||||
let _ = self.send_notification("slint/lsp_to_preview".to_string(), message);
|
let _ = self.send_notification("slint/lsp_to_preview".to_string(), message);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -277,10 +277,12 @@ fn main_loop(connection: Connection, init_param: InitializeParams, cli_args: Cli
|
||||||
let contents = std::fs::read_to_string(&path);
|
let contents = std::fs::read_to_string(&path);
|
||||||
if let Ok(contents) = &contents {
|
if let Ok(contents) = &contents {
|
||||||
if let Ok(url) = Url::from_file_path(&path) {
|
if let Ok(url) = Url::from_file_path(&path) {
|
||||||
server_notifier.send_message_to_preview(LspToPreviewMessage::SetContents {
|
server_notifier.send_message_to_preview(
|
||||||
url: VersionedUrl::new(url, None),
|
common::LspToPreviewMessage::SetContents {
|
||||||
contents: contents.clone(),
|
url: common::VersionedUrl::new(url, None),
|
||||||
})
|
contents: contents.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(contents)
|
Some(contents)
|
||||||
|
|
@ -406,6 +408,28 @@ async fn handle_notification(req: lsp_server::Notification, ctx: &Rc<Context>) -
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
||||||
|
async fn send_workspace_edit(
|
||||||
|
server_notifier: ServerNotifier,
|
||||||
|
label: Option<String>,
|
||||||
|
edit: Result<lsp_types::WorkspaceEdit>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let edit = edit?;
|
||||||
|
|
||||||
|
let response = server_notifier
|
||||||
|
.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
||||||
|
lsp_types::ApplyWorkspaceEditParams { label, edit },
|
||||||
|
)?
|
||||||
|
.await?;
|
||||||
|
if !response.applied {
|
||||||
|
return Err(response
|
||||||
|
.failure_reason
|
||||||
|
.unwrap_or("Operation failed, no specific reason given".into())
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
|
||||||
async fn handle_preview_to_lsp_message(
|
async fn handle_preview_to_lsp_message(
|
||||||
message: crate::common::PreviewToLspMessage,
|
message: crate::common::PreviewToLspMessage,
|
||||||
|
|
@ -442,49 +466,28 @@ async fn handle_preview_to_lsp_message(
|
||||||
crate::language::request_state(ctx);
|
crate::language::request_state(ctx);
|
||||||
}
|
}
|
||||||
M::AddComponent { label, component } => {
|
M::AddComponent { label, component } => {
|
||||||
let edit = match crate::language::add_component(ctx, component) {
|
let _ = send_workspace_edit(
|
||||||
Ok(edit) => edit,
|
ctx.server_notifier.clone(),
|
||||||
Err(e) => {
|
label,
|
||||||
eprintln!("Error: {}", e);
|
element_edit::add_component(ctx, component),
|
||||||
return Ok(());
|
)
|
||||||
}
|
.await;
|
||||||
};
|
|
||||||
let response = ctx
|
|
||||||
.server_notifier
|
|
||||||
.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
|
||||||
lsp_types::ApplyWorkspaceEditParams { label, edit },
|
|
||||||
)?
|
|
||||||
.await?;
|
|
||||||
if !response.applied {
|
|
||||||
return Err(response
|
|
||||||
.failure_reason
|
|
||||||
.unwrap_or("Operation failed, no specific reason given".into())
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M::UpdateElement { position, properties } => {
|
M::UpdateElement { label, position, properties } => {
|
||||||
let edit = match crate::language::update_element(ctx, position, properties) {
|
let _ = send_workspace_edit(
|
||||||
Ok(e) => e,
|
ctx.server_notifier.clone(),
|
||||||
Err(e) => {
|
label,
|
||||||
eprintln!("Error: {e}");
|
element_edit::update_element(ctx, position, properties),
|
||||||
return Ok(());
|
)
|
||||||
}
|
.await;
|
||||||
};
|
}
|
||||||
let response = ctx
|
M::RemoveElement { label, position } => {
|
||||||
.server_notifier
|
let _ = send_workspace_edit(
|
||||||
.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
ctx.server_notifier.clone(),
|
||||||
lsp_types::ApplyWorkspaceEditParams {
|
label,
|
||||||
label: Some("Element update".to_string()),
|
element_edit::remove_element(ctx, position),
|
||||||
edit,
|
)
|
||||||
},
|
.await;
|
||||||
)?
|
|
||||||
.await?;
|
|
||||||
if !response.applied {
|
|
||||||
return Err(response
|
|
||||||
.failure_reason
|
|
||||||
.unwrap_or("Operation failed, no specific reason given".into())
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -143,18 +143,32 @@ fn drop_component(
|
||||||
}
|
}
|
||||||
|
|
||||||
// triggered from the UI, running in UI thread
|
// triggered from the UI, running in UI thread
|
||||||
fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32) {
|
fn delete_selected_element() {
|
||||||
let Some(selected) = PREVIEW_STATE.with(move |preview_state| {
|
let Some(selected) = selected_element() else {
|
||||||
let preview_state = preview_state.borrow();
|
|
||||||
preview_state.selected.clone()
|
|
||||||
}) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(selected_element_struct) = selected_element() else {
|
let Ok(url) = Url::from_file_path(&selected.path) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(selected_element) = selected_element_struct.as_element() else {
|
|
||||||
|
let cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||||
|
let Some((version, _)) = cache.source_code.get(&url).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
send_message_to_lsp(crate::common::PreviewToLspMessage::RemoveElement {
|
||||||
|
label: Some("Deleting element".to_string()),
|
||||||
|
position: VersionedPosition::new(VersionedUrl::new(url, version), selected.offset),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// triggered from the UI, running in UI thread
|
||||||
|
fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32) {
|
||||||
|
let Some(selected) = selected_element() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(selected_element) = selected.as_element() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(component_instance) = component_instance() else {
|
let Some(component_instance) = component_instance() else {
|
||||||
|
|
@ -182,27 +196,32 @@ fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32)
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let properties = {
|
let (properties, op) = {
|
||||||
let mut p = Vec::with_capacity(4);
|
let mut p = Vec::with_capacity(4);
|
||||||
|
let mut op = "";
|
||||||
if geometry.origin.x != x {
|
if geometry.origin.x != x {
|
||||||
p.push(crate::common::PropertyChange::new(
|
p.push(crate::common::PropertyChange::new(
|
||||||
"x",
|
"x",
|
||||||
format!("{}px", (x - parent_x).round()),
|
format!("{}px", (x - parent_x).round()),
|
||||||
));
|
));
|
||||||
|
op = "Moving";
|
||||||
}
|
}
|
||||||
if geometry.origin.y != y {
|
if geometry.origin.y != y {
|
||||||
p.push(crate::common::PropertyChange::new(
|
p.push(crate::common::PropertyChange::new(
|
||||||
"y",
|
"y",
|
||||||
format!("{}px", (y - parent_y).round()),
|
format!("{}px", (y - parent_y).round()),
|
||||||
));
|
));
|
||||||
|
op = "Moving";
|
||||||
}
|
}
|
||||||
if geometry.size.width != width {
|
if geometry.size.width != width {
|
||||||
p.push(crate::common::PropertyChange::new("width", format!("{}px", width.round())));
|
p.push(crate::common::PropertyChange::new("width", format!("{}px", width.round())));
|
||||||
|
op = "Resizing";
|
||||||
}
|
}
|
||||||
if geometry.size.height != height {
|
if geometry.size.height != height {
|
||||||
p.push(crate::common::PropertyChange::new("height", format!("{}px", height.round())));
|
p.push(crate::common::PropertyChange::new("height", format!("{}px", height.round())));
|
||||||
|
op = "Resizing";
|
||||||
}
|
}
|
||||||
p
|
(p, op)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !properties.is_empty() {
|
if !properties.is_empty() {
|
||||||
|
|
@ -216,6 +235,7 @@ fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32)
|
||||||
};
|
};
|
||||||
|
|
||||||
send_message_to_lsp(crate::common::PreviewToLspMessage::UpdateElement {
|
send_message_to_lsp(crate::common::PreviewToLspMessage::UpdateElement {
|
||||||
|
label: Some(format!("{op} element")),
|
||||||
position: VersionedPosition::new(VersionedUrl::new(url, version), selected.offset),
|
position: VersionedPosition::new(VersionedUrl::new(url, version), selected.offset),
|
||||||
properties,
|
properties,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ pub fn create_ui(style: String, experimental: bool) -> Result<PreviewUi, Platfor
|
||||||
ui.on_can_drop(super::can_drop_component);
|
ui.on_can_drop(super::can_drop_component);
|
||||||
ui.on_drop(super::drop_component);
|
ui.on_drop(super::drop_component);
|
||||||
ui.on_selected_element_update_geometry(super::change_geometry_of_selected_element);
|
ui.on_selected_element_update_geometry(super::change_geometry_of_selected_element);
|
||||||
|
ui.on_selected_element_delete(super::delete_selected_element);
|
||||||
|
|
||||||
Ok(ui)
|
Ok(ui)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ component SelectionFrame {
|
||||||
|
|
||||||
callback update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
callback update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
||||||
callback select-behind(/* x */ length, /* y */ length, /* enter component? */ bool, /* same file? */ bool);
|
callback select-behind(/* x */ length, /* y */ length, /* enter component? */ bool, /* same file? */ bool);
|
||||||
|
callback selected-element-delete();
|
||||||
|
|
||||||
if !selection.is-primary || !experimental: Rectangle {
|
if !selection.is-primary || !experimental: Rectangle {
|
||||||
x: 0;
|
x: 0;
|
||||||
|
|
@ -97,6 +98,7 @@ export component DrawArea {
|
||||||
callback reselect(); // Reselect element e.g. after changing the window size (which may move the element)
|
callback reselect(); // Reselect element e.g. after changing the window size (which may move the element)
|
||||||
callback unselect();
|
callback unselect();
|
||||||
callback selected-element-update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
callback selected-element-update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
||||||
|
callback selected-element-delete();
|
||||||
|
|
||||||
preferred-height: max(i-preview-area-container.preferred-height, i-preview-area-container.min-height) + 2 * i-scroll-view.border;
|
preferred-height: max(i-preview-area-container.preferred-height, i-preview-area-container.min-height) + 2 * i-scroll-view.border;
|
||||||
preferred-width: max(i-preview-area-container.preferred-width, i-preview-area-container.min-width) + 2 * i-scroll-view.border;
|
preferred-width: max(i-preview-area-container.preferred-width, i-preview-area-container.min-width) + 2 * i-scroll-view.border;
|
||||||
|
|
@ -212,6 +214,10 @@ export component DrawArea {
|
||||||
root.selected-element-update-geometry(x, y, w, h);
|
root.selected-element-update-geometry(x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selected-element-delete() => {
|
||||||
|
root.selected-element-delete();
|
||||||
|
}
|
||||||
|
|
||||||
select-behind(x, y, c, f) => { root.select-behind(x, y, c, f); }
|
select-behind(x, y, c, f) => { root.select-behind(x, y, c, f); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export component PreviewUi inherits Window {
|
||||||
pure callback can-drop(/* component_type */ string, /* x */ length, /* y */ length) -> bool;
|
pure callback can-drop(/* component_type */ string, /* x */ length, /* y */ length) -> bool;
|
||||||
callback drop(/* component_type */ string, /* import_path */ string, /* is_layout */ bool, /* x */ length, /* y */ length);
|
callback drop(/* component_type */ string, /* import_path */ string, /* is_layout */ bool, /* x */ length, /* y */ length);
|
||||||
callback selected-element-update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
callback selected-element-update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
||||||
|
callback selected-element-delete();
|
||||||
callback select-at(/* x */ length, /* y */ length, /* enter_component? */ bool);
|
callback select-at(/* x */ length, /* y */ length, /* enter_component? */ bool);
|
||||||
callback select-behind(/* x */ length, /* y */ length, /* enter_component* */ bool, /* reverse */ bool);
|
callback select-behind(/* x */ length, /* y */ length, /* enter_component* */ bool, /* reverse */ bool);
|
||||||
callback show-document(/* url */ string, /* line */ int, /* column */ int);
|
callback show-document(/* url */ string, /* line */ int, /* column */ int);
|
||||||
|
|
@ -65,6 +66,9 @@ export component PreviewUi inherits Window {
|
||||||
text: root.experimental ? @tr("Design Mode") : @tr("Pick Mode");
|
text: root.experimental ? @tr("Design Mode") : @tr("Pick Mode");
|
||||||
checkable: true;
|
checkable: true;
|
||||||
checked <=> root.design-mode;
|
checked <=> root.design-mode;
|
||||||
|
clicked() => {
|
||||||
|
key-handler.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|
@ -119,6 +123,7 @@ export component PreviewUi inherits Window {
|
||||||
|
|
||||||
select-at(x, y, enter_component) => { root.select-at(x, y, enter_component); }
|
select-at(x, y, enter_component) => { root.select-at(x, y, enter_component); }
|
||||||
selected-element-update-geometry(x, y, w, h) => { root.selected-element-update-geometry(x, y, w, h); }
|
selected-element-update-geometry(x, y, w, h) => { root.selected-element-update-geometry(x, y, w, h); }
|
||||||
|
selected-element-delete() => { root.selected-element-delete(); }
|
||||||
select-behind(x, y, stay_in_file, reverse) => { root.select-behind(x, y, stay_in_file, reverse); }
|
select-behind(x, y, stay_in_file, reverse) => { root.select-behind(x, y, stay_in_file, reverse); }
|
||||||
show-document(url, line, column) => { root.show-document(url, line, column); }
|
show-document(url, line, column) => { root.show-document(url, line, column); }
|
||||||
unselect() => { root.unselect(); }
|
unselect() => { root.unselect(); }
|
||||||
|
|
@ -129,4 +134,16 @@ export component PreviewUi inherits Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key-handler := FocusScope {
|
||||||
|
enabled: root.design-mode && root.experimental;
|
||||||
|
|
||||||
|
key-released(event) => {
|
||||||
|
if event.text == Key.Delete {
|
||||||
|
root.selected-element-delete();
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
reject
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#![cfg(target_arch = "wasm32")]
|
#![cfg(target_arch = "wasm32")]
|
||||||
|
|
||||||
mod common;
|
pub mod common;
|
||||||
mod fmt;
|
mod fmt;
|
||||||
mod language;
|
mod language;
|
||||||
pub mod lsp_ext;
|
pub mod lsp_ext;
|
||||||
|
|
@ -14,7 +14,7 @@ pub mod util;
|
||||||
use common::{LspToPreviewMessage, Result, VersionedUrl};
|
use common::{LspToPreviewMessage, Result, VersionedUrl};
|
||||||
use i_slint_compiler::CompilerConfiguration;
|
use i_slint_compiler::CompilerConfiguration;
|
||||||
use js_sys::Function;
|
use js_sys::Function;
|
||||||
pub use language::{Context, DocumentCache, RequestHandler};
|
pub use language::{element_edit, Context, DocumentCache, RequestHandler};
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
@ -226,6 +226,30 @@ pub fn create(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_workspace_edit(
|
||||||
|
server_notifier: ServerNotifier,
|
||||||
|
label: Option<String>,
|
||||||
|
edit: Result<lsp_types::WorkspaceEdit>,
|
||||||
|
) {
|
||||||
|
let Ok(edit) = edit else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
let fut = server_notifier.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
||||||
|
lsp_types::ApplyWorkspaceEditParams { label, edit },
|
||||||
|
);
|
||||||
|
if let Ok(fut) = fut {
|
||||||
|
// We ignore errors: If the LSP can not be reached, then all is lost
|
||||||
|
// anyway. The other thing that might go wrong is that our Workspace Edit
|
||||||
|
// refers to some outdated text. In that case the update is most likely
|
||||||
|
// in flight already and will cause the preview to re-render, which also
|
||||||
|
// invalidates all our state
|
||||||
|
let _ = fut.await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl SlintServer {
|
impl SlintServer {
|
||||||
#[cfg(all(feature = "preview-engine", feature = "preview-external"))]
|
#[cfg(all(feature = "preview-engine", feature = "preview-external"))]
|
||||||
|
|
@ -272,47 +296,25 @@ impl SlintServer {
|
||||||
crate::language::request_state(&self.ctx);
|
crate::language::request_state(&self.ctx);
|
||||||
}
|
}
|
||||||
M::AddComponent { label, component } => {
|
M::AddComponent { label, component } => {
|
||||||
let edit = crate::language::add_component(&self.ctx, component);
|
send_workspace_edit(
|
||||||
let sn = self.ctx.server_notifier.clone();
|
self.ctx.server_notifier.clone(),
|
||||||
|
label,
|
||||||
if let Ok(edit) = edit {
|
element_edit::add_component(&self.ctx, component),
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
);
|
||||||
let fut = sn.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
|
||||||
lsp_types::ApplyWorkspaceEditParams { label, edit },
|
|
||||||
);
|
|
||||||
if let Ok(fut) = fut {
|
|
||||||
// We ignore errors: If the LSP can not be reached, then all is lost
|
|
||||||
// anyway. The other thing that might go wrong is that our Workspace Edit
|
|
||||||
// refers to some outdated text. In that case the update is most likely
|
|
||||||
// in flight already and will cause the preview to re-render, which also
|
|
||||||
// invalidates all our state
|
|
||||||
let _ = fut.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M::UpdateElement { position, properties } => {
|
M::UpdateElement { label, position, properties } => {
|
||||||
let edit = crate::language::update_element(&self.ctx, position, properties);
|
send_workspace_edit(
|
||||||
let sn = self.ctx.server_notifier.clone();
|
self.ctx.server_notifier.clone(),
|
||||||
|
label,
|
||||||
if let Ok(edit) = edit {
|
element_edit::update_element(&self.ctx, position, properties),
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
);
|
||||||
let fut = sn.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
}
|
||||||
lsp_types::ApplyWorkspaceEditParams {
|
M::RemoveElement { label, position } => {
|
||||||
label: Some("Element update".to_string()),
|
send_workspace_edit(
|
||||||
edit,
|
self.ctx.server_notifier.clone(),
|
||||||
},
|
label,
|
||||||
);
|
element_edit::remove_element(&self.ctx, position),
|
||||||
if let Ok(fut) = fut {
|
);
|
||||||
// We ignore errors: If the LSP can not be reached, then all is lost
|
|
||||||
// anyway. The other thing that might go wrong is that our Workspace Edit
|
|
||||||
// refers to some outdated text. In that case the update is most likely
|
|
||||||
// in flight already and will cause the preview to re-render, which also
|
|
||||||
// invalidates all our state
|
|
||||||
let _ = fut.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue