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}