Do not send edits over the wire
This commit is contained in:
parent
f812151840
commit
aa58d0fd77
4 changed files with 225 additions and 86 deletions
|
@ -2123,9 +2123,10 @@ impl Editor {
|
||||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||||
|
|
||||||
// When buffer contents is updated and caret is moved, try triggering on type formatting.
|
// When buffer contents is updated and caret is moved, try triggering on type formatting.
|
||||||
if settings::get::<EditorSettings>(cx).use_on_type_format && text.len() == 1 {
|
if settings::get::<EditorSettings>(cx).use_on_type_format {
|
||||||
let input_char = text.chars().next().expect("single char input");
|
if let Some(on_type_format_task) =
|
||||||
if let Some(on_type_format_task) = this.trigger_on_type_format(input_char, cx) {
|
this.trigger_on_type_formatting(text.to_string(), cx)
|
||||||
|
{
|
||||||
on_type_format_task.detach_and_log_err(cx);
|
on_type_format_task.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2508,20 +2509,42 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_on_type_format(
|
fn trigger_on_type_formatting(
|
||||||
&self,
|
&self,
|
||||||
input: char,
|
input: String,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<Task<Result<()>>> {
|
) -> Option<Task<Result<()>>> {
|
||||||
|
if input.len() != 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transaction_title = format!("OnTypeFormatting after {input}");
|
||||||
|
let workspace = self.workspace(cx)?;
|
||||||
let project = self.project.as_ref()?;
|
let project = self.project.as_ref()?;
|
||||||
let position = self.selections.newest_anchor().head();
|
let position = self.selections.newest_anchor().head();
|
||||||
let (buffer, buffer_position) = self
|
let (buffer, buffer_position) = self
|
||||||
.buffer
|
.buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.text_anchor_for_position(position.clone(), cx)?;
|
.text_anchor_for_position(position.clone(), cx)?;
|
||||||
|
let on_type_formatting = project.update(cx, |project, cx| {
|
||||||
|
project.on_type_format(buffer, buffer_position, input, cx)
|
||||||
|
});
|
||||||
|
|
||||||
Some(project.update(cx, |project, cx| {
|
Some(cx.spawn(|editor, mut cx| async move {
|
||||||
project.on_type_format(buffer.clone(), buffer_position, input, cx)
|
let project_transaction = on_type_formatting.await?;
|
||||||
|
Self::open_project_transaction(
|
||||||
|
&editor,
|
||||||
|
workspace.downgrade(),
|
||||||
|
project_transaction,
|
||||||
|
transaction_title,
|
||||||
|
cx.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
editor.refresh_document_highlights(cx);
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project,
|
DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project,
|
||||||
ProjectTransaction,
|
ProjectTransaction,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::proto::{self, PeerId};
|
use client::proto::{self, PeerId};
|
||||||
use fs::LineEnding;
|
use fs::LineEnding;
|
||||||
|
@ -123,6 +123,7 @@ pub(crate) struct OnTypeFormatting {
|
||||||
pub position: PointUtf16,
|
pub position: PointUtf16,
|
||||||
pub trigger: String,
|
pub trigger: String,
|
||||||
pub options: FormattingOptions,
|
pub options: FormattingOptions,
|
||||||
|
pub push_to_history: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct FormattingOptions {
|
pub(crate) struct FormattingOptions {
|
||||||
|
@ -1627,7 +1628,7 @@ impl LspCommand for GetCodeActions {
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl LspCommand for OnTypeFormatting {
|
impl LspCommand for OnTypeFormatting {
|
||||||
type Response = Vec<(Range<Anchor>, String)>;
|
type Response = ProjectTransaction;
|
||||||
type LspRequest = lsp::request::OnTypeFormatting;
|
type LspRequest = lsp::request::OnTypeFormatting;
|
||||||
type ProtoRequest = proto::OnTypeFormatting;
|
type ProtoRequest = proto::OnTypeFormatting;
|
||||||
|
|
||||||
|
@ -1667,14 +1668,23 @@ impl LspCommand for OnTypeFormatting {
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<Vec<(Range<Anchor>, String)>> {
|
) -> Result<ProjectTransaction> {
|
||||||
cx.update(|cx| {
|
if let Some(edits) = message {
|
||||||
project.update(cx, |project, cx| {
|
let (lsp_adapter, lsp_server) =
|
||||||
project.edits_from_lsp(&buffer, message.into_iter().flatten(), server_id, None, cx)
|
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
|
||||||
})
|
Project::deserialize_edits(
|
||||||
})
|
project,
|
||||||
.await
|
buffer,
|
||||||
.context("LSP edits conversion")
|
edits,
|
||||||
|
self.push_to_history,
|
||||||
|
lsp_adapter,
|
||||||
|
lsp_server,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Ok(ProjectTransaction::default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
|
||||||
|
@ -1714,58 +1724,38 @@ impl LspCommand for OnTypeFormatting {
|
||||||
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
|
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||||
trigger: message.trigger.clone(),
|
trigger: message.trigger.clone(),
|
||||||
options: lsp_formatting_options(tab_size.get()).into(),
|
options: lsp_formatting_options(tab_size.get()).into(),
|
||||||
|
push_to_history: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_to_proto(
|
fn response_to_proto(
|
||||||
response: Vec<(Range<Anchor>, String)>,
|
response: ProjectTransaction,
|
||||||
_: &mut Project,
|
project: &mut Project,
|
||||||
_: PeerId,
|
peer_id: PeerId,
|
||||||
buffer_version: &clock::Global,
|
_: &clock::Global,
|
||||||
_: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> proto::OnTypeFormattingResponse {
|
) -> proto::OnTypeFormattingResponse {
|
||||||
|
let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
|
||||||
proto::OnTypeFormattingResponse {
|
proto::OnTypeFormattingResponse {
|
||||||
entries: response
|
transaction: Some(transaction),
|
||||||
.into_iter()
|
|
||||||
.map(
|
|
||||||
|(response_range, new_text)| proto::OnTypeFormattingResponseEntry {
|
|
||||||
start: Some(language::proto::serialize_anchor(&response_range.start)),
|
|
||||||
end: Some(language::proto::serialize_anchor(&response_range.end)),
|
|
||||||
new_text,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.collect(),
|
|
||||||
version: serialize_version(&buffer_version),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn response_from_proto(
|
async fn response_from_proto(
|
||||||
self,
|
self,
|
||||||
message: proto::OnTypeFormattingResponse,
|
message: proto::OnTypeFormattingResponse,
|
||||||
_: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
_: ModelHandle<Buffer>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<Vec<(Range<Anchor>, String)>> {
|
) -> Result<ProjectTransaction> {
|
||||||
buffer
|
let message = message
|
||||||
.update(&mut cx, |buffer, _| {
|
.transaction
|
||||||
buffer.wait_for_version(deserialize_version(&message.version))
|
.ok_or_else(|| anyhow!("missing transaction"))?;
|
||||||
|
project
|
||||||
|
.update(&mut cx, |project, cx| {
|
||||||
|
project.deserialize_project_transaction(message, self.push_to_history, cx)
|
||||||
})
|
})
|
||||||
.await?;
|
.await
|
||||||
message
|
|
||||||
.entries
|
|
||||||
.into_iter()
|
|
||||||
.map(|entry| {
|
|
||||||
let start = entry
|
|
||||||
.start
|
|
||||||
.and_then(language::proto::deserialize_anchor)
|
|
||||||
.ok_or_else(|| anyhow!("invalid start"))?;
|
|
||||||
let end = entry
|
|
||||||
.end
|
|
||||||
.and_then(language::proto::deserialize_anchor)
|
|
||||||
.ok_or_else(|| anyhow!("invalid end"))?;
|
|
||||||
Ok((start..end, entry.new_text))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
|
fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
|
||||||
|
|
|
@ -417,6 +417,7 @@ impl Project {
|
||||||
client.add_model_request_handler(Self::handle_delete_project_entry);
|
client.add_model_request_handler(Self::handle_delete_project_entry);
|
||||||
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
|
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
|
||||||
client.add_model_request_handler(Self::handle_apply_code_action);
|
client.add_model_request_handler(Self::handle_apply_code_action);
|
||||||
|
client.add_model_request_handler(Self::handle_on_type_formatting);
|
||||||
client.add_model_request_handler(Self::handle_reload_buffers);
|
client.add_model_request_handler(Self::handle_reload_buffers);
|
||||||
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
||||||
client.add_model_request_handler(Self::handle_format_buffers);
|
client.add_model_request_handler(Self::handle_format_buffers);
|
||||||
|
@ -429,7 +430,6 @@ impl Project {
|
||||||
client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
|
client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
|
||||||
client.add_model_request_handler(Self::handle_lsp_command::<PrepareRename>);
|
client.add_model_request_handler(Self::handle_lsp_command::<PrepareRename>);
|
||||||
client.add_model_request_handler(Self::handle_lsp_command::<PerformRename>);
|
client.add_model_request_handler(Self::handle_lsp_command::<PerformRename>);
|
||||||
client.add_model_request_handler(Self::handle_lsp_command::<OnTypeFormatting>);
|
|
||||||
client.add_model_request_handler(Self::handle_search_project);
|
client.add_model_request_handler(Self::handle_search_project);
|
||||||
client.add_model_request_handler(Self::handle_get_project_symbols);
|
client.add_model_request_handler(Self::handle_get_project_symbols);
|
||||||
client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
|
client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
|
||||||
|
@ -4035,6 +4035,118 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_on_type_formatting(
|
||||||
|
&self,
|
||||||
|
buffer: ModelHandle<Buffer>,
|
||||||
|
position: Anchor,
|
||||||
|
trigger: String,
|
||||||
|
push_to_history: bool,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<ProjectTransaction>> {
|
||||||
|
if self.is_local() {
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
// Do not allow multiple concurrent formatting requests for the
|
||||||
|
// same buffer.
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.buffers_being_formatted
|
||||||
|
.insert(buffer.read(cx).remote_id())
|
||||||
|
});
|
||||||
|
|
||||||
|
let _cleanup = defer({
|
||||||
|
let this = this.clone();
|
||||||
|
let mut cx = cx.clone();
|
||||||
|
let closure_buffer = buffer.clone();
|
||||||
|
move || {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.buffers_being_formatted
|
||||||
|
.remove(&closure_buffer.read(cx).remote_id());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buffer
|
||||||
|
.update(&mut cx, |buffer, _| {
|
||||||
|
buffer.wait_for_edits(Some(position.timestamp))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
|
this.on_type_format(buffer, position, trigger, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
} else if let Some(project_id) = self.remote_id() {
|
||||||
|
let client = self.client.clone();
|
||||||
|
let request = proto::OnTypeFormatting {
|
||||||
|
project_id,
|
||||||
|
buffer_id: buffer.read(cx).remote_id(),
|
||||||
|
position: Some(serialize_anchor(&position)),
|
||||||
|
trigger,
|
||||||
|
version: serialize_version(&buffer.read(cx).version()),
|
||||||
|
};
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let response = client
|
||||||
|
.request(request)
|
||||||
|
.await?
|
||||||
|
.transaction
|
||||||
|
.ok_or_else(|| anyhow!("missing transaction"))?;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.deserialize_project_transaction(response, push_to_history, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Task::ready(Err(anyhow!("project does not have a remote id")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn deserialize_edits(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
buffer_to_edit: ModelHandle<Buffer>,
|
||||||
|
edits: Vec<lsp::TextEdit>,
|
||||||
|
push_to_history: bool,
|
||||||
|
_: Arc<CachedLspAdapter>,
|
||||||
|
language_server: Arc<LanguageServer>,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Result<ProjectTransaction> {
|
||||||
|
let edits = this
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.edits_from_lsp(
|
||||||
|
&buffer_to_edit,
|
||||||
|
edits,
|
||||||
|
language_server.server_id(),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let transaction = buffer_to_edit.update(cx, |buffer, cx| {
|
||||||
|
buffer.finalize_last_transaction();
|
||||||
|
buffer.start_transaction();
|
||||||
|
for (range, text) in edits {
|
||||||
|
buffer.edit([(range, text)], None, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.end_transaction(cx).is_some() {
|
||||||
|
let transaction = buffer.finalize_last_transaction().unwrap().clone();
|
||||||
|
if !push_to_history {
|
||||||
|
buffer.forget_transaction(transaction.id);
|
||||||
|
}
|
||||||
|
Some(transaction)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut project_transaction = ProjectTransaction::default();
|
||||||
|
if let Some(transaction) = transaction {
|
||||||
|
project_transaction.0.insert(buffer_to_edit, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(project_transaction)
|
||||||
|
}
|
||||||
|
|
||||||
async fn deserialize_workspace_edit(
|
async fn deserialize_workspace_edit(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
edit: lsp::WorkspaceEdit,
|
edit: lsp::WorkspaceEdit,
|
||||||
|
@ -4204,39 +4316,24 @@ impl Project {
|
||||||
&self,
|
&self,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
position: T,
|
position: T,
|
||||||
input: char,
|
trigger: String,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<ProjectTransaction>> {
|
||||||
let tab_size = buffer.read_with(cx, |buffer, cx| {
|
let tab_size = buffer.read_with(cx, |buffer, cx| {
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
let language_name = buffer.language().map(|language| language.name());
|
||||||
language_settings(language_name.as_deref(), cx).tab_size
|
language_settings(language_name.as_deref(), cx).tab_size
|
||||||
});
|
});
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
let edits_task = self.request_lsp(
|
self.request_lsp(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
OnTypeFormatting {
|
OnTypeFormatting {
|
||||||
position,
|
position,
|
||||||
trigger: input.to_string(),
|
trigger,
|
||||||
options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
|
options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
|
||||||
|
push_to_history: true,
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
|
|
||||||
cx.spawn(|_project, mut cx| async move {
|
|
||||||
let edits = edits_task
|
|
||||||
.await
|
|
||||||
.context("requesting OnTypeFormatting edits for char '{new_char}'")?;
|
|
||||||
|
|
||||||
if !edits.is_empty() {
|
|
||||||
cx.update(|cx| {
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.edit(edits, None, cx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
|
@ -5809,6 +5906,42 @@ impl Project {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_on_type_formatting(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::OnTypeFormatting>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::OnTypeFormattingResponse> {
|
||||||
|
let sender_id = envelope.original_sender_id()?;
|
||||||
|
let on_type_formatting = this.update(&mut cx, |this, cx| {
|
||||||
|
let buffer = this
|
||||||
|
.opened_buffers
|
||||||
|
.get(&envelope.payload.buffer_id)
|
||||||
|
.and_then(|buffer| buffer.upgrade(cx))
|
||||||
|
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
|
||||||
|
let position = envelope
|
||||||
|
.payload
|
||||||
|
.position
|
||||||
|
.and_then(deserialize_anchor)
|
||||||
|
.ok_or_else(|| anyhow!("invalid position"))?;
|
||||||
|
Ok::<_, anyhow::Error>(this.apply_on_type_formatting(
|
||||||
|
buffer,
|
||||||
|
position,
|
||||||
|
envelope.payload.trigger.clone(),
|
||||||
|
false,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let project_transaction = on_type_formatting.await?;
|
||||||
|
let project_transaction = this.update(&mut cx, |this, cx| {
|
||||||
|
this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
|
||||||
|
});
|
||||||
|
Ok(proto::OnTypeFormattingResponse {
|
||||||
|
transaction: Some(project_transaction),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_lsp_command<T: LspCommand>(
|
async fn handle_lsp_command<T: LspCommand>(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
envelope: TypedEnvelope<T::ProtoRequest>,
|
envelope: TypedEnvelope<T::ProtoRequest>,
|
||||||
|
@ -6379,7 +6512,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn edits_from_lsp(
|
fn edits_from_lsp(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
|
lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
|
||||||
|
|
|
@ -682,14 +682,7 @@ message OnTypeFormatting {
|
||||||
}
|
}
|
||||||
|
|
||||||
message OnTypeFormattingResponse {
|
message OnTypeFormattingResponse {
|
||||||
repeated OnTypeFormattingResponseEntry entries = 1;
|
ProjectTransaction transaction = 1;
|
||||||
repeated VectorClockEntry version = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OnTypeFormattingResponseEntry {
|
|
||||||
Anchor start = 1;
|
|
||||||
Anchor end = 2;
|
|
||||||
string new_text = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message PerformRenameResponse {
|
message PerformRenameResponse {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue