Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lsp: Move properties handling into common #5477

Merged
merged 3 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 137 additions & 10 deletions tools/lsp/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::path::Path;
use std::{collections::HashMap, path::PathBuf};

pub mod component_catalog;
pub mod properties;
pub mod rename_component;
#[cfg(test)]
pub mod test;
Expand Down Expand Up @@ -94,19 +95,36 @@ impl DocumentCache {
.and_then(|doc| doc.node.as_ref()?.source_file.version())
}

pub fn get_document(&self, url: &Url) -> Option<&Document> {
pub fn get_document<'a>(&'a self, url: &'_ Url) -> Option<&'a Document> {
let path = uri_to_file(url)?;
self.0.get_document(&path)
}

pub fn get_document_by_path(&self, path: &Path) -> Option<&Document> {
pub fn get_document_by_path<'a>(&'a self, path: &'_ Path) -> Option<&'a Document> {
self.0.get_document(path)
}

pub fn get_document_for_source_file(&self, source_file: &SourceFile) -> Option<&Document> {
pub fn get_document_for_source_file<'a>(
&'a self,
source_file: &'_ SourceFile,
) -> Option<&'a Document> {
self.0.get_document(source_file.path())
}

pub fn get_document_and_offset<'a>(
&'a self,
text_document_uri: &'_ Url,
pos: &'_ lsp_types::Position,
) -> Option<(&'a i_slint_compiler::object_tree::Document, u32)> {
let doc = self.get_document(text_document_uri)?;
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))
}

pub fn all_url_documents(&self) -> impl Iterator<Item = (Url, &Document)> + '_ {
self.0.all_file_documents().filter_map(|(p, d)| Some((file_to_uri(p)?, d)))
}
Expand Down Expand Up @@ -172,6 +190,48 @@ impl DocumentCache {
pub fn compiler_configuration(&self) -> &CompilerConfiguration {
&self.0.compiler_config
}

pub fn element_at_position(
&self,
text_document_uri: &Url,
pos: &lsp_types::Position,
) -> Option<ElementRcNode> {
fn element_contains(
element: &i_slint_compiler::object_tree::ElementRc,
offset: u32,
) -> Option<usize> {
element.borrow().debug.iter().position(|n| {
n.node.parent().map_or(false, |n| n.text_range().contains(offset.into()))
})
}

let (doc, offset) = self.get_document_and_offset(text_document_uri, pos)?;

for component in &doc.inner_components {
let root_element = component.root_element.clone();
let Some(root_debug_index) = element_contains(&root_element, offset) else {
continue;
};

let mut element =
ElementRcNode { element: root_element, debug_index: root_debug_index };
while element.contains_offset(offset) {
if let Some((c, i)) = element
.element
.clone()
.borrow()
.children
.iter()
.find_map(|c| element_contains(c, offset).map(|i| (c, i)))
{
element = ElementRcNode { element: c.clone(), debug_index: i };
} else {
return Some(element);
}
}
}
None
}
}

pub fn extract_element(node: SyntaxNode) -> Option<syntax_nodes::Element> {
Expand Down Expand Up @@ -375,6 +435,12 @@ 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 {
self.with_element_node(|node| {
node.parent().map_or(false, |n| n.text_range().contains(offset.into()))
})
}
}

pub fn create_workspace_edit(uri: Url, version: UrlVersion, edits: Vec<TextEdit>) -> WorkspaceEdit {
Expand Down Expand Up @@ -568,13 +634,6 @@ pub enum PreviewToLspMessage {
/// Request all documents and configuration to be sent from the LSP to the
/// Preview.
RequestState { unused: bool },
/// Update properties on an element at `position`
/// The LSP side needs to look at properties: It sees way more of them!
UpdateElement {
label: Option<String>,
position: VersionedPosition,
properties: Vec<PropertyChange>,
},
/// Pass a `WorkspaceEdit` on to the editor
SendWorkspaceEdit { label: Option<String>, edit: lsp_types::WorkspaceEdit },
}
Expand Down Expand Up @@ -687,6 +746,8 @@ pub mod lsp_to_editor {

#[cfg(test)]
mod tests {
use crate::test::complex_document_cache;

use super::*;

#[test]
Expand Down Expand Up @@ -716,4 +777,70 @@ mod tests {

assert_eq!(back_conversion1, builtin_path1);
}

fn id_at_position(dc: &DocumentCache, url: &Url, line: u32, character: u32) -> Option<String> {
let result = dc.element_at_position(url, &lsp_types::Position { line, character })?;
let element = result.element.borrow();
Some(element.id.clone())
}

fn base_type_at_position(
dc: &DocumentCache,
url: &Url,
line: u32,
character: u32,
) -> Option<String> {
let result = dc.element_at_position(url, &lsp_types::Position { line, character })?;
let element = result.element.borrow();
Some(format!("{}", &element.base_type))
}

#[test]
fn test_element_at_position_no_element() {
let (dc, url, _) = complex_document_cache();
assert_eq!(id_at_position(&dc, &url, 0, 10), None);
// TODO: This is past the end of the line and should thus return None
assert_eq!(id_at_position(&dc, &url, 42, 90), Some(String::new()));
assert_eq!(id_at_position(&dc, &url, 1, 0), None);
assert_eq!(id_at_position(&dc, &url, 55, 1), None);
assert_eq!(id_at_position(&dc, &url, 56, 5), None);
}

#[test]
fn test_element_at_position_no_such_document() {
let (dc, _, _) = complex_document_cache();
assert_eq!(id_at_position(&dc, &Url::parse("https://foo.bar/baz").unwrap(), 5, 0), None);
}

#[test]
fn test_element_at_position_root() {
let (dc, url, _) = complex_document_cache();

assert_eq!(id_at_position(&dc, &url, 2, 30), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 2, 32), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 2, 42), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 3, 0), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 3, 53), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 4, 19), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 5, 0), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 6, 8), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 6, 15), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 6, 23), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 8, 15), Some("root".to_string()));
assert_eq!(id_at_position(&dc, &url, 12, 3), Some("root".to_string())); // right before child // TODO: Seems wrong!
assert_eq!(id_at_position(&dc, &url, 51, 5), Some("root".to_string())); // right after child // TODO: Why does this not work?
assert_eq!(id_at_position(&dc, &url, 52, 0), Some("root".to_string()));
}

