lsp_command.rs

  1use crate::{DocumentHighlight, Location, Project, ProjectTransaction};
  2use anyhow::{anyhow, Result};
  3use async_trait::async_trait;
  4use client::{proto, PeerId};
  5use gpui::{AppContext, AsyncAppContext, ModelHandle};
  6use language::{
  7    point_from_lsp,
  8    proto::{deserialize_anchor, serialize_anchor},
  9    range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToLspPosition, ToPointUtf16,
 10};
 11use lsp::DocumentHighlightKind;
 12use std::{cmp::Reverse, ops::Range, path::Path};
 13
 14#[async_trait(?Send)]
 15pub(crate) trait LspCommand: 'static + Sized {
 16    type Response: 'static + Default + Send;
 17    type LspRequest: 'static + Send + lsp::request::Request;
 18    type ProtoRequest: 'static + Send + proto::RequestMessage;
 19
 20    fn to_lsp(
 21        &self,
 22        path: &Path,
 23        cx: &AppContext,
 24    ) -> <Self::LspRequest as lsp::request::Request>::Params;
 25    async fn response_from_lsp(
 26        self,
 27        message: <Self::LspRequest as lsp::request::Request>::Result,
 28        project: ModelHandle<Project>,
 29        buffer: ModelHandle<Buffer>,
 30        cx: AsyncAppContext,
 31    ) -> Result<Self::Response>;
 32
 33    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
 34    async fn from_proto(
 35        message: Self::ProtoRequest,
 36        project: ModelHandle<Project>,
 37        buffer: ModelHandle<Buffer>,
 38        cx: AsyncAppContext,
 39    ) -> Result<Self>;
 40    fn response_to_proto(
 41        response: Self::Response,
 42        project: &mut Project,
 43        peer_id: PeerId,
 44        buffer_version: &clock::Global,
 45        cx: &AppContext,
 46    ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
 47    async fn response_from_proto(
 48        self,
 49        message: <Self::ProtoRequest as proto::RequestMessage>::Response,
 50        project: ModelHandle<Project>,
 51        buffer: ModelHandle<Buffer>,
 52        cx: AsyncAppContext,
 53    ) -> Result<Self::Response>;
 54    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
 55}
 56
 57pub(crate) struct PrepareRename {
 58    pub position: PointUtf16,
 59}
 60
 61pub(crate) struct PerformRename {
 62    pub position: PointUtf16,
 63    pub new_name: String,
 64    pub push_to_history: bool,
 65}
 66
 67pub(crate) struct GetDefinition {
 68    pub position: PointUtf16,
 69}
 70
 71pub(crate) struct GetReferences {
 72    pub position: PointUtf16,
 73}
 74
 75pub(crate) struct GetDocumentHighlights {
 76    pub position: PointUtf16,
 77}
 78
 79#[async_trait(?Send)]
 80impl LspCommand for PrepareRename {
 81    type Response = Option<Range<Anchor>>;
 82    type LspRequest = lsp::request::PrepareRenameRequest;
 83    type ProtoRequest = proto::PrepareRename;
 84
 85    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
 86        lsp::TextDocumentPositionParams {
 87            text_document: lsp::TextDocumentIdentifier {
 88                uri: lsp::Url::from_file_path(path).unwrap(),
 89            },
 90            position: self.position.to_lsp_position(),
 91        }
 92    }
 93
 94    async fn response_from_lsp(
 95        self,
 96        message: Option<lsp::PrepareRenameResponse>,
 97        _: ModelHandle<Project>,
 98        buffer: ModelHandle<Buffer>,
 99        cx: AsyncAppContext,
100    ) -> Result<Option<Range<Anchor>>> {
101        buffer.read_with(&cx, |buffer, _| {
102            if let Some(
103                lsp::PrepareRenameResponse::Range(range)
104                | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
105            ) = message
106            {
107                let Range { start, end } = range_from_lsp(range);
108                if buffer.clip_point_utf16(start, Bias::Left) == start
109                    && buffer.clip_point_utf16(end, Bias::Left) == end
110                {
111                    return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
112                }
113            }
114            Ok(None)
115        })
116    }
117
118    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
119        proto::PrepareRename {
120            project_id,
121            buffer_id: buffer.remote_id(),
122            position: Some(language::proto::serialize_anchor(
123                &buffer.anchor_before(self.position),
124            )),
125            version: (&buffer.version()).into(),
126        }
127    }
128
129    async fn from_proto(
130        message: proto::PrepareRename,
131        _: ModelHandle<Project>,
132        buffer: ModelHandle<Buffer>,
133        mut cx: AsyncAppContext,
134    ) -> Result<Self> {
135        let position = message
136            .position
137            .and_then(deserialize_anchor)
138            .ok_or_else(|| anyhow!("invalid position"))?;
139        buffer
140            .update(&mut cx, |buffer, _| {
141                buffer.wait_for_version(message.version.into())
142            })
143            .await;
144
145        Ok(Self {
146            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
147        })
148    }
149
150    fn response_to_proto(
151        range: Option<Range<Anchor>>,
152        _: &mut Project,
153        _: PeerId,
154        buffer_version: &clock::Global,
155        _: &AppContext,
156    ) -> proto::PrepareRenameResponse {
157        proto::PrepareRenameResponse {
158            can_rename: range.is_some(),
159            start: range
160                .as_ref()
161                .map(|range| language::proto::serialize_anchor(&range.start)),
162            end: range
163                .as_ref()
164                .map(|range| language::proto::serialize_anchor(&range.end)),
165            version: buffer_version.into(),
166        }
167    }
168
169    async fn response_from_proto(
170        self,
171        message: proto::PrepareRenameResponse,
172        _: ModelHandle<Project>,
173        buffer: ModelHandle<Buffer>,
174        mut cx: AsyncAppContext,
175    ) -> Result<Option<Range<Anchor>>> {
176        if message.can_rename {
177            buffer
178                .update(&mut cx, |buffer, _| {
179                    buffer.wait_for_version(message.version.into())
180                })
181                .await;
182            let start = message.start.and_then(deserialize_anchor);
183            let end = message.end.and_then(deserialize_anchor);
184            Ok(start.zip(end).map(|(start, end)| start..end))
185        } else {
186            Ok(None)
187        }
188    }
189
190    fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
191        message.buffer_id
192    }
193}
194
195#[async_trait(?Send)]
196impl LspCommand for PerformRename {
197    type Response = ProjectTransaction;
198    type LspRequest = lsp::request::Rename;
199    type ProtoRequest = proto::PerformRename;
200
201    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
202        lsp::RenameParams {
203            text_document_position: lsp::TextDocumentPositionParams {
204                text_document: lsp::TextDocumentIdentifier {
205                    uri: lsp::Url::from_file_path(path).unwrap(),
206                },
207                position: self.position.to_lsp_position(),
208            },
209            new_name: self.new_name.clone(),
210            work_done_progress_params: Default::default(),
211        }
212    }
213
214    async fn response_from_lsp(
215        self,
216        message: Option<lsp::WorkspaceEdit>,
217        project: ModelHandle<Project>,
218        buffer: ModelHandle<Buffer>,
219        mut cx: AsyncAppContext,
220    ) -> Result<ProjectTransaction> {
221        if let Some(edit) = message {
222            let (language_name, language_server) = buffer.read_with(&cx, |buffer, _| {
223                let language = buffer
224                    .language()
225                    .ok_or_else(|| anyhow!("buffer's language was removed"))?;
226                let language_server = buffer
227                    .language_server()
228                    .cloned()
229                    .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
230                Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
231            })?;
232            Project::deserialize_workspace_edit(
233                project,
234                edit,
235                self.push_to_history,
236                language_name,
237                language_server,
238                &mut cx,
239            )
240            .await
241        } else {
242            Ok(ProjectTransaction::default())
243        }
244    }
245
246    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
247        proto::PerformRename {
248            project_id,
249            buffer_id: buffer.remote_id(),
250            position: Some(language::proto::serialize_anchor(
251                &buffer.anchor_before(self.position),
252            )),
253            new_name: self.new_name.clone(),
254            version: (&buffer.version()).into(),
255        }
256    }
257
258    async fn from_proto(
259        message: proto::PerformRename,
260        _: ModelHandle<Project>,
261        buffer: ModelHandle<Buffer>,
262        mut cx: AsyncAppContext,
263    ) -> Result<Self> {
264        let position = message
265            .position
266            .and_then(deserialize_anchor)
267            .ok_or_else(|| anyhow!("invalid position"))?;
268        buffer
269            .update(&mut cx, |buffer, _| {
270                buffer.wait_for_version(message.version.into())
271            })
272            .await;
273        Ok(Self {
274            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
275            new_name: message.new_name,
276            push_to_history: false,
277        })
278    }
279
280    fn response_to_proto(
281        response: ProjectTransaction,
282        project: &mut Project,
283        peer_id: PeerId,
284        _: &clock::Global,
285        cx: &AppContext,
286    ) -> proto::PerformRenameResponse {
287        let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
288        proto::PerformRenameResponse {
289            transaction: Some(transaction),
290        }
291    }
292
293    async fn response_from_proto(
294        self,
295        message: proto::PerformRenameResponse,
296        project: ModelHandle<Project>,
297        _: ModelHandle<Buffer>,
298        mut cx: AsyncAppContext,
299    ) -> Result<ProjectTransaction> {
300        let message = message
301            .transaction
302            .ok_or_else(|| anyhow!("missing transaction"))?;
303        project
304            .update(&mut cx, |project, cx| {
305                project.deserialize_project_transaction(message, self.push_to_history, cx)
306            })
307            .await
308    }
309
310    fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
311        message.buffer_id
312    }
313}
314
315#[async_trait(?Send)]
316impl LspCommand for GetDefinition {
317    type Response = Vec<Location>;
318    type LspRequest = lsp::request::GotoDefinition;
319    type ProtoRequest = proto::GetDefinition;
320
321    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoDefinitionParams {
322        lsp::GotoDefinitionParams {
323            text_document_position_params: lsp::TextDocumentPositionParams {
324                text_document: lsp::TextDocumentIdentifier {
325                    uri: lsp::Url::from_file_path(path).unwrap(),
326                },
327                position: self.position.to_lsp_position(),
328            },
329            work_done_progress_params: Default::default(),
330            partial_result_params: Default::default(),
331        }
332    }
333
334    async fn response_from_lsp(
335        self,
336        message: Option<lsp::GotoDefinitionResponse>,
337        project: ModelHandle<Project>,
338        buffer: ModelHandle<Buffer>,
339        mut cx: AsyncAppContext,
340    ) -> Result<Vec<Location>> {
341        let mut definitions = Vec::new();
342        let (language, language_server) = buffer
343            .read_with(&cx, |buffer, _| {
344                buffer
345                    .language()
346                    .cloned()
347                    .zip(buffer.language_server().cloned())
348            })
349            .ok_or_else(|| anyhow!("buffer no longer has language server"))?;
350
351        if let Some(message) = message {
352            let mut unresolved_locations = Vec::new();
353            match message {
354                lsp::GotoDefinitionResponse::Scalar(loc) => {
355                    unresolved_locations.push((loc.uri, loc.range));
356                }
357                lsp::GotoDefinitionResponse::Array(locs) => {
358                    unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
359                }
360                lsp::GotoDefinitionResponse::Link(links) => {
361                    unresolved_locations.extend(
362                        links
363                            .into_iter()
364                            .map(|l| (l.target_uri, l.target_selection_range)),
365                    );
366                }
367            }
368
369            for (target_uri, target_range) in unresolved_locations {
370                let target_buffer_handle = project
371                    .update(&mut cx, |this, cx| {
372                        this.open_local_buffer_via_lsp(
373                            target_uri,
374                            language.name().to_string(),
375                            language_server.clone(),
376                            cx,
377                        )
378                    })
379                    .await?;
380
381                cx.read(|cx| {
382                    let target_buffer = target_buffer_handle.read(cx);
383                    let target_start = target_buffer
384                        .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
385                    let target_end = target_buffer
386                        .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
387                    definitions.push(Location {
388                        buffer: target_buffer_handle,
389                        range: target_buffer.anchor_after(target_start)
390                            ..target_buffer.anchor_before(target_end),
391                    });
392                });
393            }
394        }
395
396        Ok(definitions)
397    }
398
399    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
400        proto::GetDefinition {
401            project_id,
402            buffer_id: buffer.remote_id(),
403            position: Some(language::proto::serialize_anchor(
404                &buffer.anchor_before(self.position),
405            )),
406            version: (&buffer.version()).into(),
407        }
408    }
409
410    async fn from_proto(
411        message: proto::GetDefinition,
412        _: ModelHandle<Project>,
413        buffer: ModelHandle<Buffer>,
414        mut cx: AsyncAppContext,
415    ) -> Result<Self> {
416        let position = message
417            .position
418            .and_then(deserialize_anchor)
419            .ok_or_else(|| anyhow!("invalid position"))?;
420        buffer
421            .update(&mut cx, |buffer, _| {
422                buffer.wait_for_version(message.version.into())
423            })
424            .await;
425        Ok(Self {
426            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
427        })
428    }
429
430    fn response_to_proto(
431        response: Vec<Location>,
432        project: &mut Project,
433        peer_id: PeerId,
434        _: &clock::Global,
435        cx: &AppContext,
436    ) -> proto::GetDefinitionResponse {
437        let locations = response
438            .into_iter()
439            .map(|definition| {
440                let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
441                proto::Location {
442                    start: Some(serialize_anchor(&definition.range.start)),
443                    end: Some(serialize_anchor(&definition.range.end)),
444                    buffer: Some(buffer),
445                }
446            })
447            .collect();
448        proto::GetDefinitionResponse { locations }
449    }
450
451    async fn response_from_proto(
452        self,
453        message: proto::GetDefinitionResponse,
454        project: ModelHandle<Project>,
455        _: ModelHandle<Buffer>,
456        mut cx: AsyncAppContext,
457    ) -> Result<Vec<Location>> {
458        let mut locations = Vec::new();
459        for location in message.locations {
460            let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
461            let buffer = project
462                .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
463                .await?;
464            let start = location
465                .start
466                .and_then(deserialize_anchor)
467                .ok_or_else(|| anyhow!("missing target start"))?;
468            let end = location
469                .end
470                .and_then(deserialize_anchor)
471                .ok_or_else(|| anyhow!("missing target end"))?;
472            buffer
473                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
474                .await;
475            locations.push(Location {
476                buffer,
477                range: start..end,
478            })
479        }
480        Ok(locations)
481    }
482
483    fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
484        message.buffer_id
485    }
486}
487
488#[async_trait(?Send)]
489impl LspCommand for GetReferences {
490    type Response = Vec<Location>;
491    type LspRequest = lsp::request::References;
492    type ProtoRequest = proto::GetReferences;
493
494    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::ReferenceParams {
495        lsp::ReferenceParams {
496            text_document_position: lsp::TextDocumentPositionParams {
497                text_document: lsp::TextDocumentIdentifier {
498                    uri: lsp::Url::from_file_path(path).unwrap(),
499                },
500                position: self.position.to_lsp_position(),
501            },
502            work_done_progress_params: Default::default(),
503            partial_result_params: Default::default(),
504            context: lsp::ReferenceContext {
505                include_declaration: true,
506            },
507        }
508    }
509
510    async fn response_from_lsp(
511        self,
512        locations: Option<Vec<lsp::Location>>,
513        project: ModelHandle<Project>,
514        buffer: ModelHandle<Buffer>,
515        mut cx: AsyncAppContext,
516    ) -> Result<Vec<Location>> {
517        let mut references = Vec::new();
518        let (language, language_server) = buffer
519            .read_with(&cx, |buffer, _| {
520                buffer
521                    .language()
522                    .cloned()
523                    .zip(buffer.language_server().cloned())
524            })
525            .ok_or_else(|| anyhow!("buffer no longer has language server"))?;
526
527        if let Some(locations) = locations {
528            for lsp_location in locations {
529                let target_buffer_handle = project
530                    .update(&mut cx, |this, cx| {
531                        this.open_local_buffer_via_lsp(
532                            lsp_location.uri,
533                            language.name().to_string(),
534                            language_server.clone(),
535                            cx,
536                        )
537                    })
538                    .await?;
539
540                cx.read(|cx| {
541                    let target_buffer = target_buffer_handle.read(cx);
542                    let target_start = target_buffer
543                        .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
544                    let target_end = target_buffer
545                        .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
546                    references.push(Location {
547                        buffer: target_buffer_handle,
548                        range: target_buffer.anchor_after(target_start)
549                            ..target_buffer.anchor_before(target_end),
550                    });
551                });
552            }
553        }
554
555        Ok(references)
556    }
557
558    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
559        proto::GetReferences {
560            project_id,
561            buffer_id: buffer.remote_id(),
562            position: Some(language::proto::serialize_anchor(
563                &buffer.anchor_before(self.position),
564            )),
565            version: (&buffer.version()).into(),
566        }
567    }
568
569    async fn from_proto(
570        message: proto::GetReferences,
571        _: ModelHandle<Project>,
572        buffer: ModelHandle<Buffer>,
573        mut cx: AsyncAppContext,
574    ) -> Result<Self> {
575        let position = message
576            .position
577            .and_then(deserialize_anchor)
578            .ok_or_else(|| anyhow!("invalid position"))?;
579        buffer
580            .update(&mut cx, |buffer, _| {
581                buffer.wait_for_version(message.version.into())
582            })
583            .await;
584        Ok(Self {
585            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
586        })
587    }
588
589    fn response_to_proto(
590        response: Vec<Location>,
591        project: &mut Project,
592        peer_id: PeerId,
593        _: &clock::Global,
594        cx: &AppContext,
595    ) -> proto::GetReferencesResponse {
596        let locations = response
597            .into_iter()
598            .map(|definition| {
599                let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
600                proto::Location {
601                    start: Some(serialize_anchor(&definition.range.start)),
602                    end: Some(serialize_anchor(&definition.range.end)),
603                    buffer: Some(buffer),
604                }
605            })
606            .collect();
607        proto::GetReferencesResponse { locations }
608    }
609
610    async fn response_from_proto(
611        self,
612        message: proto::GetReferencesResponse,
613        project: ModelHandle<Project>,
614        _: ModelHandle<Buffer>,
615        mut cx: AsyncAppContext,
616    ) -> Result<Vec<Location>> {
617        let mut locations = Vec::new();
618        for location in message.locations {
619            let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
620            let target_buffer = project
621                .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
622                .await?;
623            let start = location
624                .start
625                .and_then(deserialize_anchor)
626                .ok_or_else(|| anyhow!("missing target start"))?;
627            let end = location
628                .end
629                .and_then(deserialize_anchor)
630                .ok_or_else(|| anyhow!("missing target end"))?;
631            target_buffer
632                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
633                .await;
634            locations.push(Location {
635                buffer: target_buffer,
636                range: start..end,
637            })
638        }
639        Ok(locations)
640    }
641
642    fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 {
643        message.buffer_id
644    }
645}
646
647#[async_trait(?Send)]
648impl LspCommand for GetDocumentHighlights {
649    type Response = Vec<DocumentHighlight>;
650    type LspRequest = lsp::request::DocumentHighlightRequest;
651    type ProtoRequest = proto::GetDocumentHighlights;
652
653    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::DocumentHighlightParams {
654        lsp::DocumentHighlightParams {
655            text_document_position_params: lsp::TextDocumentPositionParams {
656                text_document: lsp::TextDocumentIdentifier {
657                    uri: lsp::Url::from_file_path(path).unwrap(),
658                },
659                position: self.position.to_lsp_position(),
660            },
661            work_done_progress_params: Default::default(),
662            partial_result_params: Default::default(),
663        }
664    }
665
666    async fn response_from_lsp(
667        self,
668        lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
669        _: ModelHandle<Project>,
670        buffer: ModelHandle<Buffer>,
671        cx: AsyncAppContext,
672    ) -> Result<Vec<DocumentHighlight>> {
673        buffer.read_with(&cx, |buffer, _| {
674            let mut lsp_highlights = lsp_highlights.unwrap_or_default();
675            lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end)));
676            Ok(lsp_highlights
677                .into_iter()
678                .map(|lsp_highlight| {
679                    let start = buffer
680                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.start), Bias::Left);
681                    let end = buffer
682                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left);
683                    DocumentHighlight {
684                        range: buffer.anchor_after(start)..buffer.anchor_before(end),
685                        kind: lsp_highlight
686                            .kind
687                            .unwrap_or(lsp::DocumentHighlightKind::READ),
688                    }
689                })
690                .collect())
691        })
692    }
693
694    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentHighlights {
695        proto::GetDocumentHighlights {
696            project_id,
697            buffer_id: buffer.remote_id(),
698            position: Some(language::proto::serialize_anchor(
699                &buffer.anchor_before(self.position),
700            )),
701            version: (&buffer.version()).into(),
702        }
703    }
704
705    async fn from_proto(
706        message: proto::GetDocumentHighlights,
707        _: ModelHandle<Project>,
708        buffer: ModelHandle<Buffer>,
709        mut cx: AsyncAppContext,
710    ) -> Result<Self> {
711        let position = message
712            .position
713            .and_then(deserialize_anchor)
714            .ok_or_else(|| anyhow!("invalid position"))?;
715        buffer
716            .update(&mut cx, |buffer, _| {
717                buffer.wait_for_version(message.version.into())
718            })
719            .await;
720        Ok(Self {
721            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
722        })
723    }
724
725    fn response_to_proto(
726        response: Vec<DocumentHighlight>,
727        _: &mut Project,
728        _: PeerId,
729        _: &clock::Global,
730        _: &AppContext,
731    ) -> proto::GetDocumentHighlightsResponse {
732        let highlights = response
733            .into_iter()
734            .map(|highlight| proto::DocumentHighlight {
735                start: Some(serialize_anchor(&highlight.range.start)),
736                end: Some(serialize_anchor(&highlight.range.end)),
737                kind: match highlight.kind {
738                    DocumentHighlightKind::TEXT => proto::document_highlight::Kind::Text.into(),
739                    DocumentHighlightKind::WRITE => proto::document_highlight::Kind::Write.into(),
740                    DocumentHighlightKind::READ => proto::document_highlight::Kind::Read.into(),
741                    _ => proto::document_highlight::Kind::Text.into(),
742                },
743            })
744            .collect();
745        proto::GetDocumentHighlightsResponse { highlights }
746    }
747
748    async fn response_from_proto(
749        self,
750        message: proto::GetDocumentHighlightsResponse,
751        _: ModelHandle<Project>,
752        buffer: ModelHandle<Buffer>,
753        mut cx: AsyncAppContext,
754    ) -> Result<Vec<DocumentHighlight>> {
755        let mut highlights = Vec::new();
756        for highlight in message.highlights {
757            let start = highlight
758                .start
759                .and_then(deserialize_anchor)
760                .ok_or_else(|| anyhow!("missing target start"))?;
761            let end = highlight
762                .end
763                .and_then(deserialize_anchor)
764                .ok_or_else(|| anyhow!("missing target end"))?;
765            buffer
766                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
767                .await;
768            let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
769                Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
770                Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ,
771                Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE,
772                None => DocumentHighlightKind::TEXT,
773            };
774            highlights.push(DocumentHighlight {
775                range: start..end,
776                kind,
777            });
778        }
779        Ok(highlights)
780    }
781
782    fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> u64 {
783        message.buffer_id
784    }
785}