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