From cab41c3d0c64db6e1ba01bd197be62e1d64e09e0 Mon Sep 17 00:00:00 2001 From: Tobias Hunger Date: Fri, 30 Aug 2024 19:53:00 +0000 Subject: [PATCH] lsp: Use more TextSize I got carried away, I replaced `u32` with `TextSize` in one place because that's what I got and I did not want to convert... and then I fixed the fallout. No functional change is intended in any of this. FIXUP --- tools/lsp/common.rs | 41 ++++-- tools/lsp/common/document_cache.rs | 28 ++-- tools/lsp/common/text_edit.rs | 134 +++++++++++------- tools/lsp/language.rs | 15 +- tools/lsp/language/completion.rs | 18 ++- tools/lsp/language/goto.rs | 41 +++--- tools/lsp/language/hover.rs | 188 +++++++++++++------------ tools/lsp/preview.rs | 18 +-- tools/lsp/preview/drop_location.rs | 65 +++++---- tools/lsp/preview/element_selection.rs | 22 +-- tools/lsp/preview/ui.rs | 6 +- 11 files changed, 331 insertions(+), 245 deletions(-) diff --git a/tools/lsp/common.rs b/tools/lsp/common.rs index 3d4042a6a9..5313ddbbbe 100644 --- a/tools/lsp/common.rs +++ b/tools/lsp/common.rs @@ -4,7 +4,7 @@ //! Data structures common between LSP and previewer use i_slint_compiler::object_tree::ElementRc; -use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxNode}; +use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxNode, TextSize}; use lsp_types::{TextEdit, Url, WorkspaceEdit}; use std::path::Path; @@ -112,7 +112,7 @@ impl std::cmp::PartialEq for ElementRcNode { impl std::fmt::Debug for ElementRcNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let (path, offset) = self.path_and_offset(); - write!(f, "ElementNode {{ {path:?}:{offset} }}") + write!(f, "ElementNode {{ {path:?}:{offset:?} }}") } } @@ -201,10 +201,8 @@ impl ElementRcNode { func(find_element_with_decoration(&elem.debug.get(self.debug_index).unwrap().node)) } - pub fn path_and_offset(&self) -> (PathBuf, u32) { - self.with_element_node(|n| { - (n.source_file.path().to_owned(), u32::from(n.text_range().start())) - }) + pub fn path_and_offset(&self) -> (PathBuf, TextSize) { + self.with_element_node(|n| (n.source_file.path().to_owned(), n.text_range().start())) } pub fn as_element(&self) -> &ElementRc { @@ -278,9 +276,9 @@ impl ElementRcNode { std::rc::Rc::ptr_eq(&s.source_file, &o.source_file) && s.text_range() == o.text_range() } - pub fn contains_offset(&self, offset: u32) -> bool { + pub fn contains_offset(&self, offset: TextSize) -> bool { self.with_element_node(|node| { - node.parent().map_or(false, |n| n.text_range().contains(offset.into())) + node.parent().map_or(false, |n| n.text_range().contains(offset)) }) } } @@ -403,9 +401,24 @@ impl std::fmt::Debug for VersionedUrl { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)] pub struct Position { /// The file url - pub url: Url, + url: Url, /// The offset in the file pointed to by the `url` - pub offset: u32, + offset: u32, +} + +#[allow(unused)] +impl Position { + pub fn new(url: Url, offset: TextSize) -> Self { + Self { url, offset: offset.into() } + } + + pub fn url(&self) -> &Url { + &self.url + } + + pub fn offset(&self) -> TextSize { + self.offset.into() + } } /// A versioned file @@ -419,8 +432,8 @@ pub struct VersionedPosition { #[allow(unused)] impl VersionedPosition { - pub fn new(url: VersionedUrl, offset: u32) -> Self { - VersionedPosition { url, offset } + pub fn new(url: VersionedUrl, offset: TextSize) -> Self { + Self { url, offset: offset.into() } } pub fn url(&self) -> &Url { @@ -431,8 +444,8 @@ impl VersionedPosition { self.url.version() } - pub fn offset(&self) -> u32 { - self.offset + pub fn offset(&self) -> TextSize { + self.offset.into() } } diff --git a/tools/lsp/common/document_cache.rs b/tools/lsp/common/document_cache.rs index b1de26a609..49150aa587 100644 --- a/tools/lsp/common/document_cache.rs +++ b/tools/lsp/common/document_cache.rs @@ -5,6 +5,7 @@ use i_slint_compiler::diagnostics::{BuildDiagnostics, SourceFile}; use i_slint_compiler::object_tree::Document; +use i_slint_compiler::parser::TextSize; use i_slint_compiler::typeloader::TypeLoader; use i_slint_compiler::typeregister::TypeRegister; use lsp_types::Url; @@ -206,14 +207,15 @@ impl DocumentCache { &'a self, text_document_uri: &'_ Url, pos: &'_ lsp_types::Position, - ) -> Option<(&'a i_slint_compiler::object_tree::Document, u32)> { + ) -> Option<(&'a i_slint_compiler::object_tree::Document, TextSize)> { let doc = self.get_document(text_document_uri)?; - let o = doc + let o = (doc .node .as_ref()? .source_file - .offset(pos.line as usize + 1, pos.character as usize + 1) as u32; - doc.node.as_ref()?.text_range().contains_inclusive(o.into()).then_some((doc, o)) + .offset(pos.line as usize + 1, pos.character as usize + 1) as u32) + .into(); + doc.node.as_ref()?.text_range().contains_inclusive(o).then_some((doc, o)) } pub fn all_url_documents(&self) -> impl Iterator + '_ { @@ -292,15 +294,17 @@ impl DocumentCache { fn element_at_document_and_offset( &self, document: &i_slint_compiler::object_tree::Document, - offset: u32, + offset: TextSize, ) -> Option { fn element_contains( element: &i_slint_compiler::object_tree::ElementRc, - offset: u32, + offset: TextSize, ) -> Option { - element.borrow().debug.iter().position(|n| { - n.node.parent().map_or(false, |n| n.text_range().contains(offset.into())) - }) + element + .borrow() + .debug + .iter() + .position(|n| n.node.parent().map_or(false, |n| n.text_range().contains(offset))) } for component in &document.inner_components { @@ -329,7 +333,11 @@ impl DocumentCache { None } - pub fn element_at_offset(&self, text_document_uri: &Url, offset: u32) -> Option { + pub fn element_at_offset( + &self, + text_document_uri: &Url, + offset: TextSize, + ) -> Option { let doc = self.get_document(text_document_uri)?; self.element_at_document_and_offset(doc, offset) } diff --git a/tools/lsp/common/text_edit.rs b/tools/lsp/common/text_edit.rs index f793e6bd9d..1ef0fb5712 100644 --- a/tools/lsp/common/text_edit.rs +++ b/tools/lsp/common/text_edit.rs @@ -3,12 +3,14 @@ use std::collections::HashMap; +use i_slint_compiler::parser::TextSize; + use crate::common; #[derive(Clone, Debug)] pub struct TextOffsetAdjustment { - pub start_offset: u32, - pub end_offset: u32, + pub start_offset: TextSize, + pub end_offset: TextSize, pub new_text_length: u32, } @@ -25,25 +27,33 @@ impl TextOffsetAdjustment { ); let eo = source_file .offset(edit.range.end.line as usize + 1, edit.range.end.character as usize + 1); - (std::cmp::min(so, eo) as u32, std::cmp::max(so, eo) as u32) + ( + TextSize::new(std::cmp::min(so, eo) as u32), + TextSize::new(std::cmp::max(so, eo) as u32), + ) }; Self { start_offset, end_offset, new_text_length } } - pub fn adjust(&self, offset: u32) -> u32 { + pub fn adjust(&self, offset: TextSize) -> TextSize { // This is a bit simplistic... Worst case: Some unexpected element gets selected. We can live with that. debug_assert!(self.end_offset >= self.start_offset); let old_length = self.end_offset - self.start_offset; if offset >= self.end_offset { - offset + self.new_text_length - old_length + offset + TextSize::new(self.new_text_length) - old_length } else if offset >= self.start_offset { - (offset as i64 + self.new_text_length as i64 - old_length as i64).clamp( - self.start_offset as i64, - self.end_offset.min(self.start_offset + self.new_text_length) as i64, - ) as u32 + ((u32::from(offset) as i64 + self.new_text_length as i64 - u32::from(old_length) as i64) + .clamp( + u32::from(self.start_offset) as i64, + u32::from( + self.end_offset + .min(self.start_offset + TextSize::new(self.new_text_length)), + ) as i64, + ) as u32) + .into() } else { offset } @@ -58,11 +68,13 @@ impl TextOffsetAdjustments { self.0.push(adjustment); } - pub fn adjust(&self, input: u32) -> u32 { - let input_ = i64::from(input); - let total_adjustment = - self.0.iter().fold(0_i64, |acc, a| acc + i64::from(a.adjust(input)) - input_); - (input_ + total_adjustment) as u32 + pub fn adjust(&self, input: TextSize) -> TextSize { + let input_ = i64::from(u32::from(input)); + let total_adjustment = self + .0 + .iter() + .fold(0_i64, |acc, a| acc + i64::from(u32::from(a.adjust(input))) - input_); + ((input_ + total_adjustment) as u32).into() } pub fn is_empty(&self) -> bool { @@ -193,8 +205,8 @@ impl TextEditor { }; let adjusted_offset = ( - self.adjustments.adjust(current_offset.0 as u32) as usize, - self.adjustments.adjust(current_offset.1 as u32) as usize, + usize::from(self.adjustments.adjust((current_offset.0 as u32).into())), + usize::from(self.adjustments.adjust((current_offset.1 as u32).into())), ); if self.contents.len() < adjusted_offset.1 { @@ -262,63 +274,87 @@ fn test_text_offset_adjustments() { let mut a = TextOffsetAdjustments::default(); // same length change a.add_adjustment(TextOffsetAdjustment { - start_offset: 10, - end_offset: 20, + start_offset: 10.into(), + end_offset: 20.into(), new_text_length: 10, }); // insert - a.add_adjustment(TextOffsetAdjustment { start_offset: 25, end_offset: 25, new_text_length: 1 }); + a.add_adjustment(TextOffsetAdjustment { + start_offset: 25.into(), + end_offset: 25.into(), + new_text_length: 1, + }); // smaller replacement - a.add_adjustment(TextOffsetAdjustment { start_offset: 30, end_offset: 40, new_text_length: 5 }); + a.add_adjustment(TextOffsetAdjustment { + start_offset: 30.into(), + end_offset: 40.into(), + new_text_length: 5, + }); // longer replacement a.add_adjustment(TextOffsetAdjustment { - start_offset: 50, - end_offset: 60, + start_offset: 50.into(), + end_offset: 60.into(), new_text_length: 20, }); // deletion - a.add_adjustment(TextOffsetAdjustment { start_offset: 70, end_offset: 80, new_text_length: 0 }); + a.add_adjustment(TextOffsetAdjustment { + start_offset: 70.into(), + end_offset: 80.into(), + new_text_length: 0, + }); - assert_eq!(a.adjust(0), 0); - assert_eq!(a.adjust(20), 20); - assert_eq!(a.adjust(25), 26); - assert_eq!(a.adjust(30), 31); - assert_eq!(a.adjust(40), 36); - assert_eq!(a.adjust(60), 66); - assert_eq!(a.adjust(70), 76); - assert_eq!(a.adjust(80), 76); + assert_eq!(a.adjust(0.into()), 0.into()); + assert_eq!(a.adjust(20.into()), 20.into()); + assert_eq!(a.adjust(25.into()), 26.into()); + assert_eq!(a.adjust(30.into()), 31.into()); + assert_eq!(a.adjust(40.into()), 36.into()); + assert_eq!(a.adjust(60.into()), 66.into()); + assert_eq!(a.adjust(70.into()), 76.into()); + assert_eq!(a.adjust(80.into()), 76.into()); } #[test] fn test_text_offset_adjustments_reverse() { let mut a = TextOffsetAdjustments::default(); // deletion - a.add_adjustment(TextOffsetAdjustment { start_offset: 70, end_offset: 80, new_text_length: 0 }); + a.add_adjustment(TextOffsetAdjustment { + start_offset: 70.into(), + end_offset: 80.into(), + new_text_length: 0, + }); // longer replacement a.add_adjustment(TextOffsetAdjustment { - start_offset: 50, - end_offset: 60, + start_offset: 50.into(), + end_offset: 60.into(), new_text_length: 20, }); // smaller replacement - a.add_adjustment(TextOffsetAdjustment { start_offset: 30, end_offset: 40, new_text_length: 5 }); + a.add_adjustment(TextOffsetAdjustment { + start_offset: 30.into(), + end_offset: 40.into(), + new_text_length: 5, + }); // insert - a.add_adjustment(TextOffsetAdjustment { start_offset: 25, end_offset: 25, new_text_length: 1 }); + a.add_adjustment(TextOffsetAdjustment { + start_offset: 25.into(), + end_offset: 25.into(), + new_text_length: 1, + }); // same length change a.add_adjustment(TextOffsetAdjustment { - start_offset: 10, - end_offset: 20, + start_offset: 10.into(), + end_offset: 20.into(), new_text_length: 10, }); - assert_eq!(a.adjust(0), 0); - assert_eq!(a.adjust(20), 20); - assert_eq!(a.adjust(25), 26); - assert_eq!(a.adjust(30), 31); - assert_eq!(a.adjust(40), 36); - assert_eq!(a.adjust(60), 66); - assert_eq!(a.adjust(70), 76); - assert_eq!(a.adjust(80), 76); + assert_eq!(a.adjust(0.into()), 0.into()); + assert_eq!(a.adjust(20.into()), 20.into()); + assert_eq!(a.adjust(25.into()), 26.into()); + assert_eq!(a.adjust(30.into()), 31.into()); + assert_eq!(a.adjust(40.into()), 36.into()); + assert_eq!(a.adjust(60.into()), 66.into()); + assert_eq!(a.adjust(70.into()), 76.into()); + assert_eq!(a.adjust(80.into()), 76.into()); } #[test] @@ -830,7 +866,7 @@ geh"# let result = editor.finalize().unwrap(); assert!(result.0.is_empty()); - assert_eq!(result.1.adjust(42), 31); + assert_eq!(result.1.adjust(42.into()), 31.into()); assert_eq!(result.2 .0, 0); assert_eq!(result.2 .1, 3 * 3 + 2); } @@ -865,7 +901,7 @@ geh"# REPLACEMENT geh"# ); - assert_eq!(result.1.adjust(42), 50); + assert_eq!(result.1.adjust(42.into()), 50.into()); assert_eq!(result.2 .0, 3 + 1); assert_eq!(result.2 .1, 3 + 1 + 3); } @@ -903,7 +939,7 @@ geh"# let result = editor.finalize().unwrap(); assert_eq!(&result.0, "REPLACEMENT"); - assert_eq!(result.1.adjust(42), 42); + assert_eq!(result.1.adjust(42.into()), 42.into()); assert_eq!(result.2 .0, 0); assert_eq!(result.2 .1, 3 * 3 + 2); } diff --git a/tools/lsp/language.rs b/tools/lsp/language.rs index d7f43a7047..63fc13ee13 100644 --- a/tools/lsp/language.rs +++ b/tools/lsp/language.rs @@ -19,7 +19,7 @@ use crate::util; use crate::wasm_prelude::*; use i_slint_compiler::object_tree::ElementRc; use i_slint_compiler::parser::{ - syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, + syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, }; use i_slint_compiler::{diagnostics::BuildDiagnostics, langtype::Type}; use lsp_types::request::{ @@ -345,7 +345,10 @@ pub fn register_request_handlers(rh: &mut RequestHandler) { { let range = util::node_to_lsp_range(&p); ctx.server_notifier.send_message_to_preview( - common::LspToPreviewMessage::HighlightFromEditor { url: Some(uri), offset }, + common::LspToPreviewMessage::HighlightFromEditor { + url: Some(uri), + offset: offset.into(), + }, ); return Ok(Some(vec![lsp_types::DocumentHighlight { range, kind: None }])); } @@ -561,7 +564,7 @@ fn token_descr( document_cache: &mut DocumentCache, text_document_uri: &Url, pos: &Position, -) -> Option<(SyntaxToken, u32)> { +) -> Option<(SyntaxToken, TextSize)> { let (doc, o) = document_cache.get_document_and_offset(text_document_uri, pos)?; let node = doc.node.as_ref()?; @@ -570,8 +573,8 @@ fn token_descr( } /// Return the token that matches best the token at cursor position -pub fn token_at_offset(doc: &syntax_nodes::Document, offset: u32) -> Option { - let mut taf = doc.token_at_offset(offset.into()); +pub fn token_at_offset(doc: &syntax_nodes::Document, offset: TextSize) -> Option { + let mut taf = doc.token_at_offset(offset); let token = match (taf.next(), taf.next()) { (None, _) => doc.last_token()?, (Some(t), None) => t, @@ -1328,7 +1331,7 @@ enum {} let check_start_with = |pos, str: &str| { let (_, offset) = dc.get_document_and_offset(&uri, &pos).unwrap(); - assert_eq!(&source[offset as usize..][..str.len()], str); + assert_eq!(&source[usize::from(offset)..][..str.len()], str); }; let DocumentSymbolResponse::Nested(result) = result else { diff --git a/tools/lsp/language/completion.rs b/tools/lsp/language/completion.rs index a3c53f95c2..68c42fef59 100644 --- a/tools/lsp/language/completion.rs +++ b/tools/lsp/language/completion.rs @@ -14,7 +14,7 @@ use i_slint_compiler::expression_tree::Expression; use i_slint_compiler::langtype::{ElementType, Type}; use i_slint_compiler::lookup::{LookupCtx, LookupObject, LookupResult}; use i_slint_compiler::object_tree::ElementRc; -use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxToken}; +use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxToken, TextSize}; use lsp_types::{ CompletionClientCapabilities, CompletionItem, CompletionItemKind, InsertTextFormat, Position, Range, TextEdit, @@ -26,7 +26,7 @@ use std::path::Path; pub(crate) fn completion_at( document_cache: &mut DocumentCache, token: SyntaxToken, - offset: u32, + offset: TextSize, client_caps: Option<&CompletionClientCapabilities>, ) -> Option> { let node = token.parent(); @@ -41,7 +41,7 @@ pub(crate) fn completion_at( return complete_path_in_string( token.source_file()?.path(), token.text(), - offset.checked_sub(token.text_range().start().into())?, + offset.checked_sub(token.text_range().start())?, ) .map(|mut r| { if node.kind() == SyntaxKind::ImportSpecifier && !token.text().contains('/') { @@ -641,12 +641,16 @@ fn resolve_type_scope( ) } -fn complete_path_in_string(base: &Path, text: &str, offset: u32) -> Option> { - if offset as usize > text.len() || offset == 0 { +fn complete_path_in_string( + base: &Path, + text: &str, + offset: TextSize, +) -> Option> { + if u32::from(offset) as usize > text.len() || offset == 0.into() { return None; } let mut text = text.strip_prefix('\"')?; - text = &text[..(offset - 1) as usize]; + text = &text[..(u32::from(offset) - 1) as usize]; let base = i_slint_compiler::typeloader::base_directory(base); let path = if let Some(last_slash) = text.rfind('/') { base.join(Path::new(&text[..last_slash])) @@ -877,7 +881,7 @@ mod tests { /// Given a source text containing the unicode emoji `🔺`, the emoji will be removed and then an autocompletion request will be done as if the cursor was there fn get_completions(file: &str) -> Option> { const CURSOR_EMOJI: char = '🔺'; - let offset = file.find(CURSOR_EMOJI).unwrap() as u32; + let offset = (file.find(CURSOR_EMOJI).unwrap() as u32).into(); let source = file.replace(CURSOR_EMOJI, ""); let (mut dc, uri, _) = crate::language::test::loaded_document_cache(source); diff --git a/tools/lsp/language/goto.rs b/tools/lsp/language/goto.rs index 7a3634708d..e088e4ba42 100644 --- a/tools/lsp/language/goto.rs +++ b/tools/lsp/language/goto.rs @@ -78,6 +78,9 @@ fn goto_node(node: &SyntaxNode) -> Option { }])) } +#[cfg(test)] +use i_slint_compiler::parser::TextSize; + #[test] fn test_goto_definition() { fn first_link(def: &GotoDefinitionResponse) -> &LocationLink { @@ -104,8 +107,8 @@ export component Test { let doc = dc.get_document(&uri).unwrap().node.clone().unwrap(); // Jump to the definition of Abc - let offset = source.find("abc := Abc").unwrap() as u32; - let token = crate::language::token_at_offset(&doc, offset + 8).unwrap(); + let offset: TextSize = (source.find("abc := Abc").unwrap() as u32).into(); + let token = crate::language::token_at_offset(&doc, offset + TextSize::new(8)).unwrap(); assert_eq!(token.text(), "Abc"); let def = goto_definition(&mut dc, token).unwrap(); let link = first_link(&def); @@ -113,8 +116,8 @@ export component Test { assert_eq!(link.target_range.start.line, 2); // Jump to the definition of abc - let offset = source.find("text: abc.hello").unwrap() as u32; - let token = crate::language::token_at_offset(&doc, offset + 7).unwrap(); + let offset: TextSize = (source.find("text: abc.hello").unwrap() as u32).into(); + let token = crate::language::token_at_offset(&doc, offset + TextSize::new(7)).unwrap(); assert_eq!(token.text(), "abc"); let def = goto_definition(&mut dc, token).unwrap(); let link = first_link(&def); @@ -122,8 +125,8 @@ export component Test { assert_eq!(link.target_range.start.line, 6); // Jump to the definition of hello - let offset = source.find("text: abc.hello").unwrap() as u32; - let token = crate::language::token_at_offset(&doc, offset + 12).unwrap(); + let offset: TextSize = (source.find("text: abc.hello").unwrap() as u32).into(); + let token = crate::language::token_at_offset(&doc, offset + TextSize::new(12)).unwrap(); assert_eq!(token.text(), "hello"); let def = goto_definition(&mut dc, token).unwrap(); let link = first_link(&def); @@ -131,7 +134,7 @@ export component Test { assert_eq!(link.target_range.start.line, 3); // Also jump to the definition of hello - let offset = source.find("hello: \"foo\"").unwrap() as u32; + let offset = (source.find("hello: \"foo\"").unwrap() as u32).into(); let token = crate::language::token_at_offset(&doc, offset).unwrap(); assert_eq!(token.text(), "hello"); let def = goto_definition(&mut dc, token).unwrap(); @@ -140,17 +143,17 @@ export component Test { assert_eq!(link.target_range.start.line, 3); // Rectangle is builtin and not accessible - let offset = source.find("rec := ").unwrap() as u32; - let token = crate::language::token_at_offset(&doc, offset + 8).unwrap(); + let offset: TextSize = (source.find("rec := ").unwrap() as u32).into(); + let token = crate::language::token_at_offset(&doc, offset + TextSize::new(8)).unwrap(); assert_eq!(token.text(), "Rectangle"); assert!(goto_definition(&mut dc, token).is_none()); // Button is builtin and not accessible - let offset = source.find("btn := ").unwrap() as u32; - let token = crate::language::token_at_offset(&doc, offset + 9).unwrap(); + let offset: TextSize = (source.find("btn := ").unwrap() as u32).into(); + let token = crate::language::token_at_offset(&doc, offset + TextSize::new(9)).unwrap(); assert_eq!(token.text(), "Button"); assert!(goto_definition(&mut dc, token).is_none()); - let offset = source.find("text: abc.hello").unwrap() as u32; + let offset = (source.find("text: abc.hello").unwrap() as u32).into(); let token = crate::language::token_at_offset(&doc, offset).unwrap(); assert_eq!(token.text(), "text"); assert!(goto_definition(&mut dc, token).is_none()); @@ -199,15 +202,15 @@ fn test_goto_definition_multi_files() { } let doc2 = dc.get_document(&url2).unwrap().node.clone().unwrap(); - let offset = source2.find("h := Hello").unwrap() as u32; - let token = crate::language::token_at_offset(&doc2, offset + 8).unwrap(); + let offset: TextSize = (source2.find("h := Hello").unwrap() as u32).into(); + let token = crate::language::token_at_offset(&doc2, offset + TextSize::new(8)).unwrap(); assert_eq!(token.text(), "Hello"); let def = goto_definition(&mut dc, token).unwrap(); let link = first_link(&def); assert_eq!(link.target_uri, url1); assert_eq!(link.target_range.start.line, 1); - let offset = source2.find("the_prop: 42").unwrap() as u32; + let offset = (source2.find("the_prop: 42").unwrap() as u32).into(); let token = crate::language::token_at_offset(&doc2, offset).unwrap(); assert_eq!(token.text(), "the_prop"); let def = goto_definition(&mut dc, token).unwrap(); @@ -215,9 +218,9 @@ fn test_goto_definition_multi_files() { assert_eq!(link.target_uri, url1); assert_eq!(link.target_range.start.line, 2); - let offset = source2.find("Hello } from ").unwrap() as u32; + let offset = (source2.find("Hello } from ").unwrap() as u32).into(); // check the string literal - let token = crate::language::token_at_offset(&doc2, offset + 20).unwrap(); + let token = crate::language::token_at_offset(&doc2, offset + TextSize::new(20)).unwrap(); assert_eq!(token.kind(), i_slint_compiler::parser::SyntaxKind::StringLiteral); let def = goto_definition(&mut dc, token).unwrap(); let link = first_link(&def); @@ -231,9 +234,9 @@ fn test_goto_definition_multi_files() { assert_eq!(link.target_uri, url1); assert_eq!(link.target_range.start.line, 1); - let offset = source2.find("Another as A } from ").unwrap() as u32; + let offset = (source2.find("Another as A } from ").unwrap() as u32).into(); // check the string literal - let token = crate::language::token_at_offset(&doc2, offset + 25).unwrap(); + let token = crate::language::token_at_offset(&doc2, offset + TextSize::new(25)).unwrap(); assert_eq!(token.kind(), i_slint_compiler::parser::SyntaxKind::StringLiteral); let def = goto_definition(&mut dc, token).unwrap(); let link = first_link(&def); diff --git a/tools/lsp/language/hover.rs b/tools/lsp/language/hover.rs index a4a19cc8e8..f6292597b9 100644 --- a/tools/lsp/language/hover.rs +++ b/tools/lsp/language/hover.rs @@ -86,9 +86,15 @@ fn from_slint_code(value: &str) -> MarkupContent { } } -#[test] -fn test_tooltip() { - let source = r#" +#[cfg(test)] +mod tests { + use super::*; + + use i_slint_compiler::parser::TextSize; + + #[test] + fn test_tooltip() { + let source = r#" global Glob { in-out property <{a:int,b:float}> hello_world; callback cb(string, int) -> [int]; @@ -120,92 +126,100 @@ export component Test { border-color: self.background; } }"#; - let (mut dc, uri, _) = crate::language::test::loaded_document_cache(source.into()); - let doc = dc.get_document(&uri).unwrap().node.clone().unwrap(); + let (mut dc, uri, _) = crate::language::test::loaded_document_cache(source.into()); + let doc = dc.get_document(&uri).unwrap().node.clone().unwrap(); - let find_tk = |needle: &str, offset: i32| { - crate::language::token_at_offset( - &doc, - (source.find(needle).unwrap_or_else(|| panic!("'{needle}' not found")) as u32) - .saturating_add_signed(offset), - ) - .unwrap() - }; + let find_tk = |needle: &str, offset: TextSize| { + crate::language::token_at_offset( + &doc, + TextSize::new( + source.find(needle).unwrap_or_else(|| panic!("'{needle}' not found")) as u32, + ) + offset, + ) + .unwrap() + }; - #[track_caller] - fn assert_tooltip(h: Option, str: &str) { - match h.unwrap().contents { - HoverContents::Markup(m) => assert_eq!(m.value, str), - x => panic!("Found {x:?} ({str})"), + #[track_caller] + fn assert_tooltip(h: Option, str: &str) { + match h.unwrap().contents { + HoverContents::Markup(m) => assert_eq!(m.value, str), + x => panic!("Found {x:?} ({str})"), + } } + + // properties + assert_tooltip( + get_tooltip(&mut dc, find_tk("hello: Glob", 0.into())), + "```slint\nproperty hello\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("Glob.hello_world", 8.into())), + "```slint\nproperty <{ a: int,b: float,}> hello-world\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.enabled", 5.into())), + "```slint\nproperty enabled\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 10.into())), + "```slint\nproperty local-prop\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("root-prop.to-float", 1.into())), + "```slint\nproperty root-prop\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("background: red", 0.into())), + "```slint\nproperty background\n```", + ); + // callbacks + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.www", 5.into())), + "```slint\npure callback www()\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("xyz(abc", 0.into())), + "```slint\ncallback xyz(string, int)\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("Glob.cb(", 6.into())), + "```slint\ncallback cb(string, int) -> [int]\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 1.into())), + "```slint\npure function fn-glob(int)\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("root.fn_loc", 8.into())), + "```slint\nfunction fn-loc() -> int\n```", + ); + // elements + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.enabled", 0.into())), + "```slint\nthe-ta := TA { /*...*/ }\n```", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("self.background", 0.into())), + "```slint\nRectangle { /*...*/ }\n```", + ); + // global + assert_tooltip( + get_tooltip(&mut dc, find_tk("hello: Glob", 8.into())), + "```slint\nglobal Glob\n```", + ); + + //components + assert_tooltip( + get_tooltip(&mut dc, find_tk("Rectangle {", 8.into())), + "Rectangle (builtin)", + ); + assert_tooltip( + get_tooltip(&mut dc, find_tk("the-ta := TA {", 11.into())), + "```slint\ncomponent TA\n```", + ); + + // enums + assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 0.into())), "enum Eee"); + assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 5.into())), "```slint\nEee.E2\n```"); } - - // properties - assert_tooltip( - get_tooltip(&mut dc, find_tk("hello: Glob", 0)), - "```slint\nproperty hello\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("Glob.hello_world", 8)), - "```slint\nproperty <{ a: int,b: float,}> hello-world\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("self.enabled", 5)), - "```slint\nproperty enabled\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 10)), - "```slint\nproperty local-prop\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("root-prop.to-float", 1)), - "```slint\nproperty root-prop\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("background: red", 0)), - "```slint\nproperty background\n```", - ); - // callbacks - assert_tooltip( - get_tooltip(&mut dc, find_tk("self.www", 5)), - "```slint\npure callback www()\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("xyz(abc", 0)), - "```slint\ncallback xyz(string, int)\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("Glob.cb(", 6)), - "```slint\ncallback cb(string, int) -> [int]\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("fn_glob(local-prop)", 1)), - "```slint\npure function fn-glob(int)\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("root.fn_loc", 8)), - "```slint\nfunction fn-loc() -> int\n```", - ); - // elements - assert_tooltip( - get_tooltip(&mut dc, find_tk("self.enabled", 0)), - "```slint\nthe-ta := TA { /*...*/ }\n```", - ); - assert_tooltip( - get_tooltip(&mut dc, find_tk("self.background", 0)), - "```slint\nRectangle { /*...*/ }\n```", - ); - // global - assert_tooltip(get_tooltip(&mut dc, find_tk("hello: Glob", 8)), "```slint\nglobal Glob\n```"); - - //components - assert_tooltip(get_tooltip(&mut dc, find_tk("Rectangle {", 8)), "Rectangle (builtin)"); - assert_tooltip( - get_tooltip(&mut dc, find_tk("the-ta := TA {", 11)), - "```slint\ncomponent TA\n```", - ); - - // enums - assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 0)), "enum Eee"); - assert_tooltip(get_tooltip(&mut dc, find_tk("Eee.E2", 5)), "```slint\nEee.E2\n```"); } diff --git a/tools/lsp/preview.rs b/tools/lsp/preview.rs index ed8f58d5c3..fd36659c0e 100644 --- a/tools/lsp/preview.rs +++ b/tools/lsp/preview.rs @@ -10,7 +10,7 @@ use crate::preview::element_selection::ElementSelection; use crate::util; use i_slint_compiler::diagnostics; use i_slint_compiler::object_tree::ElementRc; -use i_slint_compiler::parser::syntax_nodes; +use i_slint_compiler::parser::{syntax_nodes, TextSize}; use i_slint_core::component_factory::FactoryContext; use i_slint_core::lengths::{LogicalPoint, LogicalRect, LogicalSize}; use i_slint_core::model::VecModel; @@ -64,7 +64,7 @@ struct ContentCache { config: PreviewConfig, current_previewed_component: Option, loading_state: PreviewFutureState, - highlight: Option<(Url, u32)>, + highlight: Option<(Url, TextSize)>, ui_is_visible: bool, } @@ -337,7 +337,7 @@ fn evaluate_binding( ) -> Option { let element_url = Url::parse(element_url.as_ref()).ok()?; let element_version = if element_version < 0 { None } else { Some(element_version) }; - let element_offset = u32::try_from(element_offset).ok()?; + let element_offset = u32::try_from(element_offset).ok()?.into(); let property_name = property_name.to_string(); let document_cache = document_cache()?; @@ -898,7 +898,7 @@ fn finish_parsing(ok: bool) { .iter() .position(|ci| { ci.name == component - && ci.defined_at.as_ref().map(|da| &da.url) == previewed_url.as_ref() + && ci.defined_at.as_ref().map(|da| da.url()) == previewed_url.as_ref() }) .unwrap_or(usize::MAX) } else { @@ -1054,7 +1054,7 @@ pub fn load_preview(preview_component: PreviewComponent, behavior: LoadBehavior) if notify_editor { if let Some(component_instance) = component_instance() { if let Some((element, debug_index)) = component_instance - .element_node_at_source_code_position(&se.path, se.offset) + .element_node_at_source_code_position(&se.path, se.offset.into()) .first() { let Some(element_node) = ElementRcNode::new(element.clone(), *debug_index) @@ -1213,7 +1213,7 @@ fn set_preview_factory( { highlight(Some(url), offset); } else { - highlight(None, 0); + highlight(None, 0.into()); } callback(instance.clone_strong()); @@ -1228,7 +1228,7 @@ fn set_preview_factory( /// Highlight the element pointed at the offset in the path. /// When path is None, remove the highlight. -pub fn highlight(url: Option, offset: u32) { +pub fn highlight(url: Option, offset: TextSize) { let highlight = url.clone().map(|u| (u, offset)); let mut cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap(); @@ -1410,7 +1410,7 @@ fn set_selected_element( }?; let path = identifier.source_file.path().to_path_buf(); - let offset = u32::from(identifier.text_range().start()); + let offset = identifier.text_range().start(); Some(ElementSelection { path, offset, instance_index: 0 }) }) @@ -1589,7 +1589,7 @@ pub fn lsp_to_preview_message(message: crate::common::LspToPreviewMessage) { load_preview(pc, LoadBehavior::Load); } M::HighlightFromEditor { url, offset } => { - highlight(url, offset); + highlight(url, offset.into()); } } } diff --git a/tools/lsp/preview/drop_location.rs b/tools/lsp/preview/drop_location.rs index c97540bca1..2dc222f207 100644 --- a/tools/lsp/preview/drop_location.rs +++ b/tools/lsp/preview/drop_location.rs @@ -325,7 +325,7 @@ fn insert_position_at_end( ) -> Option { target_element_node.with_element_node(|node| { let closing_brace = crate::util::last_non_ws_token(node)?; - let closing_brace_offset = Into::::into(closing_brace.text_range().start()); + let closing_brace_offset = closing_brace.text_range().start(); let before_closing = closing_brace.prev_token()?; @@ -350,7 +350,7 @@ fn insert_position_at_end( format!("\n{indent} "), format!("{indent} "), indent, - closing_brace_offset - ws_len, + closing_brace_offset - TextSize::new(ws_len), ws_len, ) } else { @@ -398,7 +398,7 @@ fn insert_position_before_child( assert!(index == child_index); let first_token = child_node.first_token()?; - let first_token_offset = u32::from(first_token.text_range().start()); + let first_token_offset = first_token.text_range().start(); let before_first_token = first_token.prev_token()?; let (pre_indent, indent) = if before_first_token.kind() == SyntaxKind::Whitespace @@ -493,7 +493,7 @@ fn insert_position_before_first_component( if let Some(component) = first_component_node { // have a component node! let first_token = component.first_token()?; - let first_token_offset = u32::from(first_token.text_range().start()); + let first_token_offset = first_token.text_range().start(); if let Some(before_first_token) = first_token.prev_token() { let (pre_indent, replacement_range) = find_pre_indent_and_replacement(&before_first_token); @@ -501,7 +501,7 @@ fn insert_position_before_first_component( Some(InsertInformation { insertion_position: common::VersionedPosition::new( url, - first_token_offset - replacement_range, + first_token_offset - TextSize::new(replacement_range), ), replacement_range, pre_indent, @@ -525,7 +525,7 @@ fn insert_position_before_first_component( Some(InsertInformation { insertion_position: common::VersionedPosition::new( url, - u32::from(document.text_range().end()) - replacement_range, + document.text_range().end() - TextSize::new(replacement_range), ), replacement_range, pre_indent, @@ -535,10 +535,7 @@ fn insert_position_before_first_component( } else { // Entire document is empty Some(InsertInformation { - insertion_position: common::VersionedPosition::new( - url, - u32::from(document.text_range().end()), - ), + insertion_position: common::VersionedPosition::new(url, document.text_range().end()), replacement_range: 0, pre_indent: String::new(), indent: String::new(), @@ -559,12 +556,14 @@ pub fn add_new_component( ); let selection_offset = insert_position.insertion_position.offset() - + new_text - .chars() - .take_while(|c| c.is_whitespace()) - .map(|c| c.len_utf8() as u32) - .sum::() - + "component ".len() as u32; + + TextSize::new( + new_text + .chars() + .take_while(|c| c.is_whitespace()) + .map(|c| c.len_utf8() as u32) + .sum::() + + "component ".len() as u32, + ); let source_file = document.source_file.clone(); let path = source_file.path().to_path_buf(); @@ -575,7 +574,8 @@ pub fn add_new_component( ); let end_pos = util::text_size_to_lsp_position( &source_file, - (insert_position.insertion_position.offset() + insert_position.replacement_range).into(), + insert_position.insertion_position.offset() + + TextSize::new(insert_position.replacement_range), ); let edit = lsp_types::TextEdit { range: lsp_types::Range::new(start_pos, end_pos), new_text }; @@ -861,7 +861,7 @@ pub fn can_move_to( pub struct DropData { /// The offset to select next. This is different from the insert position /// due to indentation, etc. - pub selection_offset: u32, + pub selection_offset: TextSize, pub path: std::path::PathBuf, } @@ -1006,8 +1006,10 @@ pub fn drop_at( }; let mut selection_offset = drop_info.insert_info.insertion_position.offset() - + new_text.chars().take_while(|c| c.is_whitespace()).map(|c| c.len_utf8()).sum::() - as u32; + + TextSize::new( + new_text.chars().take_while(|c| c.is_whitespace()).map(|c| c.len_utf8()).sum::() + as u32, + ); let (path, _) = drop_info.target_element_node.path_and_offset(); @@ -1019,7 +1021,7 @@ pub fn drop_at( if let Some(edit) = completion::create_import_edit(doc, component_type, &import_file) { if let Some(sf) = doc.node.as_ref().map(|n| &n.source_file) { selection_offset = - text_edit::TextOffsetAdjustment::new(&edit, sf).adjust(selection_offset); + text_edit::TextOffsetAdjustment::new(&edit, sf).adjust(selection_offset.into()); } edits.push(edit); } @@ -1028,8 +1030,8 @@ pub fn drop_at( drop_ignored_elements_from_node(&drop_info.target_element_node, &source_file) .drain(..) .inspect(|te| { - selection_offset = - text_edit::TextOffsetAdjustment::new(te, &source_file).adjust(selection_offset); + selection_offset = text_edit::TextOffsetAdjustment::new(te, &source_file) + .adjust(selection_offset.into()); }), ); @@ -1039,9 +1041,8 @@ pub fn drop_at( ); let end_pos = util::text_size_to_lsp_position( &source_file, - (drop_info.insert_info.insertion_position.offset() - + drop_info.insert_info.replacement_range) - .into(), + drop_info.insert_info.insertion_position.offset() + + TextSize::new(drop_info.insert_info.replacement_range), ); edits.push(lsp_types::TextEdit { range: lsp_types::Range::new(start_pos, end_pos), new_text }); @@ -1202,8 +1203,10 @@ pub fn create_move_element_workspace_edit( let source_file = doc.node.as_ref().unwrap().source_file.clone(); let mut selection_offset = drop_info.insert_info.insertion_position.offset() - + new_text.chars().take_while(|c| c.is_whitespace()).map(|c| c.len_utf8()).sum::() - as u32; + + TextSize::new( + new_text.chars().take_while(|c| c.is_whitespace()).map(|c| c.len_utf8()).sum::() + as u32, + ); let mut edits = Vec::with_capacity(3); @@ -1250,8 +1253,8 @@ pub fn create_move_element_workspace_edit( let end_pos = util::text_size_to_lsp_position( &source_file, (drop_info.insert_info.insertion_position.offset() - + drop_info.insert_info.replacement_range) - .into(), + + TextSize::new(drop_info.insert_info.replacement_range)) + .into(), ); edits.push(common::SingleTextEdit::from_path( &document_cache, @@ -1468,7 +1471,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 582 assert_eq!(&result[0].contents, output); assert_eq!(drop_data.path, test::main_test_file_name()); - assert_eq!(drop_data.selection_offset, selection_offset); + assert_eq!(drop_data.selection_offset, selection_offset.into()); assert_eq!(super::workspace_edit_compiles(&document_cache, &workspace_edit), true); } diff --git a/tools/lsp/preview/element_selection.rs b/tools/lsp/preview/element_selection.rs index fe4419d082..f21afd0130 100644 --- a/tools/lsp/preview/element_selection.rs +++ b/tools/lsp/preview/element_selection.rs @@ -3,7 +3,10 @@ use std::{path::PathBuf, rc::Rc}; -use i_slint_compiler::object_tree::{Component, ElementRc}; +use i_slint_compiler::{ + object_tree::{Component, ElementRc}, + parser::TextSize, +}; use i_slint_core::lengths::LogicalPoint; use slint_interpreter::ComponentInstance; @@ -14,7 +17,7 @@ use super::{ext::ElementRcNodeExt, ui}; #[derive(Clone, Debug)] pub struct ElementSelection { pub path: PathBuf, - pub offset: u32, + pub offset: TextSize, pub instance_index: usize, } @@ -23,7 +26,7 @@ impl ElementSelection { let component_instance = super::component_instance()?; let elements = - component_instance.element_node_at_source_code_position(&self.path, self.offset); + component_instance.element_node_at_source_code_position(&self.path, self.offset.into()); elements.get(self.instance_index).or_else(|| elements.first()).map(|(e, _)| e.clone()) } @@ -33,8 +36,7 @@ impl ElementSelection { let debug_index = { let e = element.borrow(); e.debug.iter().position(|d| { - d.node.source_file.path() == self.path - && u32::from(d.node.text_range().start()) == self.offset + d.node.source_file.path() == self.path && d.node.text_range().start() == self.offset }) }; @@ -88,7 +90,7 @@ pub fn unselect_element() { pub fn select_element_at_source_code_position( path: PathBuf, - offset: u32, + offset: TextSize, position: Option, notify_editor_about_selection_after_update: bool, ) { @@ -107,11 +109,11 @@ pub fn select_element_at_source_code_position( fn select_element_at_source_code_position_impl( component_instance: &ComponentInstance, path: PathBuf, - offset: u32, + offset: TextSize, position: Option, notify_editor_about_selection_after_update: bool, ) { - let positions = component_instance.component_positions(&path, offset); + let positions = component_instance.component_positions(&path, offset.into()); let instance_index = position .and_then(|p| positions.iter().enumerate().find_map(|(i, g)| g.contains(p).then_some(i))) @@ -397,7 +399,7 @@ pub fn reselect_element() { let Some(component_instance) = super::component_instance() else { return; }; - let positions = component_instance.component_positions(&selected.path, selected.offset); + let positions = component_instance.component_positions(&selected.path, selected.offset.into()); super::set_selected_element(Some(selected), &positions, false); } @@ -467,7 +469,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401 for (candidate, expected_offset) in covers_center.iter().zip(&expected_offsets) { let (path, offset) = candidate.as_element_node().unwrap().path_and_offset(); assert_eq!(&path, &test_file); - assert_eq!(offset, *expected_offset); + assert_eq!(offset, (*expected_offset).into()); } let covers_below = super::collect_all_element_nodes_covering( diff --git a/tools/lsp/preview/ui.rs b/tools/lsp/preview/ui.rs index a39abbe894..cc626e061d 100644 --- a/tools/lsp/preview/ui.rs +++ b/tools/lsp/preview/ui.rs @@ -102,7 +102,7 @@ pub fn convert_diagnostics(diagnostics: &[slint_interpreter::Diagnostic]) -> Vec } fn extract_definition_location(ci: &ComponentInformation) -> (SharedString, SharedString) { - let Some(url) = ci.defined_at.as_ref().map(|da| &da.url) else { + let Some(url) = ci.defined_at.as_ref().map(|da| da.url()) else { return (Default::default(), Default::default()); }; @@ -138,11 +138,11 @@ pub fn ui_set_known_components( }; if let Some(position) = &ci.defined_at { - if let Some(library) = position.url.path().strip_prefix("/@") { + if let Some(library) = position.url().path().strip_prefix("/@") { library_map.entry(format!("@{library}")).or_default().push(item); } else { let path = i_slint_compiler::pathutils::clean_path( - &(position.url.to_file_path().unwrap_or_default()), + &(position.url().to_file_path().unwrap_or_default()), ); if path != PathBuf::new() { if longest_path_prefix == PathBuf::new() {