1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4// cSpell: ignore descr rfind unindented
5
6pub mod completion;
7mod component_catalog;
8mod formatting;
9mod goto;
10pub mod properties;
11mod semantic_tokens;
12#[cfg(test)]
13pub mod test;
14
15use crate::common::{self, Result};
16use crate::util;
17
18#[cfg(target_arch = "wasm32")]
19use crate::wasm_prelude::*;
20use i_slint_compiler::object_tree::ElementRc;
21use i_slint_compiler::parser::{syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken};
22use i_slint_compiler::pathutils::clean_path;
23use i_slint_compiler::CompilerConfiguration;
24use i_slint_compiler::{
25 diagnostics::{BuildDiagnostics, SourceFileVersion},
26 langtype::Type,
27};
28use i_slint_compiler::{typeloader::TypeLoader, typeregister::TypeRegister};
29use lsp_types::request::{
30 CodeActionRequest, CodeLensRequest, ColorPresentationRequest, Completion, DocumentColor,
31 DocumentHighlightRequest, DocumentSymbolRequest, ExecuteCommand, Formatting, GotoDefinition,
32 HoverRequest, PrepareRenameRequest, Rename, SemanticTokensFullRequest,
33};
34use lsp_types::{
35 ClientCapabilities, CodeActionOrCommand, CodeActionProviderCapability, CodeLens,
36 CodeLensOptions, Color, ColorInformation, ColorPresentation, Command, CompletionOptions,
37 DocumentSymbol, DocumentSymbolResponse, Hover, InitializeParams, InitializeResult, OneOf,
38 Position, PrepareRenameResponse, PublishDiagnosticsParams, RenameOptions,
39 SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities,
40 ServerInfo, TextDocumentSyncCapability, TextEdit, Url, WorkDoneProgressOptions,
41};
42use std::cell::RefCell;
43use std::collections::HashMap;
44use std::future::Future;
45use std::path::PathBuf;
46use std::pin::Pin;
47use std::rc::Rc;
48
49const QUERY_PROPERTIES_COMMAND: &str = "slint/queryProperties";
50const REMOVE_BINDING_COMMAND: &str = "slint/removeBinding";
51const SHOW_PREVIEW_COMMAND: &str = "slint/showPreview";
52const SET_BINDING_COMMAND: &str = "slint/setBinding";
53
54pub fn uri_to_file(uri: &lsp_types::Url) -> Option<PathBuf> {
55 let path: PathBuf = uri.to_file_path().ok()?;
56 let cleaned_path: PathBuf = clean_path(&path);
57 Some(cleaned_path)
58}
59
60fn command_list() -> Vec<String> {
61 vec![
62 QUERY_PROPERTIES_COMMAND.into(),
63 REMOVE_BINDING_COMMAND.into(),
64 #[cfg(any(feature = "preview-builtin", feature = "preview-external"))]
65 SHOW_PREVIEW_COMMAND.into(),
66 SET_BINDING_COMMAND.into(),
67 ]
68}
69
70fn create_show_preview_command(
71 pretty: bool,
72 file: &lsp_types::Url,
73 component_name: &str,
74) -> Command {
75 let title: String = format!("{}Show Preview", if pretty { &"▶ " } else { &"" });
76 Command::new(
77 title,
78 SHOW_PREVIEW_COMMAND.into(),
79 arguments:Some(vec![file.as_str().into(), component_name.into()]),
80 )
81}
82
83#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
84pub fn request_state(ctx: &std::rc::Rc<Context>) {
85 let cache = ctx.document_cache.borrow();
86 let documents = &cache.documents;
87
88 for (p, d) in documents.all_file_documents() {
89 if let Some(node) = &d.node {
90 if p.starts_with("builtin:/") {
91 continue; // The preview knows these, too.
92 }
93 let Ok(url) = Url::from_file_path(p) else {
94 continue;
95 };
96 ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::SetContents {
97 url: common::VersionedUrl::new(url, node.source_file.version()),
98 contents: node.text().to_string(),
99 })
100 }
101 }
102 ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::SetConfiguration {
103 config: cache.preview_config.clone(),
104 });
105 if let Some(c) = ctx.to_show.borrow().clone() {
106 ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::ShowPreview(c))
107 }
108}
109
110/// A cache of loaded documents
111pub struct DocumentCache {
112 pub(crate) documents: TypeLoader,
113 preview_config: common::PreviewConfig,
114}
115
116impl DocumentCache {
117 pub fn new(config: CompilerConfiguration) -> Self {
118 let documents: TypeLoader =
119 TypeLoader::new(global_type_registry:TypeRegister::builtin(), compiler_config:config, &mut BuildDiagnostics::default());
120 Self { documents, preview_config: Default::default() }
121 }
122
123 pub fn document_version(&self, target_uri: &lsp_types::Url) -> SourceFileVersion {
124 self.documents
125 .get_document(&uri_to_file(target_uri).unwrap_or_default())
126 .and_then(|doc: &Document| doc.node.as_ref()?.source_file.version())
127 }
128}
129
130pub struct Context {
131 pub document_cache: RefCell<DocumentCache>,
132 pub server_notifier: crate::ServerNotifier,
133 pub init_param: InitializeParams,
134 /// The last component for which the user clicked "show preview"
135 pub to_show: RefCell<Option<common::PreviewComponent>>,
136}
137
138#[derive(Default)]
139pub struct RequestHandler(
140 pub HashMap<
141 &'static str,
142 Box<
143 dyn Fn(
144 serde_json::Value,
145 Rc<Context>,
146 ) -> Pin<Box<dyn Future<Output = Result<serde_json::Value>>>>,
147 >,
148 >,
149);
150
151impl RequestHandler {
152 pub fn register<
153 R: lsp_types::request::Request,
154 Fut: Future<Output = Result<R::Result>> + 'static,
155 >(
156 &mut self,
157 handler: fn(R::Params, Rc<Context>) -> Fut,
158 ) where
159 R::Params: 'static,
160 {
161 self.0.insert(
162 R::METHOD,
163 v:Box::new(move |value: Value, ctx: Rc| {
164 Box::pin(async move {
165 let params: ::Params = serde_json::from_value(value)
166 .map_err(|e: Error| format!("error when deserializing request: {e:?}"))?;
167 handler(params, ctx).await.map(|x: ::Result| serde_json::to_value(x).unwrap())
168 })
169 }),
170 );
171 }
172}
173
174pub fn server_initialize_result(client_cap: &ClientCapabilities) -> InitializeResult {
175 InitializeResult {
176 capabilities: ServerCapabilities {
177 completion_provider: Some(CompletionOptions {
178 resolve_provider: None,
179 trigger_characters: Some(vec![".".to_owned()]),
180 work_done_progress_options: WorkDoneProgressOptions::default(),
181 all_commit_characters: None,
182 completion_item: None,
183 }),
184 definition_provider: Some(OneOf::Left(true)),
185 text_document_sync: Some(TextDocumentSyncCapability::Kind(
186 lsp_types::TextDocumentSyncKind::FULL,
187 )),
188 code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
189 execute_command_provider: Some(lsp_types::ExecuteCommandOptions {
190 commands: command_list(),
191 ..Default::default()
192 }),
193 document_symbol_provider: Some(OneOf::Left(true)),
194 color_provider: Some(true.into()),
195 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
196 semantic_tokens_provider: Some(
197 SemanticTokensOptions {
198 legend: SemanticTokensLegend {
199 token_types: semantic_tokens::LEGEND_TYPES.to_vec(),
200 token_modifiers: semantic_tokens::LEGEND_MODS.to_vec(),
201 },
202 full: Some(SemanticTokensFullOptions::Bool(true)),
203 ..Default::default()
204 }
205 .into(),
206 ),
207 document_highlight_provider: Some(OneOf::Left(true)),
208 rename_provider: Some(
209 if client_cap
210 .text_document
211 .as_ref()
212 .and_then(|td| td.rename.as_ref())
213 .and_then(|r| r.prepare_support)
214 .unwrap_or(false)
215 {
216 OneOf::Right(RenameOptions {
217 prepare_provider: Some(true),
218 work_done_progress_options: WorkDoneProgressOptions::default(),
219 })
220 } else {
221 OneOf::Left(true)
222 },
223 ),
224 document_formatting_provider: Some(OneOf::Left(true)),
225 ..ServerCapabilities::default()
226 },
227 server_info: Some(ServerInfo {
228 name: env!("CARGO_PKG_NAME").to_string(),
229 version: Some(env!("CARGO_PKG_VERSION").to_string()),
230 }),
231 offset_encoding: Some("utf-8".to_string()),
232 }
233}
234
235pub fn register_request_handlers(rh: &mut RequestHandler) {
236 rh.register::<GotoDefinition, _>(|params, ctx| async move {
237 let document_cache = &mut ctx.document_cache.borrow_mut();
238 let result = token_descr(
239 document_cache,
240 &params.text_document_position_params.text_document.uri,
241 &params.text_document_position_params.position,
242 )
243 .and_then(|token| goto::goto_definition(document_cache, token.0));
244 Ok(result)
245 });
246 rh.register::<Completion, _>(|params, ctx| async move {
247 let document_cache = &mut ctx.document_cache.borrow_mut();
248
249 let result = token_descr(
250 document_cache,
251 &params.text_document_position.text_document.uri,
252 &params.text_document_position.position,
253 )
254 .and_then(|token| {
255 completion::completion_at(
256 document_cache,
257 token.0,
258 token.1,
259 ctx.init_param
260 .capabilities
261 .text_document
262 .as_ref()
263 .and_then(|t| t.completion.as_ref()),
264 )
265 .map(Into::into)
266 });
267 Ok(result)
268 });
269 rh.register::<HoverRequest, _>(|_params, _ctx| async move {
270 /*let result =
271 token_descr(document_cache, params.text_document_position_params).map(|x| Hover {
272 contents: lsp_types::HoverContents::Scalar(MarkedString::from_language_code(
273 "text".into(),
274 format!("{:?}", x.token),
275 )),
276 range: None,
277 });
278 let resp = Response::new_ok(id, result);
279 connection.sender.send(Message::Response(resp))?;*/
280 Ok(None::<Hover>)
281 });
282 rh.register::<CodeActionRequest, _>(|params, ctx| async move {
283 let document_cache = &mut ctx.document_cache.borrow_mut();
284
285 let result = token_descr(document_cache, &params.text_document.uri, &params.range.start)
286 .and_then(|(token, _)| {
287 get_code_actions(document_cache, token, &ctx.init_param.capabilities)
288 });
289 Ok(result)
290 });
291 rh.register::<ExecuteCommand, _>(|params, ctx| async move {
292 if params.command.as_str() == SHOW_PREVIEW_COMMAND {
293 #[cfg(any(feature = "preview-builtin", feature = "preview-external"))]
294 show_preview_command(&params.arguments, &ctx)?;
295 return Ok(None::<serde_json::Value>);
296 }
297 if params.command.as_str() == QUERY_PROPERTIES_COMMAND {
298 return Ok(Some(query_properties_command(&params.arguments, &ctx)?));
299 }
300 if params.command.as_str() == SET_BINDING_COMMAND {
301 return Ok(Some(set_binding_command(&params.arguments, &ctx).await?));
302 }
303 if params.command.as_str() == REMOVE_BINDING_COMMAND {
304 return Ok(Some(remove_binding_command(&params.arguments, &ctx).await?));
305 }
306 Ok(None::<serde_json::Value>)
307 });
308 rh.register::<DocumentColor, _>(|params, ctx| async move {
309 let document_cache = &mut ctx.document_cache.borrow_mut();
310 Ok(get_document_color(document_cache, &params.text_document).unwrap_or_default())
311 });
312 rh.register::<ColorPresentationRequest, _>(|params, _ctx| async move {
313 // Convert the color from the color picker to a string representation. This could try to produce a minimal
314 // representation.
315 let requested_color = params.color;
316
317 let color_literal = if requested_color.alpha < 1. {
318 format!(
319 "#{:0>2x}{:0>2x}{:0>2x}{:0>2x}",
320 (requested_color.red * 255.) as u8,
321 (requested_color.green * 255.) as u8,
322 (requested_color.blue * 255.) as u8,
323 (requested_color.alpha * 255.) as u8
324 )
325 } else {
326 format!(
327 "#{:0>2x}{:0>2x}{:0>2x}",
328 (requested_color.red * 255.) as u8,
329 (requested_color.green * 255.) as u8,
330 (requested_color.blue * 255.) as u8,
331 )
332 };
333
334 Ok(vec![ColorPresentation { label: color_literal, ..Default::default() }])
335 });
336 rh.register::<DocumentSymbolRequest, _>(|params, ctx| async move {
337 let document_cache = &mut ctx.document_cache.borrow_mut();
338 Ok(get_document_symbols(document_cache, &params.text_document))
339 });
340 rh.register::<CodeLensRequest, _>(|params, ctx| async move {
341 let document_cache = &mut ctx.document_cache.borrow_mut();
342 Ok(get_code_lenses(document_cache, &params.text_document))
343 });
344 rh.register::<SemanticTokensFullRequest, _>(|params, ctx| async move {
345 let document_cache = &mut ctx.document_cache.borrow_mut();
346 Ok(semantic_tokens::get_semantic_tokens(document_cache, &params.text_document))
347 });
348 rh.register::<DocumentHighlightRequest, _>(|_params, ctx| async move {
349 let document_cache = &mut ctx.document_cache.borrow_mut();
350 let uri = _params.text_document_position_params.text_document.uri;
351 if let Some((tk, offset)) =
352 token_descr(document_cache, &uri, &_params.text_document_position_params.position)
353 {
354 let p = tk.parent();
355 if p.kind() == SyntaxKind::QualifiedName
356 && p.parent().map_or(false, |n| n.kind() == SyntaxKind::Element)
357 {
358 if let Some(range) = util::map_node(&p) {
359 ctx.server_notifier.send_message_to_preview(
360 common::LspToPreviewMessage::HighlightFromEditor { url: Some(uri), offset },
361 );
362 return Ok(Some(vec![lsp_types::DocumentHighlight { range, kind: None }]));
363 }
364 }
365
366 if let Some(value) = find_element_id_for_highlight(&tk, &p) {
367 ctx.server_notifier.send_message_to_preview(
368 common::LspToPreviewMessage::HighlightFromEditor { url: None, offset: 0 },
369 );
370 return Ok(Some(
371 value
372 .into_iter()
373 .map(|r| lsp_types::DocumentHighlight {
374 range: util::map_range(&p.source_file, r),
375 kind: None,
376 })
377 .collect(),
378 ));
379 }
380 }
381 ctx.server_notifier.send_message_to_preview(
382 common::LspToPreviewMessage::HighlightFromEditor { url: None, offset: 0 },
383 );
384 Ok(None)
385 });
386 rh.register::<Rename, _>(|params, ctx| async move {
387 let mut document_cache = ctx.document_cache.borrow_mut();
388 let uri = params.text_document_position.text_document.uri;
389 if let Some((tk, _off)) =
390 token_descr(&mut document_cache, &uri, &params.text_document_position.position)
391 {
392 let p = tk.parent();
393 let version = p.source_file.version();
394 if let Some(value) = find_element_id_for_highlight(&tk, &tk.parent()) {
395 let edits: Vec<_> = value
396 .into_iter()
397 .map(|r| TextEdit {
398 range: util::map_range(&p.source_file, r),
399 new_text: params.new_name.clone(),
400 })
401 .collect();
402 return Ok(Some(common::create_workspace_edit(uri, version, edits)));
403 }
404 };
405 Err("This symbol cannot be renamed. (Only element id can be renamed at the moment)".into())
406 });
407 rh.register::<PrepareRenameRequest, _>(|params, ctx| async move {
408 let mut document_cache = ctx.document_cache.borrow_mut();
409 let uri = params.text_document.uri;
410 if let Some((tk, _off)) = token_descr(&mut document_cache, &uri, &params.position) {
411 if find_element_id_for_highlight(&tk, &tk.parent()).is_some() {
412 return Ok(util::map_token(&tk).map(PrepareRenameResponse::Range));
413 }
414 };
415 Ok(None)
416 });
417 rh.register::<Formatting, _>(|params, ctx| async move {
418 let document_cache = ctx.document_cache.borrow_mut();
419 Ok(formatting::format_document(params, &document_cache))
420 });
421}
422
423#[cfg(any(feature = "preview-builtin", feature = "preview-external"))]
424pub fn show_preview_command(params: &[serde_json::Value], ctx: &Rc<Context>) -> Result<()> {
425 let document_cache = &mut ctx.document_cache.borrow_mut();
426 let config = &document_cache.documents.compiler_config;
427
428 let e = || "InvalidParameter";
429
430 let url: Url = serde_json::from_value(params.first().ok_or_else(e)?.clone())?;
431 // Normalize the URL to make sure it is encoded the same way as what the preview expect from other URLs
432 let url = Url::from_file_path(uri_to_file(&url).ok_or_else(e)?).map_err(|_| e())?;
433
434 let component =
435 params.get(1).and_then(|v| v.as_str()).filter(|v| !v.is_empty()).map(|v| v.to_string());
436
437 let c = common::PreviewComponent {
438 url,
439 component,
440 style: config.style.clone().unwrap_or_default(),
441 };
442 ctx.to_show.replace(Some(c.clone()));
443 ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::ShowPreview(c));
444
445 // Update known Components
446 report_known_components(document_cache, ctx);
447
448 Ok(())
449}
450
451pub fn query_properties_command(
452 params: &[serde_json::Value],
453 ctx: &Rc<Context>,
454) -> Result<serde_json::Value> {
455 let document_cache = &mut ctx.document_cache.borrow_mut();
456
457 let text_document_uri = serde_json::from_value::<lsp_types::TextDocumentIdentifier>(
458 params.first().ok_or("No text document provided")?.clone(),
459 )?
460 .uri;
461 let position = serde_json::from_value::<lsp_types::Position>(
462 params.get(1).ok_or("No position provided")?.clone(),
463 )?;
464
465 let source_version = if let Some(v) = document_cache.document_version(&text_document_uri) {
466 Some(v)
467 } else {
468 return Ok(serde_json::to_value(properties::QueryPropertyResponse::no_element_response(
469 text_document_uri.to_string(),
470 -1,
471 ))
472 .expect("Failed to serialize none-element property query result!"));
473 };
474
475 if let Some(element) =
476 element_at_position(&document_cache.documents, &text_document_uri, &position)
477 {
478 properties::query_properties(&text_document_uri, source_version, &element)
479 .map(|r| serde_json::to_value(r).expect("Failed to serialize property query result!"))
480 } else {
481 Ok(serde_json::to_value(properties::QueryPropertyResponse::no_element_response(
482 text_document_uri.to_string(),
483 source_version.unwrap_or(i32::MIN),
484 ))
485 .expect("Failed to serialize none-element property query result!"))
486 }
487}
488
489pub async fn set_binding_command(
490 params: &[serde_json::Value],
491 ctx: &Rc<Context>,
492) -> Result<serde_json::Value> {
493 let text_document = serde_json::from_value::<lsp_types::OptionalVersionedTextDocumentIdentifier>(
494 params.first().ok_or("No text document provided")?.clone(),
495 )?;
496 let element_range = serde_json::from_value::<lsp_types::Range>(
497 params.get(1).ok_or("No element range provided")?.clone(),
498 )?;
499 let property_name = serde_json::from_value::<String>(
500 params.get(2).ok_or("No property name provided")?.clone(),
501 )?;
502 let new_expression =
503 serde_json::from_value::<String>(params.get(3).ok_or("No expression provided")?.clone())?;
504 let dry_run = {
505 if let Some(p) = params.get(4) {
506 serde_json::from_value::<bool>(p.clone())
507 } else {
508 Ok(true)
509 }
510 }?;
511
512 let (result, edit) = {
513 let document_cache = &mut ctx.document_cache.borrow_mut();
514 let uri = text_document.uri;
515 let version = document_cache.document_version(&uri);
516 if let Some(source_version) = text_document.version {
517 if let Some(current_version) = version {
518 if current_version != source_version {
519 return Err(
520 "Document version mismatch. Please refresh your property information"
521 .into(),
522 );
523 }
524 } else {
525 return Err(format!("Document with uri {uri} not found in cache").into());
526 }
527 }
528
529 let element = element_at_position(&document_cache.documents, &uri, &element_range.start)
530 .ok_or_else(|| {
531 format!("No element found at the given start position {:?}", &element_range.start)
532 })?;
533
534 let node_range =
535 element.with_element_node(|node| util::map_node(node)).ok_or("Failed to map node")?;
536
537 if node_range.start != element_range.start {
538 return Err(format!(
539 "Element found, but does not start at the expected place (){:?} != {:?}).",
540 node_range.start, element_range.start
541 )
542 .into());
543 }
544 if node_range.end != element_range.end {
545 return Err(format!(
546 "Element found, but does not end at the expected place (){:?} != {:?}).",
547 node_range.end, element_range.end
548 )
549 .into());
550 }
551
552 properties::set_binding(
553 document_cache,
554 &uri,
555 version,
556 &element,
557 &property_name,
558 new_expression,
559 )?
560 };
561
562 if !dry_run {
563 if let Some(edit) = edit {
564 let response = ctx
565 .server_notifier
566 .send_request::<lsp_types::request::ApplyWorkspaceEdit>(
567 lsp_types::ApplyWorkspaceEditParams { label: Some("set binding".into()), edit },
568 )?
569 .await?;
570 if !response.applied {
571 return Err(response
572 .failure_reason
573 .unwrap_or("Operation failed, no specific reason given".into())
574 .into());
575 }
576 }
577 }
578
579 Ok(serde_json::to_value(result).expect("Failed to serialize set_binding result!"))
580}
581
582pub async fn remove_binding_command(
583 params: &[serde_json::Value],
584 ctx: &Rc<Context>,
585) -> Result<serde_json::Value> {
586 let text_document = serde_json::from_value::<lsp_types::OptionalVersionedTextDocumentIdentifier>(
587 params.first().ok_or("No text document provided")?.clone(),
588 )?;
589 let element_range = serde_json::from_value::<lsp_types::Range>(
590 params.get(1).ok_or("No element range provided")?.clone(),
591 )?;
592 let property_name = serde_json::from_value::<String>(
593 params.get(2).ok_or("No property name provided")?.clone(),
594 )?;
595
596 let edit = {
597 let document_cache = &mut ctx.document_cache.borrow_mut();
598 let uri = text_document.uri;
599 let version = document_cache.document_version(&uri);
600
601 if let Some(source_version) = text_document.version {
602 if let Some(current_version) = version {
603 if current_version != source_version {
604 return Err(
605 "Document version mismatch. Please refresh your property information"
606 .into(),
607 );
608 }
609 } else {
610 return Err(format!("Document with uri {uri} not found in cache").into());
611 }
612 }
613
614 let element = element_at_position(&document_cache.documents, &uri, &element_range.start)
615 .ok_or_else(|| {
616 format!("No element found at the given start position {:?}", &element_range.start)
617 })?;
618
619 let node_range =
620 element.with_element_node(|node| util::map_node(node)).ok_or("Failed to map node")?;
621
622 if node_range.start != element_range.start {
623 return Err(format!(
624 "Element found, but does not start at the expected place (){:?} != {:?}).",
625 node_range.start, element_range.start
626 )
627 .into());
628 }
629 if node_range.end != element_range.end {
630 return Err(format!(
631 "Element found, but does not end at the expected place (){:?} != {:?}).",
632 node_range.end, element_range.end
633 )
634 .into());
635 }
636
637 properties::remove_binding(uri, version, &element, &property_name)?
638 };
639
640 let response = ctx
641 .server_notifier
642 .send_request::<lsp_types::request::ApplyWorkspaceEdit>(
643 lsp_types::ApplyWorkspaceEditParams { label: Some("set binding".into()), edit },
644 )?
645 .await?;
646
647 if !response.applied {
648 return Err(response
649 .failure_reason
650 .unwrap_or("Operation failed, no specific reason given".into())
651 .into());
652 }
653
654 Ok(serde_json::to_value(()).expect("Failed to serialize ()!"))
655}
656
657pub(crate) async fn reload_document_impl(
658 ctx: Option<&Rc<Context>>,
659 mut content: String,
660 url: lsp_types::Url,
661 version: Option<i32>,
662 document_cache: &mut DocumentCache,
663) -> HashMap<Url, Vec<lsp_types::Diagnostic>> {
664 let Some(path) = uri_to_file(&url) else { return Default::default() };
665 // Normalize the URL
666 let Ok(url) = Url::from_file_path(path.clone()) else { return Default::default() };
667 if path.extension().map_or(false, |e| e == "rs") {
668 content = match i_slint_compiler::lexer::extract_rust_macro(content) {
669 Some(content) => content,
670 // A rust file without a rust macro, just ignore it
671 None => return [(url, vec![])].into_iter().collect(),
672 };
673 }
674
675 if let Some(ctx) = ctx {
676 ctx.server_notifier.send_message_to_preview(common::LspToPreviewMessage::SetContents {
677 url: common::VersionedUrl::new(url, version),
678 contents: content.clone(),
679 });
680 }
681 let mut diag = BuildDiagnostics::default();
682 document_cache.documents.load_file(&path, version, &path, content, false, &mut diag).await;
683
684 // Always provide diagnostics for all files. Empty diagnostics clear any previous ones.
685 let mut lsp_diags: HashMap<Url, Vec<lsp_types::Diagnostic>> = core::iter::once(&path)
686 .chain(diag.all_loaded_files.iter())
687 .map(|path| {
688 let uri = Url::from_file_path(path).unwrap();
689 (uri, Default::default())
690 })
691 .collect();
692
693 for d in diag.into_iter() {
694 #[cfg(not(target_arch = "wasm32"))]
695 if d.source_file().unwrap().is_relative() {
696 continue;
697 }
698 let uri = Url::from_file_path(d.source_file().unwrap()).unwrap();
699 lsp_diags.entry(uri).or_default().push(util::to_lsp_diag(&d));
700 }
701
702 lsp_diags
703}
704
705fn report_known_components(document_cache: &mut DocumentCache, ctx: &Rc<Context>) {
706 let mut components: Vec = Vec::new();
707 component_catalog::builtin_components(document_cache, &mut components);
708 component_catalog::all_exported_components(
709 document_cache,
710 &mut |ci| ci.is_global,
711 &mut components,
712 );
713
714 components.sort_by(|a: &ComponentInformation, b: &ComponentInformation| a.name.cmp(&b.name));
715
716 let url: Option = ctx.to_show.borrow().as_ref().map(|pc: &PreviewComponent| {
717 let url: Url = pc.url.clone();
718 let file: PathBuf = PathBuf::from(url.to_string());
719 let version: Option = document_cache.document_version(&url);
720
721 component_catalog::file_local_components(document_cache, &file, &mut components);
722
723 common::VersionedUrl::new(url, version)
724 });
725
726 ctx.server_notifier
727 .send_message_to_preview(message:common::LspToPreviewMessage::KnownComponents { url, components });
728}
729
730pub async fn reload_document(
731 ctx: &Rc<Context>,
732 content: String,
733 url: lsp_types::Url,
734 version: Option<i32>,
735 document_cache: &mut DocumentCache,
736) -> Result<()> {
737 let lsp_diags: HashMap> =
738 reload_document_impl(ctx:Some(ctx), content, url.clone(), version, document_cache).await;
739
740 for (uri: Url, diagnostics: Vec) in lsp_diags {
741 ctx.server_notifier.send_notification(
742 method:"textDocument/publishDiagnostics".into(),
743 params:PublishDiagnosticsParams { uri, diagnostics, version: None },
744 )?;
745 }
746
747 // Tell Preview about the Components:
748 report_known_components(document_cache, ctx);
749
750 Ok(())
751}
752
753fn get_document_and_offset<'a>(
754 type_loader: &'a TypeLoader,
755 text_document_uri: &'a Url,
756 pos: &'a Position,
757) -> Option<(&'a i_slint_compiler::object_tree::Document, u32)> {
758 let path: PathBuf = uri_to_file(text_document_uri)?;
759 let doc: &Document = type_loader.get_document(&path)?;
760 let o: u32 = doc.node.as_ref()?.source_file.offset(pos.line as usize + 1, pos.character as usize + 1)
761 as u32;
762 doc.node.as_ref()?.text_range().contains_inclusive(o.into()).then_some((doc, o))
763}
764
765fn element_contains(
766 element: &i_slint_compiler::object_tree::ElementRc,
767 offset: u32,
768) -> Option<usize> {
769 elementIter<'_, (Element, Option<…>)>
770 .borrow()
771 .debug
772 .iter()
773 .position(|n: &(Element, Option)| n.0.parent().map_or(false, |n| n.text_range().contains(offset.into())))
774}
775
776fn element_node_contains(element: &common::ElementRcNode, offset: u32) -> bool {
777 element.with_element_node(|node: &Element| {
778 node.parent().map_or(false, |n| n.text_range().contains(offset.into()))
779 })
780}
781
782pub fn element_at_position(
783 type_loader: &TypeLoader,
784 text_document_uri: &Url,
785 pos: &Position,
786) -> Option<common::ElementRcNode> {
787 let (doc, offset) = get_document_and_offset(type_loader, text_document_uri, pos)?;
788
789 for component in &doc.inner_components {
790 let root_element = component.root_element.clone();
791 let Some(root_debug_index) = element_contains(&root_element, offset) else {
792 continue;
793 };
794
795 let mut element =
796 common::ElementRcNode { element: root_element, debug_index: root_debug_index };
797 while element_node_contains(&element, offset) {
798 if let Some((c, i)) = element
799 .element
800 .clone()
801 .borrow()
802 .children
803 .iter()
804 .find_map(|c| element_contains(c, offset).map(|i| (c, i)))
805 {
806 element = common::ElementRcNode { element: c.clone(), debug_index: i };
807 } else {
808 return Some(element);
809 }
810 }
811 }
812 None
813}
814
815/// return the token, and the offset within the file
816fn token_descr(
817 document_cache: &mut DocumentCache,
818 text_document_uri: &Url,
819 pos: &Position,
820) -> Option<(SyntaxToken, u32)> {
821 let (doc: &Document, o: u32) = get_document_and_offset(&document_cache.documents, text_document_uri, pos)?;
822 let node: &Document = doc.node.as_ref()?;
823
824 let token: SyntaxToken = token_at_offset(doc:node, offset:o)?;
825 Some((token, o))
826}
827
828/// Return the token that matches best the token at cursor position
829pub fn token_at_offset(doc: &syntax_nodes::Document, offset: u32) -> Option<SyntaxToken> {
830 let mut taf = doc.token_at_offset(offset.into());
831 let token: SyntaxToken = match (taf.next(), taf.next()) {
832 (None, _) => doc.last_token()?,
833 (Some(t), None) => t,
834 (Some(l), Some(r)) => match (l.kind(), r.kind()) {
835 // Prioritize identifier
836 (SyntaxKind::Identifier, _) => l,
837 (_, SyntaxKind::Identifier) => r,
838 // then the dot
839 (SyntaxKind::Dot, _) => l,
840 (_, SyntaxKind::Dot) => r,
841 // de-prioritize the white spaces
842 (SyntaxKind::Whitespace, _) => r,
843 (SyntaxKind::Comment, _) => r,
844 (_, SyntaxKind::Whitespace) => l,
845 (_, SyntaxKind::Comment) => l,
846 _ => l,
847 },
848 };
849 Some(SyntaxToken { token, source_file: doc.source_file.clone() })
850}
851
852fn has_experimental_client_capability(capabilities: &ClientCapabilities, name: &str) -> bool {
853 capabilities
854 .experimental
855 .as_ref()
856 .and_then(|o| o.get(name).and_then(|v| v.as_bool()))
857 .unwrap_or(default:false)
858}
859
860fn get_code_actions(
861 document_cache: &mut DocumentCache,
862 token: SyntaxToken,
863 client_capabilities: &ClientCapabilities,
864) -> Option<Vec<CodeActionOrCommand>> {
865 let node = token.parent();
866 let uri = Url::from_file_path(token.source_file.path()).ok()?;
867 let mut result = vec![];
868
869 let component = syntax_nodes::Component::new(node.clone())
870 .or_else(|| {
871 syntax_nodes::DeclaredIdentifier::new(node.clone())
872 .and_then(|n| n.parent())
873 .and_then(syntax_nodes::Component::new)
874 })
875 .or_else(|| {
876 syntax_nodes::QualifiedName::new(node.clone())
877 .and_then(|n| n.parent())
878 .and_then(syntax_nodes::Element::new)
879 .and_then(|n| n.parent())
880 .and_then(syntax_nodes::Component::new)
881 });
882
883 #[cfg(any(feature = "preview-builtin", feature = "preview-external"))]
884 {
885 if let Some(component) = &component {
886 if let Some(component_name) =
887 i_slint_compiler::parser::identifier_text(&component.DeclaredIdentifier())
888 {
889 result.push(CodeActionOrCommand::Command(create_show_preview_command(
890 false,
891 &uri,
892 &component_name,
893 )))
894 }
895 }
896 }
897
898 if token.kind() == SyntaxKind::StringLiteral && node.kind() == SyntaxKind::Expression {
899 let r = util::map_range(&token.source_file, node.text_range());
900 let edits = vec![
901 TextEdit::new(lsp_types::Range::new(r.start, r.start), "@tr(".into()),
902 TextEdit::new(lsp_types::Range::new(r.end, r.end), ")".into()),
903 ];
904 result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
905 title: "Wrap in `@tr()`".into(),
906 edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
907 ..Default::default()
908 }));
909 } else if token.kind() == SyntaxKind::Identifier
910 && node.kind() == SyntaxKind::QualifiedName
911 && node.parent().map(|n| n.kind()) == Some(SyntaxKind::Element)
912 {
913 let is_lookup_error = {
914 let global_tr = document_cache.documents.global_type_registry.borrow();
915 let tr = document_cache
916 .documents
917 .get_document(token.source_file.path())
918 .map(|doc| &doc.local_registry)
919 .unwrap_or(&global_tr);
920 util::lookup_current_element_type(node.clone(), tr).is_none()
921 };
922 if is_lookup_error {
923 // Couldn't lookup the element, there is probably an error. Suggest an edit
924 let text = token.text();
925 completion::build_import_statements_edits(
926 &token,
927 document_cache,
928 &mut |name| name == text,
929 &mut |_name, file, edit| {
930 result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
931 title: format!("Add import from \"{file}\""),
932 kind: Some(lsp_types::CodeActionKind::QUICKFIX),
933 edit: common::create_workspace_edit_from_source_file(
934 &token.source_file,
935 vec![edit],
936 ),
937 ..Default::default()
938 }))
939 },
940 );
941 }
942
943 if has_experimental_client_capability(client_capabilities, "snippetTextEdit") {
944 let r = util::map_range(&token.source_file, node.parent().unwrap().text_range());
945 let element = element_at_position(&document_cache.documents, &uri, &r.start);
946 let element_indent = element.as_ref().and_then(util::find_element_indent);
947 let indented_lines = node
948 .parent()
949 .unwrap()
950 .text()
951 .to_string()
952 .lines()
953 .map(
954 |line| if line.is_empty() { line.to_string() } else { format!(" {}", line) },
955 )
956 .collect::<Vec<String>>();
957 let edits = vec![TextEdit::new(
958 lsp_types::Range::new(r.start, r.end),
959 format!(
960 "${{0:element}} {{\n{}{}\n}}",
961 element_indent.unwrap_or("".into()),
962 indented_lines.join("\n")
963 ),
964 )];
965 result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
966 title: "Wrap in element".into(),
967 kind: Some(lsp_types::CodeActionKind::REFACTOR),
968 edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
969 ..Default::default()
970 }));
971
972 // Collect all normal, repeated, and conditional sub-elements and any
973 // whitespace in between for substituting the parent element with its
974 // sub-elements, dropping its own properties, callbacks etc.
975 fn is_sub_element(kind: SyntaxKind) -> bool {
976 matches!(
977 kind,
978 SyntaxKind::SubElement
979 | SyntaxKind::RepeatedElement
980 | SyntaxKind::ConditionalElement
981 )
982 }
983 let sub_elements = node
984 .parent()
985 .unwrap()
986 .children_with_tokens()
987 .skip_while(|n| !is_sub_element(n.kind()))
988 .filter(|n| match n {
989 NodeOrToken::Node(_) => is_sub_element(n.kind()),
990 NodeOrToken::Token(t) => {
991 t.kind() == SyntaxKind::Whitespace
992 && t.next_sibling_or_token().map_or(false, |n| is_sub_element(n.kind()))
993 }
994 })
995 .collect::<Vec<_>>();
996
997 if match component {
998 // A top-level component element can only be removed if it contains
999 // exactly one sub-element (without any condition or assignment)
1000 // that can substitute the component element.
1001 Some(_) => {
1002 sub_elements.len() == 1
1003 && sub_elements.first().and_then(|n| {
1004 n.as_node().unwrap().first_child_or_token().map(|n| n.kind())
1005 }) == Some(SyntaxKind::Element)
1006 }
1007 // Any other element can be removed in favor of one or more sub-elements.
1008 None => sub_elements.iter().any(|n| n.kind() == SyntaxKind::SubElement),
1009 } {
1010 let unindented_lines = sub_elements
1011 .iter()
1012 .map(|n| match n {
1013 NodeOrToken::Node(n) => n
1014 .text()
1015 .to_string()
1016 .lines()
1017 .map(|line| line.strip_prefix(" ").unwrap_or(line).to_string())
1018 .collect::<Vec<_>>()
1019 .join("\n"),
1020 NodeOrToken::Token(t) => {
1021 t.text().strip_suffix(" ").unwrap_or(t.text()).to_string()
1022 }
1023 })
1024 .collect::<Vec<String>>();
1025 let edits = vec![TextEdit::new(
1026 lsp_types::Range::new(r.start, r.end),
1027 unindented_lines.concat(),
1028 )];
1029 result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1030 title: "Remove element".into(),
1031 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1032 edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
1033 ..Default::default()
1034 }));
1035 }
1036
1037 // We have already checked that the node is a qualified name of an element.
1038 // Check whether the element is a direct sub-element of another element
1039 // meaning that it can be repeated or made conditional.
1040 if node // QualifiedName
1041 .parent() // Element
1042 .unwrap()
1043 .parent()
1044 .filter(|n| n.kind() == SyntaxKind::SubElement)
1045 .and_then(|p| p.parent())
1046 .is_some_and(|n| n.kind() == SyntaxKind::Element)
1047 {
1048 let edits = vec![TextEdit::new(
1049 lsp_types::Range::new(r.start, r.start),
1050 "for ${1:name}[index] in ${0:model} : ".to_string(),
1051 )];
1052 result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1053 title: "Repeat element".into(),
1054 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1055 edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
1056 ..Default::default()
1057 }));
1058
1059 let edits = vec![TextEdit::new(
1060 lsp_types::Range::new(r.start, r.start),
1061 "if ${0:condition} : ".to_string(),
1062 )];
1063 result.push(CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1064 title: "Make conditional".into(),
1065 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1066 edit: common::create_workspace_edit_from_source_file(&token.source_file, edits),
1067 ..Default::default()
1068 }));
1069 }
1070 }
1071 }
1072
1073 (!result.is_empty()).then_some(result)
1074}
1075
1076fn get_document_color(
1077 document_cache: &mut DocumentCache,
1078 text_document: &lsp_types::TextDocumentIdentifier,
1079) -> Option<Vec<ColorInformation>> {
1080 let mut result = Vec::new();
1081 let uri_path = uri_to_file(&text_document.uri)?;
1082 let doc = document_cache.documents.get_document(&uri_path)?;
1083 let root_node = doc.node.as_ref()?;
1084 let mut token = root_node.first_token()?;
1085 loop {
1086 if token.kind() == SyntaxKind::ColorLiteral {
1087 (|| -> Option<()> {
1088 let range = util::map_token(&token)?;
1089 let col = i_slint_compiler::literals::parse_color_literal(token.text())?;
1090 let shift = |s: u32| -> f32 { ((col >> s) & 0xff) as f32 / 255. };
1091 result.push(ColorInformation {
1092 range,
1093 color: Color {
1094 alpha: shift(24),
1095 red: shift(16),
1096 green: shift(8),
1097 blue: shift(0),
1098 },
1099 });
1100 Some(())
1101 })();
1102 }
1103 token = match token.next_token() {
1104 Some(token) => token,
1105 None => break Some(result),
1106 }
1107 }
1108}
1109
1110/// Retrieve the document outline
1111fn get_document_symbols(
1112 document_cache: &mut DocumentCache,
1113 text_document: &lsp_types::TextDocumentIdentifier,
1114) -> Option<DocumentSymbolResponse> {
1115 let uri_path = uri_to_file(&text_document.uri)?;
1116 let doc = document_cache.documents.get_document(&uri_path)?;
1117
1118 // DocumentSymbol doesn't implement default and some field depends on features or are deprecated
1119 let ds: DocumentSymbol = serde_json::from_value(
1120 serde_json::json!({ "name" : "", "kind": 255, "range" : lsp_types::Range::default(), "selectionRange" : lsp_types::Range::default() })
1121 )
1122 .unwrap();
1123
1124 let inner_components = doc.inner_components.clone();
1125 let inner_types = doc.inner_types.clone();
1126
1127 let mut r = inner_components
1128 .iter()
1129 .filter_map(|c| {
1130 let root_element = c.root_element.borrow();
1131 let element_node = &root_element.debug.first()?.0;
1132 let component_node = syntax_nodes::Component::new(element_node.parent()?)?;
1133 let selection_range = util::map_node(&component_node.DeclaredIdentifier())?;
1134 if c.id.is_empty() {
1135 // Symbols with empty names are invalid
1136 return None;
1137 }
1138
1139 Some(DocumentSymbol {
1140 range: util::map_node(&component_node)?,
1141 selection_range,
1142 name: c.id.clone(),
1143 kind: if c.is_global() {
1144 lsp_types::SymbolKind::OBJECT
1145 } else {
1146 lsp_types::SymbolKind::CLASS
1147 },
1148 children: gen_children(&c.root_element, &ds),
1149 ..ds.clone()
1150 })
1151 })
1152 .collect::<Vec<_>>();
1153
1154 r.extend(inner_types.iter().filter_map(|c| match c {
1155 Type::Struct { name: Some(name), node: Some(node), .. } => Some(DocumentSymbol {
1156 range: util::map_node(node.parent().as_ref()?)?,
1157 selection_range: util::map_node(
1158 &node.parent()?.child_node(SyntaxKind::DeclaredIdentifier)?,
1159 )?,
1160 name: name.clone(),
1161 kind: lsp_types::SymbolKind::STRUCT,
1162 ..ds.clone()
1163 }),
1164 Type::Enumeration(enumeration) => enumeration.node.as_ref().and_then(|node| {
1165 Some(DocumentSymbol {
1166 range: util::map_node(node)?,
1167 selection_range: util::map_node(&node.DeclaredIdentifier())?,
1168 name: enumeration.name.clone(),
1169 kind: lsp_types::SymbolKind::ENUM,
1170 ..ds.clone()
1171 })
1172 }),
1173 _ => None,
1174 }));
1175
1176 fn gen_children(elem: &ElementRc, ds: &DocumentSymbol) -> Option<Vec<DocumentSymbol>> {
1177 let r = elem
1178 .borrow()
1179 .children
1180 .iter()
1181 .filter_map(|child| {
1182 let e = child.borrow();
1183 let element_node = &e.debug.first()?.0;
1184 let sub_element_node = element_node.parent()?;
1185 debug_assert_eq!(sub_element_node.kind(), SyntaxKind::SubElement);
1186 Some(DocumentSymbol {
1187 range: util::map_node(&sub_element_node)?,
1188 selection_range: util::map_node(element_node.QualifiedName().as_ref()?)?,
1189 name: e.base_type.to_string(),
1190 detail: (!e.id.is_empty()).then(|| e.id.clone()),
1191 kind: lsp_types::SymbolKind::VARIABLE,
1192 children: gen_children(child, ds),
1193 ..ds.clone()
1194 })
1195 })
1196 .collect::<Vec<_>>();
1197 (!r.is_empty()).then_some(r)
1198 }
1199
1200 r.sort_by(|a, b| a.range.start.cmp(&b.range.start));
1201
1202 Some(r.into())
1203}
1204
1205fn get_code_lenses(
1206 document_cache: &mut DocumentCache,
1207 text_document: &lsp_types::TextDocumentIdentifier,
1208) -> Option<Vec<CodeLens>> {
1209 if cfg!(any(feature = "preview-builtin", feature = "preview-external")) {
1210 let filepath: PathBuf = uri_to_file(&text_document.uri)?;
1211 let doc: &Document = document_cache.documents.get_document(&filepath)?;
1212
1213 let inner_components: Vec> = doc.inner_components.clone();
1214
1215 let mut r: Vec = vec![];
1216
1217 // Handle preview lens
1218 r.extend(iter:inner_components.iter().filter(|c: &&Rc| !c.is_global()).filter_map(|c: &Rc| {
1219 Some(CodeLens {
1220 range: util::map_node(&c.root_element.borrow().debug.first()?.0)?,
1221 command: Some(create_show_preview_command(pretty:true, &text_document.uri, component_name:c.id.as_str())),
1222 data: None,
1223 })
1224 }));
1225
1226 Some(r)
1227 } else {
1228 None
1229 }
1230}
1231
1232/// If the token is matching a Element ID, return the list of all element id in the same component
1233fn find_element_id_for_highlight(
1234 token: &SyntaxToken,
1235 parent: &SyntaxNode,
1236) -> Option<Vec<rowan::TextRange>> {
1237 fn is_element_id(tk: &SyntaxToken, parent: &SyntaxNode) -> bool {
1238 if tk.kind() != SyntaxKind::Identifier {
1239 return false;
1240 }
1241 if parent.kind() == SyntaxKind::SubElement {
1242 return true;
1243 };
1244 if parent.kind() == SyntaxKind::QualifiedName
1245 && matches!(
1246 parent.parent().map(|n| n.kind()),
1247 Some(SyntaxKind::Expression | SyntaxKind::StatePropertyChange)
1248 )
1249 {
1250 let mut c = parent.children_with_tokens();
1251 if let Some(NodeOrToken::Token(first)) = c.next() {
1252 return first.text_range() == tk.text_range()
1253 && matches!(c.next(), Some(NodeOrToken::Token(second)) if second.kind() == SyntaxKind::Dot);
1254 }
1255 }
1256
1257 false
1258 }
1259 if is_element_id(token, parent) {
1260 // An id: search all use of the id in this Component
1261 let mut candidate = parent.parent();
1262 while let Some(c) = candidate {
1263 if c.kind() == SyntaxKind::Component {
1264 let mut ranges = Vec::new();
1265 let mut found_definition = false;
1266 recurse(&mut ranges, &mut found_definition, c, token.text());
1267 fn recurse(
1268 ranges: &mut Vec<rowan::TextRange>,
1269 found_definition: &mut bool,
1270 c: SyntaxNode,
1271 text: &str,
1272 ) {
1273 for x in c.children_with_tokens() {
1274 match x {
1275 NodeOrToken::Node(n) => recurse(ranges, found_definition, n, text),
1276 NodeOrToken::Token(tk) => {
1277 if is_element_id(&tk, &c) && tk.text() == text {
1278 ranges.push(tk.text_range());
1279 if c.kind() == SyntaxKind::SubElement {
1280 *found_definition = true;
1281 }
1282 }
1283 }
1284 }
1285 }
1286 }
1287 if !found_definition {
1288 return None;
1289 }
1290 return Some(ranges);
1291 }
1292 candidate = c.parent()
1293 }
1294 }
1295 None
1296}
1297
1298pub async fn load_configuration(ctx: &Context) -> Result<()> {
1299 if !ctx
1300 .init_param
1301 .capabilities
1302 .workspace
1303 .as_ref()
1304 .and_then(|w| w.configuration)
1305 .unwrap_or(false)
1306 {
1307 return Ok(());
1308 }
1309
1310 let r = ctx
1311 .server_notifier
1312 .send_request::<lsp_types::request::WorkspaceConfiguration>(
1313 lsp_types::ConfigurationParams {
1314 items: vec![lsp_types::ConfigurationItem {
1315 scope_uri: None,
1316 section: Some("slint".into()),
1317 }],
1318 },
1319 )?
1320 .await?;
1321
1322 let document_cache = &mut ctx.document_cache.borrow_mut();
1323 let mut hide_ui = None;
1324 for v in r {
1325 if let Some(o) = v.as_object() {
1326 if let Some(ip) = o.get("includePaths").and_then(|v| v.as_array()) {
1327 if !ip.is_empty() {
1328 document_cache.documents.compiler_config.include_paths =
1329 ip.iter().filter_map(|x| x.as_str()).map(PathBuf::from).collect();
1330 }
1331 }
1332 if let Some(lp) = o.get("libraryPaths").and_then(|v| v.as_object()) {
1333 if !lp.is_empty() {
1334 document_cache.documents.compiler_config.library_paths = lp
1335 .iter()
1336 .filter_map(|(k, v)| v.as_str().map(|v| (k.to_string(), PathBuf::from(v))))
1337 .collect();
1338 }
1339 }
1340 if let Some(style) =
1341 o.get("preview").and_then(|v| v.as_object()?.get("style")?.as_str())
1342 {
1343 if !style.is_empty() {
1344 document_cache.documents.compiler_config.style = Some(style.into());
1345 }
1346 }
1347 hide_ui = o.get("preview").and_then(|v| v.as_object()?.get("hide_ui")?.as_bool());
1348 }
1349 }
1350
1351 // Always load the widgets so we can auto-complete them
1352 let mut diag = BuildDiagnostics::default();
1353 document_cache.documents.import_component("std-widgets.slint", "StyleMetrics", &mut diag).await;
1354
1355 let cc = &document_cache.documents.compiler_config;
1356 let config = common::PreviewConfig {
1357 hide_ui,
1358 style: cc.style.clone().unwrap_or_default(),
1359 include_paths: cc.include_paths.clone(),
1360 library_paths: cc.library_paths.clone(),
1361 };
1362 document_cache.preview_config = config.clone();
1363 ctx.server_notifier
1364 .send_message_to_preview(common::LspToPreviewMessage::SetConfiguration { config });
1365 Ok(())
1366}
1367
1368#[cfg(test)]
1369pub mod tests {
1370 use super::*;
1371
1372 use lsp_types::WorkspaceEdit;
1373
1374 use test::{complex_document_cache, loaded_document_cache};
1375
1376 #[test]
1377 fn test_reload_document_invalid_contents() {
1378 let (_, url, diag) = loaded_document_cache("This is not valid!".into());
1379
1380 assert!(diag.len() == 1); // Only one URL is known
1381
1382 let diagnostics = diag.get(&url).expect("URL not found in result");
1383 assert_eq!(diagnostics.len(), 1);
1384 assert_eq!(diagnostics[0].severity, Some(lsp_types::DiagnosticSeverity::ERROR));
1385 }
1386
1387 #[test]
1388 fn test_reload_document_valid_contents() {
1389 let (_, url, diag) =
1390 loaded_document_cache(r#"export component Main inherits Rectangle { }"#.into());
1391
1392 assert!(diag.len() == 1); // Only one URL is known
1393 let diagnostics = diag.get(&url).expect("URL not found in result");
1394 assert!(diagnostics.is_empty());
1395 }
1396
1397 #[test]
1398 fn test_text_document_color_no_color_set() {
1399 let (mut dc, uri, _) = loaded_document_cache(
1400 r#"
1401 component Main inherits Rectangle { }
1402 "#
1403 .into(),
1404 );
1405
1406 let result = get_document_color(&mut dc, &lsp_types::TextDocumentIdentifier { uri })
1407 .expect("Color Vec was returned");
1408 assert!(result.is_empty());
1409 }
1410
1411 #[test]
1412 fn test_text_document_color_rgba_color() {
1413 let (mut dc, uri, _) = loaded_document_cache(
1414 r#"
1415 component Main inherits Rectangle {
1416 background: #1200FF80;
1417 }
1418 "#
1419 .into(),
1420 );
1421
1422 let result = get_document_color(&mut dc, &lsp_types::TextDocumentIdentifier { uri })
1423 .expect("Color Vec was returned");
1424
1425 assert_eq!(result.len(), 1);
1426
1427 let start = &result[0].range.start;
1428 assert_eq!(start.line, 2);
1429 assert_eq!(start.character, 28); // TODO: Why is this not 30?
1430
1431 let end = &result[0].range.end;
1432 assert_eq!(end.line, 2);
1433 assert_eq!(end.character, 37); // TODO: Why is this not 39?
1434
1435 let color = &result[0].color;
1436 assert_eq!(f64::trunc(color.red as f64 * 255.0), 18.0);
1437 assert_eq!(f64::trunc(color.green as f64 * 255.0), 0.0);
1438 assert_eq!(f64::trunc(color.blue as f64 * 255.0), 255.0);
1439 assert_eq!(f64::trunc(color.alpha as f64 * 255.0), 128.0);
1440 }
1441
1442 fn id_at_position(
1443 dc: &mut DocumentCache,
1444 url: &Url,
1445 line: u32,
1446 character: u32,
1447 ) -> Option<String> {
1448 let result = element_at_position(&dc.documents, url, &Position { line, character })?;
1449 let element = result.element.borrow();
1450 Some(element.id.clone())
1451 }
1452
1453 fn base_type_at_position(
1454 dc: &mut DocumentCache,
1455 url: &Url,
1456 line: u32,
1457 character: u32,
1458 ) -> Option<String> {
1459 let result = element_at_position(&dc.documents, url, &Position { line, character })?;
1460 let element = result.element.borrow();
1461 Some(format!("{}", &element.base_type))
1462 }
1463
1464 #[test]
1465 fn test_element_at_position_no_element() {
1466 let (mut dc, url, _) = complex_document_cache();
1467 assert_eq!(id_at_position(&mut dc, &url, 0, 10), None);
1468 // TODO: This is past the end of the line and should thus return None
1469 assert_eq!(id_at_position(&mut dc, &url, 42, 90), Some(String::new()));
1470 assert_eq!(id_at_position(&mut dc, &url, 1, 0), None);
1471 assert_eq!(id_at_position(&mut dc, &url, 55, 1), None);
1472 assert_eq!(id_at_position(&mut dc, &url, 56, 5), None);
1473 }
1474
1475 #[test]
1476 fn test_element_at_position_no_such_document() {
1477 let (mut dc, _, _) = complex_document_cache();
1478 assert_eq!(
1479 id_at_position(&mut dc, &Url::parse("https://foo.bar/baz").unwrap(), 5, 0),
1480 None
1481 );
1482 }
1483
1484 #[test]
1485 fn test_element_at_position_root() {
1486 let (mut dc, url, _) = complex_document_cache();
1487
1488 assert_eq!(id_at_position(&mut dc, &url, 2, 30), Some("root".to_string()));
1489 assert_eq!(id_at_position(&mut dc, &url, 2, 32), Some("root".to_string()));
1490 assert_eq!(id_at_position(&mut dc, &url, 2, 42), Some("root".to_string()));
1491 assert_eq!(id_at_position(&mut dc, &url, 3, 0), Some("root".to_string()));
1492 assert_eq!(id_at_position(&mut dc, &url, 3, 53), Some("root".to_string()));
1493 assert_eq!(id_at_position(&mut dc, &url, 4, 19), Some("root".to_string()));
1494 assert_eq!(id_at_position(&mut dc, &url, 5, 0), Some("root".to_string()));
1495 assert_eq!(id_at_position(&mut dc, &url, 6, 8), Some("root".to_string()));
1496 assert_eq!(id_at_position(&mut dc, &url, 6, 15), Some("root".to_string()));
1497 assert_eq!(id_at_position(&mut dc, &url, 6, 23), Some("root".to_string()));
1498 assert_eq!(id_at_position(&mut dc, &url, 8, 15), Some("root".to_string()));
1499 assert_eq!(id_at_position(&mut dc, &url, 12, 3), Some("root".to_string())); // right before child // TODO: Seems wrong!
1500 assert_eq!(id_at_position(&mut dc, &url, 51, 5), Some("root".to_string())); // right after child // TODO: Why does this not work?
1501 assert_eq!(id_at_position(&mut dc, &url, 52, 0), Some("root".to_string()));
1502 }
1503
1504 #[test]
1505 fn test_element_at_position_child() {
1506 let (mut dc, url, _) = complex_document_cache();
1507
1508 assert_eq!(base_type_at_position(&mut dc, &url, 12, 4), Some("VerticalBox".to_string()));
1509 assert_eq!(base_type_at_position(&mut dc, &url, 14, 22), Some("HorizontalBox".to_string()));
1510 assert_eq!(base_type_at_position(&mut dc, &url, 15, 33), Some("Text".to_string()));
1511 assert_eq!(base_type_at_position(&mut dc, &url, 27, 4), Some("VerticalBox".to_string()));
1512 assert_eq!(base_type_at_position(&mut dc, &url, 28, 8), Some("Text".to_string()));
1513 assert_eq!(base_type_at_position(&mut dc, &url, 51, 4), Some("VerticalBox".to_string()));
1514 }
1515
1516 #[test]
1517 fn test_document_symbols() {
1518 let (mut dc, uri, _) = complex_document_cache();
1519
1520 let result =
1521 get_document_symbols(&mut dc, &lsp_types::TextDocumentIdentifier { uri }).unwrap();
1522
1523 if let DocumentSymbolResponse::Nested(result) = result {
1524 assert_eq!(result.len(), 1);
1525
1526 let first = result.first().unwrap();
1527 assert_eq!(&first.name, "MainWindow");
1528 } else {
1529 unreachable!();
1530 }
1531 }
1532
1533 #[test]
1534 fn test_document_symbols_hello_world() {
1535 let (mut dc, uri, _) = loaded_document_cache(
1536 r#"import { Button, VerticalBox } from "std-widgets.slint";
1537component Demo {
1538 VerticalBox {
1539 alignment: start;
1540 Text {
1541 text: "Hello World!";
1542 font-size: 24px;
1543 horizontal-alignment: center;
1544 }
1545 Image {
1546 source: @image-url("https://slint.dev/logo/slint-logo-full-light.svg");
1547 height: 100px;
1548 }
1549 HorizontalLayout { alignment: center; Button { text: "OK!"; } }
1550 }
1551}
1552 "#
1553 .into(),
1554 );
1555 let result =
1556 get_document_symbols(&mut dc, &lsp_types::TextDocumentIdentifier { uri }).unwrap();
1557
1558 if let DocumentSymbolResponse::Nested(result) = result {
1559 assert_eq!(result.len(), 1);
1560
1561 let first = result.first().unwrap();
1562 assert_eq!(&first.name, "Demo");
1563 } else {
1564 unreachable!();
1565 }
1566 }
1567
1568 #[test]
1569 fn test_document_symbols_no_empty_names() {
1570 // issue #3979
1571 let (mut dc, uri, _) = loaded_document_cache(
1572 r#"import { Button, VerticalBox } from "std-widgets.slint";
1573struct Foo {}
1574enum Bar {}
1575export component Yo { Rectangle {} }
1576export component {}
1577struct {}
1578enum {}
1579 "#
1580 .into(),
1581 );
1582 let result =
1583 get_document_symbols(&mut dc, &lsp_types::TextDocumentIdentifier { uri }).unwrap();
1584
1585 if let DocumentSymbolResponse::Nested(result) = result {
1586 assert_eq!(result.len(), 3);
1587 assert_eq!(result[0].name, "Foo");
1588 assert_eq!(result[1].name, "Bar");
1589 assert_eq!(result[2].name, "Yo");
1590 } else {
1591 unreachable!();
1592 }
1593 }
1594
1595 #[test]
1596 fn test_document_symbols_positions() {
1597 let source = r#"import { Button } from "std-widgets.slint";
1598
1599 enum TheEnum {
1600 Abc, Def
1601 }/*TheEnum*/
1602
1603 component FooBar {
1604 in property <TheEnum> the-enum;
1605 HorizontalLayout {
1606 btn := Button {}
1607 Rectangle {
1608 ta := TouchArea {}
1609 Image {
1610 }
1611 }
1612 }/*HorizontalLayout*/
1613 }/*FooBar*/
1614
1615 struct Str { abc: string }
1616
1617 export global SomeGlobal {
1618 in-out property<Str> prop;
1619 }/*SomeGlobal*/
1620
1621 export component TestWindow inherits Window {
1622 FooBar {}
1623 }/*TestWindow*/
1624 "#;
1625
1626 let (mut dc, uri, _) = test::loaded_document_cache(source.into());
1627
1628 let result =
1629 get_document_symbols(&mut dc, &lsp_types::TextDocumentIdentifier { uri: uri.clone() })
1630 .unwrap();
1631
1632 let check_start_with = |pos, str: &str| {
1633 let (_, offset) = get_document_and_offset(&dc.documents, &uri, &pos).unwrap();
1634 assert_eq!(&source[offset as usize..][..str.len()], str);
1635 };
1636
1637 let DocumentSymbolResponse::Nested(result) = result else {
1638 panic!("not nested {result:?}")
1639 };
1640
1641 assert_eq!(result.len(), 5);
1642 assert_eq!(result[0].name, "TheEnum");
1643 check_start_with(result[0].range.start, "enum TheEnum {");
1644 check_start_with(result[0].range.end, "/*TheEnum*/");
1645 check_start_with(result[0].selection_range.start, "TheEnum {");
1646 check_start_with(result[0].selection_range.end, " {");
1647 assert_eq!(result[1].name, "FooBar");
1648 check_start_with(result[1].range.start, "component FooBar {");
1649 check_start_with(result[1].range.end, "/*FooBar*/");
1650 check_start_with(result[1].selection_range.start, "FooBar {");
1651 check_start_with(result[1].selection_range.end, " {");
1652 assert_eq!(result[2].name, "Str");
1653 check_start_with(result[2].range.start, "struct Str {");
1654 check_start_with(result[2].range.end, "\n");
1655 check_start_with(result[2].selection_range.start, "Str {");
1656 check_start_with(result[2].selection_range.end, " {");
1657 assert_eq!(result[3].name, "SomeGlobal");
1658 check_start_with(result[3].range.start, "global SomeGlobal");
1659 check_start_with(result[3].range.end, "/*SomeGlobal*/");
1660 check_start_with(result[3].selection_range.start, "SomeGlobal {");
1661 check_start_with(result[3].selection_range.end, " {");
1662 assert_eq!(result[4].name, "TestWindow");
1663 check_start_with(result[4].range.start, "component TestWindow inherits Window {");
1664 check_start_with(result[4].range.end, "/*TestWindow*/");
1665 check_start_with(result[4].selection_range.start, "TestWindow inherits");
1666 check_start_with(result[4].selection_range.end, " inherits");
1667
1668 macro_rules! tree {
1669 ($root:literal $($more:literal)*) => {
1670 result[$root] $(.children.as_ref().unwrap()[$more])*
1671 };
1672 }
1673 assert_eq!(tree!(1 0).name, "HorizontalLayout");
1674 check_start_with(tree!(1 0).range.start, "HorizontalLayout {");
1675 check_start_with(tree!(1 0).range.end, "/*HorizontalLayout*/");
1676 assert_eq!(tree!(1 0 0).name, "Button");
1677 assert_eq!(tree!(1 0 0).detail, Some("btn".into()));
1678 check_start_with(tree!(1 0 0).range.start, "btn := Button");
1679
1680 assert_eq!(tree!(1 0 1 0).name, "TouchArea");
1681 assert_eq!(tree!(1 0 1 0).detail, Some("ta".into()));
1682 check_start_with(tree!(1 0 1 0).range.start, "ta := TouchArea");
1683 }
1684
1685 #[test]
1686 fn test_code_actions() {
1687 let (mut dc, url, _) = loaded_document_cache(
1688 r#"import { Button, VerticalBox, HorizontalBox} from "std-widgets.slint";
1689
1690export component TestWindow inherits Window {
1691 VerticalBox {
1692 alignment: start;
1693
1694 Text {
1695 text: "Hello World!";
1696 font-size: 20px;
1697 }
1698
1699 input := LineEdit {
1700 placeholder-text: "Enter your name";
1701 }
1702
1703 if (true): HorizontalBox {
1704 alignment: end;
1705
1706 Button { text: "Cancel"; }
1707
1708 Button {
1709 text: "OK";
1710 primary: true;
1711 }
1712 }
1713 }
1714}"#
1715 .into(),
1716 );
1717 let mut capabilities = ClientCapabilities::default();
1718
1719 let text_literal = lsp_types::Range::new(Position::new(7, 18), Position::new(7, 32));
1720 assert_eq!(
1721 token_descr(&mut dc, &url, &text_literal.start)
1722 .and_then(|(token, _)| get_code_actions(&mut dc, token, &capabilities)),
1723 Some(vec![CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1724 title: "Wrap in `@tr()`".into(),
1725 edit: Some(WorkspaceEdit {
1726 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1727 lsp_types::TextDocumentEdit {
1728 text_document: lsp_types::OptionalVersionedTextDocumentIdentifier {
1729 version: Some(42),
1730 uri: url.clone(),
1731 },
1732 edits: vec![
1733 lsp_types::OneOf::Left(TextEdit::new(
1734 lsp_types::Range::new(text_literal.start, text_literal.start),
1735 "@tr(".into()
1736 )),
1737 lsp_types::OneOf::Left(TextEdit::new(
1738 lsp_types::Range::new(text_literal.end, text_literal.end),
1739 ")".into()
1740 )),
1741 ],
1742 }
1743 ])),
1744 ..Default::default()
1745 }),
1746 ..Default::default()
1747 })]),
1748 );
1749
1750 let text_element = lsp_types::Range::new(Position::new(6, 8), Position::new(9, 9));
1751 for offset in 0..=4 {
1752 let pos = Position::new(text_element.start.line, text_element.start.character + offset);
1753
1754 capabilities.experimental = None;
1755 assert_eq!(
1756 token_descr(&mut dc, &url, &pos).and_then(|(token, _)| get_code_actions(
1757 &mut dc,
1758 token,
1759 &capabilities
1760 )),
1761 None
1762 );
1763
1764 capabilities.experimental = Some(serde_json::json!({"snippetTextEdit": true}));
1765 assert_eq!(
1766 token_descr(&mut dc, &url, &pos).and_then(|(token, _)| get_code_actions(
1767 &mut dc,
1768 token,
1769 &capabilities
1770 )),
1771 Some(vec![
1772 CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1773 title: "Wrap in element".into(),
1774 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1775 edit: Some(WorkspaceEdit {
1776 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1777 lsp_types::TextDocumentEdit {
1778 text_document:
1779 lsp_types::OptionalVersionedTextDocumentIdentifier {
1780 version: Some(42),
1781 uri: url.clone(),
1782 },
1783 edits: vec![lsp_types::OneOf::Left(TextEdit::new(
1784 text_element,
1785 r#"${0:element} {
1786 Text {
1787 text: "Hello World!";
1788 font-size: 20px;
1789 }
1790}"#
1791 .into()
1792 ))],
1793 },
1794 ])),
1795 ..Default::default()
1796 }),
1797 ..Default::default()
1798 }),
1799 CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1800 title: "Repeat element".into(),
1801 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1802 edit: Some(WorkspaceEdit {
1803 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1804 lsp_types::TextDocumentEdit {
1805 text_document:
1806 lsp_types::OptionalVersionedTextDocumentIdentifier {
1807 version: Some(42),
1808 uri: url.clone(),
1809 },
1810 edits: vec![lsp_types::OneOf::Left(TextEdit::new(
1811 lsp_types::Range::new(
1812 text_element.start,
1813 text_element.start
1814 ),
1815 r#"for ${1:name}[index] in ${0:model} : "#.into()
1816 ))],
1817 }
1818 ])),
1819 ..Default::default()
1820 }),
1821 ..Default::default()
1822 }),
1823 CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1824 title: "Make conditional".into(),
1825 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1826 edit: Some(WorkspaceEdit {
1827 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1828 lsp_types::TextDocumentEdit {
1829 text_document:
1830 lsp_types::OptionalVersionedTextDocumentIdentifier {
1831 version: Some(42),
1832 uri: url.clone(),
1833 },
1834 edits: vec![lsp_types::OneOf::Left(TextEdit::new(
1835 lsp_types::Range::new(
1836 text_element.start,
1837 text_element.start
1838 ),
1839 r#"if ${0:condition} : "#.into()
1840 ))],
1841 }
1842 ])),
1843 ..Default::default()
1844 }),
1845 ..Default::default()
1846 }),
1847 ])
1848 );
1849 }
1850
1851 let horizontal_box = lsp_types::Range::new(Position::new(15, 19), Position::new(24, 9));
1852
1853 capabilities.experimental = None;
1854 assert_eq!(
1855 token_descr(&mut dc, &url, &horizontal_box.start)
1856 .and_then(|(token, _)| get_code_actions(&mut dc, token, &capabilities)),
1857 None
1858 );
1859
1860 capabilities.experimental = Some(serde_json::json!({"snippetTextEdit": true}));
1861 assert_eq!(
1862 token_descr(&mut dc, &url, &horizontal_box.start)
1863 .and_then(|(token, _)| get_code_actions(&mut dc, token, &capabilities)),
1864 Some(vec![
1865 CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1866 title: "Wrap in element".into(),
1867 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1868 edit: Some(WorkspaceEdit {
1869 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1870 lsp_types::TextDocumentEdit {
1871 text_document: lsp_types::OptionalVersionedTextDocumentIdentifier {
1872 version: Some(42),
1873 uri: url.clone(),
1874 },
1875 edits: vec![lsp_types::OneOf::Left(TextEdit::new(
1876 horizontal_box,
1877 r#"${0:element} {
1878 HorizontalBox {
1879 alignment: end;
1880
1881 Button { text: "Cancel"; }
1882
1883 Button {
1884 text: "OK";
1885 primary: true;
1886 }
1887 }
1888}"#
1889 .into()
1890 ))]
1891 }
1892 ])),
1893 ..Default::default()
1894 }),
1895 ..Default::default()
1896 }),
1897 CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1898 title: "Remove element".into(),
1899 kind: Some(lsp_types::CodeActionKind::REFACTOR),
1900 edit: Some(WorkspaceEdit {
1901 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1902 lsp_types::TextDocumentEdit {
1903 text_document: lsp_types::OptionalVersionedTextDocumentIdentifier {
1904 version: Some(42),
1905 uri: url.clone(),
1906 },
1907 edits: vec![lsp_types::OneOf::Left(TextEdit::new(
1908 horizontal_box,
1909 r#"Button { text: "Cancel"; }
1910
1911 Button {
1912 text: "OK";
1913 primary: true;
1914 }"#
1915 .into()
1916 ))]
1917 }
1918 ])),
1919 ..Default::default()
1920 }),
1921 ..Default::default()
1922 }),
1923 ])
1924 );
1925
1926 let line_edit = Position::new(11, 20);
1927 let import_pos = lsp_types::Position::new(0, 43);
1928 capabilities.experimental = None;
1929 assert_eq!(
1930 token_descr(&mut dc, &url, &line_edit).and_then(|(token, _)| get_code_actions(
1931 &mut dc,
1932 token,
1933 &capabilities
1934 )),
1935 Some(vec![CodeActionOrCommand::CodeAction(lsp_types::CodeAction {
1936 title: "Add import from \"std-widgets.slint\"".into(),
1937 kind: Some(lsp_types::CodeActionKind::QUICKFIX),
1938 edit: Some(WorkspaceEdit {
1939 document_changes: Some(lsp_types::DocumentChanges::Edits(vec![
1940 lsp_types::TextDocumentEdit {
1941 text_document: lsp_types::OptionalVersionedTextDocumentIdentifier {
1942 version: Some(42),
1943 uri: url.clone(),
1944 },
1945 edits: vec![lsp_types::OneOf::Left(TextEdit::new(
1946 lsp_types::Range::new(import_pos, import_pos),
1947 ", LineEdit".into()
1948 ))]
1949 }
1950 ])),
1951 ..Default::default()
1952 }),
1953 ..Default::default()
1954 }),])
1955 );
1956 }
1957}
1958