lsp_command.rs

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