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}