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