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