#[test]
fn test_element_at_position_child() {
let (dc, url, _) = complex_document_cache();

assert_eq!(base_type_at_position(&dc, &url, 12, 4), Some("VerticalBox".to_string()));
assert_eq!(base_type_at_position(&dc, &url, 14, 22), Some("HorizontalBox".to_string()));
assert_eq!(base_type_at_position(&dc, &url, 15, 33), Some("Text".to_string()));
assert_eq!(base_type_at_position(&dc, &url, 27, 4), Some("VerticalBox".to_string()));
assert_eq!(base_type_at_position(&dc, &url, 28, 8), Some("Text".to_string()));
assert_eq!(base_type_at_position(&dc, &url, 51, 4), Some("VerticalBox".to_string()));
}
}
32 changes: 13 additions & 19 deletions tools/lsp/language/properties.rs → tools/lsp/common/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

use crate::common::{self, Result};
use crate::language;
use crate::util;

use i_slint_compiler::diagnostics::{BuildDiagnostics, SourceFileVersion, Spanned};
Expand Down Expand Up @@ -606,7 +605,7 @@ fn set_binding_on_known_property(
}

pub fn set_binding(
document_cache: &language::DocumentCache,
document_cache: &common::DocumentCache,
uri: &lsp_types::Url,
version: SourceFileVersion,
element: &common::ElementRcNode,
Expand Down Expand Up @@ -679,7 +678,7 @@ pub fn set_binding(

#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
pub fn set_bindings(
document_cache: &language::DocumentCache,
document_cache: &common::DocumentCache,
uri: lsp_types::Url,
version: SourceFileVersion,
element: &common::ElementRcNode,
Expand Down Expand Up @@ -721,7 +720,7 @@ pub fn set_bindings(

#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
fn element_at_source_code_position(
document_cache: &mut language::DocumentCache,
document_cache: &common::DocumentCache,
position: &common::VersionedPosition,
) -> Result<common::ElementRcNode> {
if &document_cache.document_version(position.url()) != position.version() {
Expand All @@ -739,22 +738,21 @@ fn element_at_source_code_position(
.ok_or_else(|| "Document had no node".to_string())?;
let element_position = util::map_position(&source_file, position.offset().into());

Ok(language::element_at_position(document_cache, position.url(), &element_position)
.ok_or_else(|| {
format!("No element found at the given start position {:?}", &element_position)
})?)
Ok(document_cache.element_at_position(position.url(), &element_position).ok_or_else(|| {
format!("No element found at the given start position {:?}", &element_position)
})?)
}

#[cfg(any(feature = "preview-external", feature = "preview-engine"))]
pub fn update_element_properties(
ctx: &language::Context,
document_cache: &common::DocumentCache,
position: common::VersionedPosition,
properties: Vec<common::PropertyChange>,
) -> Result<lsp_types::WorkspaceEdit> {
let element = element_at_source_code_position(&mut ctx.document_cache.borrow_mut(), &position)?;
let element = element_at_source_code_position(document_cache, &position)?;

let (_, e) = set_bindings(
&ctx.document_cache.borrow_mut(),
document_cache,
position.url().clone(),
*position.version(),
&element,
Expand Down Expand Up @@ -857,11 +855,8 @@ mod tests {
document_cache: &common::DocumentCache,
url: &lsp_types::Url,
) -> Option<(common::ElementRcNode, Vec<PropertyInformation>)> {
let element = language::element_at_position(
document_cache,
url,
&lsp_types::Position { line, character },
)?;
let element =
document_cache.element_at_position(url, &lsp_types::Position { line, character })?;
Some((element.clone(), get_properties(&element)))
}

Expand All @@ -871,7 +866,7 @@ mod tests {
) -> Option<(
common::ElementRcNode,
Vec<PropertyInformation>,
language::DocumentCache,
common::DocumentCache,
lsp_types::Url,
)> {
let (dc, url, _) = complex_document_cache();
Expand Down Expand Up @@ -914,8 +909,7 @@ mod tests {
fn test_element_information() {
let (document_cache, url, _) = complex_document_cache();
let element =
language::element_at_position(&document_cache, &url, &lsp_types::Position::new(33, 4))
.unwrap();
document_cache.element_at_position(&url, &lsp_types::Position::new(33, 4)).unwrap();

let result = get_element_information(&element);

Expand Down
2 changes: 1 addition & 1 deletion tools/lsp/common/rename_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ pub fn rename_component_from_definition(
}

let component_type = identifier.text().to_string().trim().to_string();
if &component_type == new_name {
if component_type == new_name {
return Ok(lsp_types::WorkspaceEdit::default());
}

Expand Down
Loading
Loading