Merge branch 'main' into site-v2

This commit is contained in:
Nate
2021-09-24 16:52:06 -04:00
19 changed files with 658 additions and 363 deletions

42
Cargo.lock generated
View File

@@ -867,6 +867,9 @@ name = "cc"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
dependencies = [
"jobserver",
]
[[package]]
name = "cexpr"
@@ -2614,6 +2617,15 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.1.22"
@@ -6037,4 +6049,34 @@ dependencies = [
"serde 1.0.125",
"smol",
"tempdir",
"zstd",
]
[[package]]
name = "zstd"
version = "0.9.0+zstd.1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07749a5dc2cb6b36661290245e350f15ec3bbb304e493db54a1d354480522ccd"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "4.1.1+zstd.1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c91c90f2c593b003603e5e0493c837088df4469da25aafff8bce42ba48caf079"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "1.6.1+zstd.1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615120c7a2431d16cf1cf979e7fc31ba7a5b5e5707b29c8a99e5dbf8a8392a33"
dependencies = [
"cc",
"libc",
]

View File

@@ -669,8 +669,9 @@ mod tests {
);
}
#[crate::test(self)]
#[crate::test(self, retries = 5)]
fn test_wrap_shaped_line(cx: &mut crate::MutableAppContext) {
// This is failing intermittently on CI and we don't have time to figure it out
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let text_layout_cache = TextLayoutCache::new(font_system.clone());

View File

@@ -18,7 +18,7 @@ use scrypt::{
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, convert::TryFrom, sync::Arc};
use surf::{StatusCode, Url};
use tide::{log, Server};
use tide::{log, Error, Server};
use zrpc::auth as zed_auth;
static CURRENT_GITHUB_USER: &'static str = "current_github_user";
@@ -33,51 +33,48 @@ pub struct User {
pub is_admin: bool,
}
pub struct VerifyToken;
pub async fn process_auth_header(request: &Request) -> tide::Result<UserId> {
let mut auth_header = request
.header("Authorization")
.ok_or_else(|| {
Error::new(
StatusCode::BadRequest,
anyhow!("missing authorization header"),
)
})?
.last()
.as_str()
.split_whitespace();
let user_id = UserId(auth_header.next().unwrap_or("").parse().map_err(|_| {
Error::new(
StatusCode::BadRequest,
anyhow!("missing user id in authorization header"),
)
})?);
let access_token = auth_header.next().ok_or_else(|| {
Error::new(
StatusCode::BadRequest,
anyhow!("missing access token in authorization header"),
)
})?;
#[async_trait]
impl tide::Middleware<Arc<AppState>> for VerifyToken {
async fn handle(
&self,
mut request: Request,
next: tide::Next<'_, Arc<AppState>>,
) -> tide::Result {
let mut auth_header = request
.header("Authorization")
.ok_or_else(|| anyhow!("no authorization header"))?
.last()
.as_str()
.split_whitespace();
let user_id = UserId(
auth_header
.next()
.ok_or_else(|| anyhow!("missing user id in authorization header"))?
.parse()?,
);
let access_token = auth_header
.next()
.ok_or_else(|| anyhow!("missing access token in authorization header"))?;
let state = request.state().clone();
let mut credentials_valid = false;
for password_hash in state.db.get_access_token_hashes(user_id).await? {
if verify_access_token(&access_token, &password_hash)? {
credentials_valid = true;
break;
}
}
if credentials_valid {
request.set_ext(user_id);
Ok(next.run(request).await)
} else {
let mut response = tide::Response::new(StatusCode::Unauthorized);
response.set_body("invalid credentials");
Ok(response)
let state = request.state().clone();
let mut credentials_valid = false;
for password_hash in state.db.get_access_token_hashes(user_id).await? {
if verify_access_token(&access_token, &password_hash)? {
credentials_valid = true;
break;
}
}
if !credentials_valid {
Err(Error::new(
StatusCode::Unauthorized,
anyhow!("invalid credentials"),
))?;
}
Ok(user_id)
}
#[async_trait]
@@ -263,11 +260,13 @@ async fn post_sign_out(mut request: Request) -> tide::Result {
Ok(tide::Redirect::new("/").into())
}
const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
pub async fn create_access_token(db: &db::Db, user_id: UserId) -> tide::Result<String> {
let access_token = zed_auth::random_token();
let access_token_hash =
hash_access_token(&access_token).context("failed to hash access token")?;
db.create_access_token_hash(user_id, access_token_hash)
db.create_access_token_hash(user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE)
.await?;
Ok(access_token)
}

View File

@@ -175,25 +175,48 @@ impl Db {
pub async fn create_access_token_hash(
&self,
user_id: UserId,
access_token_hash: String,
access_token_hash: &str,
max_access_token_count: usize,
) -> Result<()> {
test_support!(self, {
let query = "
INSERT INTO access_tokens (user_id, hash)
VALUES ($1, $2)
";
sqlx::query(query)
let insert_query = "
INSERT INTO access_tokens (user_id, hash)
VALUES ($1, $2);
";
let cleanup_query = "
DELETE FROM access_tokens
WHERE id IN (
SELECT id from access_tokens
WHERE user_id = $1
ORDER BY id DESC
OFFSET $3
)
";
let mut tx = self.pool.begin().await?;
sqlx::query(insert_query)
.bind(user_id.0)
.bind(access_token_hash)
.execute(&self.pool)
.await
.map(drop)
.execute(&mut tx)
.await?;
sqlx::query(cleanup_query)
.bind(user_id.0)
.bind(access_token_hash)
.bind(max_access_token_count as u32)
.execute(&mut tx)
.await?;
tx.commit().await
})
}
pub async fn get_access_token_hashes(&self, user_id: UserId) -> Result<Vec<String>> {
test_support!(self, {
let query = "SELECT hash FROM access_tokens WHERE user_id = $1";
let query = "
SELECT hash
FROM access_tokens
WHERE user_id = $1
ORDER BY id DESC
";
sqlx::query_scalar(query)
.bind(user_id.0)
.fetch_all(&self.pool)
@@ -652,4 +675,36 @@ pub mod tests {
assert_eq!(msg1_id, msg3_id);
assert_eq!(msg2_id, msg4_id);
}
}
#[gpui::test]
async fn test_create_access_tokens() {
let test_db = TestDb::new();
let db = test_db.db();
let user = db.create_user("the-user", false).await.unwrap();
db.create_access_token_hash(user, "h1", 3).await.unwrap();
db.create_access_token_hash(user, "h2", 3).await.unwrap();
assert_eq!(
db.get_access_token_hashes(user).await.unwrap(),
&["h2".to_string(), "h1".to_string()]
);
db.create_access_token_hash(user, "h3", 3).await.unwrap();
assert_eq!(
db.get_access_token_hashes(user).await.unwrap(),
&["h3".to_string(), "h2".to_string(), "h1".to_string(),]
);
db.create_access_token_hash(user, "h4", 3).await.unwrap();
assert_eq!(
db.get_access_token_hashes(user).await.unwrap(),
&["h4".to_string(), "h3".to_string(), "h2".to_string(),]
);
db.create_access_token_hash(user, "h5", 3).await.unwrap();
assert_eq!(
db.get_access_token_hashes(user).await.unwrap(),
&["h5".to_string(), "h4".to_string(), "h3".to_string()]
);
}
}

View File

@@ -1,7 +1,7 @@
mod store;
use super::{
auth,
auth::process_auth_header,
db::{ChannelId, MessageId, UserId},
AppState,
};
@@ -885,8 +885,7 @@ where
pub fn add_routes(app: &mut tide::Server<Arc<AppState>>, rpc: &Arc<Peer>) {
let server = Server::new(app.state().clone(), rpc.clone(), None);
app.at("/rpc").with(auth::VerifyToken).get(move |request: Request<Arc<AppState>>| {
let user_id = request.ext::<UserId>().copied();
app.at("/rpc").get(move |request: Request<Arc<AppState>>| {
let server = server.clone();
async move {
const WEBSOCKET_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@@ -894,8 +893,11 @@ pub fn add_routes(app: &mut tide::Server<Arc<AppState>>, rpc: &Arc<Peer>) {
let connection_upgrade = header_contains_ignore_case(&request, CONNECTION, "upgrade");
let upgrade_to_websocket = header_contains_ignore_case(&request, UPGRADE, "websocket");
let upgrade_requested = connection_upgrade && upgrade_to_websocket;
let client_protocol_version: Option<u32> = request
.header("X-Zed-Protocol-Version")
.and_then(|v| v.as_str().parse().ok());
if !upgrade_requested {
if !upgrade_requested || client_protocol_version != Some(zrpc::PROTOCOL_VERSION) {
return Ok(Response::new(StatusCode::UpgradeRequired));
}
@@ -904,6 +906,8 @@ pub fn add_routes(app: &mut tide::Server<Arc<AppState>>, rpc: &Arc<Peer>) {
None => return Err(anyhow!("expected sec-websocket-key"))?,
};
let user_id = process_auth_header(&request).await?;
let mut response = Response::new(StatusCode::SwitchingProtocols);
response.insert_header(UPGRADE, "websocket");
response.insert_header(CONNECTION, "Upgrade");
@@ -914,10 +918,17 @@ pub fn add_routes(app: &mut tide::Server<Arc<AppState>>, rpc: &Arc<Peer>) {
let http_res: &mut tide::http::Response = response.as_mut();
let upgrade_receiver = http_res.recv_upgrade().await;
let addr = request.remote().unwrap_or("unknown").to_string();
let user_id = user_id.ok_or_else(|| anyhow!("user_id is not present on request. ensure auth::VerifyToken middleware is present"))?;
task::spawn(async move {
if let Some(stream) = upgrade_receiver.await {
server.handle_connection(Connection::new(WebSocketStream::from_raw_socket(stream, Role::Server, None).await), addr, user_id).await;
server
.handle_connection(
Connection::new(
WebSocketStream::from_raw_socket(stream, Role::Server, None).await,
),
addr,
user_id,
)
.await;
}
});

View File

@@ -11,6 +11,7 @@ title = "$text.0"
avatar_width = 20
icon_color = "$text.2.color"
avatar = { corner_radius = 10, border = { width = 1, color = "#00000088" } }
outdated_warning = { extends = "$text.2", size = 13 }
[workspace.titlebar.offline_icon]
padding = { right = 4 }

View File

@@ -29,7 +29,6 @@ use smol::Timer;
use std::{
cell::RefCell,
cmp::{self, Ordering},
iter::FromIterator,
mem,
ops::{Range, RangeInclusive},
path::Path,
@@ -299,7 +298,7 @@ pub struct Editor {
pending_selection: Option<Selection>,
next_selection_id: usize,
add_selections_state: Option<AddSelectionsState>,
select_larger_syntax_node_stack: Vec<Vec<Selection>>,
select_larger_syntax_node_stack: Vec<Arc<[Selection]>>,
scroll_position: Vector2F,
scroll_top_anchor: Anchor,
autoscroll_requested: bool,
@@ -511,15 +510,14 @@ impl Editor {
return false;
}
let first_cursor_top = self
.selections(cx)
let selections = self.selections(cx);
let first_cursor_top = selections
.first()
.unwrap()
.head()
.to_display_point(&display_map, Bias::Left)
.row() as f32;
let last_cursor_bottom = self
.selections(cx)
let last_cursor_bottom = selections
.last()
.unwrap()
.head()
@@ -561,12 +559,13 @@ impl Editor {
scroll_width: f32,
max_glyph_width: f32,
layouts: &[text_layout::Line],
cx: &mut MutableAppContext,
cx: &mut ViewContext<Self>,
) -> bool {
let selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut target_left = std::f32::INFINITY;
let mut target_right = 0.0_f32;
for selection in self.selections(cx) {
for selection in selections.iter() {
let head = selection.head().to_display_point(&display_map, Bias::Left);
let start_column = head.column().saturating_sub(3);
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
@@ -655,12 +654,10 @@ impl Editor {
fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
if let Some(selection) = self.pending_selection.take() {
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
let ix = self.selection_insertion_index(&selections, &selection.start, cx.as_ref());
selections.insert(ix, selection);
self.update_selections(selections, false, cx);
} else {
log::error!("end_selection dispatched with no pending selection");
}
}
@@ -669,12 +666,13 @@ impl Editor {
}
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
let selections = self.selections(cx.as_ref());
if let Some(pending_selection) = self.pending_selection.take() {
let selections = self.selections(cx);
if selections.is_empty() {
self.update_selections(vec![pending_selection], true, cx);
}
} else {
let selections = self.selections(cx);
let mut oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
if selections.len() == 1 {
oldest_selection.start = oldest_selection.head().clone();
@@ -743,8 +741,9 @@ impl Editor {
pub fn insert(&mut self, action: &Insert, cx: &mut ViewContext<Self>) {
let mut old_selections = SmallVec::<[_; 32]>::new();
{
let selections = self.selections(cx);
let buffer = self.buffer.read(cx);
for selection in self.selections(cx.as_ref()) {
for selection in selections.iter() {
let start = selection.start.to_offset(buffer);
let end = selection.end.to_offset(buffer);
old_selections.push((selection.id, start..end));
@@ -790,7 +789,7 @@ impl Editor {
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
{
let buffer = self.buffer.read(cx);
@@ -814,7 +813,7 @@ impl Editor {
pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
let buffer = self.buffer.read(cx);
for selection in &mut selections {
@@ -837,14 +836,14 @@ impl Editor {
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let app = cx.as_ref();
let buffer = self.buffer.read(app);
let buffer = self.buffer.read(cx);
let mut new_cursors = Vec::new();
let mut edit_ranges = Vec::new();
let mut selections = self.selections(app).iter().peekable();
let mut selections = selections.iter().peekable();
while let Some(selection) = selections.next() {
let mut rows = selection.spanned_rows(false, &display_map).buffer_rows;
let goal_display_column = selection
@@ -914,7 +913,7 @@ impl Editor {
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
// Temporarily bias selections right to allow newly duplicate lines to push them down
// when the selections are at the beginning of a line.
@@ -974,8 +973,8 @@ impl Editor {
pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let app = cx.as_ref();
let buffer = self.buffer.read(cx);
let mut edits = Vec::new();
@@ -983,7 +982,7 @@ impl Editor {
let mut old_folds = Vec::new();
let mut new_folds = Vec::new();
let mut selections = self.selections(app).iter().peekable();
let mut selections = selections.iter().peekable();
let mut contiguous_selections = Vec::new();
while let Some(selection) = selections.next() {
// Accumulate contiguous regions of rows that we want to move.
@@ -1064,8 +1063,8 @@ impl Editor {
pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let app = cx.as_ref();
let buffer = self.buffer.read(cx);
let mut edits = Vec::new();
@@ -1073,7 +1072,7 @@ impl Editor {
let mut old_folds = Vec::new();
let mut new_folds = Vec::new();
let mut selections = self.selections(app).iter().peekable();
let mut selections = selections.iter().peekable();
let mut contiguous_selections = Vec::new();
while let Some(selection) = selections.next() {
// Accumulate contiguous regions of rows that we want to move.
@@ -1151,7 +1150,7 @@ impl Editor {
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let mut text = String::new();
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
let mut clipboard_selections = Vec::with_capacity(selections.len());
{
let buffer = self.buffer.read(cx);
@@ -1186,12 +1185,12 @@ impl Editor {
}
pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
let selections = self.selections(cx);
let buffer = self.buffer.read(cx);
let max_point = buffer.max_point();
let mut text = String::new();
let selections = self.selections(cx.as_ref());
let mut clipboard_selections = Vec::with_capacity(selections.len());
for selection in selections {
for selection in selections.iter() {
let mut start = selection.start.to_point(buffer);
let mut end = selection.end.to_point(buffer);
let is_entire_line = start == end;
@@ -1218,24 +1217,28 @@ impl Editor {
if let Some(item) = cx.as_mut().read_from_clipboard() {
let clipboard_text = item.text();
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
let selections = self.selections(cx.as_ref()).to_vec();
let selections = self.selections(cx);
let all_selections_were_entire_line =
clipboard_selections.iter().all(|s| s.is_entire_line);
if clipboard_selections.len() != selections.len() {
let merged_selection = ClipboardSelection {
len: clipboard_selections.iter().map(|s| s.len).sum(),
is_entire_line: clipboard_selections.iter().all(|s| s.is_entire_line),
};
clipboard_selections.clear();
clipboard_selections.push(merged_selection);
}
self.start_transaction(cx);
let mut start_offset = 0;
let mut new_selections = Vec::with_capacity(selections.len());
let mut clipboard_chars = clipboard_text.chars().cycle();
for (selection, clipboard_selection) in
selections.iter().zip(clipboard_selections.iter().cycle())
{
let to_insert =
String::from_iter(clipboard_chars.by_ref().take(clipboard_selection.len));
for (i, selection) in selections.iter().enumerate() {
let to_insert;
let entire_line;
if let Some(clipboard_selection) = clipboard_selections.get(i) {
let end_offset = start_offset + clipboard_selection.len;
to_insert = &clipboard_text[start_offset..end_offset];
entire_line = clipboard_selection.is_entire_line;
start_offset = end_offset
} else {
to_insert = clipboard_text.as_str();
entire_line = all_selections_were_entire_line;
}
self.buffer.update(cx, |buffer, cx| {
let selection_start = selection.start.to_point(&*buffer);
@@ -1246,7 +1249,7 @@ impl Editor {
// selection was copied. If this selection is also currently empty,
// then paste the line before the current line of the buffer.
let new_selection_start = selection.end.bias_right(buffer);
if selection_start == selection_end && clipboard_selection.is_entire_line {
if selection_start == selection_end && entire_line {
let line_start = Point::new(selection_start.row, 0);
buffer.edit(Some(line_start..line_start), to_insert, cx);
} else {
@@ -1281,8 +1284,7 @@ impl Editor {
pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let app = cx.as_ref();
let mut selections = self.selections(app).to_vec();
let mut selections = self.selections(cx).to_vec();
{
for selection in &mut selections {
let start = selection.start.to_display_point(&display_map, Bias::Left);
@@ -1305,7 +1307,7 @@ impl Editor {
pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
let buffer = self.buffer.read(cx);
for selection in &mut selections {
@@ -1321,7 +1323,7 @@ impl Editor {
pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
for selection in &mut selections {
let start = selection.start.to_display_point(&display_map, Bias::Left);
@@ -1344,7 +1346,7 @@ impl Editor {
pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
let app = cx.as_ref();
let buffer = self.buffer.read(app);
@@ -1364,7 +1366,7 @@ impl Editor {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
} else {
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
for selection in &mut selections {
let start = selection.start.to_display_point(&display_map, Bias::Left);
@@ -1387,7 +1389,7 @@ impl Editor {
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
let app = cx.as_ref();
let buffer = self.buffer.read(app);
@@ -1406,7 +1408,7 @@ impl Editor {
cx.propagate_action();
} else {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx.as_ref()).to_vec();
let mut selections = self.selections(cx).to_vec();
{
for selection in &mut selections {
let start = selection.start.to_display_point(&display_map, Bias::Left);
@@ -1490,8 +1492,26 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
self.start_transaction(cx);
self.select_to_previous_word_boundary(&SelectToPreviousWordBoundary, cx);
self.backspace(&Backspace, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx).to_vec();
{
let buffer = self.buffer.read(cx);
for selection in &mut selections {
let range = selection.point_range(buffer);
if range.start == range.end {
let head = selection.head().to_display_point(&display_map, Bias::Left);
let cursor = display_map.anchor_before(
movement::prev_word_boundary(&display_map, head).unwrap(),
Bias::Right,
);
selection.set_head(&buffer, cursor);
selection.goal = SelectionGoal::None;
}
}
}
self.update_selections(selections, true, cx);
self.insert(&Insert(String::new()), cx);
self.end_transaction(cx);
}
@@ -1542,8 +1562,26 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
self.start_transaction(cx);
self.select_to_next_word_boundary(&SelectToNextWordBoundary, cx);
self.delete(&Delete, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections(cx).to_vec();
{
let buffer = self.buffer.read(cx);
for selection in &mut selections {
let range = selection.point_range(buffer);
if range.start == range.end {
let head = selection.head().to_display_point(&display_map, Bias::Left);
let cursor = display_map.anchor_before(
movement::next_word_boundary(&display_map, head).unwrap(),
Bias::Right,
);
selection.set_head(&buffer, cursor);
selection.goal = SelectionGoal::None;
}
}
}
self.update_selections(selections, true, cx);
self.insert(&Insert(String::new()), cx);
self.end_transaction(cx);
}
@@ -1661,7 +1699,7 @@ impl Editor {
}
pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
let mut selection = self.selections(cx.as_ref()).last().unwrap().clone();
let mut selection = self.selections(cx).last().unwrap().clone();
selection.set_head(self.buffer.read(cx), Anchor::min());
self.update_selections(vec![selection], true, cx);
}
@@ -1680,7 +1718,7 @@ impl Editor {
}
pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
let mut selection = self.selections(cx.as_ref()).last().unwrap().clone();
let mut selection = self.selections(cx).last().unwrap().clone();
selection.set_head(self.buffer.read(cx), Anchor::max());
self.update_selections(vec![selection], true, cx);
}
@@ -1698,8 +1736,8 @@ impl Editor {
pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
let mut selections = self.selections(cx).to_vec();
let buffer = self.buffer.read(cx);
let max_point = buffer.max_point();
for selection in &mut selections {
let rows = selection.spanned_rows(true, &display_map).buffer_rows;
@@ -1715,12 +1753,12 @@ impl Editor {
_: &SplitSelectionIntoLines,
cx: &mut ViewContext<Self>,
) {
let app = cx.as_ref();
let buffer = self.buffer.read(app);
let selections = self.selections(cx);
let buffer = self.buffer.read(cx);
let mut to_unfold = Vec::new();
let mut new_selections = Vec::new();
for selection in self.selections(app) {
for selection in selections.iter() {
let range = selection.point_range(buffer).sorted();
if range.start.row != range.end.row {
new_selections.push(Selection {
@@ -1860,14 +1898,14 @@ impl Editor {
_: &SelectLargerSyntaxNode,
cx: &mut ViewContext<Self>,
) {
let old_selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
let mut selected_larger_node = false;
let old_selections = self.selections(cx).to_vec();
let mut new_selection_ranges = Vec::new();
for selection in &old_selections {
for selection in old_selections.iter() {
let old_range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
let mut new_range = old_range.clone();
while let Some(containing_range) = buffer.range_for_syntax_ancestor(new_range.clone()) {
@@ -1908,7 +1946,7 @@ impl Editor {
) {
let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
if let Some(selections) = stack.pop() {
self.update_selections(selections, true, cx);
self.update_selections(selections.to_vec(), true, cx);
}
self.select_larger_syntax_node_stack = stack;
}
@@ -1918,8 +1956,8 @@ impl Editor {
_: &MoveToEnclosingBracket,
cx: &mut ViewContext<Self>,
) {
let mut selections = self.selections(cx).to_vec();
let buffer = self.buffer.read(cx.as_ref());
let mut selections = self.selections(cx.as_ref()).to_vec();
for selection in &mut selections {
let selection_range = selection.offset_range(buffer);
if let Some((open_range, close_range)) =
@@ -2033,12 +2071,14 @@ impl Editor {
}
}
fn selections<'a>(&self, cx: &'a AppContext) -> &'a [Selection] {
fn selections(&mut self, cx: &mut ViewContext<Self>) -> Arc<[Selection]> {
self.end_selection(cx);
let buffer = self.buffer.read(cx);
&buffer
buffer
.selection_set(self.selection_set_id)
.unwrap()
.selections
.clone()
}
fn update_selections(
@@ -2112,8 +2152,9 @@ impl Editor {
pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
let selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
for selection in self.selections(cx) {
for selection in selections.iter() {
let range = selection.display_range(&display_map).sorted();
let buffer_start_row = range.start.to_buffer_point(&display_map, Bias::Left).row;
@@ -2134,10 +2175,10 @@ impl Editor {
}
pub fn unfold(&mut self, _: &Unfold, cx: &mut ViewContext<Self>) {
let selections = self.selections(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
let ranges = self
.selections(cx)
let ranges = selections
.iter()
.map(|s| {
let range = s.display_range(&display_map).sorted();
@@ -2195,9 +2236,9 @@ impl Editor {
}
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
let selections = self.selections(cx);
let buffer = self.buffer.read(cx);
let ranges = self
.selections(cx.as_ref())
let ranges = selections
.iter()
.map(|s| s.point_range(buffer).sorted())
.collect();
@@ -3223,24 +3264,13 @@ mod tests {
);
});
view.update(cx, |view, cx| {
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx);
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
]
);
});
view.update(cx, |view, cx| {
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx);
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
]
);
});
@@ -3272,30 +3302,19 @@ mod tests {
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
]
);
});
view.update(cx, |view, cx| {
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx);
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7),
DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
]
);
});
view.update(cx, |view, cx| {
view.move_to_next_word_boundary(&MoveToNextWordBoundary, cx);
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
]
);
});
@@ -3307,7 +3326,7 @@ mod tests {
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2),
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3),
]
);
});
@@ -3318,7 +3337,7 @@ mod tests {
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7),
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 0),
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 2),
]
);
});
@@ -3329,37 +3348,7 @@ mod tests {
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2),
]
);
});
view.update(cx, |view, cx| {
view.delete_to_next_word_boundary(&DeleteToNextWordBoundary, cx);
assert_eq!(
view.display_text(cx),
"use std::s::{foo, bar}\n\n {az.qux()}"
);
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 10)..DisplayPoint::new(0, 10),
DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
]
);
});
view.update(cx, |view, cx| {
view.delete_to_previous_word_boundary(&DeleteToPreviousWordBoundary, cx);
assert_eq!(
view.display_text(cx),
"use std::::{foo, bar}\n\n az.qux()}"
);
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 3),
]
);
});
@@ -3415,11 +3404,52 @@ mod tests {
view.move_to_previous_word_boundary(&MoveToPreviousWordBoundary, cx);
assert_eq!(
view.selection_ranges(cx),
&[DisplayPoint::new(1, 15)..DisplayPoint::new(1, 15)]
&[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
);
});
}
#[gpui::test]
fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four", cx));
let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| {
build_editor(buffer.clone(), settings, cx)
});
view.update(cx, |view, cx| {
view.select_display_ranges(
&[
// an empty selection - the preceding word fragment is deleted
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
// characters selected - they are deleted
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
],
cx,
)
.unwrap();
view.delete_to_previous_word_boundary(&DeleteToPreviousWordBoundary, cx);
});
assert_eq!(buffer.read(cx).text(), "e two te four");
view.update(cx, |view, cx| {
view.select_display_ranges(
&[
// an empty selection - the following word fragment is deleted
DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
// characters selected - they are deleted
DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
],
cx,
)
.unwrap();
view.delete_to_next_word_boundary(&DeleteToNextWordBoundary, cx);
});
assert_eq!(buffer.read(cx).text(), "e t te our");
}
#[gpui::test]
fn test_backspace(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| {
@@ -3685,7 +3715,7 @@ mod tests {
#[gpui::test]
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four five six ", cx));
let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four five six ", cx));
let settings = settings::test(&cx).1;
let view = cx
.add_window(Default::default(), |cx| {
@@ -3695,7 +3725,7 @@ mod tests {
// Cut with three selections. Clipboard text is divided into three slices.
view.update(cx, |view, cx| {
view.select_ranges(vec![0..4, 8..14, 19..24], false, cx);
view.select_ranges(vec![0..7, 11..17, 22..27], false, cx);
view.cut(&Cut, cx);
assert_eq!(view.display_text(cx), "two four six ");
});
@@ -3704,13 +3734,13 @@ mod tests {
view.update(cx, |view, cx| {
view.select_ranges(vec![4..4, 9..9, 13..13], false, cx);
view.paste(&Paste, cx);
assert_eq!(view.display_text(cx), "two one four three six five ");
assert_eq!(view.display_text(cx), "two one four three six five ");
assert_eq!(
view.selection_ranges(cx),
&[
DisplayPoint::new(0, 8)..DisplayPoint::new(0, 8),
DisplayPoint::new(0, 19)..DisplayPoint::new(0, 19),
DisplayPoint::new(0, 28)..DisplayPoint::new(0, 28)
DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
DisplayPoint::new(0, 22)..DisplayPoint::new(0, 22),
DisplayPoint::new(0, 31)..DisplayPoint::new(0, 31)
]
);
});
@@ -3719,13 +3749,13 @@ mod tests {
// match the number of slices in the clipboard, the entire clipboard text
// is pasted at each cursor.
view.update(cx, |view, cx| {
view.select_ranges(vec![0..0, 28..28], false, cx);
view.select_ranges(vec![0..0, 31..31], false, cx);
view.insert(&Insert("( ".into()), cx);
view.paste(&Paste, cx);
view.insert(&Insert(") ".into()), cx);
assert_eq!(
view.display_text(cx),
"( one three five ) two one four three six five ( one three five ) "
"( one three five ) two one four three six five ( one three five ) "
);
});
@@ -3734,7 +3764,7 @@ mod tests {
view.insert(&Insert("123\n4567\n89\n".into()), cx);
assert_eq!(
view.display_text(cx),
"123\n4567\n89\n( one three five ) two one four three six five ( one three five ) "
"123\n4567\n89\n( one three five ) two one four three six five ( one three five ) "
);
});
@@ -3752,7 +3782,7 @@ mod tests {
view.cut(&Cut, cx);
assert_eq!(
view.display_text(cx),
"13\n9\n( one three five ) two one four three six five ( one three five ) "
"13\n9\n( one three five ) two one four three six five ( one three five ) "
);
});
@@ -3771,7 +3801,7 @@ mod tests {
view.paste(&Paste, cx);
assert_eq!(
view.display_text(cx),
"123\n4567\n9\n( 8ne three five ) two one four three six five ( one three five ) "
"123\n4567\n9\n( 8ne three five ) two one four three six five ( one three five ) "
);
assert_eq!(
view.selection_ranges(cx),
@@ -3805,7 +3835,7 @@ mod tests {
view.paste(&Paste, cx);
assert_eq!(
view.display_text(cx),
"123\n123\n123\n67\n123\n9\n( 8ne three five ) two one four three six five ( one three five ) "
"123\n123\n123\n67\n123\n9\n( 8ne three five ) two one four three six five ( one three five ) "
);
assert_eq!(
view.selection_ranges(cx),

View File

@@ -101,7 +101,10 @@ pub fn line_end(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<Display
Ok(map.clip_point(line_end, Bias::Left))
}
pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Result<DisplayPoint> {
pub fn prev_word_boundary(
map: &DisplayMapSnapshot,
mut point: DisplayPoint,
) -> Result<DisplayPoint> {
let mut line_start = 0;
if point.row() > 0 {
if let Some(indent) = map.soft_wrap_indent(point.row() - 1) {
@@ -111,39 +114,52 @@ pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Resu
if point.column() == line_start {
if point.row() == 0 {
Ok(DisplayPoint::new(0, 0))
return Ok(DisplayPoint::new(0, 0));
} else {
let row = point.row() - 1;
Ok(map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left))
point = map.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left);
}
} else {
let mut boundary = DisplayPoint::new(point.row(), 0);
let mut column = 0;
let mut prev_c = None;
for c in map.chars_at(DisplayPoint::new(point.row(), 0)) {
if column >= point.column() {
break;
}
if prev_c.is_none() || char_kind(prev_c.unwrap()) != char_kind(c) {
*boundary.column_mut() = column;
}
prev_c = Some(c);
column += c.len_utf8() as u32;
}
Ok(boundary)
}
let mut boundary = DisplayPoint::new(point.row(), 0);
let mut column = 0;
let mut prev_char_kind = CharKind::Newline;
for c in map.chars_at(DisplayPoint::new(point.row(), 0)) {
if column >= point.column() {
break;
}
let char_kind = char_kind(c);
if char_kind != prev_char_kind
&& char_kind != CharKind::Whitespace
&& char_kind != CharKind::Newline
{
*boundary.column_mut() = column;
}
prev_char_kind = char_kind;
column += c.len_utf8() as u32;
}
Ok(boundary)
}
pub fn next_word_boundary(
map: &DisplayMapSnapshot,
mut point: DisplayPoint,
) -> Result<DisplayPoint> {
let mut prev_c = None;
let mut prev_char_kind = None;
for c in map.chars_at(point) {
if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) {
break;
let char_kind = char_kind(c);
if let Some(prev_char_kind) = prev_char_kind {
if c == '\n' {
break;
}
if prev_char_kind != char_kind
&& prev_char_kind != CharKind::Whitespace
&& prev_char_kind != CharKind::Newline
{
break;
}
}
if c == '\n' {
@@ -152,7 +168,7 @@ pub fn next_word_boundary(
} else {
*point.column_mut() += c.len_utf8() as u32;
}
prev_c = Some(c);
prev_char_kind = Some(char_kind);
}
Ok(point)
}
@@ -192,7 +208,7 @@ mod tests {
.unwrap();
let font_size = 14.0;
let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ", cx));
let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ hi—jk", cx));
let display_map =
cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -202,7 +218,7 @@ mod tests {
);
assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),
DisplayPoint::new(0, 6)
DisplayPoint::new(0, 2)
);
assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(),
@@ -210,7 +226,7 @@ mod tests {
);
assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(),
DisplayPoint::new(0, 1)
DisplayPoint::new(0, 0)
);
assert_eq!(
prev_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(),
@@ -223,7 +239,7 @@ mod tests {
);
assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 1)).unwrap(),
DisplayPoint::new(0, 2)
DisplayPoint::new(0, 6)
);
assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 2)).unwrap(),
@@ -231,7 +247,7 @@ mod tests {
);
assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 6)).unwrap(),
DisplayPoint::new(0, 7)
DisplayPoint::new(0, 12)
);
assert_eq!(
next_word_boundary(&snapshot, DisplayPoint::new(0, 7)).unwrap(),

View File

@@ -438,29 +438,37 @@ mod tests {
use crate::{
editor::{self, Insert},
fs::FakeFs,
test::{temp_tree, test_app_state},
test::test_app_state,
workspace::Workspace,
};
use serde_json::json;
use std::fs;
use tempdir::TempDir;
use std::path::PathBuf;
#[gpui::test]
async fn test_matching_paths(mut cx: gpui::TestAppContext) {
let tmp_dir = TempDir::new("example").unwrap();
fs::create_dir(tmp_dir.path().join("a")).unwrap();
fs::write(tmp_dir.path().join("a/banana"), "banana").unwrap();
fs::write(tmp_dir.path().join("a/bandana"), "bandana").unwrap();
let app_state = cx.update(test_app_state);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"a": {
"banana": "",
"bandana": "",
}
}),
)
.await;
cx.update(|cx| {
super::init(cx);
editor::init(cx);
});
let app_state = cx.update(test_app_state);
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_worktree(tmp_dir.path(), cx)
workspace.add_worktree(Path::new("/root"), cx)
})
.await
.unwrap();
@@ -572,17 +580,17 @@ mod tests {
#[gpui::test]
async fn test_single_file_worktrees(mut cx: gpui::TestAppContext) {
let temp_dir = TempDir::new("test-single-file-worktrees").unwrap();
let dir_path = temp_dir.path().join("the-parent-dir");
let file_path = dir_path.join("the-file");
fs::create_dir(&dir_path).unwrap();
fs::write(&file_path, "").unwrap();
let app_state = cx.update(test_app_state);
app_state
.fs
.as_fake()
.insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_worktree(&file_path, cx)
workspace.add_worktree(Path::new("/root/the-parent-dir/the-file"), cx)
})
.await
.unwrap();
@@ -620,18 +628,25 @@ mod tests {
#[gpui::test(retries = 5)]
async fn test_multiple_matches_with_same_relative_path(mut cx: gpui::TestAppContext) {
let tmp_dir = temp_tree(json!({
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}));
let app_state = cx.update(test_app_state);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}),
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.open_paths(
&[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")],
&[PathBuf::from("/root/dir1"), PathBuf::from("/root/dir2")],
cx,
)
})

View File

@@ -29,6 +29,8 @@ pub trait Fs: Send + Sync {
latency: Duration,
) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
fn is_fake(&self) -> bool;
#[cfg(any(test, feature = "test-support"))]
fn as_fake(&self) -> &FakeFs;
}
#[derive(Clone, Debug)]
@@ -125,6 +127,11 @@ impl Fs for RealFs {
fn is_fake(&self) -> bool {
false
}
#[cfg(any(test, feature = "test-support"))]
fn as_fake(&self) -> &FakeFs {
panic!("called `RealFs::as_fake`")
}
}
#[derive(Clone, Debug)]
@@ -413,4 +420,9 @@ impl Fs for FakeFs {
fn is_fake(&self) -> bool {
true
}
#[cfg(any(test, feature = "test-support"))]
fn as_fake(&self) -> &FakeFs {
self
}
}

View File

@@ -55,6 +55,8 @@ pub struct Client {
#[derive(Error, Debug)]
pub enum EstablishConnectionError {
#[error("upgrade required")]
UpgradeRequired,
#[error("unauthorized")]
Unauthorized,
#[error("{0}")]
@@ -68,8 +70,10 @@ pub enum EstablishConnectionError {
impl From<WebsocketError> for EstablishConnectionError {
fn from(error: WebsocketError) -> Self {
if let WebsocketError::Http(response) = &error {
if response.status() == StatusCode::UNAUTHORIZED {
return EstablishConnectionError::Unauthorized;
match response.status() {
StatusCode::UNAUTHORIZED => return EstablishConnectionError::Unauthorized,
StatusCode::UPGRADE_REQUIRED => return EstablishConnectionError::UpgradeRequired,
_ => {}
}
}
EstablishConnectionError::Other(error.into())
@@ -85,6 +89,7 @@ impl EstablishConnectionError {
#[derive(Copy, Clone, Debug)]
pub enum Status {
SignedOut,
UpgradeRequired,
Authenticating,
Connecting,
ConnectionError,
@@ -227,7 +232,7 @@ impl Client {
}
}));
}
Status::SignedOut => {
Status::SignedOut | Status::UpgradeRequired => {
state._maintain_connection.take();
}
_ => {}
@@ -346,6 +351,7 @@ impl Client {
| Status::Reconnecting { .. }
| Status::Authenticating
| Status::Reauthenticating => return Ok(()),
Status::UpgradeRequired => return Err(EstablishConnectionError::UpgradeRequired)?,
};
if was_disconnected {
@@ -388,22 +394,25 @@ impl Client {
self.set_connection(conn, cx).await;
Ok(())
}
Err(err) => {
if matches!(err, EstablishConnectionError::Unauthorized) {
self.state.write().credentials.take();
if used_keychain {
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
self.set_status(Status::SignedOut, cx);
self.authenticate_and_connect(cx).await
} else {
self.set_status(Status::ConnectionError, cx);
Err(err)?
}
Err(EstablishConnectionError::Unauthorized) => {
self.state.write().credentials.take();
if used_keychain {
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
self.set_status(Status::SignedOut, cx);
self.authenticate_and_connect(cx).await
} else {
self.set_status(Status::ConnectionError, cx);
Err(err)?
Err(EstablishConnectionError::Unauthorized)?
}
}
Err(EstablishConnectionError::UpgradeRequired) => {
self.set_status(Status::UpgradeRequired, cx);
Err(EstablishConnectionError::UpgradeRequired)?
}
Err(error) => {
self.set_status(Status::ConnectionError, cx);
Err(error)?
}
}
}
@@ -489,10 +498,12 @@ impl Client {
credentials: &Credentials,
cx: &AsyncAppContext,
) -> Task<Result<Connection, EstablishConnectionError>> {
let request = Request::builder().header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
);
let request = Request::builder()
.header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
.header("X-Zed-Protocol-Version", zrpc::PROTOCOL_VERSION);
cx.background().spawn(async move {
if let Some(host) = ZED_SERVER_URL.strip_prefix("https://") {
let stream = smol::net::TcpStream::connect(host).await?;

View File

@@ -1,7 +1,7 @@
use crate::{
assets::Assets,
channel::ChannelList,
fs::RealFs,
fs::FakeFs,
http::{HttpClient, Request, Response, ServerResponse},
language::LanguageRegistry,
rpc::{self, Client, Credentials, EstablishConnectionError},
@@ -177,7 +177,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
channel_list: cx.add_model(|cx| ChannelList::new(user_store.clone(), rpc.clone(), cx)),
rpc,
user_store,
fs: Arc::new(RealFs),
fs: Arc::new(FakeFs::new()),
})
}

View File

@@ -54,6 +54,7 @@ pub struct Titlebar {
pub offline_icon: OfflineIcon,
pub icon_color: Color,
pub avatar: ImageStyle,
pub outdated_warning: ContainedText,
}
#[derive(Clone, Deserialize)]
@@ -169,7 +170,7 @@ pub struct Selector {
pub active_item: ContainedLabel,
}
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
pub struct ContainedText {
#[serde(flatten)]
pub container: ContainerStyle,

View File

@@ -1041,6 +1041,16 @@ impl Workspace {
.with_style(theme.workspace.titlebar.offline_icon.container)
.boxed(),
),
rpc::Status::UpgradeRequired => Some(
Label::new(
"Please update Zed to collaborate".to_string(),
theme.workspace.titlebar.outdated_warning.text.clone(),
)
.contained()
.with_style(theme.workspace.titlebar.outdated_warning.container)
.aligned()
.boxed(),
),
_ => None,
}
}
@@ -1195,11 +1205,9 @@ mod tests {
editor::{Editor, Insert},
fs::FakeFs,
test::{temp_tree, test_app_state},
worktree::WorktreeHandle,
};
use serde_json::json;
use std::{collections::HashSet, fs};
use tempdir::TempDir;
use std::collections::HashSet;
#[gpui::test]
async fn test_open_paths_action(mut cx: gpui::TestAppContext) {
@@ -1268,20 +1276,26 @@ mod tests {
#[gpui::test]
async fn test_open_entry(mut cx: gpui::TestAppContext) {
let dir = temp_tree(json!({
"a": {
"file1": "contents 1",
"file2": "contents 2",
"file3": "contents 3",
},
}));
let app_state = cx.update(test_app_state);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"a": {
"file1": "contents 1",
"file2": "contents 2",
"file3": "contents 3",
},
}),
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_worktree(dir.path(), cx)
workspace.add_worktree(Path::new("/root"), cx)
})
.await
.unwrap();
@@ -1445,28 +1459,30 @@ mod tests {
#[gpui::test]
async fn test_save_conflicting_item(mut cx: gpui::TestAppContext) {
let dir = temp_tree(json!({
"a.txt": "",
}));
let app_state = cx.update(test_app_state);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"a.txt": "",
}),
)
.await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_worktree(dir.path(), cx)
workspace.add_worktree(Path::new("/root"), cx)
})
.await
.unwrap();
let tree = cx.read(|cx| {
let mut trees = workspace.read(cx).worktrees().iter();
trees.next().unwrap().clone()
});
tree.flush_fs_events(&cx).await;
// Open a file within an existing worktree.
cx.update(|cx| {
workspace.update(cx, |view, cx| {
view.open_paths(&[dir.path().join("a.txt")], cx)
view.open_paths(&[PathBuf::from("/root/a.txt")], cx)
})
})
.await;
@@ -1477,7 +1493,12 @@ mod tests {
});
cx.update(|cx| editor.update(cx, |editor, cx| editor.insert(&Insert("x".into()), cx)));
fs::write(dir.path().join("a.txt"), "changed").unwrap();
app_state
.fs
.as_fake()
.insert_file("/root/a.txt", "changed".to_string())
.await
.unwrap();
editor
.condition(&cx, |editor, cx| editor.has_conflict(cx))
.await;
@@ -1493,12 +1514,12 @@ mod tests {
#[gpui::test]
async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
let dir = TempDir::new("test-new-file").unwrap();
let app_state = cx.update(test_app_state);
app_state.fs.as_fake().insert_dir("/root").await.unwrap();
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_worktree(dir.path(), cx)
workspace.add_worktree(Path::new("/root"), cx)
})
.await
.unwrap();
@@ -1511,7 +1532,6 @@ mod tests {
.unwrap()
.clone()
});
tree.flush_fs_events(&cx).await;
// Create a new untitled buffer
let editor = workspace.update(&mut cx, |workspace, cx| {
@@ -1537,7 +1557,7 @@ mod tests {
workspace.save_active_item(&Save, cx)
});
cx.simulate_new_path_selection(|parent_dir| {
assert_eq!(parent_dir, dir.path());
assert_eq!(parent_dir, Path::new("/root"));
Some(parent_dir.join("the-new-name.rs"))
});
cx.read(|cx| {
@@ -1598,8 +1618,8 @@ mod tests {
async fn test_setting_language_when_saving_as_single_file_worktree(
mut cx: gpui::TestAppContext,
) {
let dir = TempDir::new("test-new-file").unwrap();
let app_state = cx.update(test_app_state);
app_state.fs.as_fake().insert_dir("/root").await.unwrap();
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
// Create a new untitled buffer
@@ -1623,7 +1643,7 @@ mod tests {
workspace.update(&mut cx, |workspace, cx| {
workspace.save_active_item(&Save, cx)
});
cx.simulate_new_path_selection(|_| Some(dir.path().join("the-new-name.rs")));
cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
editor
.condition(&cx, |editor, cx| !editor.is_dirty(cx))
@@ -1640,7 +1660,7 @@ mod tests {
cx.update(init);
let app_state = cx.update(test_app_state);
cx.dispatch_global_action(OpenNew(app_state));
cx.dispatch_global_action(OpenNew(app_state.clone()));
let window_id = *cx.window_ids().first().unwrap();
let workspace = cx.root_view::<Workspace>(window_id).unwrap();
let editor = workspace.update(&mut cx, |workspace, cx| {
@@ -1660,10 +1680,8 @@ mod tests {
workspace.save_active_item(&Save, cx)
});
let dir = TempDir::new("test-new-empty-workspace").unwrap();
cx.simulate_new_path_selection(|_| {
Some(dir.path().canonicalize().unwrap().join("the-new-name"))
});
app_state.fs.as_fake().insert_dir("/root").await.unwrap();
cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name")));
editor
.condition(&cx, |editor, cx| editor.title(cx) == "the-new-name")
@@ -1676,20 +1694,26 @@ mod tests {
#[gpui::test]
async fn test_pane_actions(mut cx: gpui::TestAppContext) {
cx.update(|cx| pane::init(cx));
let dir = temp_tree(json!({
"a": {
"file1": "contents 1",
"file2": "contents 2",
"file3": "contents 3",
},
}));
let app_state = cx.update(test_app_state);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"a": {
"file1": "contents 1",
"file2": "contents 2",
"file3": "contents 3",
},
}),
)
.await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_worktree(dir.path(), cx)
workspace.add_worktree(Path::new("/root"), cx)
})
.await
.unwrap();

View File

@@ -161,6 +161,7 @@ impl Worktree {
entries_by_id_edits.push(Edit::Insert(PathEntry {
id: entry.id,
path: entry.path.clone(),
is_ignored: entry.is_ignored,
scan_id: 0,
}));
entries_by_path_edits.push(Edit::Insert(entry));
@@ -1083,7 +1084,7 @@ impl LocalWorktree {
async move {
let mut prev_snapshot = snapshot;
while let Ok(snapshot) = snapshots_to_send_rx.recv().await {
let message = snapshot.build_update(&prev_snapshot, remote_id);
let message = snapshot.build_update(&prev_snapshot, remote_id, false);
match rpc.send(message).await {
Ok(()) => prev_snapshot = snapshot,
Err(err) => log::error!("error sending snapshot diff {}", err),
@@ -1140,6 +1141,7 @@ impl LocalWorktree {
let entries = snapshot
.entries_by_path
.cursor::<(), ()>()
.filter(|e| !e.is_ignored)
.map(Into::into)
.collect();
proto::ShareWorktree {
@@ -1373,11 +1375,24 @@ impl Snapshot {
self.id
}
pub fn build_update(&self, other: &Self, worktree_id: u64) -> proto::UpdateWorktree {
pub fn build_update(
&self,
other: &Self,
worktree_id: u64,
include_ignored: bool,
) -> proto::UpdateWorktree {
let mut updated_entries = Vec::new();
let mut removed_entries = Vec::new();
let mut self_entries = self.entries_by_id.cursor::<(), ()>().peekable();
let mut other_entries = other.entries_by_id.cursor::<(), ()>().peekable();
let mut self_entries = self
.entries_by_id
.cursor::<(), ()>()
.filter(|e| include_ignored || !e.is_ignored)
.peekable();
let mut other_entries = other
.entries_by_id
.cursor::<(), ()>()
.filter(|e| include_ignored || !e.is_ignored)
.peekable();
loop {
match (self_entries.peek(), other_entries.peek()) {
(Some(self_entry), Some(other_entry)) => match self_entry.id.cmp(&other_entry.id) {
@@ -1443,6 +1458,7 @@ impl Snapshot {
entries_by_id_edits.push(Edit::Insert(PathEntry {
id: entry.id,
path: entry.path.clone(),
is_ignored: entry.is_ignored,
scan_id,
}));
entries_by_path_edits.push(Edit::Insert(entry));
@@ -1526,6 +1542,7 @@ impl Snapshot {
PathEntry {
id: entry.id,
path: entry.path.clone(),
is_ignored: entry.is_ignored,
scan_id: self.scan_id,
},
&(),
@@ -1561,6 +1578,7 @@ impl Snapshot {
entries_by_id_edits.push(Edit::Insert(PathEntry {
id: entry.id,
path: entry.path.clone(),
is_ignored: entry.is_ignored,
scan_id: self.scan_id,
}));
entries_by_path_edits.push(Edit::Insert(entry));
@@ -1933,6 +1951,7 @@ impl sum_tree::Summary for EntrySummary {
struct PathEntry {
id: usize,
path: Arc<Path>,
is_ignored: bool,
scan_id: usize,
}
@@ -2412,7 +2431,8 @@ impl BackgroundScanner {
ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
}
let mut edits = Vec::new();
let mut entries_by_id_edits = Vec::new();
let mut entries_by_path_edits = Vec::new();
for mut entry in snapshot.child_entries(&job.path).cloned() {
let was_ignored = entry.is_ignored;
entry.is_ignored = ignore_stack.is_path_ignored(&entry.path, entry.is_dir());
@@ -2433,10 +2453,17 @@ impl BackgroundScanner {
}
if entry.is_ignored != was_ignored {
edits.push(Edit::Insert(entry));
let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
path_entry.scan_id = snapshot.scan_id;
path_entry.is_ignored = entry.is_ignored;
entries_by_id_edits.push(Edit::Insert(path_entry));
entries_by_path_edits.push(Edit::Insert(entry));
}
}
self.snapshot.lock().entries_by_path.edit(edits, &());
let mut snapshot = self.snapshot.lock();
snapshot.entries_by_path.edit(entries_by_path_edits, &());
snapshot.entries_by_id.edit(entries_by_id_edits, &());
}
}
@@ -3000,10 +3027,10 @@ mod tests {
// Update the remote worktree. Check that it becomes consistent with the
// local worktree.
remote.update(&mut cx, |remote, cx| {
let update_message = tree
.read(cx)
.snapshot()
.build_update(&initial_snapshot, worktree_id);
let update_message =
tree.read(cx)
.snapshot()
.build_update(&initial_snapshot, worktree_id, true);
remote
.as_remote_mut()
.unwrap()
@@ -3175,6 +3202,7 @@ mod tests {
scanner.snapshot().check_invariants();
let mut events = Vec::new();
let mut snapshots = Vec::new();
let mut mutations_len = operations;
while mutations_len > 1 {
if !events.is_empty() && rng.gen_bool(0.4) {
@@ -3187,6 +3215,10 @@ mod tests {
events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
mutations_len -= 1;
}
if rng.gen_bool(0.2) {
snapshots.push(scanner.snapshot());
}
}
log::info!("Quiescing: {:#?}", events);
smol::block_on(scanner.process_events(events));
@@ -3200,7 +3232,40 @@ mod tests {
scanner.executor.clone(),
);
smol::block_on(new_scanner.scan_dirs()).unwrap();
assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
assert_eq!(
scanner.snapshot().to_vec(true),
new_scanner.snapshot().to_vec(true)
);
for mut prev_snapshot in snapshots {
let include_ignored = rng.gen::<bool>();
if !include_ignored {
let mut entries_by_path_edits = Vec::new();
let mut entries_by_id_edits = Vec::new();
for entry in prev_snapshot
.entries_by_id
.cursor::<(), ()>()
.filter(|e| e.is_ignored)
{
entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
entries_by_id_edits.push(Edit::Remove(entry.id));
}
prev_snapshot
.entries_by_path
.edit(entries_by_path_edits, &());
prev_snapshot.entries_by_id.edit(entries_by_id_edits, &());
}
let update = scanner
.snapshot()
.build_update(&prev_snapshot, 0, include_ignored);
prev_snapshot.apply_update(update).unwrap();
assert_eq!(
prev_snapshot.to_vec(true),
scanner.snapshot().to_vec(include_ignored)
);
}
}
fn randomly_mutate_tree(
@@ -3390,10 +3455,12 @@ mod tests {
}
}
fn to_vec(&self) -> Vec<(&Path, u64, bool)> {
fn to_vec(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
let mut paths = Vec::new();
for entry in self.entries_by_path.cursor::<(), ()>() {
paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
if include_ignored || !entry.is_ignored {
paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
}
}
paths.sort_by(|a, b| a.0.cmp(&b.0));
paths

View File

@@ -20,6 +20,7 @@ prost = "0.7"
rand = "0.8"
rsa = "0.4"
serde = { version = "1", features = ["derive"] }
zstd = "0.9"
[build-dependencies]
prost-build = { git = "https://github.com/tokio-rs/prost", rev = "6cf97ea422b09d98de34643c4dda2d4f8b7e23e6" }

View File

@@ -4,3 +4,5 @@ mod peer;
pub mod proto;
pub use conn::Connection;
pub use peer::*;
pub const PROTOCOL_VERSION: u32 = 1;

View File

@@ -87,7 +87,7 @@ pub struct Peer {
struct ConnectionState {
outgoing_tx: mpsc::Sender<proto::Envelope>,
next_message_id: Arc<AtomicU32>,
response_channels: Arc<Mutex<HashMap<u32, mpsc::Sender<proto::Envelope>>>>,
response_channels: Arc<Mutex<Option<HashMap<u32, mpsc::Sender<proto::Envelope>>>>>,
}
impl Peer {
@@ -115,7 +115,7 @@ impl Peer {
let connection_state = ConnectionState {
outgoing_tx,
next_message_id: Default::default(),
response_channels: Default::default(),
response_channels: Arc::new(Mutex::new(Some(Default::default()))),
};
let mut writer = MessageStream::new(connection.tx);
let mut reader = MessageStream::new(connection.rx);
@@ -123,7 +123,7 @@ impl Peer {
let this = self.clone();
let response_channels = connection_state.response_channels.clone();
let handle_io = async move {
loop {
let result = 'outer: loop {
let read_message = reader.read_message().fuse();
futures::pin_mut!(read_message);
loop {
@@ -131,7 +131,7 @@ impl Peer {
incoming = read_message => match incoming {
Ok(incoming) => {
if let Some(responding_to) = incoming.responding_to {
let channel = response_channels.lock().await.remove(&responding_to);
let channel = response_channels.lock().await.as_mut().unwrap().remove(&responding_to);
if let Some(mut tx) = channel {
tx.send(incoming).await.ok();
} else {
@@ -140,9 +140,7 @@ impl Peer {
} else {
if let Some(envelope) = proto::build_typed_envelope(connection_id, incoming) {
if incoming_tx.send(envelope).await.is_err() {
response_channels.lock().await.clear();
this.connections.write().await.remove(&connection_id);
return Ok(())
break 'outer Ok(())
}
} else {
log::error!("unable to construct a typed envelope");
@@ -152,28 +150,24 @@ impl Peer {
break;
}
Err(error) => {
response_channels.lock().await.clear();
this.connections.write().await.remove(&connection_id);
Err(error).context("received invalid RPC message")?;
break 'outer Err(error).context("received invalid RPC message")
}
},
outgoing = outgoing_rx.recv().fuse() => match outgoing {
Some(outgoing) => {
if let Err(result) = writer.write_message(&outgoing).await {
response_channels.lock().await.clear();
this.connections.write().await.remove(&connection_id);
Err(result).context("failed to write RPC message")?;
break 'outer Err(result).context("failed to write RPC message")
}
}
None => {
response_channels.lock().await.clear();
this.connections.write().await.remove(&connection_id);
return Ok(())
}
None => break 'outer Ok(()),
}
}
}
}
};
response_channels.lock().await.take();
this.connections.write().await.remove(&connection_id);
result
};
self.connections
@@ -226,6 +220,8 @@ impl Peer {
.response_channels
.lock()
.await
.as_mut()
.ok_or_else(|| anyhow!("connection was closed"))?
.insert(message_id, tx);
connection
.outgoing_tx
@@ -520,8 +516,7 @@ mod tests {
#[test]
fn test_io_error() {
smol::block_on(async move {
let (client_conn, server_conn, _) = Connection::in_memory();
drop(server_conn);
let (client_conn, mut server_conn, _) = Connection::in_memory();
let client = Peer::new();
let (connection_id, io_handler, mut incoming) =
@@ -529,11 +524,14 @@ mod tests {
smol::spawn(io_handler).detach();
smol::spawn(async move { incoming.next().await }).detach();
let err = client
.request(connection_id, proto::Ping {})
.await
.unwrap_err();
assert_eq!(err.to_string(), "connection was closed");
let response = smol::spawn(client.request(connection_id, proto::Ping {}));
let _request = server_conn.rx.next().await.unwrap().unwrap();
drop(server_conn);
assert_eq!(
response.await.unwrap_err().to_string(),
"connection was closed"
);
});
}
}

View File

@@ -192,11 +192,15 @@ entity_messages!(channel_id, ChannelMessageSent);
/// A stream of protobuf messages.
pub struct MessageStream<S> {
stream: S,
encoding_buffer: Vec<u8>,
}
impl<S> MessageStream<S> {
pub fn new(stream: S) -> Self {
Self { stream }
Self {
stream,
encoding_buffer: Vec::new(),
}
}
pub fn inner_mut(&mut self) -> &mut S {
@@ -210,10 +214,12 @@ where
{
/// Write a given protobuf message to the stream.
pub async fn write_message(&mut self, message: &Envelope) -> Result<(), WebSocketError> {
let mut buffer = Vec::with_capacity(message.encoded_len());
self.encoding_buffer.resize(message.encoded_len(), 0);
self.encoding_buffer.clear();
message
.encode(&mut buffer)
.encode(&mut self.encoding_buffer)
.map_err(|err| io::Error::from(err))?;
let buffer = zstd::stream::encode_all(self.encoding_buffer.as_slice(), 4).unwrap();
self.stream.send(WebSocketMessage::Binary(buffer)).await?;
Ok(())
}
@@ -228,7 +234,10 @@ where
while let Some(bytes) = self.stream.next().await {
match bytes? {
WebSocketMessage::Binary(bytes) => {
let envelope = Envelope::decode(bytes.as_slice()).map_err(io::Error::from)?;
self.encoding_buffer.clear();
zstd::stream::copy_decode(bytes.as_slice(), &mut self.encoding_buffer).unwrap();
let envelope = Envelope::decode(self.encoding_buffer.as_slice())
.map_err(io::Error::from)?;
return Ok(envelope);
}
WebSocketMessage::Close(_) => break,