proto.rs

  1//! Handles conversions of `language` items to and from the [`rpc`] protocol.
  2
  3use crate::{
  4    diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
  5    Language, LanguageRegistry,
  6};
  7use anyhow::{anyhow, Result};
  8use clock::ReplicaId;
  9use lsp::{DiagnosticSeverity, LanguageServerId};
 10use rpc::proto;
 11use std::{ops::Range, sync::Arc};
 12use text::*;
 13
 14pub use proto::{BufferState, Operation};
 15
 16/// Deserializes a `[text::LineEnding]` from the RPC representation.
 17pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
 18    match message {
 19        proto::LineEnding::Unix => text::LineEnding::Unix,
 20        proto::LineEnding::Windows => text::LineEnding::Windows,
 21    }
 22}
 23
 24/// Serializes a [`text::LineEnding`] to be sent over RPC.
 25pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
 26    match message {
 27        text::LineEnding::Unix => proto::LineEnding::Unix,
 28        text::LineEnding::Windows => proto::LineEnding::Windows,
 29    }
 30}
 31
 32/// Serializes a [`crate::Operation`] to be sent over RPC.
 33pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
 34    proto::Operation {
 35        variant: Some(match operation {
 36            crate::Operation::Buffer(text::Operation::Edit(edit)) => {
 37                proto::operation::Variant::Edit(serialize_edit_operation(edit))
 38            }
 39
 40            crate::Operation::Buffer(text::Operation::Undo(undo)) => {
 41                proto::operation::Variant::Undo(proto::operation::Undo {
 42                    replica_id: undo.timestamp.replica_id as u32,
 43                    lamport_timestamp: undo.timestamp.value,
 44                    version: serialize_version(&undo.version),
 45                    counts: undo
 46                        .counts
 47                        .iter()
 48                        .map(|(edit_id, count)| proto::UndoCount {
 49                            replica_id: edit_id.replica_id as u32,
 50                            lamport_timestamp: edit_id.value,
 51                            count: *count,
 52                        })
 53                        .collect(),
 54                })
 55            }
 56
 57            crate::Operation::UpdateSelections {
 58                selections,
 59                line_mode,
 60                lamport_timestamp,
 61                cursor_shape,
 62            } => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
 63                replica_id: lamport_timestamp.replica_id as u32,
 64                lamport_timestamp: lamport_timestamp.value,
 65                selections: serialize_selections(selections),
 66                line_mode: *line_mode,
 67                cursor_shape: serialize_cursor_shape(cursor_shape) as i32,
 68            }),
 69
 70            crate::Operation::UpdateDiagnostics {
 71                lamport_timestamp,
 72                server_id,
 73                diagnostics,
 74            } => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
 75                replica_id: lamport_timestamp.replica_id as u32,
 76                lamport_timestamp: lamport_timestamp.value,
 77                server_id: server_id.0 as u64,
 78                diagnostics: serialize_diagnostics(diagnostics.iter()),
 79            }),
 80
 81            crate::Operation::UpdateCompletionTriggers {
 82                triggers,
 83                lamport_timestamp,
 84            } => proto::operation::Variant::UpdateCompletionTriggers(
 85                proto::operation::UpdateCompletionTriggers {
 86                    replica_id: lamport_timestamp.replica_id as u32,
 87                    lamport_timestamp: lamport_timestamp.value,
 88                    triggers: triggers.clone(),
 89                },
 90            ),
 91        }),
 92    }
 93}
 94
 95/// Serializes an [`EditOperation`] to be sent over RPC.
 96pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
 97    proto::operation::Edit {
 98        replica_id: operation.timestamp.replica_id as u32,
 99        lamport_timestamp: operation.timestamp.value,
100        version: serialize_version(&operation.version),
101        ranges: operation.ranges.iter().map(serialize_range).collect(),
102        new_text: operation
103            .new_text
104            .iter()
105            .map(|text| text.to_string())
106            .collect(),
107    }
108}
109
110/// Serializes an entry in the undo map to be sent over RPC.
111pub fn serialize_undo_map_entry(
112    (edit_id, counts): (&clock::Lamport, &[(clock::Lamport, u32)]),
113) -> proto::UndoMapEntry {
114    proto::UndoMapEntry {
115        replica_id: edit_id.replica_id as u32,
116        local_timestamp: edit_id.value,
117        counts: counts
118            .iter()
119            .map(|(undo_id, count)| proto::UndoCount {
120                replica_id: undo_id.replica_id as u32,
121                lamport_timestamp: undo_id.value,
122                count: *count,
123            })
124            .collect(),
125    }
126}
127
128/// Splits the given list of operations into chunks.
129pub fn split_operations(
130    mut operations: Vec<proto::Operation>,
131) -> impl Iterator<Item = Vec<proto::Operation>> {
132    #[cfg(any(test, feature = "test-support"))]
133    const CHUNK_SIZE: usize = 5;
134
135    #[cfg(not(any(test, feature = "test-support")))]
136    const CHUNK_SIZE: usize = 100;
137
138    let mut done = false;
139    std::iter::from_fn(move || {
140        if done {
141            return None;
142        }
143
144        let operations = operations
145            .drain(..std::cmp::min(CHUNK_SIZE, operations.len()))
146            .collect::<Vec<_>>();
147        if operations.is_empty() {
148            done = true;
149        }
150        Some(operations)
151    })
152}
153
154/// Serializes selections to be sent over RPC.
155pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
156    selections.iter().map(serialize_selection).collect()
157}
158
159/// Serializes a [`Selection`] to be sent over RPC.
160pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
161    proto::Selection {
162        id: selection.id as u64,
163        start: Some(proto::EditorAnchor {
164            anchor: Some(serialize_anchor(&selection.start)),
165            excerpt_id: 0,
166        }),
167        end: Some(proto::EditorAnchor {
168            anchor: Some(serialize_anchor(&selection.end)),
169            excerpt_id: 0,
170        }),
171        reversed: selection.reversed,
172    }
173}
174
175/// Serializes a [`CursorShape`] to be sent over RPC.
176pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape {
177    match cursor_shape {
178        CursorShape::Bar => proto::CursorShape::CursorBar,
179        CursorShape::Block => proto::CursorShape::CursorBlock,
180        CursorShape::Underscore => proto::CursorShape::CursorUnderscore,
181        CursorShape::Hollow => proto::CursorShape::CursorHollow,
182    }
183}
184
185/// Deserializes a [`CursorShape`] from the RPC representation.
186pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape {
187    match cursor_shape {
188        proto::CursorShape::CursorBar => CursorShape::Bar,
189        proto::CursorShape::CursorBlock => CursorShape::Block,
190        proto::CursorShape::CursorUnderscore => CursorShape::Underscore,
191        proto::CursorShape::CursorHollow => CursorShape::Hollow,
192    }
193}
194
195/// Serializes a list of diagnostics to be sent over RPC.
196pub fn serialize_diagnostics<'a>(
197    diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<Anchor>>,
198) -> Vec<proto::Diagnostic> {
199    diagnostics
200        .into_iter()
201        .map(|entry| proto::Diagnostic {
202            source: entry.diagnostic.source.clone(),
203            start: Some(serialize_anchor(&entry.range.start)),
204            end: Some(serialize_anchor(&entry.range.end)),
205            message: entry.diagnostic.message.clone(),
206            severity: match entry.diagnostic.severity {
207                DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error,
208                DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning,
209                DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information,
210                DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
211                _ => proto::diagnostic::Severity::None,
212            } as i32,
213            group_id: entry.diagnostic.group_id as u64,
214            is_primary: entry.diagnostic.is_primary,
215            is_valid: true,
216            code: entry.diagnostic.code.clone(),
217            is_disk_based: entry.diagnostic.is_disk_based,
218            is_unnecessary: entry.diagnostic.is_unnecessary,
219        })
220        .collect()
221}
222
223/// Serializes an [`Anchor`] to be sent over RPC.
224pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
225    proto::Anchor {
226        replica_id: anchor.timestamp.replica_id as u32,
227        timestamp: anchor.timestamp.value,
228        offset: anchor.offset as u64,
229        bias: match anchor.bias {
230            Bias::Left => proto::Bias::Left as i32,
231            Bias::Right => proto::Bias::Right as i32,
232        },
233        buffer_id: anchor.buffer_id.map(Into::into),
234    }
235}
236
237// This behavior is currently copied in the collab database, for snapshotting channel notes
238/// Deserializes an [`crate::Operation`] from the RPC representation.
239pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operation> {
240    Ok(
241        match message
242            .variant
243            .ok_or_else(|| anyhow!("missing operation variant"))?
244        {
245            proto::operation::Variant::Edit(edit) => {
246                crate::Operation::Buffer(text::Operation::Edit(deserialize_edit_operation(edit)))
247            }
248            proto::operation::Variant::Undo(undo) => {
249                crate::Operation::Buffer(text::Operation::Undo(UndoOperation {
250                    timestamp: clock::Lamport {
251                        replica_id: undo.replica_id as ReplicaId,
252                        value: undo.lamport_timestamp,
253                    },
254                    version: deserialize_version(&undo.version),
255                    counts: undo
256                        .counts
257                        .into_iter()
258                        .map(|c| {
259                            (
260                                clock::Lamport {
261                                    replica_id: c.replica_id as ReplicaId,
262                                    value: c.lamport_timestamp,
263                                },
264                                c.count,
265                            )
266                        })
267                        .collect(),
268                }))
269            }
270            proto::operation::Variant::UpdateSelections(message) => {
271                let selections = message
272                    .selections
273                    .into_iter()
274                    .filter_map(|selection| {
275                        Some(Selection {
276                            id: selection.id as usize,
277                            start: deserialize_anchor(selection.start?.anchor?)?,
278                            end: deserialize_anchor(selection.end?.anchor?)?,
279                            reversed: selection.reversed,
280                            goal: SelectionGoal::None,
281                        })
282                    })
283                    .collect::<Vec<_>>();
284
285                crate::Operation::UpdateSelections {
286                    lamport_timestamp: clock::Lamport {
287                        replica_id: message.replica_id as ReplicaId,
288                        value: message.lamport_timestamp,
289                    },
290                    selections: Arc::from(selections),
291                    line_mode: message.line_mode,
292                    cursor_shape: deserialize_cursor_shape(
293                        proto::CursorShape::from_i32(message.cursor_shape)
294                            .ok_or_else(|| anyhow!("Missing cursor shape"))?,
295                    ),
296                }
297            }
298            proto::operation::Variant::UpdateDiagnostics(message) => {
299                crate::Operation::UpdateDiagnostics {
300                    lamport_timestamp: clock::Lamport {
301                        replica_id: message.replica_id as ReplicaId,
302                        value: message.lamport_timestamp,
303                    },
304                    server_id: LanguageServerId(message.server_id as usize),
305                    diagnostics: deserialize_diagnostics(message.diagnostics),
306                }
307            }
308            proto::operation::Variant::UpdateCompletionTriggers(message) => {
309                crate::Operation::UpdateCompletionTriggers {
310                    triggers: message.triggers,
311                    lamport_timestamp: clock::Lamport {
312                        replica_id: message.replica_id as ReplicaId,
313                        value: message.lamport_timestamp,
314                    },
315                }
316            }
317        },
318    )
319}
320
321/// Deserializes an [`EditOperation`] from the RPC representation.
322pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
323    EditOperation {
324        timestamp: clock::Lamport {
325            replica_id: edit.replica_id as ReplicaId,
326            value: edit.lamport_timestamp,
327        },
328        version: deserialize_version(&edit.version),
329        ranges: edit.ranges.into_iter().map(deserialize_range).collect(),
330        new_text: edit.new_text.into_iter().map(Arc::from).collect(),
331    }
332}
333
334/// Deserializes an entry in the undo map from the RPC representation.
335pub fn deserialize_undo_map_entry(
336    entry: proto::UndoMapEntry,
337) -> (clock::Lamport, Vec<(clock::Lamport, u32)>) {
338    (
339        clock::Lamport {
340            replica_id: entry.replica_id as u16,
341            value: entry.local_timestamp,
342        },
343        entry
344            .counts
345            .into_iter()
346            .map(|undo_count| {
347                (
348                    clock::Lamport {
349                        replica_id: undo_count.replica_id as u16,
350                        value: undo_count.lamport_timestamp,
351                    },
352                    undo_count.count,
353                )
354            })
355            .collect(),
356    )
357}
358
359/// Deserializes selections from the RPC representation.
360pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selection<Anchor>]> {
361    Arc::from(
362        selections
363            .into_iter()
364            .filter_map(deserialize_selection)
365            .collect::<Vec<_>>(),
366    )
367}
368
369/// Deserializes a [`Selection`] from the RPC representation.
370pub fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor>> {
371    Some(Selection {
372        id: selection.id as usize,
373        start: deserialize_anchor(selection.start?.anchor?)?,
374        end: deserialize_anchor(selection.end?.anchor?)?,
375        reversed: selection.reversed,
376        goal: SelectionGoal::None,
377    })
378}
379
380/// Deserializes a list of diagnostics from the RPC representation.
381pub fn deserialize_diagnostics(
382    diagnostics: Vec<proto::Diagnostic>,
383) -> Arc<[DiagnosticEntry<Anchor>]> {
384    diagnostics
385        .into_iter()
386        .filter_map(|diagnostic| {
387            Some(DiagnosticEntry {
388                range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?,
389                diagnostic: Diagnostic {
390                    source: diagnostic.source,
391                    severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? {
392                        proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR,
393                        proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING,
394                        proto::diagnostic::Severity::Information => DiagnosticSeverity::INFORMATION,
395                        proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT,
396                        proto::diagnostic::Severity::None => return None,
397                    },
398                    message: diagnostic.message,
399                    group_id: diagnostic.group_id as usize,
400                    code: diagnostic.code,
401                    is_primary: diagnostic.is_primary,
402                    is_disk_based: diagnostic.is_disk_based,
403                    is_unnecessary: diagnostic.is_unnecessary,
404                },
405            })
406        })
407        .collect()
408}
409
410/// Deserializes an [`Anchor`] from the RPC representation.
411pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
412    let buffer_id = if let Some(id) = anchor.buffer_id {
413        Some(BufferId::new(id).ok()?)
414    } else {
415        None
416    };
417    Some(Anchor {
418        timestamp: clock::Lamport {
419            replica_id: anchor.replica_id as ReplicaId,
420            value: anchor.timestamp,
421        },
422        offset: anchor.offset as usize,
423        bias: match proto::Bias::from_i32(anchor.bias)? {
424            proto::Bias::Left => Bias::Left,
425            proto::Bias::Right => Bias::Right,
426        },
427        buffer_id,
428    })
429}
430
431/// Returns a `[clock::Lamport`] timestamp for the given [`proto::Operation`].
432pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<clock::Lamport> {
433    let replica_id;
434    let value;
435    match operation.variant.as_ref()? {
436        proto::operation::Variant::Edit(op) => {
437            replica_id = op.replica_id;
438            value = op.lamport_timestamp;
439        }
440        proto::operation::Variant::Undo(op) => {
441            replica_id = op.replica_id;
442            value = op.lamport_timestamp;
443        }
444        proto::operation::Variant::UpdateDiagnostics(op) => {
445            replica_id = op.replica_id;
446            value = op.lamport_timestamp;
447        }
448        proto::operation::Variant::UpdateSelections(op) => {
449            replica_id = op.replica_id;
450            value = op.lamport_timestamp;
451        }
452        proto::operation::Variant::UpdateCompletionTriggers(op) => {
453            replica_id = op.replica_id;
454            value = op.lamport_timestamp;
455        }
456    }
457
458    Some(clock::Lamport {
459        replica_id: replica_id as ReplicaId,
460        value,
461    })
462}
463
464/// Serializes a [`Completion`] to be sent over RPC.
465pub fn serialize_completion(completion: &Completion) -> proto::Completion {
466    proto::Completion {
467        old_start: Some(serialize_anchor(&completion.old_range.start)),
468        old_end: Some(serialize_anchor(&completion.old_range.end)),
469        new_text: completion.new_text.clone(),
470        server_id: completion.server_id.0 as u64,
471        lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
472    }
473}
474
475/// Deserializes a [`Completion`] from the RPC representation.
476pub async fn deserialize_completion(
477    completion: proto::Completion,
478    language: Option<Arc<Language>>,
479    language_registry: &Arc<LanguageRegistry>,
480) -> Result<Completion> {
481    let old_start = completion
482        .old_start
483        .and_then(deserialize_anchor)
484        .ok_or_else(|| anyhow!("invalid old start"))?;
485    let old_end = completion
486        .old_end
487        .and_then(deserialize_anchor)
488        .ok_or_else(|| anyhow!("invalid old end"))?;
489    let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
490
491    let mut label = None;
492    if let Some(language) = language {
493        if let Some(adapter) = language_registry.lsp_adapters(&language).first() {
494            label = adapter
495                .label_for_completion(&lsp_completion, &language)
496                .await;
497        }
498    }
499
500    Ok(Completion {
501        old_range: old_start..old_end,
502        new_text: completion.new_text,
503        label: label.unwrap_or_else(|| {
504            CodeLabel::plain(
505                lsp_completion.label.clone(),
506                lsp_completion.filter_text.as_deref(),
507            )
508        }),
509        documentation: None,
510        server_id: LanguageServerId(completion.server_id as usize),
511        lsp_completion,
512    })
513}
514
515/// Serializes a [`CodeAction`] to be sent over RPC.
516pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
517    proto::CodeAction {
518        server_id: action.server_id.0 as u64,
519        start: Some(serialize_anchor(&action.range.start)),
520        end: Some(serialize_anchor(&action.range.end)),
521        lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
522    }
523}
524
525/// Deserializes a [`CodeAction`] from the RPC representation.
526pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
527    let start = action
528        .start
529        .and_then(deserialize_anchor)
530        .ok_or_else(|| anyhow!("invalid start"))?;
531    let end = action
532        .end
533        .and_then(deserialize_anchor)
534        .ok_or_else(|| anyhow!("invalid end"))?;
535    let lsp_action = serde_json::from_slice(&action.lsp_action)?;
536    Ok(CodeAction {
537        server_id: LanguageServerId(action.server_id as usize),
538        range: start..end,
539        lsp_action,
540    })
541}
542
543/// Serializes a [`Transaction`] to be sent over RPC.
544pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
545    proto::Transaction {
546        id: Some(serialize_timestamp(transaction.id)),
547        edit_ids: transaction
548            .edit_ids
549            .iter()
550            .copied()
551            .map(serialize_timestamp)
552            .collect(),
553        start: serialize_version(&transaction.start),
554    }
555}
556
557/// Deserializes a [`Transaction`] from the RPC representation.
558pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transaction> {
559    Ok(Transaction {
560        id: deserialize_timestamp(
561            transaction
562                .id
563                .ok_or_else(|| anyhow!("missing transaction id"))?,
564        ),
565        edit_ids: transaction
566            .edit_ids
567            .into_iter()
568            .map(deserialize_timestamp)
569            .collect(),
570        start: deserialize_version(&transaction.start),
571    })
572}
573
574/// Serializes a [`clock::Lamport`] timestamp to be sent over RPC.
575pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp {
576    proto::LamportTimestamp {
577        replica_id: timestamp.replica_id as u32,
578        value: timestamp.value,
579    }
580}
581
582/// Deserializes a [`clock::Lamport`] timestamp from the RPC representation.
583pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lamport {
584    clock::Lamport {
585        replica_id: timestamp.replica_id as ReplicaId,
586        value: timestamp.value,
587    }
588}
589
590/// Serializes a range of [`FullOffset`]s to be sent over RPC.
591pub fn serialize_range(range: &Range<FullOffset>) -> proto::Range {
592    proto::Range {
593        start: range.start.0 as u64,
594        end: range.end.0 as u64,
595    }
596}
597
598/// Deserializes a range of [`FullOffset`]s from the RPC representation.
599pub fn deserialize_range(range: proto::Range) -> Range<FullOffset> {
600    FullOffset(range.start as usize)..FullOffset(range.end as usize)
601}
602
603/// Deserializes a clock version from the RPC representation.
604pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global {
605    let mut version = clock::Global::new();
606    for entry in message {
607        version.observe(clock::Lamport {
608            replica_id: entry.replica_id as ReplicaId,
609            value: entry.timestamp,
610        });
611    }
612    version
613}
614
615/// Serializes a clock version to be sent over RPC.
616pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
617    version
618        .iter()
619        .map(|entry| proto::VectorClockEntry {
620            replica_id: entry.replica_id as u32,
621            timestamp: entry.value,
622        })
623        .collect()
624}