use super::dto::{CheckedHierarchyItem, HierarchyItem, MAX_POSITION_VALUE, Range, lsp_kind_to_u32};
use super::encoding_ctx::EncodingCtx;
pub(super) struct LspHierarchyItem {
pub(super) name: String,
pub(super) kind: lsp_types::SymbolKind,
pub(super) detail: Option<String>,
pub(super) uri: lsp_types::Uri,
pub(super) range: lsp_types::Range,
pub(super) selection_range: lsp_types::Range,
pub(super) data: Option<serde_json::Value>,
}
impl From<lsp_types::CallHierarchyItem> for LspHierarchyItem {
fn from(item: lsp_types::CallHierarchyItem) -> Self {
Self {
name: item.name,
kind: item.kind,
detail: item.detail,
uri: item.uri,
range: item.range,
selection_range: item.selection_range,
data: item.data,
}
}
}
impl From<lsp_types::TypeHierarchyItem> for LspHierarchyItem {
fn from(item: lsp_types::TypeHierarchyItem) -> Self {
Self {
name: item.name,
kind: item.kind,
detail: item.detail,
uri: item.uri,
range: item.range,
selection_range: item.selection_range,
data: item.data,
}
}
}
impl From<LspHierarchyItem> for lsp_types::CallHierarchyItem {
fn from(item: LspHierarchyItem) -> Self {
Self {
name: item.name,
kind: item.kind,
tags: None,
detail: item.detail,
uri: item.uri,
range: item.range,
selection_range: item.selection_range,
data: item.data,
}
}
}
impl From<LspHierarchyItem> for lsp_types::TypeHierarchyItem {
fn from(item: LspHierarchyItem) -> Self {
Self {
name: item.name,
kind: item.kind,
tags: None,
detail: item.detail,
uri: item.uri,
range: item.range,
selection_range: item.selection_range,
data: item.data,
}
}
}
fn clamp_to_input_limit(mut range: Range) -> Range {
for position in [&mut range.start, &mut range.end] {
position.line = position.line.min(MAX_POSITION_VALUE);
position.character = position.character.min(MAX_POSITION_VALUE);
}
range
}
pub(super) async fn hierarchy_item_to_mcp<Lsp>(item: Lsp, ctx: &EncodingCtx) -> HierarchyItem
where
Lsp: Into<LspHierarchyItem>,
{
let item = item.into();
let out_of_workspace = ctx.is_out_of_workspace(&item.uri);
let range = clamp_to_input_limit(ctx.normalize_range(&item.uri, item.range).await);
let selection_range =
clamp_to_input_limit(ctx.normalize_range(&item.uri, item.selection_range).await);
HierarchyItem {
name: item.name,
kind: lsp_kind_to_u32(item.kind),
detail: item.detail,
uri: item.uri.to_string(),
range,
selection_range,
data: item.data,
out_of_workspace,
}
}
pub(super) async fn hierarchy_item_to_lsp<Lsp>(
item: CheckedHierarchyItem,
uri: lsp_types::Uri,
ctx: &EncodingCtx,
) -> Lsp
where
Lsp: From<LspHierarchyItem>,
{
let range = ctx.denormalize_range(&uri, item.range).await;
let selection_range = ctx.denormalize_range(&uri, item.selection_range).await;
Lsp::from(LspHierarchyItem {
name: item.name,
kind: lsp_types::SymbolKind::from(item.kind),
detail: item.detail,
uri,
range,
selection_range,
data: item.data,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bridge::translator::dto::Position2D;
use crate::bridge::translator::testing::test_ctx;
fn lsp_type_item(uri: &lsp_types::Uri) -> lsp_types::TypeHierarchyItem {
let range = lsp_types::Range {
start: lsp_types::Position {
line: 2,
character: 4,
},
end: lsp_types::Position {
line: 3,
character: 0,
},
};
lsp_types::TypeHierarchyItem {
name: "Base".to_string(),
kind: lsp_types::SymbolKind::from(5),
tags: None,
detail: Some("class".to_string()),
uri: uri.clone(),
range,
selection_range: range,
data: Some(serde_json::json!({"usr": "c:@S@Base"})),
}
}
#[tokio::test]
async fn end_of_line_sentinel_is_clamped_and_round_trips_without_rejection() {
let ctx = test_ctx();
let uri = lsp_types::Uri::from("file:///a.cpp");
let mut item = lsp_type_item(&uri);
item.range.end.character = 2_147_483_647;
item.selection_range.end.character = 2_147_483_647;
let dto: HierarchyItem = hierarchy_item_to_mcp(item, &ctx).await;
assert_eq!(dto.range.end.character, MAX_POSITION_VALUE);
assert_eq!(dto.selection_range.end.character, MAX_POSITION_VALUE);
assert!(CheckedHierarchyItem::from_client(dto).is_ok());
}
#[tokio::test]
async fn type_item_round_trips_through_mcp_form() {
let ctx = test_ctx();
let uri = lsp_types::Uri::from("file:///a.cpp");
let original = lsp_type_item(&uri);
let dto: HierarchyItem = hierarchy_item_to_mcp(original.clone(), &ctx).await;
assert_eq!(dto.kind, 5);
assert_eq!(
dto.range.start,
Position2D {
line: 3,
character: 5
}
);
let back: lsp_types::TypeHierarchyItem =
hierarchy_item_to_lsp(CheckedHierarchyItem::from_client(dto).unwrap(), uri, &ctx).await;
assert_eq!(back, original);
}
#[tokio::test]
async fn call_item_converts_through_the_same_path() {
let ctx = test_ctx();
let uri = lsp_types::Uri::from("file:///a.cpp");
let item = lsp_type_item(&uri);
let call = lsp_types::CallHierarchyItem {
name: item.name,
kind: item.kind,
tags: None,
detail: item.detail,
uri: item.uri,
range: item.range,
selection_range: item.selection_range,
data: item.data,
};
let dto: HierarchyItem = hierarchy_item_to_mcp(call.clone(), &ctx).await;
let back: lsp_types::CallHierarchyItem = hierarchy_item_to_lsp(
CheckedHierarchyItem::from_client(dto).unwrap(),
call.uri.clone(),
&ctx,
)
.await;
assert_eq!(back, call);
}
}