Implement Buffer::format
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
67991b413c
commit
310def2923
13 changed files with 384 additions and 53 deletions
|
@ -691,6 +691,14 @@ impl Client {
|
||||||
) -> impl Future<Output = Result<()>> {
|
) -> impl Future<Output = Result<()>> {
|
||||||
self.peer.respond(receipt, response)
|
self.peer.respond(receipt, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn respond_with_error<T: RequestMessage>(
|
||||||
|
&self,
|
||||||
|
receipt: Receipt<T>,
|
||||||
|
error: proto::Error,
|
||||||
|
) -> impl Future<Output = Result<()>> {
|
||||||
|
self.peer.respond_with_error(receipt, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
|
fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Editor, Event};
|
use crate::{Autoscroll, Editor, Event};
|
||||||
use crate::{MultiBuffer, ToPoint as _};
|
use crate::{MultiBuffer, ToPoint as _};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -11,6 +11,7 @@ use project::{File, ProjectPath, Worktree};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use text::{Point, Selection};
|
use text::{Point, Selection};
|
||||||
|
use util::TryFutureExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
ItemHandle, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView, WeakItemHandle,
|
ItemHandle, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView, WeakItemHandle,
|
||||||
Workspace,
|
Workspace,
|
||||||
|
@ -141,9 +142,17 @@ impl ItemView for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<Result<()>>> {
|
fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<Result<()>>> {
|
||||||
let save = self.buffer().update(cx, |b, cx| b.save(cx))?;
|
let buffer = self.buffer().clone();
|
||||||
Ok(cx.spawn(|_, _| async move {
|
Ok(cx.spawn(|editor, mut cx| async move {
|
||||||
save.await?;
|
buffer
|
||||||
|
.update(&mut cx, |buffer, cx| buffer.format(cx).log_err())
|
||||||
|
.await;
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
editor.request_autoscroll(Autoscroll::Fit, cx)
|
||||||
|
});
|
||||||
|
buffer
|
||||||
|
.update(&mut cx, |buffer, cx| buffer.save(cx))?
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -798,6 +798,20 @@ impl MultiBuffer {
|
||||||
cx.emit(event.clone());
|
cx.emit(event.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
|
let mut format_tasks = Vec::new();
|
||||||
|
for BufferState { buffer, .. } in self.buffers.borrow().values() {
|
||||||
|
format_tasks.push(buffer.update(cx, |buffer, cx| buffer.format(cx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.spawn(|_, _| async move {
|
||||||
|
for format in format_tasks {
|
||||||
|
format.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
|
pub fn save(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
|
||||||
let mut save_tasks = Vec::new();
|
let mut save_tasks = Vec::new();
|
||||||
for BufferState { buffer, .. } in self.buffers.borrow().values() {
|
for BufferState { buffer, .. } in self.buffers.borrow().values() {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use crate::diagnostic_set::{DiagnosticEntry, DiagnosticGroup};
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
diagnostic_set::DiagnosticSet,
|
diagnostic_set::DiagnosticSet,
|
||||||
highlight_map::{HighlightId, HighlightMap},
|
highlight_map::{HighlightId, HighlightMap},
|
||||||
proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, LanguageServerConfig,
|
proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, LanguageServerConfig,
|
||||||
PLAIN_TEXT,
|
PLAIN_TEXT,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
||||||
|
range_from_lsp, ToPointUtf16,
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use futures::FutureExt as _;
|
use futures::FutureExt as _;
|
||||||
|
@ -180,6 +183,9 @@ pub trait File {
|
||||||
|
|
||||||
fn load_local(&self, cx: &AppContext) -> Option<Task<Result<String>>>;
|
fn load_local(&self, cx: &AppContext) -> Option<Task<Result<String>>>;
|
||||||
|
|
||||||
|
fn format_remote(&self, buffer_id: u64, cx: &mut MutableAppContext)
|
||||||
|
-> Option<Task<Result<()>>>;
|
||||||
|
|
||||||
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
|
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
|
||||||
|
|
||||||
fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
|
fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
|
||||||
|
@ -437,6 +443,65 @@ impl Buffer {
|
||||||
self.file.as_deref()
|
self.file.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
|
let file = if let Some(file) = self.file.as_ref() {
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
return Task::ready(Err(anyhow!("buffer has no file")));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(LanguageServerState { server, .. }) = self.language_server.as_ref() {
|
||||||
|
let server = server.clone();
|
||||||
|
let abs_path = file.abs_path().unwrap();
|
||||||
|
let version = self.version();
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let edits = server
|
||||||
|
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(
|
||||||
|
lsp::Url::from_file_path(&abs_path).unwrap(),
|
||||||
|
),
|
||||||
|
options: Default::default(),
|
||||||
|
work_done_progress_params: Default::default(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(edits) = edits {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
if this.version == version {
|
||||||
|
for edit in &edits {
|
||||||
|
let range = range_from_lsp(edit.range);
|
||||||
|
if this.clip_point_utf16(range.start, Bias::Left) != range.start
|
||||||
|
|| this.clip_point_utf16(range.end, Bias::Left) != range.end
|
||||||
|
{
|
||||||
|
return Err(anyhow!(
|
||||||
|
"invalid formatting edits received from language server"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for edit in edits.into_iter().rev() {
|
||||||
|
this.edit([range_from_lsp(edit.range)], edit.new_text, cx);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("buffer edited since starting to format"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let format = file.format_remote(self.remote_id(), cx.as_mut());
|
||||||
|
cx.spawn(|_, _| async move {
|
||||||
|
if let Some(format) = format {
|
||||||
|
format.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(
|
pub fn save(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use highlight_map::HighlightMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{path::Path, str, sync::Arc};
|
use std::{ops::Range, path::Path, str, sync::Arc};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use tree_sitter::{self, Query};
|
use tree_sitter::{self, Query};
|
||||||
pub use tree_sitter::{Parser, Tree};
|
pub use tree_sitter::{Parser, Tree};
|
||||||
|
@ -33,6 +33,10 @@ lazy_static! {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToPointUtf16 {
|
||||||
|
fn to_point_utf16(self) -> PointUtf16;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct LanguageConfig {
|
pub struct LanguageConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -244,3 +248,15 @@ impl LanguageServerConfig {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToPointUtf16 for lsp::Position {
|
||||||
|
fn to_point_utf16(self) -> PointUtf16 {
|
||||||
|
PointUtf16::new(self.line, self.character)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
|
||||||
|
let start = PointUtf16::new(range.start.line, range.start.character);
|
||||||
|
let end = PointUtf16::new(range.end.line, range.end.character);
|
||||||
|
start..end
|
||||||
|
}
|
||||||
|
|
|
@ -494,17 +494,25 @@ impl FakeLanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive_request<T: request::Request>(&mut self) -> (RequestId<T>, T::Params) {
|
pub async fn receive_request<T: request::Request>(&mut self) -> (RequestId<T>, T::Params) {
|
||||||
|
loop {
|
||||||
self.receive().await;
|
self.receive().await;
|
||||||
let request = serde_json::from_slice::<Request<T::Params>>(&self.buffer).unwrap();
|
if let Ok(request) = serde_json::from_slice::<Request<T::Params>>(&self.buffer) {
|
||||||
assert_eq!(request.method, T::METHOD);
|
assert_eq!(request.method, T::METHOD);
|
||||||
assert_eq!(request.jsonrpc, JSON_RPC_VERSION);
|
assert_eq!(request.jsonrpc, JSON_RPC_VERSION);
|
||||||
(
|
return (
|
||||||
RequestId {
|
RequestId {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
_type: std::marker::PhantomData,
|
_type: std::marker::PhantomData,
|
||||||
},
|
},
|
||||||
request.params,
|
request.params,
|
||||||
)
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"skipping message in fake language server {:?}",
|
||||||
|
std::str::from_utf8(&self.buffer)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
|
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
|
||||||
|
|
|
@ -308,6 +308,7 @@ impl Project {
|
||||||
client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
|
client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
|
||||||
client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
|
client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
|
||||||
client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
|
client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
|
||||||
|
client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -808,6 +809,21 @@ impl Project {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_format_buffer(
|
||||||
|
&mut self,
|
||||||
|
envelope: TypedEnvelope<proto::FormatBuffer>,
|
||||||
|
rpc: Arc<Client>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
|
if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
|
||||||
|
worktree.update(cx, |worktree, cx| {
|
||||||
|
worktree.handle_format_buffer(envelope, rpc, cx)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_open_buffer(
|
pub fn handle_open_buffer(
|
||||||
&mut self,
|
&mut self,
|
||||||
envelope: TypedEnvelope<proto::OpenBuffer>,
|
envelope: TypedEnvelope<proto::OpenBuffer>,
|
||||||
|
|
|
@ -15,8 +15,8 @@ use gpui::{
|
||||||
Task, UpgradeModelHandle, WeakModelHandle,
|
Task, UpgradeModelHandle, WeakModelHandle,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, File as _, Language, LanguageRegistry,
|
range_from_lsp, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, File as _, Language,
|
||||||
Operation, PointUtf16, Rope,
|
LanguageRegistry, Operation, PointUtf16, Rope,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use lsp::LanguageServer;
|
use lsp::LanguageServer;
|
||||||
|
@ -34,7 +34,7 @@ use std::{
|
||||||
ffi::{OsStr, OsString},
|
ffi::{OsStr, OsString},
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
ops::{Deref, Range},
|
ops::Deref,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
|
@ -580,6 +580,49 @@ impl Worktree {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_format_buffer(
|
||||||
|
&mut self,
|
||||||
|
envelope: TypedEnvelope<proto::FormatBuffer>,
|
||||||
|
rpc: Arc<Client>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let sender_id = envelope.original_sender_id()?;
|
||||||
|
let this = self.as_local().unwrap();
|
||||||
|
let buffer = this
|
||||||
|
.shared_buffers
|
||||||
|
.get(&sender_id)
|
||||||
|
.and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
|
||||||
|
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
|
||||||
|
|
||||||
|
let receipt = envelope.receipt();
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
let format = buffer.update(&mut cx, |buffer, cx| buffer.format(cx)).await;
|
||||||
|
// We spawn here in order to enqueue the sending of `Ack` *after* transmission of edits
|
||||||
|
// associated with formatting.
|
||||||
|
cx.spawn(|_| async move {
|
||||||
|
dbg!("responding");
|
||||||
|
match format {
|
||||||
|
Ok(()) => rpc.respond(receipt, proto::Ack {}).await?,
|
||||||
|
Err(error) => {
|
||||||
|
rpc.respond_with_error(
|
||||||
|
receipt,
|
||||||
|
proto::Error {
|
||||||
|
message: error.to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_snapshot(&mut self, cx: &mut ModelContext<Self>) {
|
fn poll_snapshot(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Local(worktree) => {
|
Self::Local(worktree) => {
|
||||||
|
@ -880,6 +923,7 @@ impl Worktree {
|
||||||
)),
|
)),
|
||||||
} {
|
} {
|
||||||
cx.spawn(|worktree, mut cx| async move {
|
cx.spawn(|worktree, mut cx| async move {
|
||||||
|
dbg!(&operation);
|
||||||
if let Err(error) = rpc
|
if let Err(error) = rpc
|
||||||
.request(proto::UpdateBuffer {
|
.request(proto::UpdateBuffer {
|
||||||
project_id,
|
project_id,
|
||||||
|
@ -2259,6 +2303,27 @@ impl language::File for File {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_remote(
|
||||||
|
&self,
|
||||||
|
buffer_id: u64,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let worktree = self.worktree.read(cx);
|
||||||
|
let worktree_id = worktree.id().to_proto();
|
||||||
|
let worktree = worktree.as_remote()?;
|
||||||
|
let rpc = worktree.client.clone();
|
||||||
|
let project_id = worktree.project_id;
|
||||||
|
Some(cx.foreground().spawn(async move {
|
||||||
|
rpc.request(proto::FormatBuffer {
|
||||||
|
project_id,
|
||||||
|
worktree_id,
|
||||||
|
buffer_id,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) {
|
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) {
|
||||||
self.worktree.update(cx, |worktree, cx| {
|
self.worktree.update(cx, |worktree, cx| {
|
||||||
worktree.send_buffer_update(buffer_id, operation, cx);
|
worktree.send_buffer_update(buffer_id, operation, cx);
|
||||||
|
@ -3180,22 +3245,6 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ToPointUtf16 {
|
|
||||||
fn to_point_utf16(self) -> PointUtf16;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToPointUtf16 for lsp::Position {
|
|
||||||
fn to_point_utf16(self) -> PointUtf16 {
|
|
||||||
PointUtf16::new(self.line, self.character)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
|
|
||||||
let start = PointUtf16::new(range.start.line, range.start.character);
|
|
||||||
let end = PointUtf16::new(range.end.line, range.end.character);
|
|
||||||
start..end
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -35,22 +35,23 @@ message Envelope {
|
||||||
UpdateBuffer update_buffer = 27;
|
UpdateBuffer update_buffer = 27;
|
||||||
SaveBuffer save_buffer = 28;
|
SaveBuffer save_buffer = 28;
|
||||||
BufferSaved buffer_saved = 29;
|
BufferSaved buffer_saved = 29;
|
||||||
|
FormatBuffer format_buffer = 30;
|
||||||
|
|
||||||
GetChannels get_channels = 30;
|
GetChannels get_channels = 31;
|
||||||
GetChannelsResponse get_channels_response = 31;
|
GetChannelsResponse get_channels_response = 32;
|
||||||
JoinChannel join_channel = 32;
|
JoinChannel join_channel = 33;
|
||||||
JoinChannelResponse join_channel_response = 33;
|
JoinChannelResponse join_channel_response = 34;
|
||||||
LeaveChannel leave_channel = 34;
|
LeaveChannel leave_channel = 35;
|
||||||
SendChannelMessage send_channel_message = 35;
|
SendChannelMessage send_channel_message = 36;
|
||||||
SendChannelMessageResponse send_channel_message_response = 36;
|
SendChannelMessageResponse send_channel_message_response = 37;
|
||||||
ChannelMessageSent channel_message_sent = 37;
|
ChannelMessageSent channel_message_sent = 38;
|
||||||
GetChannelMessages get_channel_messages = 38;
|
GetChannelMessages get_channel_messages = 39;
|
||||||
GetChannelMessagesResponse get_channel_messages_response = 39;
|
GetChannelMessagesResponse get_channel_messages_response = 40;
|
||||||
|
|
||||||
UpdateContacts update_contacts = 40;
|
UpdateContacts update_contacts = 41;
|
||||||
|
|
||||||
GetUsers get_users = 41;
|
GetUsers get_users = 42;
|
||||||
GetUsersResponse get_users_response = 42;
|
GetUsersResponse get_users_response = 43;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +169,12 @@ message BufferSaved {
|
||||||
Timestamp mtime = 5;
|
Timestamp mtime = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FormatBuffer {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 worktree_id = 2;
|
||||||
|
uint64 buffer_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message UpdateDiagnosticSummary {
|
message UpdateDiagnosticSummary {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 worktree_id = 2;
|
uint64 worktree_id = 2;
|
||||||
|
|
|
@ -398,7 +398,7 @@ mod tests {
|
||||||
proto::OpenBufferResponse {
|
proto::OpenBufferResponse {
|
||||||
buffer: Some(proto::Buffer {
|
buffer: Some(proto::Buffer {
|
||||||
id: 101,
|
id: 101,
|
||||||
visible_text: "path/one content".to_string(),
|
content: "path/one content".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -419,7 +419,7 @@ mod tests {
|
||||||
proto::OpenBufferResponse {
|
proto::OpenBufferResponse {
|
||||||
buffer: Some(proto::Buffer {
|
buffer: Some(proto::Buffer {
|
||||||
id: 102,
|
id: 102,
|
||||||
visible_text: "path/two content".to_string(),
|
content: "path/two content".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -448,7 +448,7 @@ mod tests {
|
||||||
proto::OpenBufferResponse {
|
proto::OpenBufferResponse {
|
||||||
buffer: Some(proto::Buffer {
|
buffer: Some(proto::Buffer {
|
||||||
id: 101,
|
id: 101,
|
||||||
visible_text: "path/one content".to_string(),
|
content: "path/one content".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -458,7 +458,7 @@ mod tests {
|
||||||
proto::OpenBufferResponse {
|
proto::OpenBufferResponse {
|
||||||
buffer: Some(proto::Buffer {
|
buffer: Some(proto::Buffer {
|
||||||
id: 102,
|
id: 102,
|
||||||
visible_text: "path/two content".to_string(),
|
content: "path/two content".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,7 @@ messages!(
|
||||||
DiskBasedDiagnosticsUpdated,
|
DiskBasedDiagnosticsUpdated,
|
||||||
DiskBasedDiagnosticsUpdating,
|
DiskBasedDiagnosticsUpdating,
|
||||||
Error,
|
Error,
|
||||||
|
FormatBuffer,
|
||||||
GetChannelMessages,
|
GetChannelMessages,
|
||||||
GetChannelMessagesResponse,
|
GetChannelMessagesResponse,
|
||||||
GetChannels,
|
GetChannels,
|
||||||
|
@ -162,6 +163,7 @@ messages!(
|
||||||
);
|
);
|
||||||
|
|
||||||
request_messages!(
|
request_messages!(
|
||||||
|
(FormatBuffer, Ack),
|
||||||
(GetChannelMessages, GetChannelMessagesResponse),
|
(GetChannelMessages, GetChannelMessagesResponse),
|
||||||
(GetChannels, GetChannelsResponse),
|
(GetChannels, GetChannelsResponse),
|
||||||
(GetUsers, GetUsersResponse),
|
(GetUsers, GetUsersResponse),
|
||||||
|
@ -185,6 +187,7 @@ entity_messages!(
|
||||||
CloseBuffer,
|
CloseBuffer,
|
||||||
DiskBasedDiagnosticsUpdated,
|
DiskBasedDiagnosticsUpdated,
|
||||||
DiskBasedDiagnosticsUpdating,
|
DiskBasedDiagnosticsUpdating,
|
||||||
|
FormatBuffer,
|
||||||
JoinProject,
|
JoinProject,
|
||||||
LeaveProject,
|
LeaveProject,
|
||||||
OpenBuffer,
|
OpenBuffer,
|
||||||
|
|
|
@ -79,6 +79,7 @@ impl Server {
|
||||||
.add_handler(Server::update_buffer)
|
.add_handler(Server::update_buffer)
|
||||||
.add_handler(Server::buffer_saved)
|
.add_handler(Server::buffer_saved)
|
||||||
.add_handler(Server::save_buffer)
|
.add_handler(Server::save_buffer)
|
||||||
|
.add_handler(Server::format_buffer)
|
||||||
.add_handler(Server::get_channels)
|
.add_handler(Server::get_channels)
|
||||||
.add_handler(Server::get_users)
|
.add_handler(Server::get_users)
|
||||||
.add_handler(Server::join_channel)
|
.add_handler(Server::join_channel)
|
||||||
|
@ -660,6 +661,30 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn format_buffer(
|
||||||
|
self: Arc<Server>,
|
||||||
|
request: TypedEnvelope<proto::FormatBuffer>,
|
||||||
|
) -> tide::Result<()> {
|
||||||
|
let host;
|
||||||
|
{
|
||||||
|
let state = self.state();
|
||||||
|
let project = state
|
||||||
|
.read_project(request.payload.project_id, request.sender_id)
|
||||||
|
.ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?;
|
||||||
|
host = project.host_connection_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sender = request.sender_id;
|
||||||
|
let receipt = request.receipt();
|
||||||
|
let response = self
|
||||||
|
.peer
|
||||||
|
.forward_request(sender, host, request.payload.clone())
|
||||||
|
.await?;
|
||||||
|
self.peer.respond(receipt, response).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_buffer(
|
async fn update_buffer(
|
||||||
self: Arc<Server>,
|
self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::UpdateBuffer>,
|
request: TypedEnvelope<proto::UpdateBuffer>,
|
||||||
|
@ -2001,6 +2026,111 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test(iterations = 1, seed = 2)]
|
||||||
|
async fn test_formatting_buffer(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
||||||
|
cx_a.foreground().forbid_parking();
|
||||||
|
let mut lang_registry = Arc::new(LanguageRegistry::new());
|
||||||
|
let fs = Arc::new(FakeFs::new());
|
||||||
|
|
||||||
|
// Set up a fake language server.
|
||||||
|
let (language_server_config, mut fake_language_server) =
|
||||||
|
LanguageServerConfig::fake(cx_a.background()).await;
|
||||||
|
Arc::get_mut(&mut lang_registry)
|
||||||
|
.unwrap()
|
||||||
|
.add(Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Rust".to_string(),
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
language_server: Some(language_server_config),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Connect to a server as 2 clients.
|
||||||
|
let mut server = TestServer::start(cx_a.foreground()).await;
|
||||||
|
let client_a = server.create_client(&mut cx_a, "user_a").await;
|
||||||
|
let client_b = server.create_client(&mut cx_b, "user_b").await;
|
||||||
|
|
||||||
|
// Share a project as client A
|
||||||
|
fs.insert_tree(
|
||||||
|
"/a",
|
||||||
|
json!({
|
||||||
|
".zed.toml": r#"collaborators = ["user_b"]"#,
|
||||||
|
"a.rs": "let one = two",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let project_a = cx_a.update(|cx| {
|
||||||
|
Project::local(
|
||||||
|
client_a.clone(),
|
||||||
|
client_a.user_store.clone(),
|
||||||
|
lang_registry.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let worktree_a = project_a
|
||||||
|
.update(&mut cx_a, |p, cx| p.add_local_worktree("/a", cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
worktree_a
|
||||||
|
.read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||||
|
.await;
|
||||||
|
let project_id = project_a
|
||||||
|
.update(&mut cx_a, |project, _| project.next_remote_id())
|
||||||
|
.await;
|
||||||
|
project_a
|
||||||
|
.update(&mut cx_a, |project, cx| project.share(cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Join the worktree as client B.
|
||||||
|
let project_b = Project::remote(
|
||||||
|
project_id,
|
||||||
|
client_b.clone(),
|
||||||
|
client_b.user_store.clone(),
|
||||||
|
lang_registry.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
&mut cx_b.to_async(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Open the file to be formatted on client B.
|
||||||
|
let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone());
|
||||||
|
let buffer_b = cx_b
|
||||||
|
.background()
|
||||||
|
.spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let format = buffer_b.update(&mut cx_b, |buffer, cx| buffer.format(cx));
|
||||||
|
let (request_id, _) = fake_language_server
|
||||||
|
.receive_request::<lsp::request::Formatting>()
|
||||||
|
.await;
|
||||||
|
fake_language_server
|
||||||
|
.respond(
|
||||||
|
request_id,
|
||||||
|
Some(vec![
|
||||||
|
lsp::TextEdit {
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
|
||||||
|
new_text: "h".to_string(),
|
||||||
|
},
|
||||||
|
lsp::TextEdit {
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
|
||||||
|
new_text: "y".to_string(),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
format.await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
buffer_b.read_with(&cx_b, |buffer, _| buffer.text()),
|
||||||
|
"let honey = two"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_basic_chat(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
async fn test_basic_chat(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
||||||
cx_a.foreground().forbid_parking();
|
cx_a.foreground().forbid_parking();
|
||||||
|
|
|
@ -593,6 +593,12 @@ impl Chunk {
|
||||||
|
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
point.row += 1;
|
point.row += 1;
|
||||||
|
if point.row > target.row {
|
||||||
|
panic!(
|
||||||
|
"point {:?} is beyond the end of a line with length {}",
|
||||||
|
target, point.column
|
||||||
|
);
|
||||||
|
}
|
||||||
point.column = 0;
|
point.column = 0;
|
||||||
} else {
|
} else {
|
||||||
point.column += ch.len_utf16() as u32;
|
point.column += ch.len_utf16() as u32;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue