use crate::{DocumentHighlight, Location, Project, ProjectTransaction}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::{proto, PeerId}; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToPointUtf16, }; use lsp::{DocumentHighlightKind, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path}; #[async_trait(?Send)] pub(crate) trait LspCommand: 'static + Sized { type Response: 'static + Default + Send; type LspRequest: 'static + Send + lsp::request::Request; type ProtoRequest: 'static + Send + proto::RequestMessage; fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool { true } fn to_lsp( &self, path: &Path, cx: &AppContext, ) -> ::Params; async fn response_from_lsp( self, message: ::Result, project: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result; fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest; async fn from_proto( message: Self::ProtoRequest, project: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result; fn response_to_proto( response: Self::Response, project: &mut Project, peer_id: PeerId, buffer_version: &clock::Global, cx: &AppContext, ) -> ::Response; async fn response_from_proto( self, message: ::Response, project: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result; fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64; } pub(crate) struct PrepareRename { pub position: PointUtf16, } pub(crate) struct PerformRename { pub position: PointUtf16, pub new_name: String, pub push_to_history: bool, } pub(crate) struct GetDefinition { pub position: PointUtf16, } pub(crate) struct GetReferences { pub position: PointUtf16, } pub(crate) struct GetDocumentHighlights { pub position: PointUtf16, } pub(crate) struct GetHover { pub position: PointUtf16, } #[async_trait(?Send)] impl LspCommand for PrepareRename { type Response = Option>; type LspRequest = lsp::request::PrepareRenameRequest; type ProtoRequest = proto::PrepareRename; fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider { rename.prepare_provider == Some(true) } else { false } } fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams { lsp::TextDocumentPositionParams { text_document: lsp::TextDocumentIdentifier { uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), } } async fn response_from_lsp( self, message: Option, _: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result>> { buffer.read_with(&cx, |buffer, _| { if let Some( lsp::PrepareRenameResponse::Range(range) | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. }, ) = message { let Range { start, end } = range_from_lsp(range); if buffer.clip_point_utf16(start, Bias::Left) == start && buffer.clip_point_utf16(end, Bias::Left) == end { return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end))); } } Ok(None) }) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename { proto::PrepareRename { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } } async fn from_proto( message: proto::PrepareRename, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; Ok(Self { position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } fn response_to_proto( range: Option>, _: &mut Project, _: PeerId, buffer_version: &clock::Global, _: &AppContext, ) -> proto::PrepareRenameResponse { proto::PrepareRenameResponse { can_rename: range.is_some(), start: range .as_ref() .map(|range| language::proto::serialize_anchor(&range.start)), end: range .as_ref() .map(|range| language::proto::serialize_anchor(&range.end)), version: serialize_version(buffer_version), } } async fn response_from_proto( self, message: proto::PrepareRenameResponse, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result>> { if message.can_rename { buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; let start = message.start.and_then(deserialize_anchor); let end = message.end.and_then(deserialize_anchor); Ok(start.zip(end).map(|(start, end)| start..end)) } else { Ok(None) } } fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 { message.buffer_id } } #[async_trait(?Send)] impl LspCommand for PerformRename { type Response = ProjectTransaction; type LspRequest = lsp::request::Rename; type ProtoRequest = proto::PerformRename; fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams { lsp::RenameParams { text_document_position: lsp::TextDocumentPositionParams { text_document: lsp::TextDocumentIdentifier { uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, new_name: self.new_name.clone(), work_done_progress_params: Default::default(), } } async fn response_from_lsp( self, message: Option, project: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { if let Some(edit) = message { let (lsp_adapter, lsp_server) = project .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) .cloned() }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; Project::deserialize_workspace_edit( project, edit, self.push_to_history, lsp_adapter, lsp_server, &mut cx, ) .await } else { Ok(ProjectTransaction::default()) } } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename { proto::PerformRename { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), new_name: self.new_name.clone(), version: serialize_version(&buffer.version()), } } async fn from_proto( message: proto::PerformRename, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; Ok(Self { position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), new_name: message.new_name, push_to_history: false, }) } fn response_to_proto( response: ProjectTransaction, project: &mut Project, peer_id: PeerId, _: &clock::Global, cx: &AppContext, ) -> proto::PerformRenameResponse { let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx); proto::PerformRenameResponse { transaction: Some(transaction), } } async fn response_from_proto( self, message: proto::PerformRenameResponse, project: ModelHandle, _: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let message = message .transaction .ok_or_else(|| anyhow!("missing transaction"))?; project .update(&mut cx, |project, cx| { project.deserialize_project_transaction(message, self.push_to_history, cx) }) .await } fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 { message.buffer_id } } #[async_trait(?Send)] impl LspCommand for GetDefinition { type Response = Vec; type LspRequest = lsp::request::GotoDefinition; type ProtoRequest = proto::GetDefinition; fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoDefinitionParams { lsp::GotoDefinitionParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document: lsp::TextDocumentIdentifier { uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, work_done_progress_params: Default::default(), partial_result_params: Default::default(), } } async fn response_from_lsp( self, message: Option, project: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result> { let mut definitions = Vec::new(); let (lsp_adapter, language_server) = project .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) .cloned() }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; if let Some(message) = message { let mut unresolved_locations = Vec::new(); match message { lsp::GotoDefinitionResponse::Scalar(loc) => { unresolved_locations.push((loc.uri, loc.range)); } lsp::GotoDefinitionResponse::Array(locs) => { unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range))); } lsp::GotoDefinitionResponse::Link(links) => { unresolved_locations.extend( links .into_iter() .map(|l| (l.target_uri, l.target_selection_range)), ); } } for (target_uri, target_range) in unresolved_locations { let target_buffer_handle = project .update(&mut cx, |this, cx| { this.open_local_buffer_via_lsp( target_uri, lsp_adapter.clone(), language_server.clone(), cx, ) }) .await?; cx.read(|cx| { let target_buffer = target_buffer_handle.read(cx); let target_start = target_buffer .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left); let target_end = target_buffer .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left); definitions.push(Location { buffer: target_buffer_handle, range: target_buffer.anchor_after(target_start) ..target_buffer.anchor_before(target_end), }); }); } } Ok(definitions) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition { proto::GetDefinition { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } } async fn from_proto( message: proto::GetDefinition, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; Ok(Self { position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } fn response_to_proto( response: Vec, project: &mut Project, peer_id: PeerId, _: &clock::Global, cx: &AppContext, ) -> proto::GetDefinitionResponse { let locations = response .into_iter() .map(|definition| { let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx); proto::Location { start: Some(serialize_anchor(&definition.range.start)), end: Some(serialize_anchor(&definition.range.end)), buffer: Some(buffer), } }) .collect(); proto::GetDefinitionResponse { locations } } async fn response_from_proto( self, message: proto::GetDefinitionResponse, project: ModelHandle, _: ModelHandle, mut cx: AsyncAppContext, ) -> Result> { let mut locations = Vec::new(); for location in message.locations { let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?; let buffer = project .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) .await?; let start = location .start .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target start"))?; let end = location .end .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target end"))?; buffer .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) .await; locations.push(Location { buffer, range: start..end, }) } Ok(locations) } fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 { message.buffer_id } } #[async_trait(?Send)] impl LspCommand for GetReferences { type Response = Vec; type LspRequest = lsp::request::References; type ProtoRequest = proto::GetReferences; fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::ReferenceParams { lsp::ReferenceParams { text_document_position: lsp::TextDocumentPositionParams { text_document: lsp::TextDocumentIdentifier { uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, work_done_progress_params: Default::default(), partial_result_params: Default::default(), context: lsp::ReferenceContext { include_declaration: true, }, } } async fn response_from_lsp( self, locations: Option>, project: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result> { let mut references = Vec::new(); let (lsp_adapter, language_server) = project .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) .cloned() }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; if let Some(locations) = locations { for lsp_location in locations { let target_buffer_handle = project .update(&mut cx, |this, cx| { this.open_local_buffer_via_lsp( lsp_location.uri, lsp_adapter.clone(), language_server.clone(), cx, ) }) .await?; cx.read(|cx| { let target_buffer = target_buffer_handle.read(cx); let target_start = target_buffer .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left); let target_end = target_buffer .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left); references.push(Location { buffer: target_buffer_handle, range: target_buffer.anchor_after(target_start) ..target_buffer.anchor_before(target_end), }); }); } } Ok(references) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences { proto::GetReferences { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } } async fn from_proto( message: proto::GetReferences, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; Ok(Self { position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } fn response_to_proto( response: Vec, project: &mut Project, peer_id: PeerId, _: &clock::Global, cx: &AppContext, ) -> proto::GetReferencesResponse { let locations = response .into_iter() .map(|definition| { let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx); proto::Location { start: Some(serialize_anchor(&definition.range.start)), end: Some(serialize_anchor(&definition.range.end)), buffer: Some(buffer), } }) .collect(); proto::GetReferencesResponse { locations } } async fn response_from_proto( self, message: proto::GetReferencesResponse, project: ModelHandle, _: ModelHandle, mut cx: AsyncAppContext, ) -> Result> { let mut locations = Vec::new(); for location in message.locations { let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?; let target_buffer = project .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) .await?; let start = location .start .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target start"))?; let end = location .end .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target end"))?; target_buffer .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) .await; locations.push(Location { buffer: target_buffer, range: start..end, }) } Ok(locations) } fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 { message.buffer_id } } #[async_trait(?Send)] impl LspCommand for GetDocumentHighlights { type Response = Vec; type LspRequest = lsp::request::DocumentHighlightRequest; type ProtoRequest = proto::GetDocumentHighlights; fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { capabilities.document_highlight_provider.is_some() } fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::DocumentHighlightParams { lsp::DocumentHighlightParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document: lsp::TextDocumentIdentifier { uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, work_done_progress_params: Default::default(), partial_result_params: Default::default(), } } async fn response_from_lsp( self, lsp_highlights: Option>, _: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result> { buffer.read_with(&cx, |buffer, _| { let mut lsp_highlights = lsp_highlights.unwrap_or_default(); lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end))); Ok(lsp_highlights .into_iter() .map(|lsp_highlight| { let start = buffer .clip_point_utf16(point_from_lsp(lsp_highlight.range.start), Bias::Left); let end = buffer .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left); DocumentHighlight { range: buffer.anchor_after(start)..buffer.anchor_before(end), kind: lsp_highlight .kind .unwrap_or(lsp::DocumentHighlightKind::READ), } }) .collect()) }) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentHighlights { proto::GetDocumentHighlights { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } } async fn from_proto( message: proto::GetDocumentHighlights, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; Ok(Self { position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } fn response_to_proto( response: Vec, _: &mut Project, _: PeerId, _: &clock::Global, _: &AppContext, ) -> proto::GetDocumentHighlightsResponse { let highlights = response .into_iter() .map(|highlight| proto::DocumentHighlight { start: Some(serialize_anchor(&highlight.range.start)), end: Some(serialize_anchor(&highlight.range.end)), kind: match highlight.kind { DocumentHighlightKind::TEXT => proto::document_highlight::Kind::Text.into(), DocumentHighlightKind::WRITE => proto::document_highlight::Kind::Write.into(), DocumentHighlightKind::READ => proto::document_highlight::Kind::Read.into(), _ => proto::document_highlight::Kind::Text.into(), }, }) .collect(); proto::GetDocumentHighlightsResponse { highlights } } async fn response_from_proto( self, message: proto::GetDocumentHighlightsResponse, _: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result> { let mut highlights = Vec::new(); for highlight in message.highlights { let start = highlight .start .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target start"))?; let end = highlight .end .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("missing target end"))?; buffer .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end])) .await; let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) { Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT, Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ, Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE, None => DocumentHighlightKind::TEXT, }; highlights.push(DocumentHighlight { range: start..end, kind, }); } Ok(highlights) } fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> u64 { message.buffer_id } } #[async_trait(?Send)] impl LspCommand for GetHover { // TODO: proper response type type Response = Option; type LspRequest = lsp::request::HoverRequest; type ProtoRequest = proto::GetHover; fn to_lsp(&self, path: &Path, cx: &AppContext) -> lsp::HoverParams { lsp::HoverParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document: lsp::TextDocumentIdentifier { uri: lsp::Url::from_file_path(path).unwrap(), }, position: point_to_lsp(self.position), }, work_done_progress_params: Default::default(), } } async fn response_from_lsp( self, message: Option, project: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result { // let (lsp_adapter, language_server) = project // .read_with(&cx, |project, cx| { // project // .language_server_for_buffer(buffer.read(cx), cx) // .cloned() // }) // .ok_or_else(|| anyhow!("no language server found for buffer"))?; // TODO: what here? Ok(Some( message.ok_or_else(|| anyhow!("invalid lsp response"))?, )) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest { proto::GetHover { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version), } } async fn from_proto( message: Self::ProtoRequest, project: ModelHandle, buffer: ModelHandle, mut cx: AsyncAppContext, ) -> Result { let position = message .position .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(message.version)) }) .await; Ok(Self { position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)), }) } fn response_to_proto( response: Self::Response, project: &mut Project, peer_id: PeerId, buffer_version: &clock::Global, cx: &AppContext, ) -> proto::GetHoverResponse { todo!() } async fn response_from_proto( self, message: ::Response, project: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result { todo!() } fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64 { message.buffer_id } }