mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 17:28:40 +00:00
Replicate diagnostics to remote buffers
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
40c861c249
commit
0e62ddbb65
6 changed files with 197 additions and 51 deletions
|
@ -176,12 +176,17 @@ impl<T> AnchorRangeMap<T> {
|
|||
self.entries.len()
|
||||
}
|
||||
|
||||
pub fn from_raw(version: clock::Global, entries: Vec<(Range<(FullOffset, Bias)>, T)>) -> Self {
|
||||
pub fn from_full_offset_ranges(
|
||||
version: clock::Global,
|
||||
entries: Vec<(Range<(FullOffset, Bias)>, T)>,
|
||||
) -> Self {
|
||||
Self { version, entries }
|
||||
}
|
||||
|
||||
pub fn raw_entries(&self) -> &[(Range<(FullOffset, Bias)>, T)] {
|
||||
&self.entries
|
||||
pub fn full_offset_ranges(&self) -> impl Iterator<Item = (Range<FullOffset>, &T)> {
|
||||
self.entries
|
||||
.iter()
|
||||
.map(|(range, value)| (range.start.0..range.end.0, value))
|
||||
}
|
||||
|
||||
pub fn point_ranges<'a>(
|
||||
|
@ -270,6 +275,10 @@ impl<T: Clone> Default for AnchorRangeMultimap<T> {
|
|||
}
|
||||
|
||||
impl<T: Clone> AnchorRangeMultimap<T> {
|
||||
pub fn version(&self) -> &clock::Global {
|
||||
&self.version
|
||||
}
|
||||
|
||||
pub fn intersecting_ranges<'a, I, O>(
|
||||
&'a self,
|
||||
range: Range<I>,
|
||||
|
@ -336,6 +345,35 @@ impl<T: Clone> AnchorRangeMultimap<T> {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_full_offset_ranges(
|
||||
version: clock::Global,
|
||||
start_bias: Bias,
|
||||
end_bias: Bias,
|
||||
entries: impl Iterator<Item = (Range<FullOffset>, T)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
version,
|
||||
start_bias,
|
||||
end_bias,
|
||||
entries: SumTree::from_iter(
|
||||
entries.map(|(range, value)| AnchorRangeMultimapEntry {
|
||||
range: FullOffsetRange {
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
},
|
||||
value,
|
||||
}),
|
||||
&(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn full_offset_ranges(&self) -> impl Iterator<Item = (Range<FullOffset>, &T)> {
|
||||
self.entries
|
||||
.cursor::<()>()
|
||||
.map(|entry| (entry.range.start..entry.range.end, &entry.value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> sum_tree::Item for AnchorRangeMultimapEntry<T> {
|
||||
|
|
|
@ -9,7 +9,7 @@ pub use self::{
|
|||
language::{BracketPair, Language, LanguageConfig, LanguageRegistry},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
pub use buffer::{Buffer as TextBuffer, *};
|
||||
pub use buffer::{Buffer as TextBuffer, Operation as _, *};
|
||||
use clock::ReplicaId;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
|
||||
|
@ -99,6 +99,12 @@ struct LanguageServerSnapshot {
|
|||
path: Arc<Path>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Operation {
|
||||
Buffer(buffer::Operation),
|
||||
UpdateDiagnostics(AnchorRangeMultimap<Diagnostic>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Event {
|
||||
Edited,
|
||||
|
@ -256,7 +262,7 @@ impl Buffer {
|
|||
let ops = message
|
||||
.history
|
||||
.into_iter()
|
||||
.map(|op| Operation::Edit(proto::deserialize_edit_operation(op)));
|
||||
.map(|op| buffer::Operation::Edit(proto::deserialize_edit_operation(op)));
|
||||
buffer.apply_ops(ops)?;
|
||||
for set in message.selections {
|
||||
let set = proto::deserialize_selection_set(set);
|
||||
|
@ -278,6 +284,7 @@ impl Buffer {
|
|||
.selection_sets()
|
||||
.map(|(_, set)| proto::serialize_selection_set(set))
|
||||
.collect(),
|
||||
diagnostics: Some(proto::serialize_diagnostics(&self.diagnostics)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -761,6 +768,7 @@ impl Buffer {
|
|||
}
|
||||
|
||||
self.diagnostics_update_count += 1;
|
||||
self.send_operation(Operation::UpdateDiagnostics(self.diagnostics.clone()), cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1240,7 +1248,7 @@ impl Buffer {
|
|||
}
|
||||
|
||||
self.end_transaction(None, cx).unwrap();
|
||||
self.send_operation(Operation::Edit(edit), cx);
|
||||
self.send_operation(Operation::Buffer(buffer::Operation::Edit(edit)), cx);
|
||||
}
|
||||
|
||||
fn did_edit(
|
||||
|
@ -1269,10 +1277,10 @@ impl Buffer {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) -> SelectionSetId {
|
||||
let operation = self.text.add_selection_set(selections);
|
||||
if let Operation::UpdateSelections { set_id, .. } = &operation {
|
||||
if let buffer::Operation::UpdateSelections { set_id, .. } = &operation {
|
||||
let set_id = *set_id;
|
||||
cx.notify();
|
||||
self.send_operation(operation, cx);
|
||||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
set_id
|
||||
} else {
|
||||
unreachable!()
|
||||
|
@ -1287,7 +1295,7 @@ impl Buffer {
|
|||
) -> Result<()> {
|
||||
let operation = self.text.update_selection_set(set_id, selections)?;
|
||||
cx.notify();
|
||||
self.send_operation(operation, cx);
|
||||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1297,7 +1305,7 @@ impl Buffer {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
let operation = self.text.set_active_selection_set(set_id)?;
|
||||
self.send_operation(operation, cx);
|
||||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1308,7 +1316,7 @@ impl Buffer {
|
|||
) -> Result<()> {
|
||||
let operation = self.text.remove_selection_set(set_id)?;
|
||||
cx.notify();
|
||||
self.send_operation(operation, cx);
|
||||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1320,7 +1328,17 @@ impl Buffer {
|
|||
self.pending_autoindent.take();
|
||||
let was_dirty = self.is_dirty();
|
||||
let old_version = self.version.clone();
|
||||
self.text.apply_ops(ops)?;
|
||||
let buffer_ops = ops
|
||||
.into_iter()
|
||||
.filter_map(|op| match op {
|
||||
Operation::Buffer(op) => Some(op),
|
||||
Operation::UpdateDiagnostics(diagnostics) => {
|
||||
self.apply_diagnostic_update(diagnostics, cx);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.text.apply_ops(buffer_ops)?;
|
||||
self.did_edit(&old_version, was_dirty, cx);
|
||||
// Notify independently of whether the buffer was edited as the operations could include a
|
||||
// selection update.
|
||||
|
@ -1328,6 +1346,15 @@ impl Buffer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_diagnostic_update(
|
||||
&mut self,
|
||||
diagnostics: AnchorRangeMultimap<Diagnostic>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.diagnostics = diagnostics;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
|
||||
if let Some(file) = &self.file {
|
||||
|
@ -1350,7 +1377,7 @@ impl Buffer {
|
|||
let old_version = self.version.clone();
|
||||
|
||||
for operation in self.text.undo() {
|
||||
self.send_operation(operation, cx);
|
||||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
}
|
||||
|
||||
self.did_edit(&old_version, was_dirty, cx);
|
||||
|
@ -1361,7 +1388,7 @@ impl Buffer {
|
|||
let old_version = self.version.clone();
|
||||
|
||||
for operation in self.text.redo() {
|
||||
self.send_operation(operation, cx);
|
||||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
}
|
||||
|
||||
self.did_edit(&old_version, was_dirty, cx);
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::Diagnostic;
|
||||
|
||||
use super::Operation;
|
||||
use anyhow::{anyhow, Result};
|
||||
use buffer::*;
|
||||
use clock::ReplicaId;
|
||||
use lsp::DiagnosticSeverity;
|
||||
use rpc::proto;
|
||||
|
||||
pub use proto::Buffer;
|
||||
|
@ -10,13 +14,13 @@ pub use proto::Buffer;
|
|||
pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||
proto::Operation {
|
||||
variant: Some(match operation {
|
||||
Operation::Edit(edit) => {
|
||||
Operation::Buffer(buffer::Operation::Edit(edit)) => {
|
||||
proto::operation::Variant::Edit(serialize_edit_operation(edit))
|
||||
}
|
||||
Operation::Undo {
|
||||
Operation::Buffer(buffer::Operation::Undo {
|
||||
undo,
|
||||
lamport_timestamp,
|
||||
} => proto::operation::Variant::Undo(proto::operation::Undo {
|
||||
}) => proto::operation::Variant::Undo(proto::operation::Undo {
|
||||
replica_id: undo.id.replica_id as u32,
|
||||
local_timestamp: undo.id.value,
|
||||
lamport_timestamp: lamport_timestamp.value,
|
||||
|
@ -39,44 +43,46 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
|||
.collect(),
|
||||
version: From::from(&undo.version),
|
||||
}),
|
||||
Operation::UpdateSelections {
|
||||
Operation::Buffer(buffer::Operation::UpdateSelections {
|
||||
set_id,
|
||||
selections,
|
||||
lamport_timestamp,
|
||||
} => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
|
||||
}) => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
|
||||
replica_id: set_id.replica_id as u32,
|
||||
local_timestamp: set_id.value,
|
||||
lamport_timestamp: lamport_timestamp.value,
|
||||
version: selections.version().into(),
|
||||
selections: selections
|
||||
.raw_entries()
|
||||
.iter()
|
||||
.full_offset_ranges()
|
||||
.map(|(range, state)| proto::Selection {
|
||||
id: state.id as u64,
|
||||
start: range.start.0 .0 as u64,
|
||||
end: range.end.0 .0 as u64,
|
||||
start: range.start.0 as u64,
|
||||
end: range.end.0 as u64,
|
||||
reversed: state.reversed,
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
Operation::RemoveSelections {
|
||||
Operation::Buffer(buffer::Operation::RemoveSelections {
|
||||
set_id,
|
||||
lamport_timestamp,
|
||||
} => proto::operation::Variant::RemoveSelections(proto::operation::RemoveSelections {
|
||||
}) => proto::operation::Variant::RemoveSelections(proto::operation::RemoveSelections {
|
||||
replica_id: set_id.replica_id as u32,
|
||||
local_timestamp: set_id.value,
|
||||
lamport_timestamp: lamport_timestamp.value,
|
||||
}),
|
||||
Operation::SetActiveSelections {
|
||||
Operation::Buffer(buffer::Operation::SetActiveSelections {
|
||||
set_id,
|
||||
lamport_timestamp,
|
||||
} => proto::operation::Variant::SetActiveSelections(
|
||||
}) => proto::operation::Variant::SetActiveSelections(
|
||||
proto::operation::SetActiveSelections {
|
||||
replica_id: lamport_timestamp.replica_id as u32,
|
||||
local_timestamp: set_id.map(|set_id| set_id.value),
|
||||
lamport_timestamp: lamport_timestamp.value,
|
||||
},
|
||||
),
|
||||
Operation::UpdateDiagnostics(diagnostic_set) => {
|
||||
proto::operation::Variant::UpdateDiagnostics(serialize_diagnostics(diagnostic_set))
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -102,24 +108,44 @@ pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::
|
|||
|
||||
pub fn serialize_selection_set(set: &SelectionSet) -> proto::SelectionSet {
|
||||
let version = set.selections.version();
|
||||
let entries = set.selections.raw_entries();
|
||||
let entries = set.selections.full_offset_ranges();
|
||||
proto::SelectionSet {
|
||||
replica_id: set.id.replica_id as u32,
|
||||
lamport_timestamp: set.id.value as u32,
|
||||
is_active: set.active,
|
||||
version: version.into(),
|
||||
selections: entries
|
||||
.iter()
|
||||
.map(|(range, state)| proto::Selection {
|
||||
id: state.id as u64,
|
||||
start: range.start.0 .0 as u64,
|
||||
end: range.end.0 .0 as u64,
|
||||
start: range.start.0 as u64,
|
||||
end: range.end.0 as u64,
|
||||
reversed: state.reversed,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_diagnostics(map: &AnchorRangeMultimap<Diagnostic>) -> proto::DiagnosticSet {
|
||||
proto::DiagnosticSet {
|
||||
version: map.version().into(),
|
||||
diagnostics: map
|
||||
.full_offset_ranges()
|
||||
.map(|(range, diagnostic)| proto::Diagnostic {
|
||||
start: range.start.0 as u64,
|
||||
end: range.end.0 as u64,
|
||||
message: diagnostic.message.clone(),
|
||||
severity: match diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error,
|
||||
DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning,
|
||||
DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information,
|
||||
DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
|
||||
_ => proto::diagnostic::Severity::None,
|
||||
} as i32,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||
Ok(
|
||||
match message
|
||||
|
@ -127,9 +153,9 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
|||
.ok_or_else(|| anyhow!("missing operation variant"))?
|
||||
{
|
||||
proto::operation::Variant::Edit(edit) => {
|
||||
Operation::Edit(deserialize_edit_operation(edit))
|
||||
Operation::Buffer(buffer::Operation::Edit(deserialize_edit_operation(edit)))
|
||||
}
|
||||
proto::operation::Variant::Undo(undo) => Operation::Undo {
|
||||
proto::operation::Variant::Undo(undo) => Operation::Buffer(buffer::Operation::Undo {
|
||||
lamport_timestamp: clock::Lamport {
|
||||
replica_id: undo.replica_id as ReplicaId,
|
||||
value: undo.lamport_timestamp,
|
||||
|
@ -159,7 +185,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
|||
.collect(),
|
||||
version: undo.version.into(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
proto::operation::Variant::UpdateSelections(message) => {
|
||||
let version = message.version.into();
|
||||
let entries = message
|
||||
|
@ -176,9 +202,9 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
|||
(range, state)
|
||||
})
|
||||
.collect();
|
||||
let selections = AnchorRangeMap::from_raw(version, entries);
|
||||
let selections = AnchorRangeMap::from_full_offset_ranges(version, entries);
|
||||
|
||||
Operation::UpdateSelections {
|
||||
Operation::Buffer(buffer::Operation::UpdateSelections {
|
||||
set_id: clock::Lamport {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
value: message.local_timestamp,
|
||||
|
@ -188,20 +214,22 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
|||
value: message.lamport_timestamp,
|
||||
},
|
||||
selections: Arc::from(selections),
|
||||
}
|
||||
})
|
||||
}
|
||||
proto::operation::Variant::RemoveSelections(message) => {
|
||||
Operation::Buffer(buffer::Operation::RemoveSelections {
|
||||
set_id: clock::Lamport {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
value: message.local_timestamp,
|
||||
},
|
||||
lamport_timestamp: clock::Lamport {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
value: message.lamport_timestamp,
|
||||
},
|
||||
})
|
||||
}
|
||||
proto::operation::Variant::RemoveSelections(message) => Operation::RemoveSelections {
|
||||
set_id: clock::Lamport {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
value: message.local_timestamp,
|
||||
},
|
||||
lamport_timestamp: clock::Lamport {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
value: message.lamport_timestamp,
|
||||
},
|
||||
},
|
||||
proto::operation::Variant::SetActiveSelections(message) => {
|
||||
Operation::SetActiveSelections {
|
||||
Operation::Buffer(buffer::Operation::SetActiveSelections {
|
||||
set_id: message.local_timestamp.map(|value| clock::Lamport {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
value,
|
||||
|
@ -210,7 +238,10 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
|||
replica_id: message.replica_id as ReplicaId,
|
||||
value: message.lamport_timestamp,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
proto::operation::Variant::UpdateDiagnostics(message) => {
|
||||
Operation::UpdateDiagnostics(deserialize_diagnostics(message))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -241,7 +272,7 @@ pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet {
|
|||
value: set.lamport_timestamp,
|
||||
},
|
||||
active: set.is_active,
|
||||
selections: Arc::new(AnchorRangeMap::from_raw(
|
||||
selections: Arc::new(AnchorRangeMap::from_full_offset_ranges(
|
||||
set.version.into(),
|
||||
set.selections
|
||||
.into_iter()
|
||||
|
@ -259,3 +290,26 @@ pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet {
|
|||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_diagnostics(message: proto::DiagnosticSet) -> AnchorRangeMultimap<Diagnostic> {
|
||||
AnchorRangeMultimap::from_full_offset_ranges(
|
||||
message.version.into(),
|
||||
Bias::Left,
|
||||
Bias::Right,
|
||||
message.diagnostics.into_iter().filter_map(|diagnostic| {
|
||||
Some((
|
||||
FullOffset(diagnostic.start as usize)..FullOffset(diagnostic.end as usize),
|
||||
Diagnostic {
|
||||
severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? {
|
||||
proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR,
|
||||
proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING,
|
||||
proto::diagnostic::Severity::Information => DiagnosticSeverity::INFORMATION,
|
||||
proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT,
|
||||
proto::diagnostic::Severity::None => return None,
|
||||
},
|
||||
message: diagnostic.message,
|
||||
},
|
||||
))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -228,6 +228,7 @@ message Buffer {
|
|||
string content = 2;
|
||||
repeated Operation.Edit history = 3;
|
||||
repeated SelectionSet selections = 4;
|
||||
DiagnosticSet diagnostics = 5;
|
||||
}
|
||||
|
||||
message SelectionSet {
|
||||
|
@ -245,6 +246,27 @@ message Selection {
|
|||
bool reversed = 4;
|
||||
}
|
||||
|
||||
message DiagnosticSet {
|
||||
repeated VectorClockEntry version = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
|
||||
message Diagnostic {
|
||||
uint64 start = 1;
|
||||
uint64 end = 2;
|
||||
Severity severity = 3;
|
||||
string message = 4;
|
||||
enum Severity {
|
||||
None = 0;
|
||||
Error = 1;
|
||||
Warning = 2;
|
||||
Information = 3;
|
||||
Hint = 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
message Operation {
|
||||
oneof variant {
|
||||
Edit edit = 1;
|
||||
|
@ -252,6 +274,7 @@ message Operation {
|
|||
UpdateSelections update_selections = 3;
|
||||
RemoveSelections remove_selections = 4;
|
||||
SetActiveSelections set_active_selections = 5;
|
||||
DiagnosticSet update_diagnostics = 6;
|
||||
}
|
||||
|
||||
message Edit {
|
||||
|
|
|
@ -398,6 +398,7 @@ mod tests {
|
|||
content: "path/one content".to_string(),
|
||||
history: vec![],
|
||||
selections: vec![],
|
||||
diagnostics: None,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
@ -419,6 +420,7 @@ mod tests {
|
|||
content: "path/two content".to_string(),
|
||||
history: vec![],
|
||||
selections: vec![],
|
||||
diagnostics: None,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
@ -449,6 +451,7 @@ mod tests {
|
|||
content: "path/one content".to_string(),
|
||||
history: vec![],
|
||||
selections: vec![],
|
||||
diagnostics: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -460,6 +463,7 @@ mod tests {
|
|||
content: "path/two content".to_string(),
|
||||
history: vec![],
|
||||
selections: vec![],
|
||||
diagnostics: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
set -e
|
||||
|
||||
cd server
|
||||
cd crates/server
|
||||
cargo run $@
|
||||
|
|
Loading…
Reference in a new issue