Merge branch 'main' into fix-dock-focus-issues
This commit is contained in:
commit
27a87c3d9e
77 changed files with 1093 additions and 461 deletions
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -1068,6 +1068,7 @@ dependencies = [
|
||||||
"editor",
|
"editor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"envy",
|
"envy",
|
||||||
|
"fs",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"git",
|
"git",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
@ -1083,6 +1084,7 @@ dependencies = [
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rope",
|
||||||
"rpc",
|
"rpc",
|
||||||
"scrypt",
|
"scrypt",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1574,6 +1576,7 @@ dependencies = [
|
||||||
"language",
|
"language",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
|
"rope",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
@ -1727,6 +1730,7 @@ dependencies = [
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"rope",
|
||||||
"rpc",
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
|
@ -2007,6 +2011,31 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"collections",
|
||||||
|
"fsevent",
|
||||||
|
"futures 0.3.24",
|
||||||
|
"git2",
|
||||||
|
"gpui",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"lsp",
|
||||||
|
"parking_lot 0.11.2",
|
||||||
|
"regex",
|
||||||
|
"rope",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"smol",
|
||||||
|
"tempfile",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs-set-times"
|
name = "fs-set-times"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
@ -2065,6 +2094,13 @@ version = "0.1.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
|
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.24"
|
version = "0.3.24"
|
||||||
|
@ -2265,6 +2301,7 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
|
"rope",
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
"text",
|
"text",
|
||||||
|
@ -2312,6 +2349,7 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
"menu",
|
"menu",
|
||||||
"postage",
|
"postage",
|
||||||
|
"rope",
|
||||||
"settings",
|
"settings",
|
||||||
"text",
|
"text",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
@ -2900,6 +2938,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"fs",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"git",
|
"git",
|
||||||
|
@ -2911,6 +2950,7 @@ dependencies = [
|
||||||
"postage",
|
"postage",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rope",
|
||||||
"rpc",
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -4073,6 +4113,7 @@ dependencies = [
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"db",
|
"db",
|
||||||
|
"fs",
|
||||||
"fsevent",
|
"fsevent",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
|
@ -4081,7 +4122,6 @@ dependencies = [
|
||||||
"ignore",
|
"ignore",
|
||||||
"language",
|
"language",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
|
@ -4090,6 +4130,7 @@ dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"rocksdb",
|
"rocksdb",
|
||||||
|
"rope",
|
||||||
"rpc",
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -4597,6 +4638,20 @@ dependencies = [
|
||||||
"librocksdb-sys",
|
"librocksdb-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rope"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec 0.7.2",
|
||||||
|
"bromberg_sl2",
|
||||||
|
"gpui",
|
||||||
|
"log",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"smallvec",
|
||||||
|
"sum_tree",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
|
@ -5095,14 +5150,21 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assets",
|
"assets",
|
||||||
"collections",
|
"collections",
|
||||||
|
"fs",
|
||||||
|
"futures 0.3.24",
|
||||||
"gpui",
|
"gpui",
|
||||||
"json_comments",
|
"json_comments",
|
||||||
|
"postage",
|
||||||
|
"rope",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"theme",
|
"theme",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tree-sitter",
|
||||||
|
"tree-sitter-json 0.19.0",
|
||||||
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -5651,13 +5713,12 @@ name = "text"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arrayvec 0.7.2",
|
|
||||||
"bromberg_sl2",
|
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"fs",
|
||||||
"gpui",
|
"gpui",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
@ -5665,6 +5726,7 @@ dependencies = [
|
||||||
"postage",
|
"postage",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rope",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
"util",
|
"util",
|
||||||
|
@ -6584,6 +6646,7 @@ dependencies = [
|
||||||
"nvim-rs",
|
"nvim-rs",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"project",
|
"project",
|
||||||
|
"rope",
|
||||||
"search",
|
"search",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -7274,6 +7337,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"context_menu",
|
"context_menu",
|
||||||
"drag_and_drop",
|
"drag_and_drop",
|
||||||
|
"fs",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
|
@ -7329,7 +7393,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.59.0"
|
version = "0.60.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -7357,6 +7421,7 @@ dependencies = [
|
||||||
"editor",
|
"editor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"file_finder",
|
"file_finder",
|
||||||
|
"fs",
|
||||||
"fsevent",
|
"fsevent",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
|
|
|
@ -3,6 +3,11 @@ members = ["crates/*"]
|
||||||
default-members = ["crates/zed"]
|
default-members = ["crates/zed"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
|
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||||
|
rand = { version = "0.8" }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "366210ae925d7ea0891bc7a0c738f60c77c04d7b" }
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "366210ae925d7ea0891bc7a0c738f60c77c04d7b" }
|
||||||
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
|
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
|
||||||
|
@ -21,3 +26,4 @@ split-debuginfo = "unpacked"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax = docker/dockerfile:1.2
|
# syntax = docker/dockerfile:1.2
|
||||||
|
|
||||||
FROM rust:1.62-bullseye as builder
|
FROM rust:1.64-bullseye as builder
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax = docker/dockerfile:1.2
|
# syntax = docker/dockerfile:1.2
|
||||||
|
|
||||||
FROM rust:1.62-bullseye as builder
|
FROM rust:1.64-bullseye as builder
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=./target \
|
--mount=type=cache,target=./target \
|
||||||
|
|
|
@ -376,6 +376,7 @@
|
||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||||
|
"cmd-shift-c": "collab::ToggleCollaborationMenu",
|
||||||
"cmd-alt-i": "zed::DebugElements"
|
"cmd-alt-i": "zed::DebugElements"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,6 +53,8 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
||||||
|
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
||||||
|
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
actions!(client, [Authenticate]);
|
actions!(client, [Authenticate]);
|
||||||
|
|
||||||
|
@ -330,7 +332,7 @@ impl Client {
|
||||||
let reconnect_interval = state.reconnect_interval;
|
let reconnect_interval = state.reconnect_interval;
|
||||||
state._reconnect_task = Some(cx.spawn(|cx| async move {
|
state._reconnect_task = Some(cx.spawn(|cx| async move {
|
||||||
let mut rng = StdRng::from_entropy();
|
let mut rng = StdRng::from_entropy();
|
||||||
let mut delay = Duration::from_millis(100);
|
let mut delay = INITIAL_RECONNECTION_DELAY;
|
||||||
while let Err(error) = this.authenticate_and_connect(true, &cx).await {
|
while let Err(error) = this.authenticate_and_connect(true, &cx).await {
|
||||||
log::error!("failed to connect {}", error);
|
log::error!("failed to connect {}", error);
|
||||||
if matches!(*this.status().borrow(), Status::ConnectionError) {
|
if matches!(*this.status().borrow(), Status::ConnectionError) {
|
||||||
|
@ -661,44 +663,51 @@ impl Client {
|
||||||
self.set_status(Status::Reconnecting, cx);
|
self.set_status(Status::Reconnecting, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.establish_connection(&credentials, cx).await {
|
futures::select_biased! {
|
||||||
Ok(conn) => {
|
connection = self.establish_connection(&credentials, cx).fuse() => {
|
||||||
self.state.write().credentials = Some(credentials.clone());
|
match connection {
|
||||||
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
Ok(conn) => {
|
||||||
write_credentials_to_keychain(&credentials, cx).log_err();
|
self.state.write().credentials = Some(credentials.clone());
|
||||||
}
|
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
||||||
self.set_connection(conn, cx).await;
|
write_credentials_to_keychain(&credentials, cx).log_err();
|
||||||
Ok(())
|
}
|
||||||
}
|
self.set_connection(conn, cx);
|
||||||
Err(EstablishConnectionError::Unauthorized) => {
|
Ok(())
|
||||||
self.state.write().credentials.take();
|
}
|
||||||
if read_from_keychain {
|
Err(EstablishConnectionError::Unauthorized) => {
|
||||||
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
|
self.state.write().credentials.take();
|
||||||
self.set_status(Status::SignedOut, cx);
|
if read_from_keychain {
|
||||||
self.authenticate_and_connect(false, cx).await
|
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
|
||||||
} else {
|
self.set_status(Status::SignedOut, cx);
|
||||||
self.set_status(Status::ConnectionError, cx);
|
self.authenticate_and_connect(false, cx).await
|
||||||
Err(EstablishConnectionError::Unauthorized)?
|
} else {
|
||||||
|
self.set_status(Status::ConnectionError, cx);
|
||||||
|
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)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(EstablishConnectionError::UpgradeRequired) => {
|
_ = cx.background().timer(CONNECTION_TIMEOUT).fuse() => {
|
||||||
self.set_status(Status::UpgradeRequired, cx);
|
|
||||||
Err(EstablishConnectionError::UpgradeRequired)?
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
self.set_status(Status::ConnectionError, cx);
|
self.set_status(Status::ConnectionError, cx);
|
||||||
Err(error)?
|
Err(anyhow!("timed out trying to establish connection"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_connection(self: &Arc<Self>, conn: Connection, cx: &AsyncAppContext) {
|
fn set_connection(self: &Arc<Self>, conn: Connection, cx: &AsyncAppContext) {
|
||||||
let executor = cx.background();
|
let executor = cx.background();
|
||||||
log::info!("add connection to peer");
|
log::info!("add connection to peer");
|
||||||
let (connection_id, handle_io, mut incoming) = self
|
let (connection_id, handle_io, mut incoming) = self
|
||||||
.peer
|
.peer
|
||||||
.add_connection(conn, move |duration| executor.timer(duration))
|
.add_connection(conn, move |duration| executor.timer(duration));
|
||||||
.await;
|
|
||||||
log::info!("set status to connected {}", connection_id);
|
log::info!("set status to connected {}", connection_id);
|
||||||
self.set_status(Status::Connected { connection_id }, cx);
|
self.set_status(Status::Connected { connection_id }, cx);
|
||||||
cx.foreground()
|
cx.foreground()
|
||||||
|
@ -1169,6 +1178,76 @@ mod tests {
|
||||||
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
|
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test(iterations = 10)]
|
||||||
|
async fn test_connection_timeout(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
|
||||||
|
let user_id = 5;
|
||||||
|
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||||
|
let mut status = client.status();
|
||||||
|
|
||||||
|
// Time out when client tries to connect.
|
||||||
|
client.override_authenticate(move |cx| {
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
Ok(Credentials {
|
||||||
|
user_id,
|
||||||
|
access_token: "token".into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
client.override_establish_connection(|_, cx| {
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
future::pending::<()>().await;
|
||||||
|
unreachable!()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let auth_and_connect = cx.spawn({
|
||||||
|
let client = client.clone();
|
||||||
|
|cx| async move { client.authenticate_and_connect(false, &cx).await }
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
assert!(matches!(status.next().await, Some(Status::Connecting)));
|
||||||
|
|
||||||
|
deterministic.advance_clock(CONNECTION_TIMEOUT);
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::ConnectionError { .. })
|
||||||
|
));
|
||||||
|
auth_and_connect.await.unwrap_err();
|
||||||
|
|
||||||
|
// Allow the connection to be established.
|
||||||
|
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::Connected { .. })
|
||||||
|
));
|
||||||
|
|
||||||
|
// Disconnect client.
|
||||||
|
server.forbid_connections();
|
||||||
|
server.disconnect();
|
||||||
|
while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
|
||||||
|
|
||||||
|
// Time out when re-establishing the connection.
|
||||||
|
server.allow_connections();
|
||||||
|
client.override_establish_connection(|_, cx| {
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
future::pending::<()>().await;
|
||||||
|
unreachable!()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
deterministic.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::Reconnecting { .. })
|
||||||
|
));
|
||||||
|
|
||||||
|
deterministic.advance_clock(CONNECTION_TIMEOUT);
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::ReconnectionError { .. })
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
async fn test_authenticating_more_than_once(
|
async fn test_authenticating_more_than_once(
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
|
|
|
@ -82,7 +82,7 @@ impl FakeServer {
|
||||||
|
|
||||||
let (client_conn, server_conn, _) = Connection::in_memory(cx.background());
|
let (client_conn, server_conn, _) = Connection::in_memory(cx.background());
|
||||||
let (connection_id, io, incoming) =
|
let (connection_id, io, incoming) =
|
||||||
peer.add_test_connection(server_conn, cx.background()).await;
|
peer.add_test_connection(server_conn, cx.background());
|
||||||
cx.background().spawn(io).detach();
|
cx.background().spawn(io).detach();
|
||||||
let mut state = state.lock();
|
let mut state = state.lock();
|
||||||
state.connection_id = Some(connection_id);
|
state.connection_id = Some(connection_id);
|
||||||
|
@ -101,10 +101,12 @@ impl FakeServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disconnect(&self) {
|
pub fn disconnect(&self) {
|
||||||
self.peer.disconnect(self.connection_id());
|
if self.state.lock().connection_id.is_some() {
|
||||||
let mut state = self.state.lock();
|
self.peer.disconnect(self.connection_id());
|
||||||
state.connection_id.take();
|
let mut state = self.state.lock();
|
||||||
state.incoming.take();
|
state.connection_id.take();
|
||||||
|
state.incoming.take();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_count(&self) -> usize {
|
pub fn auth_count(&self) -> usize {
|
||||||
|
|
|
@ -16,7 +16,8 @@ required-features = ["seed-support"]
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
async-trait = "0.1.50"
|
async-trait = "0.1.50"
|
||||||
async-tungstenite = "0.16"
|
async-tungstenite = "0.16"
|
||||||
|
|
|
@ -15,6 +15,7 @@ use editor::{
|
||||||
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset,
|
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset,
|
||||||
ToggleCodeActions, Undo,
|
ToggleCodeActions, Undo,
|
||||||
};
|
};
|
||||||
|
use fs::{FakeFs, Fs as _, LineEnding};
|
||||||
use futures::{channel::mpsc, Future, StreamExt as _};
|
use futures::{channel::mpsc, Future, StreamExt as _};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
executor::{self, Deterministic},
|
executor::{self, Deterministic},
|
||||||
|
@ -24,17 +25,16 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||||
LanguageConfig, LanguageRegistry, LineEnding, OffsetRangeExt, Point, Rope,
|
LanguageConfig, LanguageRegistry, OffsetRangeExt, Rope,
|
||||||
};
|
};
|
||||||
use lsp::{self, FakeLanguageServer};
|
use lsp::{self, FakeLanguageServer};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{
|
use project::{
|
||||||
fs::{FakeFs, Fs as _},
|
search::SearchQuery, worktree::WorktreeHandle, DiagnosticSummary, Project, ProjectPath,
|
||||||
search::SearchQuery,
|
ProjectStore, WorktreeId,
|
||||||
worktree::WorktreeHandle,
|
|
||||||
DiagnosticSummary, Project, ProjectPath, ProjectStore, WorktreeId,
|
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use rope::point::Point;
|
||||||
use rpc::PeerId;
|
use rpc::PeerId;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{Formatter, Settings};
|
use settings::{Formatter, Settings};
|
||||||
|
@ -2017,7 +2017,7 @@ async fn test_leaving_project(
|
||||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
let project_b1 = client_b.build_remote_project(project_id, cx_b).await;
|
||||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||||
|
|
||||||
// Client A sees that a guest has joined.
|
// Client A sees that a guest has joined.
|
||||||
|
@ -2025,20 +2025,62 @@ async fn test_leaving_project(
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
assert_eq!(project.collaborators().len(), 2);
|
assert_eq!(project.collaborators().len(), 2);
|
||||||
});
|
});
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b1.read_with(cx_b, |project, _| {
|
||||||
assert_eq!(project.collaborators().len(), 2);
|
assert_eq!(project.collaborators().len(), 2);
|
||||||
});
|
});
|
||||||
project_c.read_with(cx_c, |project, _| {
|
project_c.read_with(cx_c, |project, _| {
|
||||||
assert_eq!(project.collaborators().len(), 2);
|
assert_eq!(project.collaborators().len(), 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Drop client B's connection and ensure client A and client C observe client B leaving the project.
|
// Client B opens a buffer.
|
||||||
|
let buffer_b1 = project_b1
|
||||||
|
.update(cx_b, |project, cx| {
|
||||||
|
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
|
project.open_buffer((worktree_id, "a.txt"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
|
||||||
|
|
||||||
|
// Drop client B's project and ensure client A and client C observe client B leaving.
|
||||||
|
cx_b.update(|_| drop(project_b1));
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
project_a.read_with(cx_a, |project, _| {
|
||||||
|
assert_eq!(project.collaborators().len(), 1);
|
||||||
|
});
|
||||||
|
project_c.read_with(cx_c, |project, _| {
|
||||||
|
assert_eq!(project.collaborators().len(), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client B re-joins the project and can open buffers as before.
|
||||||
|
let project_b2 = client_b.build_remote_project(project_id, cx_b).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
project_a.read_with(cx_a, |project, _| {
|
||||||
|
assert_eq!(project.collaborators().len(), 2);
|
||||||
|
});
|
||||||
|
project_b2.read_with(cx_b, |project, _| {
|
||||||
|
assert_eq!(project.collaborators().len(), 2);
|
||||||
|
});
|
||||||
|
project_c.read_with(cx_c, |project, _| {
|
||||||
|
assert_eq!(project.collaborators().len(), 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
let buffer_b2 = project_b2
|
||||||
|
.update(cx_b, |project, cx| {
|
||||||
|
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
|
||||||
|
project.open_buffer((worktree_id, "a.txt"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
|
||||||
|
|
||||||
|
// Drop client B's connection and ensure client A and client C observe client B leaving.
|
||||||
client_b.disconnect(&cx_b.to_async()).unwrap();
|
client_b.disconnect(&cx_b.to_async()).unwrap();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
assert_eq!(project.collaborators().len(), 1);
|
assert_eq!(project.collaborators().len(), 1);
|
||||||
});
|
});
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b2.read_with(cx_b, |project, _| {
|
||||||
assert!(project.is_read_only());
|
assert!(project.is_read_only());
|
||||||
});
|
});
|
||||||
project_c.read_with(cx_c, |project, _| {
|
project_c.read_with(cx_c, |project, _| {
|
||||||
|
@ -2068,7 +2110,7 @@ async fn test_leaving_project(
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
assert_eq!(project.collaborators().len(), 0);
|
assert_eq!(project.collaborators().len(), 0);
|
||||||
});
|
});
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b2.read_with(cx_b, |project, _| {
|
||||||
assert!(project.is_read_only());
|
assert!(project.is_read_only());
|
||||||
});
|
});
|
||||||
project_c.read_with(cx_c, |project, _| {
|
project_c.read_with(cx_c, |project, _| {
|
||||||
|
@ -2117,15 +2159,10 @@ async fn test_collaborating_with_diagnostics(
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||||
let project_id = active_call_a
|
|
||||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Cause the language server to start.
|
// Cause the language server to start.
|
||||||
let _buffer = cx_a
|
let _buffer = project_a
|
||||||
.background()
|
.update(cx_a, |project, cx| {
|
||||||
.spawn(project_a.update(cx_a, |project, cx| {
|
|
||||||
project.open_buffer(
|
project.open_buffer(
|
||||||
ProjectPath {
|
ProjectPath {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
|
@ -2133,18 +2170,35 @@ async fn test_collaborating_with_diagnostics(
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}))
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Join the worktree as client B.
|
|
||||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
|
||||||
|
|
||||||
// Simulate a language server reporting errors for a file.
|
// Simulate a language server reporting errors for a file.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
.await;
|
.await;
|
||||||
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
|
lsp::PublishDiagnosticsParams {
|
||||||
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
|
version: None,
|
||||||
|
diagnostics: vec![lsp::Diagnostic {
|
||||||
|
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
|
||||||
|
message: "message 0".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Client A shares the project and, simultaneously, the language server
|
||||||
|
// publishes a diagnostic. This is done to ensure that the server always
|
||||||
|
// observes the latest diagnostics for a worktree.
|
||||||
|
let project_id = active_call_a
|
||||||
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
|
@ -2158,6 +2212,9 @@ async fn test_collaborating_with_diagnostics(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Join the worktree as client B.
|
||||||
|
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
// Wait for server to see the diagnostics update.
|
// Wait for server to see the diagnostics update.
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
{
|
{
|
||||||
|
@ -2187,24 +2244,35 @@ async fn test_collaborating_with_diagnostics(
|
||||||
|
|
||||||
// Join project as client C and observe the diagnostics.
|
// Join project as client C and observe the diagnostics.
|
||||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||||
deterministic.run_until_parked();
|
let project_c_diagnostic_summaries = Rc::new(RefCell::new(Vec::new()));
|
||||||
project_c.read_with(cx_c, |project, cx| {
|
project_c.update(cx_c, |_, cx| {
|
||||||
assert_eq!(
|
let summaries = project_c_diagnostic_summaries.clone();
|
||||||
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
|
cx.subscribe(&project_c, {
|
||||||
&[(
|
move |p, _, event, cx| {
|
||||||
ProjectPath {
|
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
|
||||||
worktree_id,
|
*summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
|
||||||
path: Arc::from(Path::new("a.rs")),
|
}
|
||||||
},
|
}
|
||||||
DiagnosticSummary {
|
})
|
||||||
error_count: 1,
|
.detach();
|
||||||
warning_count: 0,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
project_c_diagnostic_summaries.borrow().as_slice(),
|
||||||
|
&[(
|
||||||
|
ProjectPath {
|
||||||
|
worktree_id,
|
||||||
|
path: Arc::from(Path::new("a.rs")),
|
||||||
|
},
|
||||||
|
DiagnosticSummary {
|
||||||
|
error_count: 1,
|
||||||
|
warning_count: 0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
// Simulate a language server reporting more errors for a file.
|
// Simulate a language server reporting more errors for a file.
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
|
@ -2279,7 +2347,7 @@ async fn test_collaborating_with_diagnostics(
|
||||||
DiagnosticEntry {
|
DiagnosticEntry {
|
||||||
range: Point::new(0, 4)..Point::new(0, 7),
|
range: Point::new(0, 4)..Point::new(0, 7),
|
||||||
diagnostic: Diagnostic {
|
diagnostic: Diagnostic {
|
||||||
group_id: 1,
|
group_id: 2,
|
||||||
message: "message 1".to_string(),
|
message: "message 1".to_string(),
|
||||||
severity: lsp::DiagnosticSeverity::ERROR,
|
severity: lsp::DiagnosticSeverity::ERROR,
|
||||||
is_primary: true,
|
is_primary: true,
|
||||||
|
@ -2289,7 +2357,7 @@ async fn test_collaborating_with_diagnostics(
|
||||||
DiagnosticEntry {
|
DiagnosticEntry {
|
||||||
range: Point::new(0, 10)..Point::new(0, 13),
|
range: Point::new(0, 10)..Point::new(0, 13),
|
||||||
diagnostic: Diagnostic {
|
diagnostic: Diagnostic {
|
||||||
group_id: 2,
|
group_id: 3,
|
||||||
severity: lsp::DiagnosticSeverity::WARNING,
|
severity: lsp::DiagnosticSeverity::WARNING,
|
||||||
message: "message 2".to_string(),
|
message: "message 2".to_string(),
|
||||||
is_primary: true,
|
is_primary: true,
|
||||||
|
|
|
@ -365,8 +365,7 @@ impl Server {
|
||||||
timer.await;
|
timer.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.await;
|
|
||||||
|
|
||||||
tracing::info!(%user_id, %login, %connection_id, %address, "connection opened");
|
tracing::info!(%user_id, %login, %connection_id, %address, "connection opened");
|
||||||
|
|
||||||
|
@ -1013,6 +1012,21 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for language_server in &project.language_servers {
|
||||||
|
self.peer.send(
|
||||||
|
request.sender_id,
|
||||||
|
proto::UpdateLanguageServer {
|
||||||
|
project_id: project_id.to_proto(),
|
||||||
|
language_server_id: language_server.id,
|
||||||
|
variant: Some(
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1205,7 +1205,7 @@ impl Store {
|
||||||
let guest_connection = self.connections.get(guest_connection_id).unwrap();
|
let guest_connection = self.connections.get(guest_connection_id).unwrap();
|
||||||
assert!(guest_connection.projects.contains(project_id));
|
assert!(guest_connection.projects.contains(project_id));
|
||||||
}
|
}
|
||||||
assert_eq!(project.active_replica_ids.len(), project.guests.len(),);
|
assert_eq!(project.active_replica_ids.len(), project.guests.len());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
project.active_replica_ids,
|
project.active_replica_ids,
|
||||||
project
|
project
|
||||||
|
|
|
@ -17,10 +17,7 @@ use std::ops::Range;
|
||||||
use theme::Theme;
|
use theme::Theme;
|
||||||
use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace};
|
use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace};
|
||||||
|
|
||||||
actions!(
|
actions!(collab, [ToggleCollaborationMenu, ShareProject]);
|
||||||
contacts_titlebar_item,
|
|
||||||
[ToggleContactsPopover, ShareProject]
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
|
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
|
||||||
|
@ -143,7 +140,11 @@ impl CollabTitlebarItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_contacts_popover(&mut self, _: &ToggleContactsPopover, cx: &mut ViewContext<Self>) {
|
pub fn toggle_contacts_popover(
|
||||||
|
&mut self,
|
||||||
|
_: &ToggleCollaborationMenu,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
match self.contacts_popover.take() {
|
match self.contacts_popover.take() {
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
None => {
|
None => {
|
||||||
|
@ -197,7 +198,7 @@ impl CollabTitlebarItem {
|
||||||
};
|
};
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(
|
.with_child(
|
||||||
MouseEventHandler::<ToggleContactsPopover>::new(0, cx, |state, _| {
|
MouseEventHandler::<ToggleCollaborationMenu>::new(0, cx, |state, _| {
|
||||||
let style = titlebar
|
let style = titlebar
|
||||||
.toggle_contacts_button
|
.toggle_contacts_button
|
||||||
.style_for(state, self.contacts_popover.is_some());
|
.style_for(state, self.contacts_popover.is_some());
|
||||||
|
@ -214,8 +215,8 @@ impl CollabTitlebarItem {
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, |_, cx| {
|
.on_click(MouseButton::Left, move |_, cx| {
|
||||||
cx.dispatch_action(ToggleContactsPopover);
|
cx.dispatch_action(ToggleCollaborationMenu);
|
||||||
})
|
})
|
||||||
.aligned()
|
.aligned()
|
||||||
.boxed(),
|
.boxed(),
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod notifications;
|
||||||
mod project_shared_notification;
|
mod project_shared_notification;
|
||||||
|
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
pub use collab_titlebar_item::{CollabTitlebarItem, ToggleCollaborationMenu};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
@ -15,6 +15,7 @@ editor = { path = "../editor" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
|
@ -14,10 +14,10 @@ use gpui::{
|
||||||
ViewHandle, WeakViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
|
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Selection, SelectionGoal,
|
||||||
SelectionGoal,
|
|
||||||
};
|
};
|
||||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||||
|
use rope::point::Point;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -738,7 +738,8 @@ mod tests {
|
||||||
DisplayPoint,
|
DisplayPoint,
|
||||||
};
|
};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity};
|
||||||
|
use rope::point_utf16::PointUtf16;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
|
@ -30,6 +30,7 @@ gpui = { path = "../gpui" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
snippet = { path = "../snippet" }
|
snippet = { path = "../snippet" }
|
||||||
|
@ -48,7 +49,7 @@ ordered-float = "2.1.1"
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
postage = { version = "0.4", features = ["futures-traits"] }
|
postage = { version = "0.4", features = ["futures-traits"] }
|
||||||
rand = { version = "0.8.3", optional = true }
|
rand = { version = "0.8.3", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { workspace = true }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
tree-sitter-rust = { version = "*", optional = true }
|
tree-sitter-rust = { version = "*", optional = true }
|
||||||
|
|
|
@ -11,7 +11,8 @@ use gpui::{
|
||||||
fonts::{FontId, HighlightStyle},
|
fonts::{FontId, HighlightStyle},
|
||||||
Entity, ModelContext, ModelHandle,
|
Entity, ModelContext, ModelHandle,
|
||||||
};
|
};
|
||||||
use language::{OffsetUtf16, Point, Subscription as BufferSubscription};
|
use language::Subscription as BufferSubscription;
|
||||||
|
use rope::{offset_utf16::OffsetUtf16, point::Point};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||||
use sum_tree::{Bias, TreeMap};
|
use sum_tree::{Bias, TreeMap};
|
||||||
|
@ -622,7 +623,7 @@ pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{movement, test::marked_display_snapshot};
|
use crate::{movement, test::marked_display_snapshot};
|
||||||
use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
|
use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
|
||||||
use language::{Buffer, Language, LanguageConfig, RandomCharIter, SelectionGoal};
|
use language::{Buffer, Language, LanguageConfig, SelectionGoal};
|
||||||
use rand::{prelude::*, Rng};
|
use rand::{prelude::*, Rng};
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
|
@ -666,7 +667,9 @@ pub mod tests {
|
||||||
let buffer = cx.update(|cx| {
|
let buffer = cx.update(|cx| {
|
||||||
if rng.gen() {
|
if rng.gen() {
|
||||||
let len = rng.gen_range(0..10);
|
let len = rng.gen_range(0..10);
|
||||||
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
let text = util::RandomCharIter::new(&mut rng)
|
||||||
|
.take(len)
|
||||||
|
.collect::<String>();
|
||||||
MultiBuffer::build_simple(&text, cx)
|
MultiBuffer::build_simple(&text, cx)
|
||||||
} else {
|
} else {
|
||||||
MultiBuffer::build_random(&mut rng, cx)
|
MultiBuffer::build_random(&mut rng, cx)
|
||||||
|
|
|
@ -7,6 +7,7 @@ use collections::{Bound, HashMap, HashSet};
|
||||||
use gpui::{ElementBox, RenderContext};
|
use gpui::{ElementBox, RenderContext};
|
||||||
use language::{BufferSnapshot, Chunk, Patch};
|
use language::{BufferSnapshot, Chunk, Patch};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rope::point::Point;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
@ -18,7 +19,7 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use sum_tree::{Bias, SumTree};
|
use sum_tree::{Bias, SumTree};
|
||||||
use text::{Edit, Point};
|
use text::Edit;
|
||||||
|
|
||||||
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
|
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ pub struct BlockSnapshot {
|
||||||
pub struct BlockId(usize);
|
pub struct BlockId(usize);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
pub struct BlockPoint(pub super::Point);
|
pub struct BlockPoint(pub Point);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
struct BlockRow(u32);
|
struct BlockRow(u32);
|
||||||
|
@ -994,7 +995,7 @@ mod tests {
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::env;
|
use std::env;
|
||||||
use text::RandomCharIter;
|
use util::RandomCharIter;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_offset_for_row() {
|
fn test_offset_for_row() {
|
||||||
|
|
|
@ -5,8 +5,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
use gpui::fonts::HighlightStyle;
|
use gpui::fonts::HighlightStyle;
|
||||||
use language::{Chunk, Edit, Point, TextSummary};
|
use language::{Chunk, Edit, TextSummary};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rope::point::Point;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
@ -18,11 +19,11 @@ use std::{
|
||||||
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
pub struct FoldPoint(pub super::Point);
|
pub struct FoldPoint(pub Point);
|
||||||
|
|
||||||
impl FoldPoint {
|
impl FoldPoint {
|
||||||
pub fn new(row: u32, column: u32) -> Self {
|
pub fn new(row: u32, column: u32) -> Self {
|
||||||
Self(super::Point::new(row, column))
|
Self(Point::new(row, column))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn row(self) -> u32 {
|
pub fn row(self) -> u32 {
|
||||||
|
@ -1196,8 +1197,8 @@ mod tests {
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{cmp::Reverse, env, mem, sync::Arc};
|
use std::{cmp::Reverse, env, mem, sync::Arc};
|
||||||
use sum_tree::TreeMap;
|
use sum_tree::TreeMap;
|
||||||
use text::RandomCharIter;
|
|
||||||
use util::test::sample_text;
|
use util::test::sample_text;
|
||||||
|
use util::RandomCharIter;
|
||||||
use Bias::{Left, Right};
|
use Bias::{Left, Right};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
|
@ -3,11 +3,12 @@ use super::{
|
||||||
TextHighlights,
|
TextHighlights,
|
||||||
};
|
};
|
||||||
use crate::MultiBufferSnapshot;
|
use crate::MultiBufferSnapshot;
|
||||||
use language::{rope, Chunk};
|
use language::Chunk;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rope;
|
||||||
|
use rope::point::Point;
|
||||||
use std::{cmp, mem, num::NonZeroU32, ops::Range};
|
use std::{cmp, mem, num::NonZeroU32, ops::Range};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
use text::Point;
|
|
||||||
|
|
||||||
pub struct TabMap(Mutex<TabSnapshot>);
|
pub struct TabMap(Mutex<TabSnapshot>);
|
||||||
|
|
||||||
|
@ -332,11 +333,11 @@ impl TabSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
pub struct TabPoint(pub super::Point);
|
pub struct TabPoint(pub Point);
|
||||||
|
|
||||||
impl TabPoint {
|
impl TabPoint {
|
||||||
pub fn new(row: u32, column: u32) -> Self {
|
pub fn new(row: u32, column: u32) -> Self {
|
||||||
Self(super::Point::new(row, column))
|
Self(Point::new(row, column))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
|
@ -352,8 +353,8 @@ impl TabPoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<super::Point> for TabPoint {
|
impl From<Point> for TabPoint {
|
||||||
fn from(point: super::Point) -> Self {
|
fn from(point: Point) -> Self {
|
||||||
Self(point)
|
Self(point)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,7 +363,7 @@ pub type TabEdit = text::Edit<TabPoint>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct TextSummary {
|
pub struct TextSummary {
|
||||||
pub lines: super::Point,
|
pub lines: Point,
|
||||||
pub first_line_chars: u32,
|
pub first_line_chars: u32,
|
||||||
pub last_line_chars: u32,
|
pub last_line_chars: u32,
|
||||||
pub longest_row: u32,
|
pub longest_row: u32,
|
||||||
|
@ -485,7 +486,6 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{display_map::fold_map::FoldMap, MultiBuffer};
|
use crate::{display_map::fold_map::FoldMap, MultiBuffer};
|
||||||
use rand::{prelude::StdRng, Rng};
|
use rand::{prelude::StdRng, Rng};
|
||||||
use text::{RandomCharIter, Rope};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand_tabs() {
|
fn test_expand_tabs() {
|
||||||
|
@ -508,7 +508,9 @@ mod tests {
|
||||||
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
|
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
|
||||||
let len = rng.gen_range(0..30);
|
let len = rng.gen_range(0..30);
|
||||||
let buffer = if rng.gen() {
|
let buffer = if rng.gen() {
|
||||||
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
let text = util::RandomCharIter::new(&mut rng)
|
||||||
|
.take(len)
|
||||||
|
.collect::<String>();
|
||||||
MultiBuffer::build_simple(&text, cx)
|
MultiBuffer::build_simple(&text, cx)
|
||||||
} else {
|
} else {
|
||||||
MultiBuffer::build_random(&mut rng, cx)
|
MultiBuffer::build_random(&mut rng, cx)
|
||||||
|
@ -522,7 +524,7 @@ mod tests {
|
||||||
log::info!("FoldMap text: {:?}", folds_snapshot.text());
|
log::info!("FoldMap text: {:?}", folds_snapshot.text());
|
||||||
|
|
||||||
let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
|
let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
|
||||||
let text = Rope::from(tabs_snapshot.text().as_str());
|
let text = rope::Rope::from(tabs_snapshot.text().as_str());
|
||||||
log::info!(
|
log::info!(
|
||||||
"TabMap text (tab size: {}): {:?}",
|
"TabMap text (tab size: {}): {:?}",
|
||||||
tab_size,
|
tab_size,
|
||||||
|
|
|
@ -3,13 +3,14 @@ use super::{
|
||||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||||
TextHighlights,
|
TextHighlights,
|
||||||
};
|
};
|
||||||
use crate::{MultiBufferSnapshot, Point};
|
use crate::MultiBufferSnapshot;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
|
fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||||
Task,
|
Task,
|
||||||
};
|
};
|
||||||
use language::Chunk;
|
use language::Chunk;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use rope::point::Point;
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
||||||
use sum_tree::{Bias, Cursor, SumTree};
|
use sum_tree::{Bias, Cursor, SumTree};
|
||||||
|
@ -52,7 +53,7 @@ struct TransformSummary {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
pub struct WrapPoint(pub super::Point);
|
pub struct WrapPoint(pub Point);
|
||||||
|
|
||||||
pub struct WrapChunks<'a> {
|
pub struct WrapChunks<'a> {
|
||||||
input_chunks: tab_map::TabChunks<'a>,
|
input_chunks: tab_map::TabChunks<'a>,
|
||||||
|
@ -959,7 +960,7 @@ impl SumTreeExt for SumTree<Transform> {
|
||||||
|
|
||||||
impl WrapPoint {
|
impl WrapPoint {
|
||||||
pub fn new(row: u32, column: u32) -> Self {
|
pub fn new(row: u32, column: u32) -> Self {
|
||||||
Self(super::Point::new(row, column))
|
Self(Point::new(row, column))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn row(self) -> u32 {
|
pub fn row(self) -> u32 {
|
||||||
|
@ -1029,7 +1030,6 @@ mod tests {
|
||||||
MultiBuffer,
|
MultiBuffer,
|
||||||
};
|
};
|
||||||
use gpui::test::observe;
|
use gpui::test::observe;
|
||||||
use language::RandomCharIter;
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
|
@ -1067,7 +1067,9 @@ mod tests {
|
||||||
MultiBuffer::build_random(&mut rng, cx)
|
MultiBuffer::build_random(&mut rng, cx)
|
||||||
} else {
|
} else {
|
||||||
let len = rng.gen_range(0..10);
|
let len = rng.gen_range(0..10);
|
||||||
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
let text = util::RandomCharIter::new(&mut rng)
|
||||||
|
.take(len)
|
||||||
|
.collect::<String>();
|
||||||
MultiBuffer::build_simple(&text, cx)
|
MultiBuffer::build_simple(&text, cx)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,8 +43,8 @@ pub use items::MAX_TAB_TITLE_LEN;
|
||||||
pub use language::{char_kind, CharKind};
|
pub use language::{char_kind, CharKind};
|
||||||
use language::{
|
use language::{
|
||||||
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic,
|
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic,
|
||||||
DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point,
|
DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, Selection, SelectionGoal,
|
||||||
Selection, SelectionGoal, TransactionId,
|
TransactionId,
|
||||||
};
|
};
|
||||||
use link_go_to_definition::{hide_link_definition, LinkGoToDefinitionState};
|
use link_go_to_definition::{hide_link_definition, LinkGoToDefinitionState};
|
||||||
pub use multi_buffer::{
|
pub use multi_buffer::{
|
||||||
|
@ -54,6 +54,7 @@ pub use multi_buffer::{
|
||||||
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
|
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction};
|
use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||||
|
use rope::{offset_utf16::OffsetUtf16, point::Point};
|
||||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -1271,7 +1272,7 @@ impl Editor {
|
||||||
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||||
(display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
|
(display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
|
||||||
} else {
|
} else {
|
||||||
display_map.max_point().row().saturating_sub(1) as f32
|
display_map.max_point().row() as f32
|
||||||
};
|
};
|
||||||
if scroll_position.y() > max_scroll_top {
|
if scroll_position.y() > max_scroll_top {
|
||||||
scroll_position.set_y(max_scroll_top);
|
scroll_position.set_y(max_scroll_top);
|
||||||
|
|
|
@ -15,8 +15,8 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry};
|
use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry};
|
||||||
use project::FakeFs;
|
use project::FakeFs;
|
||||||
|
use rope::point::Point;
|
||||||
use settings::EditorSettings;
|
use settings::EditorSettings;
|
||||||
use text::Point;
|
|
||||||
use util::{
|
use util::{
|
||||||
assert_set_eq,
|
assert_set_eq,
|
||||||
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
|
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
|
||||||
|
|
|
@ -35,8 +35,9 @@ use gpui::{
|
||||||
WeakViewHandle,
|
WeakViewHandle,
|
||||||
};
|
};
|
||||||
use json::json;
|
use json::json;
|
||||||
use language::{Bias, DiagnosticSeverity, OffsetUtf16, Selection};
|
use language::{Bias, DiagnosticSeverity, Selection};
|
||||||
use project::ProjectPath;
|
use project::ProjectPath;
|
||||||
|
use rope::offset_utf16::OffsetUtf16;
|
||||||
use settings::{GitGutter, Settings};
|
use settings::{GitGutter, Settings};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -916,36 +917,30 @@ impl EditorElement {
|
||||||
|
|
||||||
let view = self.view.clone();
|
let view = self.view.clone();
|
||||||
let style = &self.style.theme.scrollbar;
|
let style = &self.style.theme.scrollbar;
|
||||||
let min_thumb_height =
|
|
||||||
style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
|
|
||||||
|
|
||||||
let top = bounds.min_y();
|
let top = bounds.min_y();
|
||||||
let bottom = bounds.max_y();
|
let bottom = bounds.max_y();
|
||||||
let right = bounds.max_x();
|
let right = bounds.max_x();
|
||||||
let left = right - style.width;
|
let left = right - style.width;
|
||||||
let height = bounds.height();
|
|
||||||
let row_range = &layout.scrollbar_row_range;
|
let row_range = &layout.scrollbar_row_range;
|
||||||
let max_row = layout.max_row + ((row_range.end - row_range.start) as u32);
|
let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
|
||||||
let scrollbar_start = row_range.start as f32 / max_row as f32;
|
|
||||||
let scrollbar_end = row_range.end as f32 / max_row as f32;
|
|
||||||
|
|
||||||
let mut thumb_top = top + scrollbar_start * height;
|
let mut height = bounds.height();
|
||||||
let mut thumb_bottom = top + scrollbar_end * height;
|
let mut first_row_y_offset = 0.0;
|
||||||
let thumb_center = (thumb_top + thumb_bottom) / 2.0;
|
|
||||||
|
|
||||||
if thumb_bottom - thumb_top < min_thumb_height {
|
// Impose a minimum height on the scrollbar thumb
|
||||||
thumb_top = thumb_center - min_thumb_height / 2.0;
|
let min_thumb_height =
|
||||||
thumb_bottom = thumb_center + min_thumb_height / 2.0;
|
style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
|
||||||
if thumb_top < top {
|
let thumb_height = (row_range.end - row_range.start) * height / max_row;
|
||||||
thumb_top = top;
|
if thumb_height < min_thumb_height {
|
||||||
thumb_bottom = top + min_thumb_height;
|
first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
|
||||||
}
|
height -= min_thumb_height - thumb_height;
|
||||||
if thumb_bottom > bottom {
|
|
||||||
thumb_bottom = bottom;
|
|
||||||
thumb_top = bottom - min_thumb_height;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * height / max_row };
|
||||||
|
|
||||||
|
let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
|
||||||
|
let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
|
||||||
let track_bounds = RectF::from_points(vec2f(left, top), vec2f(right, bottom));
|
let track_bounds = RectF::from_points(vec2f(left, top), vec2f(right, bottom));
|
||||||
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
|
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
|
||||||
|
|
||||||
|
@ -1587,11 +1582,14 @@ impl Element for EditorElement {
|
||||||
// The scroll position is a fractional point, the whole number of which represents
|
// The scroll position is a fractional point, the whole number of which represents
|
||||||
// the top of the window in terms of display rows.
|
// the top of the window in terms of display rows.
|
||||||
let start_row = scroll_position.y() as u32;
|
let start_row = scroll_position.y() as u32;
|
||||||
let visible_row_count = (size.y() / line_height).ceil() as u32;
|
let height_in_lines = size.y() / line_height;
|
||||||
let max_row = snapshot.max_point().row();
|
let max_row = snapshot.max_point().row();
|
||||||
|
|
||||||
// Add 1 to ensure selections bleed off screen
|
// Add 1 to ensure selections bleed off screen
|
||||||
let end_row = 1 + cmp::min(start_row + visible_row_count, max_row);
|
let end_row = 1 + cmp::min(
|
||||||
|
(scroll_position.y() + height_in_lines).ceil() as u32,
|
||||||
|
max_row,
|
||||||
|
);
|
||||||
|
|
||||||
let start_anchor = if start_row == 0 {
|
let start_anchor = if start_row == 0 {
|
||||||
Anchor::min()
|
Anchor::min()
|
||||||
|
@ -1685,8 +1683,7 @@ impl Element for EditorElement {
|
||||||
.git_diff_hunks_in_range(start_row..end_row)
|
.git_diff_hunks_in_range(start_row..end_row)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let scrollbar_row_range =
|
let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines);
|
||||||
scroll_position.y()..(scroll_position.y() + visible_row_count as f32);
|
|
||||||
|
|
||||||
let mut max_visible_line_width = 0.0;
|
let mut max_visible_line_width = 0.0;
|
||||||
let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
|
let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
|
||||||
|
@ -1723,7 +1720,7 @@ impl Element for EditorElement {
|
||||||
|
|
||||||
let scroll_max = vec2f(
|
let scroll_max = vec2f(
|
||||||
((scroll_width - text_size.x()) / em_width).max(0.0),
|
((scroll_width - text_size.x()) / em_width).max(0.0),
|
||||||
max_row.saturating_sub(1) as f32,
|
max_row as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.update_view(cx.app, |view, cx| {
|
self.update_view(cx.app, |view, cx| {
|
||||||
|
|
|
@ -11,6 +11,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::{Bias, Buffer, File as _, OffsetRangeExt, SelectionGoal};
|
use language::{Bias, Buffer, File as _, OffsetRangeExt, SelectionGoal};
|
||||||
use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath};
|
use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath};
|
||||||
|
use rope::point::Point;
|
||||||
use rpc::proto::{self, update_view};
|
use rpc::proto::{self, update_view};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -21,7 +22,7 @@ use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use text::{Point, Selection};
|
use text::Selection;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use rope::point::Point;
|
||||||
|
|
||||||
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
||||||
use crate::{char_kind, CharKind, ToPoint};
|
use crate::{char_kind, CharKind, ToPoint};
|
||||||
use language::Point;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||||
|
@ -336,7 +337,7 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range<
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
|
use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
|
||||||
use language::Point;
|
use rope::point::Point;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
|
@ -12,6 +12,7 @@ use language::{
|
||||||
DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, Outline, OutlineItem,
|
DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, Outline, OutlineItem,
|
||||||
Selection, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
|
Selection, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
|
||||||
};
|
};
|
||||||
|
use rope::{offset_utf16::OffsetUtf16, point::Point, point_utf16::PointUtf16, TextDimension};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -27,9 +28,8 @@ use std::{
|
||||||
use sum_tree::{Bias, Cursor, SumTree};
|
use sum_tree::{Bias, Cursor, SumTree};
|
||||||
use text::{
|
use text::{
|
||||||
locator::Locator,
|
locator::Locator,
|
||||||
rope::TextDimension,
|
|
||||||
subscription::{Subscription, Topic},
|
subscription::{Subscription, Topic},
|
||||||
Edit, OffsetUtf16, Point, PointUtf16, TextSummary,
|
Edit, TextSummary,
|
||||||
};
|
};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
|
@ -168,7 +168,7 @@ struct ExcerptChunks<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExcerptBytes<'a> {
|
struct ExcerptBytes<'a> {
|
||||||
content_bytes: language::rope::Bytes<'a>,
|
content_bytes: rope::Bytes<'a>,
|
||||||
footer_height: usize,
|
footer_height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,7 +1412,7 @@ impl MultiBuffer {
|
||||||
edit_count: usize,
|
edit_count: usize,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
use text::RandomCharIter;
|
use util::RandomCharIter;
|
||||||
|
|
||||||
let snapshot = self.read(cx);
|
let snapshot = self.read(cx);
|
||||||
let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new();
|
let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new();
|
||||||
|
@ -1451,7 +1451,7 @@ impl MultiBuffer {
|
||||||
) {
|
) {
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
use text::RandomCharIter;
|
use util::RandomCharIter;
|
||||||
|
|
||||||
let max_excerpts = env::var("MAX_EXCERPTS")
|
let max_excerpts = env::var("MAX_EXCERPTS")
|
||||||
.map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable"))
|
.map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable"))
|
||||||
|
@ -3337,7 +3337,7 @@ mod tests {
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{env, rc::Rc};
|
use std::{env, rc::Rc};
|
||||||
use text::{Point, RandomCharIter};
|
|
||||||
use util::test::sample_text;
|
use util::test::sample_text;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -3955,7 +3955,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
|
let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
|
||||||
let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
|
let base_text = util::RandomCharIter::new(&mut rng)
|
||||||
|
.take(10)
|
||||||
|
.collect::<String>();
|
||||||
buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
|
buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
|
||||||
buffers.last().unwrap()
|
buffers.last().unwrap()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
|
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
|
||||||
|
use rope::{offset_utf16::OffsetUtf16, point::Point, TextDimension};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
ops::{Range, Sub},
|
ops::{Range, Sub},
|
||||||
};
|
};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
use text::{rope::TextDimension, OffsetUtf16, Point};
|
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
||||||
pub struct Anchor {
|
pub struct Anchor {
|
||||||
|
|
|
@ -8,7 +8,8 @@ use std::{
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, ModelHandle, MutableAppContext};
|
use gpui::{AppContext, ModelHandle, MutableAppContext};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{rope::TextDimension, Bias, Point, Selection, SelectionGoal, ToPoint};
|
use language::{Bias, Selection, SelectionGoal, ToPoint};
|
||||||
|
use rope::{point::Point, TextDimension};
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
31
crates/fs/Cargo.toml
Normal file
31
crates/fs/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "fs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/fs.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
collections = { path = "../collections" }
|
||||||
|
gpui = { path = "../gpui" }
|
||||||
|
lsp = { path = "../lsp" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
anyhow = "1.0.57"
|
||||||
|
async-trait = "0.1"
|
||||||
|
futures = "0.3"
|
||||||
|
tempfile = "3"
|
||||||
|
fsevent = { path = "../fsevent" }
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
parking_lot = "0.11.1"
|
||||||
|
smol = "1.2.5"
|
||||||
|
regex = "1.5"
|
||||||
|
git2 = { version = "0.15", default-features = false }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = []
|
|
@ -1,10 +1,18 @@
|
||||||
|
pub mod repository;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use fsevent::EventStream;
|
use fsevent::EventStream;
|
||||||
use futures::{future::BoxFuture, Stream, StreamExt};
|
use futures::{future::BoxFuture, Stream, StreamExt};
|
||||||
use git::repository::{GitRepository, LibGitRepository};
|
use git2::Repository as LibGitRepository;
|
||||||
use language::LineEnding;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex as SyncMutex;
|
use parking_lot::Mutex as SyncMutex;
|
||||||
|
use regex::Regex;
|
||||||
|
use repository::GitRepository;
|
||||||
|
use rope::Rope;
|
||||||
use smol::io::{AsyncReadExt, AsyncWriteExt};
|
use smol::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::cmp;
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
|
@ -13,7 +21,7 @@ use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
use text::Rope;
|
use tempfile::NamedTempFile;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -21,10 +29,69 @@ use collections::{btree_map, BTreeMap};
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use git::repository::FakeGitRepositoryState;
|
use repository::FakeGitRepositoryState;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CARRIAGE_RETURNS_REGEX: Regex = Regex::new("\r\n|\r").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum LineEnding {
|
||||||
|
Unix,
|
||||||
|
Windows,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LineEnding {
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(unix)]
|
||||||
|
return Self::Unix;
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
return Self::CRLF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineEnding {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
LineEnding::Unix => "\n",
|
||||||
|
LineEnding::Windows => "\r\n",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detect(text: &str) -> Self {
|
||||||
|
let mut max_ix = cmp::min(text.len(), 1000);
|
||||||
|
while !text.is_char_boundary(max_ix) {
|
||||||
|
max_ix -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ix) = text[..max_ix].find(&['\n']) {
|
||||||
|
if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
|
||||||
|
Self::Windows
|
||||||
|
} else {
|
||||||
|
Self::Unix
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize(text: &mut String) {
|
||||||
|
if let Cow::Owned(replaced) = CARRIAGE_RETURNS_REGEX.replace_all(text, "\n") {
|
||||||
|
*text = replaced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_arc(text: Arc<str>) -> Arc<str> {
|
||||||
|
if let Cow::Owned(replaced) = CARRIAGE_RETURNS_REGEX.replace_all(&text, "\n") {
|
||||||
|
replaced.into()
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Fs: Send + Sync {
|
pub trait Fs: Send + Sync {
|
||||||
async fn create_dir(&self, path: &Path) -> Result<()>;
|
async fn create_dir(&self, path: &Path) -> Result<()>;
|
||||||
|
@ -35,6 +102,7 @@ pub trait Fs: Send + Sync {
|
||||||
async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
||||||
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
|
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
|
||||||
async fn load(&self, path: &Path) -> Result<String>;
|
async fn load(&self, path: &Path) -> Result<String>;
|
||||||
|
async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
|
||||||
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
|
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
|
||||||
async fn is_file(&self, path: &Path) -> bool;
|
async fn is_file(&self, path: &Path) -> bool;
|
||||||
|
@ -86,6 +154,33 @@ pub struct Metadata {
|
||||||
pub is_dir: bool,
|
pub is_dir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<lsp::CreateFileOptions> for CreateOptions {
|
||||||
|
fn from(options: lsp::CreateFileOptions) -> Self {
|
||||||
|
Self {
|
||||||
|
overwrite: options.overwrite.unwrap_or(false),
|
||||||
|
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lsp::RenameFileOptions> for RenameOptions {
|
||||||
|
fn from(options: lsp::RenameFileOptions) -> Self {
|
||||||
|
Self {
|
||||||
|
overwrite: options.overwrite.unwrap_or(false),
|
||||||
|
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lsp::DeleteFileOptions> for RemoveOptions {
|
||||||
|
fn from(options: lsp::DeleteFileOptions) -> Self {
|
||||||
|
Self {
|
||||||
|
recursive: options.recursive.unwrap_or(false),
|
||||||
|
ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RealFs;
|
pub struct RealFs;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -168,6 +263,18 @@ impl Fs for RealFs {
|
||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||||
|
smol::unblock(move || {
|
||||||
|
let mut tmp_file = NamedTempFile::new()?;
|
||||||
|
tmp_file.write_all(data.as_bytes())?;
|
||||||
|
tmp_file.persist(path)?;
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||||
let buffer_size = text.summary().len.min(10 * 1024);
|
let buffer_size = text.summary().len.min(10 * 1024);
|
||||||
let file = smol::fs::File::create(path).await?;
|
let file = smol::fs::File::create(path).await?;
|
||||||
|
@ -285,7 +392,7 @@ enum FakeFsEntry {
|
||||||
inode: u64,
|
inode: u64,
|
||||||
mtime: SystemTime,
|
mtime: SystemTime,
|
||||||
entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
|
entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
|
||||||
git_repo_state: Option<Arc<SyncMutex<git::repository::FakeGitRepositoryState>>>,
|
git_repo_state: Option<Arc<SyncMutex<repository::FakeGitRepositoryState>>>,
|
||||||
},
|
},
|
||||||
Symlink {
|
Symlink {
|
||||||
target: PathBuf,
|
target: PathBuf,
|
||||||
|
@ -788,6 +895,14 @@ impl Fs for FakeFs {
|
||||||
entry.file_content(&path).cloned()
|
entry.file_content(&path).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||||
|
self.simulate_random_delay().await;
|
||||||
|
let path = normalize_path(path.as_path());
|
||||||
|
self.insert_file(path, data.to_string()).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||||
self.simulate_random_delay().await;
|
self.simulate_random_delay().await;
|
||||||
let path = normalize_path(path);
|
let path = normalize_path(path);
|
||||||
|
@ -897,7 +1012,7 @@ impl Fs for FakeFs {
|
||||||
Arc::new(SyncMutex::new(FakeGitRepositoryState::default()))
|
Arc::new(SyncMutex::new(FakeGitRepositoryState::default()))
|
||||||
})
|
})
|
||||||
.clone();
|
.clone();
|
||||||
Some(git::repository::FakeGitRepository::open(state))
|
Some(repository::FakeGitRepository::open(state))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ path = "src/git.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
git2 = { version = "0.15", default-features = false }
|
rope = { path = "../rope" }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
|
@ -20,6 +20,7 @@ smol = "1.2"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
git2 = { version = "0.15", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use rope::point::Point;
|
||||||
use sum_tree::SumTree;
|
use sum_tree::SumTree;
|
||||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
|
use text::{Anchor, BufferSnapshot, OffsetRangeExt};
|
||||||
|
|
||||||
pub use git2 as libgit;
|
pub use git2 as libgit;
|
||||||
use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||||
|
|
|
@ -4,7 +4,6 @@ pub use git2 as libgit;
|
||||||
pub use lazy_static::lazy_static;
|
pub use lazy_static::lazy_static;
|
||||||
|
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod repository;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
|
pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
|
||||||
|
|
|
@ -13,5 +13,6 @@ gpui = { path = "../gpui" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
postage = { version = "0.4", features = ["futures-traits"] }
|
postage = { version = "0.4", features = ["futures-traits"] }
|
||||||
|
|
|
@ -4,8 +4,9 @@ use gpui::{
|
||||||
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use menu::{Cancel, Confirm};
|
use menu::{Cancel, Confirm};
|
||||||
|
use rope::point::Point;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use text::{Bias, Point};
|
use text::Bias;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
actions!(go_to_line, [Toggle]);
|
actions!(go_to_line, [Toggle]);
|
||||||
|
|
|
@ -25,9 +25,11 @@ client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
git = { path = "../git" }
|
git = { path = "../git" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
|
|
|
@ -13,9 +13,11 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
|
use fs::LineEnding;
|
||||||
use futures::FutureExt as _;
|
use futures::FutureExt as _;
|
||||||
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
|
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rope::point::Point;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use similar::{ChangeTag, TextDiff};
|
use similar::{ChangeTag, TextDiff};
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
|
@ -38,6 +40,8 @@ use sum_tree::TreeMap;
|
||||||
use text::operation_queue::OperationQueue;
|
use text::operation_queue::OperationQueue;
|
||||||
pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Operation as _, *};
|
pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Operation as _, *};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
use util::RandomCharIter;
|
||||||
use util::TryFutureExt as _;
|
use util::TryFutureExt as _;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -368,7 +372,7 @@ impl Buffer {
|
||||||
file,
|
file,
|
||||||
);
|
);
|
||||||
this.text.set_line_ending(proto::deserialize_line_ending(
|
this.text.set_line_ending(proto::deserialize_line_ending(
|
||||||
proto::LineEnding::from_i32(message.line_ending)
|
rpc::proto::LineEnding::from_i32(message.line_ending)
|
||||||
.ok_or_else(|| anyhow!("missing line_ending"))?,
|
.ok_or_else(|| anyhow!("missing line_ending"))?,
|
||||||
));
|
));
|
||||||
Ok(this)
|
Ok(this)
|
||||||
|
@ -862,6 +866,8 @@ impl Buffer {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.autoindent_requests.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1633,9 +1639,7 @@ impl Buffer {
|
||||||
last_end = Some(range.end);
|
last_end = Some(range.end);
|
||||||
|
|
||||||
let new_text_len = rng.gen_range(0..10);
|
let new_text_len = rng.gen_range(0..10);
|
||||||
let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng)
|
let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
|
||||||
.take(new_text_len)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
edits.push((range, new_text));
|
edits.push((range, new_text));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
|
use fs::LineEnding;
|
||||||
use gpui::{ModelHandle, MutableAppContext};
|
use gpui::{ModelHandle, MutableAppContext};
|
||||||
use proto::deserialize_operation;
|
use proto::deserialize_operation;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use rope::point::Point;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -14,7 +16,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use text::network::Network;
|
use text::network::Network;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use util::{post_inc, test::marked_text_ranges};
|
use util::{post_inc, test::marked_text_ranges, RandomCharIter};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::Diagnostic;
|
use crate::Diagnostic;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use rope::point_utf16::PointUtf16;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{Ordering, Reverse},
|
cmp::{Ordering, Reverse},
|
||||||
iter,
|
iter,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
};
|
};
|
||||||
use sum_tree::{self, Bias, SumTree};
|
use sum_tree::{self, Bias, SumTree};
|
||||||
use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
|
use text::{Anchor, FromAnchor, ToOffset};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct DiagnosticSet {
|
pub struct DiagnosticSet {
|
||||||
|
|
|
@ -22,6 +22,7 @@ use lazy_static::lazy_static;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use rope::point_utf16::PointUtf16;
|
||||||
use serde::{de, Deserialize, Deserializer};
|
use serde::{de, Deserialize, Deserializer};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
|
@ -8,19 +8,19 @@ use rpc::proto;
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
use text::*;
|
use text::*;
|
||||||
|
|
||||||
pub use proto::{BufferState, LineEnding, Operation, SelectionSet};
|
pub use proto::{BufferState, Operation, SelectionSet};
|
||||||
|
|
||||||
pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
|
pub fn deserialize_line_ending(message: proto::LineEnding) -> fs::LineEnding {
|
||||||
match message {
|
match message {
|
||||||
LineEnding::Unix => text::LineEnding::Unix,
|
proto::LineEnding::Unix => fs::LineEnding::Unix,
|
||||||
LineEnding::Windows => text::LineEnding::Windows,
|
proto::LineEnding::Windows => fs::LineEnding::Windows,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
|
pub fn serialize_line_ending(message: fs::LineEnding) -> proto::LineEnding {
|
||||||
match message {
|
match message {
|
||||||
text::LineEnding::Unix => proto::LineEnding::Unix,
|
fs::LineEnding::Unix => proto::LineEnding::Unix,
|
||||||
text::LineEnding::Windows => proto::LineEnding::Windows,
|
fs::LineEnding::Windows => proto::LineEnding::Windows,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{Grammar, InjectionConfig, Language, LanguageRegistry};
|
use crate::{Grammar, InjectionConfig, Language, LanguageRegistry};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rope::point::Point;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -10,7 +11,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use sum_tree::{Bias, SeekTarget, SumTree};
|
use sum_tree::{Bias, SeekTarget, SumTree};
|
||||||
use text::{rope, Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
|
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Rope, ToOffset, ToPoint};
|
||||||
use tree_sitter::{
|
use tree_sitter::{
|
||||||
Node, Parser, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree,
|
Node, Parser, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree,
|
||||||
};
|
};
|
||||||
|
@ -1242,7 +1243,7 @@ mod tests {
|
||||||
use crate::LanguageConfig;
|
use crate::LanguageConfig;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use std::env;
|
use std::env;
|
||||||
use text::{Buffer, Point};
|
use text::Buffer;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use util::test::marked_text_ranges;
|
use util::test::marked_text_ranges;
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,14 @@ client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
db = { path = "../db" }
|
db = { path = "../db" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
fsevent = { path = "../fsevent" }
|
fsevent = { path = "../fsevent" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
git = { path = "../git" }
|
git = { path = "../git" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
|
@ -38,7 +40,6 @@ async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
|
@ -58,6 +59,7 @@ rocksdb = "0.18"
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
db = { path = "../db", features = ["test-support"] }
|
db = { path = "../db", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
|
|
@ -8,10 +8,11 @@ use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
||||||
use language::{
|
use language::{
|
||||||
point_from_lsp, point_to_lsp,
|
point_from_lsp, point_to_lsp,
|
||||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||||
range_from_lsp, Anchor, Bias, Buffer, CachedLspAdapter, PointUtf16, ToPointUtf16,
|
range_from_lsp, Anchor, Bias, Buffer, CachedLspAdapter, ToPointUtf16,
|
||||||
};
|
};
|
||||||
use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
|
use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||||
|
use rope::point_utf16::PointUtf16;
|
||||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod fs;
|
|
||||||
mod ignore;
|
mod ignore;
|
||||||
mod lsp_command;
|
mod lsp_command;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
@ -25,9 +24,8 @@ use language::{
|
||||||
},
|
},
|
||||||
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
|
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
|
||||||
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent,
|
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent,
|
||||||
File as _, Language, LanguageRegistry, LanguageServerName, LineEnding, LocalFile,
|
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt,
|
||||||
OffsetRangeExt, Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16,
|
Operation, Patch, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
|
||||||
Transaction,
|
|
||||||
};
|
};
|
||||||
use lsp::{
|
use lsp::{
|
||||||
DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString,
|
DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString,
|
||||||
|
@ -37,6 +35,7 @@ use lsp_command::*;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use rope::point_utf16::PointUtf16;
|
||||||
use search::SearchQuery;
|
use search::SearchQuery;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use settings::{FormatOnSave, Formatter, Settings};
|
use settings::{FormatOnSave, Formatter, Settings};
|
||||||
|
@ -1085,13 +1084,6 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for worktree in self.worktrees(cx).collect::<Vec<_>>() {
|
|
||||||
worktree.update(cx, |worktree, cx| {
|
|
||||||
let worktree = worktree.as_local_mut().unwrap();
|
|
||||||
worktree_share_tasks.push(worktree.share(project_id, cx));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (server_id, status) in &self.language_server_statuses {
|
for (server_id, status) in &self.language_server_statuses {
|
||||||
self.client
|
self.client
|
||||||
.send(proto::StartLanguageServer {
|
.send(proto::StartLanguageServer {
|
||||||
|
@ -1104,6 +1096,13 @@ impl Project {
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for worktree in self.worktrees(cx).collect::<Vec<_>>() {
|
||||||
|
worktree.update(cx, |worktree, cx| {
|
||||||
|
let worktree = worktree.as_local_mut().unwrap();
|
||||||
|
worktree_share_tasks.push(worktree.share(project_id, cx));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.client_subscriptions
|
self.client_subscriptions
|
||||||
.push(self.client.add_model_for_remote_entity(project_id, cx));
|
.push(self.client.add_model_for_remote_entity(project_id, cx));
|
||||||
self.metadata_changed(cx);
|
self.metadata_changed(cx);
|
||||||
|
@ -4562,6 +4561,7 @@ impl Project {
|
||||||
buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
|
buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.shared_buffers.remove(&peer_id);
|
||||||
|
|
||||||
cx.emit(Event::CollaboratorLeft(peer_id));
|
cx.emit(Event::CollaboratorLeft(peer_id));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -6019,33 +6019,6 @@ impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<lsp::CreateFileOptions> for fs::CreateOptions {
|
|
||||||
fn from(options: lsp::CreateFileOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
overwrite: options.overwrite.unwrap_or(false),
|
|
||||||
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lsp::RenameFileOptions> for fs::RenameOptions {
|
|
||||||
fn from(options: lsp::RenameFileOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
overwrite: options.overwrite.unwrap_or(false),
|
|
||||||
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lsp::DeleteFileOptions> for fs::RemoveOptions {
|
|
||||||
fn from(options: lsp::DeleteFileOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
recursive: options.recursive.unwrap_or(false),
|
|
||||||
ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
|
fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
|
||||||
proto::Symbol {
|
proto::Symbol {
|
||||||
language_server_name: symbol.language_server_name.0.to_string(),
|
language_server_name: symbol.language_server_name.0.to_string(),
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::{worktree::WorktreeHandle, Event, *};
|
use crate::{worktree::WorktreeHandle, Event, *};
|
||||||
use fs::RealFs;
|
use fs::LineEnding;
|
||||||
|
use fs::{FakeFs, RealFs};
|
||||||
use futures::{future, StreamExt};
|
use futures::{future, StreamExt};
|
||||||
use gpui::{executor::Deterministic, test::subscribe};
|
use gpui::{executor::Deterministic, test::subscribe};
|
||||||
use language::{
|
use language::{
|
||||||
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
||||||
LineEnding, OffsetRangeExt, Point, ToPoint,
|
OffsetRangeExt, ToPoint,
|
||||||
};
|
};
|
||||||
use lsp::Url;
|
use lsp::Url;
|
||||||
|
use rope::point::Point;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
|
use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
use super::{
|
use super::{ignore::IgnoreStack, DiagnosticSummary};
|
||||||
fs::{self, Fs},
|
|
||||||
ignore::IgnoreStack,
|
|
||||||
DiagnosticSummary,
|
|
||||||
};
|
|
||||||
use crate::{copy_recursive, ProjectEntryId, RemoveOptions};
|
use crate::{copy_recursive, ProjectEntryId, RemoveOptions};
|
||||||
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use client::{proto, Client};
|
use client::{proto, Client};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
|
use fs::LineEnding;
|
||||||
|
use fs::{repository::GitRepository, Fs};
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{
|
channel::{
|
||||||
mpsc::{self, UnboundedSender},
|
mpsc::{self, UnboundedSender},
|
||||||
|
@ -17,7 +15,6 @@ use futures::{
|
||||||
Stream, StreamExt,
|
Stream, StreamExt,
|
||||||
};
|
};
|
||||||
use fuzzy::CharBag;
|
use fuzzy::CharBag;
|
||||||
use git::repository::GitRepository;
|
|
||||||
use git::{DOT_GIT, GITIGNORE};
|
use git::{DOT_GIT, GITIGNORE};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
|
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||||
|
@ -25,13 +22,14 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
proto::{deserialize_version, serialize_line_ending, serialize_version},
|
proto::{deserialize_version, serialize_line_ending, serialize_version},
|
||||||
Buffer, DiagnosticEntry, LineEnding, PointUtf16, Rope,
|
Buffer, DiagnosticEntry, Rope,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::{
|
use postage::{
|
||||||
prelude::{Sink as _, Stream as _},
|
prelude::{Sink as _, Stream as _},
|
||||||
watch,
|
watch,
|
||||||
};
|
};
|
||||||
|
use rope::point_utf16::PointUtf16;
|
||||||
|
|
||||||
use smol::channel::{self, Sender};
|
use smol::channel::{self, Sender};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -961,9 +959,20 @@ impl LocalWorktree {
|
||||||
let (snapshots_tx, mut snapshots_rx) = watch::channel_with(self.snapshot());
|
let (snapshots_tx, mut snapshots_rx) = watch::channel_with(self.snapshot());
|
||||||
let rpc = self.client.clone();
|
let rpc = self.client.clone();
|
||||||
let worktree_id = cx.model_id() as u64;
|
let worktree_id = cx.model_id() as u64;
|
||||||
|
|
||||||
|
for (path, summary) in self.diagnostic_summaries.iter() {
|
||||||
|
if let Err(e) = rpc.send(proto::UpdateDiagnosticSummary {
|
||||||
|
project_id,
|
||||||
|
worktree_id,
|
||||||
|
summary: Some(summary.to_proto(&path.0)),
|
||||||
|
}) {
|
||||||
|
return Task::ready(Err(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let maintain_remote_snapshot = cx.background().spawn({
|
let maintain_remote_snapshot = cx.background().spawn({
|
||||||
let rpc = rpc;
|
let rpc = rpc;
|
||||||
let diagnostic_summaries = self.diagnostic_summaries.clone();
|
|
||||||
async move {
|
async move {
|
||||||
let mut prev_snapshot = match snapshots_rx.recv().await {
|
let mut prev_snapshot = match snapshots_rx.recv().await {
|
||||||
Some(snapshot) => {
|
Some(snapshot) => {
|
||||||
|
@ -996,14 +1005,6 @@ impl LocalWorktree {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (path, summary) in diagnostic_summaries.iter() {
|
|
||||||
rpc.send(proto::UpdateDiagnosticSummary {
|
|
||||||
project_id,
|
|
||||||
worktree_id,
|
|
||||||
summary: Some(summary.to_proto(&path.0)),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(snapshot) = snapshots_rx.recv().await {
|
while let Some(snapshot) = snapshots_rx.recv().await {
|
||||||
send_worktree_update(
|
send_worktree_update(
|
||||||
&rpc,
|
&rpc,
|
||||||
|
@ -2970,11 +2971,10 @@ async fn send_worktree_update(client: &Arc<Client>, update: proto::UpdateWorktre
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::fs::FakeFs;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::test::FakeHttpClient;
|
use client::test::FakeHttpClient;
|
||||||
use fs::RealFs;
|
use fs::repository::FakeGitRepository;
|
||||||
use git::repository::FakeGitRepository;
|
use fs::{FakeFs, RealFs};
|
||||||
use gpui::{executor::Deterministic, TestAppContext};
|
use gpui::{executor::Deterministic, TestAppContext};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
20
crates/rope/Cargo.toml
Normal file
20
crates/rope/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "rope"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/rope.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bromberg_sl2 = "0.6"
|
||||||
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
|
sum_tree = { path = "../sum_tree" }
|
||||||
|
arrayvec = "0.7.1"
|
||||||
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = "0.8.3"
|
||||||
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
gpui = { path = "../gpui", features = ["test-support"] }
|
|
@ -1,7 +1,12 @@
|
||||||
use super::Point;
|
pub mod offset_utf16;
|
||||||
use crate::{OffsetUtf16, PointUtf16};
|
pub mod point;
|
||||||
|
pub mod point_utf16;
|
||||||
|
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use bromberg_sl2::{DigestString, HashMatrix};
|
use bromberg_sl2::{DigestString, HashMatrix};
|
||||||
|
use offset_utf16::OffsetUtf16;
|
||||||
|
use point::Point;
|
||||||
|
use point_utf16::PointUtf16;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp, fmt, io, mem, ops::Range, str};
|
use std::{cmp, fmt, io, mem, ops::Range, str};
|
||||||
use sum_tree::{Bias, Dimension, SumTree};
|
use sum_tree::{Bias, Dimension, SumTree};
|
||||||
|
@ -1073,9 +1078,9 @@ fn find_split_ix(text: &str) -> usize {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::random_char_iter::RandomCharIter;
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{cmp::Ordering, env, io::Read};
|
use std::{cmp::Ordering, env, io::Read};
|
||||||
|
use util::RandomCharIter;
|
||||||
use Bias::{Left, Right};
|
use Bias::{Left, Right};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
|
@ -113,7 +113,7 @@ impl Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn add_connection<F, Fut, Out>(
|
pub fn add_connection<F, Fut, Out>(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
create_timer: F,
|
create_timer: F,
|
||||||
|
@ -326,7 +326,7 @@ impl Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub async fn add_test_connection(
|
pub fn add_test_connection(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
executor: Arc<gpui::executor::Background>,
|
executor: Arc<gpui::executor::Background>,
|
||||||
|
@ -337,7 +337,6 @@ impl Peer {
|
||||||
) {
|
) {
|
||||||
let executor = executor.clone();
|
let executor = executor.clone();
|
||||||
self.add_connection(connection, move |duration| executor.timer(duration))
|
self.add_connection(connection, move |duration| executor.timer(duration))
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disconnect(&self, connection_id: ConnectionId) {
|
pub fn disconnect(&self, connection_id: ConnectionId) {
|
||||||
|
@ -522,21 +521,17 @@ mod tests {
|
||||||
|
|
||||||
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
let (client1_conn_id, io_task1, client1_incoming) = client1
|
let (client1_conn_id, io_task1, client1_incoming) =
|
||||||
.add_test_connection(client1_to_server_conn, cx.background())
|
client1.add_test_connection(client1_to_server_conn, cx.background());
|
||||||
.await;
|
let (_, io_task2, server_incoming1) =
|
||||||
let (_, io_task2, server_incoming1) = server
|
server.add_test_connection(server_to_client_1_conn, cx.background());
|
||||||
.add_test_connection(server_to_client_1_conn, cx.background())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
|
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
let (client2_conn_id, io_task3, client2_incoming) = client2
|
let (client2_conn_id, io_task3, client2_incoming) =
|
||||||
.add_test_connection(client2_to_server_conn, cx.background())
|
client2.add_test_connection(client2_to_server_conn, cx.background());
|
||||||
.await;
|
let (_, io_task4, server_incoming2) =
|
||||||
let (_, io_task4, server_incoming2) = server
|
server.add_test_connection(server_to_client_2_conn, cx.background());
|
||||||
.add_test_connection(server_to_client_2_conn, cx.background())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
executor.spawn(io_task1).detach();
|
executor.spawn(io_task1).detach();
|
||||||
executor.spawn(io_task2).detach();
|
executor.spawn(io_task2).detach();
|
||||||
|
@ -619,12 +614,10 @@ mod tests {
|
||||||
|
|
||||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
let (client_to_server_conn_id, io_task1, mut client_incoming) = client
|
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||||
.add_test_connection(client_to_server_conn, cx.background())
|
client.add_test_connection(client_to_server_conn, cx.background());
|
||||||
.await;
|
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||||
let (server_to_client_conn_id, io_task2, mut server_incoming) = server
|
server.add_test_connection(server_to_client_conn, cx.background());
|
||||||
.add_test_connection(server_to_client_conn, cx.background())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
executor.spawn(io_task1).detach();
|
executor.spawn(io_task1).detach();
|
||||||
executor.spawn(io_task2).detach();
|
executor.spawn(io_task2).detach();
|
||||||
|
@ -719,12 +712,10 @@ mod tests {
|
||||||
|
|
||||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
let (client_to_server_conn_id, io_task1, mut client_incoming) = client
|
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||||
.add_test_connection(client_to_server_conn, cx.background())
|
client.add_test_connection(client_to_server_conn, cx.background());
|
||||||
.await;
|
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||||
let (server_to_client_conn_id, io_task2, mut server_incoming) = server
|
server.add_test_connection(server_to_client_conn, cx.background());
|
||||||
.add_test_connection(server_to_client_conn, cx.background())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
executor.spawn(io_task1).detach();
|
executor.spawn(io_task1).detach();
|
||||||
executor.spawn(io_task2).detach();
|
executor.spawn(io_task2).detach();
|
||||||
|
@ -832,9 +823,8 @@ mod tests {
|
||||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
||||||
|
|
||||||
let client = Peer::new();
|
let client = Peer::new();
|
||||||
let (connection_id, io_handler, mut incoming) = client
|
let (connection_id, io_handler, mut incoming) =
|
||||||
.add_test_connection(client_conn, cx.background())
|
client.add_test_connection(client_conn, cx.background());
|
||||||
.await;
|
|
||||||
|
|
||||||
let (io_ended_tx, io_ended_rx) = oneshot::channel();
|
let (io_ended_tx, io_ended_rx) = oneshot::channel();
|
||||||
executor
|
executor
|
||||||
|
@ -868,9 +858,8 @@ mod tests {
|
||||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
||||||
|
|
||||||
let client = Peer::new();
|
let client = Peer::new();
|
||||||
let (connection_id, io_handler, mut incoming) = client
|
let (connection_id, io_handler, mut incoming) =
|
||||||
.add_test_connection(client_conn, cx.background())
|
client.add_test_connection(client_conn, cx.background());
|
||||||
.await;
|
|
||||||
executor.spawn(io_handler).detach();
|
executor.spawn(io_handler).detach();
|
||||||
executor
|
executor
|
||||||
.spawn(async move { incoming.next().await })
|
.spawn(async move { incoming.next().await })
|
||||||
|
|
|
@ -14,12 +14,23 @@ test-support = []
|
||||||
assets = { path = "../assets" }
|
assets = { path = "../assets" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
|
anyhow = "1.0.38"
|
||||||
|
futures = "0.3"
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0.38"
|
rope = { path = "../rope" }
|
||||||
json_comments = "0.2"
|
json_comments = "0.2"
|
||||||
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { workspace = true }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { workspace = true }
|
||||||
serde_path_to_error = "0.1.4"
|
serde_path_to_error = "0.1.4"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
tree-sitter = "*"
|
||||||
|
tree-sitter-json = "*"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
unindent = "0.1"
|
||||||
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod keymap_file;
|
mod keymap_file;
|
||||||
|
pub mod settings_file;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -12,8 +13,9 @@ use schemars::{
|
||||||
};
|
};
|
||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{collections::HashMap, num::NonZeroU32, str, sync::Arc};
|
use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc};
|
||||||
use theme::{Theme, ThemeRegistry};
|
use theme::{Theme, ThemeRegistry};
|
||||||
|
use tree_sitter::Query;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
|
pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
|
||||||
|
@ -501,6 +503,101 @@ pub fn settings_file_json_schema(
|
||||||
serde_json::to_value(root_schema).unwrap()
|
serde_json::to_value(root_schema).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_top_level_setting(
|
||||||
|
mut settings_content: String,
|
||||||
|
top_level_key: &str,
|
||||||
|
new_val: &str,
|
||||||
|
) -> String {
|
||||||
|
let mut parser = tree_sitter::Parser::new();
|
||||||
|
parser.set_language(tree_sitter_json::language()).unwrap();
|
||||||
|
let tree = parser.parse(&settings_content, None).unwrap();
|
||||||
|
|
||||||
|
let mut cursor = tree_sitter::QueryCursor::new();
|
||||||
|
|
||||||
|
let query = Query::new(
|
||||||
|
tree_sitter_json::language(),
|
||||||
|
"
|
||||||
|
(document
|
||||||
|
(object
|
||||||
|
(pair
|
||||||
|
key: (string) @key
|
||||||
|
value: (_) @value)))
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut first_key_start = None;
|
||||||
|
let mut existing_value_range = None;
|
||||||
|
let matches = cursor.matches(&query, tree.root_node(), settings_content.as_bytes());
|
||||||
|
for mat in matches {
|
||||||
|
if mat.captures.len() != 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = mat.captures[0];
|
||||||
|
let value = mat.captures[1];
|
||||||
|
|
||||||
|
first_key_start.get_or_insert_with(|| key.node.start_byte());
|
||||||
|
|
||||||
|
if let Some(key_text) = settings_content.get(key.node.byte_range()) {
|
||||||
|
if key_text == format!("\"{top_level_key}\"") {
|
||||||
|
existing_value_range = Some(value.node.byte_range());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (first_key_start, existing_value_range) {
|
||||||
|
(None, None) => {
|
||||||
|
// No document, create a new object and overwrite
|
||||||
|
settings_content.clear();
|
||||||
|
write!(
|
||||||
|
settings_content,
|
||||||
|
"{{\n \"{}\": \"{new_val}\"\n}}\n",
|
||||||
|
top_level_key
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
(_, Some(existing_value_range)) => {
|
||||||
|
// Existing theme key, overwrite
|
||||||
|
settings_content.replace_range(existing_value_range, &format!("\"{new_val}\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
(Some(first_key_start), None) => {
|
||||||
|
// No existing theme key, but other settings. Prepend new theme settings and
|
||||||
|
// match style of first key
|
||||||
|
let mut row = 0;
|
||||||
|
let mut column = 0;
|
||||||
|
for (ix, char) in settings_content.char_indices() {
|
||||||
|
if ix == first_key_start {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if char == '\n' {
|
||||||
|
row += 1;
|
||||||
|
column = 0;
|
||||||
|
} else {
|
||||||
|
column += char.len_utf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = format!(r#""{top_level_key}": "{new_val}","#);
|
||||||
|
settings_content.insert_str(first_key_start, &content);
|
||||||
|
|
||||||
|
if row > 0 {
|
||||||
|
settings_content.insert_str(
|
||||||
|
first_key_start + content.len(),
|
||||||
|
&format!("\n{:width$}", ' ', width = column),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
settings_content.insert_str(first_key_start + content.len(), " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_content
|
||||||
|
}
|
||||||
|
|
||||||
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
|
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
|
||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
*target = value;
|
*target = value;
|
||||||
|
@ -512,3 +609,108 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
|
||||||
json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
|
json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::write_top_level_setting;
|
||||||
|
use unindent::Unindent;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_theme_into_settings_with_theme() {
|
||||||
|
let settings = r#"
|
||||||
|
{
|
||||||
|
"theme": "one-dark"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let new_settings = r#"
|
||||||
|
{
|
||||||
|
"theme": "summerfruit-light"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_theme_into_empty_settings() {
|
||||||
|
let settings = r#"
|
||||||
|
{
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let new_settings = r#"
|
||||||
|
{
|
||||||
|
"theme": "summerfruit-light"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_theme_into_no_settings() {
|
||||||
|
let settings = "".to_string();
|
||||||
|
|
||||||
|
let new_settings = r#"
|
||||||
|
{
|
||||||
|
"theme": "summerfruit-light"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_theme_into_single_line_settings_without_theme() {
|
||||||
|
let settings = r#"{ "a": "", "ok": true }"#.to_string();
|
||||||
|
let new_settings = r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#;
|
||||||
|
|
||||||
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_theme_pre_object_whitespace() {
|
||||||
|
let settings = r#" { "a": "", "ok": true }"#.to_string();
|
||||||
|
let new_settings = r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#;
|
||||||
|
|
||||||
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_theme_into_multi_line_settings_without_theme() {
|
||||||
|
let settings = r#"
|
||||||
|
{
|
||||||
|
"a": "b"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let new_settings = r#"
|
||||||
|
{
|
||||||
|
"theme": "summerfruit-light",
|
||||||
|
"a": "b"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let settings_after_theme = write_top_level_setting(settings, "theme", "summerfruit-light");
|
||||||
|
|
||||||
|
assert_eq!(settings_after_theme, new_settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,59 @@
|
||||||
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{executor, MutableAppContext};
|
use gpui::{executor, MutableAppContext};
|
||||||
use postage::sink::Sink as _;
|
use postage::sink::Sink as _;
|
||||||
use postage::{prelude::Stream, watch};
|
use postage::{prelude::Stream, watch};
|
||||||
use project::Fs;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent};
|
|
||||||
use std::{path::Path, sync::Arc, time::Duration};
|
use std::{path::Path, sync::Arc, time::Duration};
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
parse_json_with_comments, write_top_level_setting, KeymapFileContent, Settings,
|
||||||
|
SettingsFileContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Switch SettingsFile to open a worktree and buffer for synchronization
|
||||||
|
// And instant updates in the Zed editor
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SettingsFile {
|
||||||
|
path: &'static Path,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingsFile {
|
||||||
|
pub fn new(path: &'static Path, fs: Arc<dyn Fs>) -> Self {
|
||||||
|
SettingsFile { path, fs }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rewrite_settings_file<F>(&self, f: F) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(String) -> String,
|
||||||
|
{
|
||||||
|
let content = self.fs.load(self.path).await?;
|
||||||
|
|
||||||
|
let new_settings = f(content);
|
||||||
|
|
||||||
|
self.fs
|
||||||
|
.atomic_write(self.path.to_path_buf(), new_settings)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_setting(key: &'static str, val: String, cx: &mut MutableAppContext) {
|
||||||
|
let settings_file = cx.global::<SettingsFile>().clone();
|
||||||
|
cx.background()
|
||||||
|
.spawn(async move {
|
||||||
|
settings_file
|
||||||
|
.rewrite_settings_file(|settings| write_top_level_setting(settings, key, &val))
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WatchedJsonFile<T>(pub watch::Receiver<T>);
|
pub struct WatchedJsonFile<T>(pub watch::Receiver<T>);
|
||||||
|
|
||||||
|
@ -73,7 +118,7 @@ pub fn watch_settings_file(
|
||||||
|
|
||||||
pub fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) {
|
pub fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) {
|
||||||
cx.clear_bindings();
|
cx.clear_bindings();
|
||||||
settings::KeymapFileContent::load_defaults(cx);
|
KeymapFileContent::load_defaults(cx);
|
||||||
content.add_to_cx(cx).log_err();
|
content.add_to_cx(cx).log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,8 +146,8 @@ pub fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use project::FakeFs;
|
use crate::{EditorSettings, SoftWrap};
|
||||||
use settings::{EditorSettings, SoftWrap};
|
use fs::FakeFs;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_watch_settings_files(cx: &mut gpui::TestAppContext) {
|
async fn test_watch_settings_files(cx: &mut gpui::TestAppContext) {
|
|
@ -330,13 +330,10 @@ impl TerminalElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut properties = Properties::new();
|
let mut properties = Properties::new();
|
||||||
if indexed
|
if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) {
|
||||||
.flags
|
|
||||||
.intersects(Flags::BOLD | Flags::BOLD_ITALIC | Flags::DIM_BOLD)
|
|
||||||
{
|
|
||||||
properties = *properties.weight(Weight::BOLD);
|
properties = *properties.weight(Weight::BOLD);
|
||||||
}
|
}
|
||||||
if indexed.flags.intersects(Flags::ITALIC | Flags::BOLD_ITALIC) {
|
if indexed.flags.intersects(Flags::ITALIC) {
|
||||||
properties = *properties.style(Italic);
|
properties = *properties.style(Italic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,23 +13,24 @@ test-support = ["rand"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
arrayvec = "0.7.1"
|
|
||||||
digest = { version = "0.9", features = ["std"] }
|
digest = { version = "0.9", features = ["std"] }
|
||||||
bromberg_sl2 = "0.6"
|
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
rand = { version = "0.8.3", optional = true }
|
rand = { version = "0.8.3", optional = true }
|
||||||
regex = "1.5"
|
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
|
util = { path = "../util" }
|
||||||
|
regex = "1.5"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use super::{Point, ToOffset};
|
|
||||||
use crate::{rope::TextDimension, BufferSnapshot, PointUtf16, ToPoint, ToPointUtf16};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use rope::{point::Point, point_utf16::PointUtf16, TextDimension};
|
||||||
use std::{cmp::Ordering, fmt::Debug, ops::Range};
|
use std::{cmp::Ordering, fmt::Debug, ops::Range};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
|
|
||||||
|
use crate::{BufferSnapshot, ToOffset, ToPoint, ToPointUtf16};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)]
|
||||||
pub struct Anchor {
|
pub struct Anchor {
|
||||||
pub timestamp: clock::Local,
|
pub timestamp: clock::Local,
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
use rand::prelude::*;
|
|
||||||
|
|
||||||
pub struct RandomCharIter<T: Rng>(T);
|
|
||||||
|
|
||||||
impl<T: Rng> RandomCharIter<T> {
|
|
||||||
pub fn new(rng: T) -> Self {
|
|
||||||
Self(rng)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Rng> Iterator for RandomCharIter<T> {
|
|
||||||
type Item = char;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()) {
|
|
||||||
return if self.0.gen_range(0..100) < 5 {
|
|
||||||
Some('\n')
|
|
||||||
} else {
|
|
||||||
Some(self.0.gen_range(b'a'..b'z' + 1).into())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.0.gen_range(0..100) {
|
|
||||||
// whitespace
|
|
||||||
0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.0).copied(),
|
|
||||||
// two-byte greek letters
|
|
||||||
20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))),
|
|
||||||
// // three-byte characters
|
|
||||||
33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(),
|
|
||||||
// // four-byte characters
|
|
||||||
46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(),
|
|
||||||
// ascii letters
|
|
||||||
_ => Some(self.0.gen_range(b'a'..b'z' + 1).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::Anchor;
|
use rope::TextDimension;
|
||||||
use crate::{rope::TextDimension, BufferSnapshot};
|
|
||||||
|
use crate::{Anchor, BufferSnapshot};
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,8 @@ mod anchor;
|
||||||
pub mod locator;
|
pub mod locator;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod network;
|
pub mod network;
|
||||||
mod offset_utf16;
|
|
||||||
pub mod operation_queue;
|
pub mod operation_queue;
|
||||||
mod patch;
|
mod patch;
|
||||||
mod point;
|
|
||||||
mod point_utf16;
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub mod random_char_iter;
|
|
||||||
pub mod rope;
|
|
||||||
mod selection;
|
mod selection;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -20,22 +14,15 @@ pub use anchor::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use lazy_static::lazy_static;
|
use fs::LineEnding;
|
||||||
use locator::Locator;
|
use locator::Locator;
|
||||||
pub use offset_utf16::*;
|
|
||||||
use operation_queue::OperationQueue;
|
use operation_queue::OperationQueue;
|
||||||
pub use patch::Patch;
|
pub use patch::Patch;
|
||||||
pub use point::*;
|
|
||||||
pub use point_utf16::*;
|
|
||||||
use postage::{barrier, oneshot, prelude::*};
|
use postage::{barrier, oneshot, prelude::*};
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
use rope::{offset_utf16::OffsetUtf16, point::Point, point_utf16::PointUtf16, TextDimension};
|
||||||
pub use random_char_iter::*;
|
|
||||||
use regex::Regex;
|
|
||||||
use rope::TextDimension;
|
|
||||||
pub use rope::{Chunks, Rope, TextSummary};
|
pub use rope::{Chunks, Rope, TextSummary};
|
||||||
pub use selection::*;
|
pub use selection::*;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
cmp::{self, Ordering, Reverse},
|
cmp::{self, Ordering, Reverse},
|
||||||
future::Future,
|
future::Future,
|
||||||
iter::Iterator,
|
iter::Iterator,
|
||||||
|
@ -49,9 +36,8 @@ pub use sum_tree::Bias;
|
||||||
use sum_tree::{FilterCursor, SumTree, TreeMap};
|
use sum_tree::{FilterCursor, SumTree, TreeMap};
|
||||||
use undo_map::UndoMap;
|
use undo_map::UndoMap;
|
||||||
|
|
||||||
lazy_static! {
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
static ref CARRIAGE_RETURNS_REGEX: Regex = Regex::new("\r\n|\r").unwrap();
|
use util::RandomCharIter;
|
||||||
}
|
|
||||||
|
|
||||||
pub type TransactionId = clock::Local;
|
pub type TransactionId = clock::Local;
|
||||||
|
|
||||||
|
@ -96,12 +82,6 @@ pub struct Transaction {
|
||||||
pub start: clock::Global,
|
pub start: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum LineEnding {
|
|
||||||
Unix,
|
|
||||||
Windows,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HistoryEntry {
|
impl HistoryEntry {
|
||||||
pub fn transaction_id(&self) -> TransactionId {
|
pub fn transaction_id(&self) -> TransactionId {
|
||||||
self.transaction.id
|
self.transaction.id
|
||||||
|
@ -1464,9 +1444,7 @@ impl Buffer {
|
||||||
last_end = Some(range.end);
|
last_end = Some(range.end);
|
||||||
|
|
||||||
let new_text_len = rng.gen_range(0..10);
|
let new_text_len = rng.gen_range(0..10);
|
||||||
let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng)
|
let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
|
||||||
.take(new_text_len)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
edits.push((range, new_text.into()));
|
edits.push((range, new_text.into()));
|
||||||
}
|
}
|
||||||
|
@ -2370,56 +2348,6 @@ impl operation_queue::Operation for Operation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LineEnding {
|
|
||||||
fn default() -> Self {
|
|
||||||
#[cfg(unix)]
|
|
||||||
return Self::Unix;
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
return Self::CRLF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineEnding {
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
LineEnding::Unix => "\n",
|
|
||||||
LineEnding::Windows => "\r\n",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn detect(text: &str) -> Self {
|
|
||||||
let mut max_ix = cmp::min(text.len(), 1000);
|
|
||||||
while !text.is_char_boundary(max_ix) {
|
|
||||||
max_ix -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ix) = text[..max_ix].find(&['\n']) {
|
|
||||||
if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
|
|
||||||
Self::Windows
|
|
||||||
} else {
|
|
||||||
Self::Unix
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn normalize(text: &mut String) {
|
|
||||||
if let Cow::Owned(replaced) = CARRIAGE_RETURNS_REGEX.replace_all(text, "\n") {
|
|
||||||
*text = replaced;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalize_arc(text: Arc<str>) -> Arc<str> {
|
|
||||||
if let Cow::Owned(replaced) = CARRIAGE_RETURNS_REGEX.replace_all(&text, "\n") {
|
|
||||||
replaced.into()
|
|
||||||
} else {
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ToOffset {
|
pub trait ToOffset {
|
||||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize;
|
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,3 +19,4 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,9 @@ impl ThemeSelector {
|
||||||
fn show_selected_theme(&mut self, cx: &mut ViewContext<Self>) {
|
fn show_selected_theme(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(mat) = self.matches.get(self.selected_index) {
|
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||||
match self.registry.get(&mat.string) {
|
match self.registry.get(&mat.string) {
|
||||||
Ok(theme) => Self::set_theme(theme, cx),
|
Ok(theme) => {
|
||||||
|
Self::set_theme(theme, cx);
|
||||||
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!("error loading theme {}: {}", mat.string, error)
|
log::error!("error loading theme {}: {}", mat.string, error)
|
||||||
}
|
}
|
||||||
|
@ -151,6 +153,10 @@ impl PickerDelegate for ThemeSelector {
|
||||||
|
|
||||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.selection_completed = true;
|
self.selection_completed = true;
|
||||||
|
|
||||||
|
let theme_name = cx.global::<Settings>().theme.meta.name.clone();
|
||||||
|
settings::settings_file::write_setting("theme", theme_name, cx);
|
||||||
|
|
||||||
cx.emit(Event::Dismissed);
|
cx.emit(Event::Dismissed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,21 +7,20 @@ edition = "2021"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = ["rand", "serde_json", "tempdir", "git2"]
|
test-support = ["serde_json", "tempdir", "git2"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
rand = { version = "0.8", optional = true }
|
rand = { workspace = true }
|
||||||
tempdir = { version = "0.3.7", optional = true }
|
tempdir = { version = "0.3.7", optional = true }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
|
serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
|
||||||
git2 = { version = "0.15", default-features = false, optional = true }
|
git2 = { version = "0.15", default-features = false, optional = true }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = { version = "0.8" }
|
|
||||||
tempdir = { version = "0.3.7" }
|
tempdir = { version = "0.3.7" }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
git2 = { version = "0.15", default-features = false }
|
git2 = { version = "0.15", default-features = false }
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
ops::AddAssign,
|
ops::AddAssign,
|
||||||
|
@ -155,6 +156,41 @@ pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
|
||||||
Defer(Some(f))
|
Defer(Some(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RandomCharIter<T: Rng>(T);
|
||||||
|
|
||||||
|
impl<T: Rng> RandomCharIter<T> {
|
||||||
|
pub fn new(rng: T) -> Self {
|
||||||
|
Self(rng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Rng> Iterator for RandomCharIter<T> {
|
||||||
|
type Item = char;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()) {
|
||||||
|
return if self.0.gen_range(0..100) < 5 {
|
||||||
|
Some('\n')
|
||||||
|
} else {
|
||||||
|
Some(self.0.gen_range(b'a'..b'z' + 1).into())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.0.gen_range(0..100) {
|
||||||
|
// whitespace
|
||||||
|
0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.0).copied(),
|
||||||
|
// two-byte greek letters
|
||||||
|
20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))),
|
||||||
|
// // three-byte characters
|
||||||
|
33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(),
|
||||||
|
// // four-byte characters
|
||||||
|
46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(),
|
||||||
|
// ascii letters
|
||||||
|
_ => Some(self.0.gen_range(b'a'..b'z' + 1).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -27,6 +27,7 @@ command_palette = { path = "../command_palette" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
|
rope = { path = "../rope" }
|
||||||
search = { path = "../search" }
|
search = { path = "../search" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
|
|
|
@ -15,7 +15,8 @@ use editor::{
|
||||||
display_map::ToDisplayPoint, Anchor, Autoscroll, Bias, ClipboardSelection, DisplayPoint,
|
display_map::ToDisplayPoint, Anchor, Autoscroll, Bias, ClipboardSelection, DisplayPoint,
|
||||||
};
|
};
|
||||||
use gpui::{actions, MutableAppContext, ViewContext};
|
use gpui::{actions, MutableAppContext, ViewContext};
|
||||||
use language::{AutoindentMode, Point, SelectionGoal};
|
use language::{AutoindentMode, SelectionGoal};
|
||||||
|
use rope::point::Point;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
|
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||||
let context_handle = self.set_state(marked_text, Mode::Normal);
|
let context_handle = self.set_state(marked_text, Mode::Normal);
|
||||||
|
|
||||||
let selection = self.editor(|editor, cx| editor.selections.newest::<language::Point>(cx));
|
let selection = self.editor(|editor, cx| editor.selections.newest::<rope::point::Point>(cx));
|
||||||
let text = self.buffer_text();
|
let text = self.buffer_text();
|
||||||
self.neovim.set_state(selection, &text).await;
|
self.neovim.set_state(selection, &text).await;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,10 @@ use async_compat::Compat;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
use gpui::keymap::Keystroke;
|
use gpui::keymap::Keystroke;
|
||||||
use language::{Point, Selection};
|
|
||||||
|
use language::Selection;
|
||||||
|
use rope::point::Point;
|
||||||
|
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
|
|
|
@ -12,7 +12,9 @@ test-support = [
|
||||||
"call/test-support",
|
"call/test-support",
|
||||||
"client/test-support",
|
"client/test-support",
|
||||||
"project/test-support",
|
"project/test-support",
|
||||||
"settings/test-support"
|
"settings/test-support",
|
||||||
|
"gpui/test-support",
|
||||||
|
"fs/test-support"
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -21,6 +23,7 @@ client = { path = "../client" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
context_menu = { path = "../context_menu" }
|
context_menu = { path = "../context_menu" }
|
||||||
drag_and_drop = { path = "../drag_and_drop" }
|
drag_and_drop = { path = "../drag_and_drop" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
|
@ -42,4 +45,5 @@ call = { path = "../call", features = ["test-support"] }
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
project = { path = "../project", features = ["test-support"] }
|
project = { path = "../project", features = ["test-support"] }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { path = "../settings", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
|
@ -16,6 +16,7 @@ use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use dock::{DefaultItemFactory, Dock, ToggleDockButton};
|
use dock::{DefaultItemFactory, Dock, ToggleDockButton};
|
||||||
use drag_and_drop::DragAndDrop;
|
use drag_and_drop::DragAndDrop;
|
||||||
|
use fs::{self, Fs};
|
||||||
use futures::{channel::oneshot, FutureExt, StreamExt};
|
use futures::{channel::oneshot, FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
|
@ -31,7 +32,7 @@ use log::{error, warn};
|
||||||
pub use pane::*;
|
pub use pane::*;
|
||||||
pub use pane_group::*;
|
pub use pane_group::*;
|
||||||
use postage::prelude::Stream;
|
use postage::prelude::Stream;
|
||||||
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
|
use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
|
||||||
use searchable::SearchableItemHandle;
|
use searchable::SearchableItemHandle;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::{Autosave, DockAnchor, Settings};
|
use settings::{Autosave, DockAnchor, Settings};
|
||||||
|
@ -929,7 +930,7 @@ impl AppState {
|
||||||
let settings = Settings::test(cx);
|
let settings = Settings::test(cx);
|
||||||
cx.set_global(settings);
|
cx.set_global(settings);
|
||||||
|
|
||||||
let fs = project::FakeFs::new(cx.background().clone());
|
let fs = fs::FakeFs::new(cx.background().clone());
|
||||||
let languages = Arc::new(LanguageRegistry::test());
|
let languages = Arc::new(LanguageRegistry::test());
|
||||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||||
let client = Client::new(http_client.clone(), cx);
|
let client = Client::new(http_client.clone(), cx);
|
||||||
|
@ -1171,6 +1172,10 @@ impl Workspace {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
|
||||||
|
self.titlebar_item.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Call the given callback with a workspace whose project is local.
|
/// Call the given callback with a workspace whose project is local.
|
||||||
///
|
///
|
||||||
/// If the given workspace has a local project, then it will be passed
|
/// If the given workspace has a local project, then it will be passed
|
||||||
|
@ -2811,8 +2816,9 @@ mod tests {
|
||||||
use crate::sidebar::SidebarItem;
|
use crate::sidebar::SidebarItem;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use fs::FakeFs;
|
||||||
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
|
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
|
||||||
use project::{FakeFs, Project, ProjectEntryId};
|
use project::{Project, ProjectEntryId};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub fn default_item_factory(
|
pub fn default_item_factory(
|
||||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
||||||
description = "The fast, collaborative code editor."
|
description = "The fast, collaborative code editor."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.59.0"
|
version = "0.60.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
|
@ -32,6 +32,7 @@ diagnostics = { path = "../diagnostics" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
file_finder = { path = "../file_finder" }
|
file_finder = { path = "../file_finder" }
|
||||||
search = { path = "../search" }
|
search = { path = "../search" }
|
||||||
|
fs = { path = "../fs" }
|
||||||
fsevent = { path = "../fsevent" }
|
fsevent = { path = "../fsevent" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
go_to_line = { path = "../go_to_line" }
|
go_to_line = { path = "../go_to_line" }
|
||||||
|
|
|
@ -14,7 +14,6 @@ use client::{
|
||||||
http::{self, HttpClient},
|
http::{self, HttpClient},
|
||||||
UserStore, ZED_SECRET_CLIENT_TOKEN,
|
UserStore, ZED_SECRET_CLIENT_TOKEN,
|
||||||
};
|
};
|
||||||
use fs::OpenOptions;
|
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
FutureExt, SinkExt, StreamExt,
|
FutureExt, SinkExt, StreamExt,
|
||||||
|
@ -26,20 +25,21 @@ use log::LevelFilter;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{Fs, ProjectStore};
|
use project::{Fs, ProjectStore};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{self, KeymapFileContent, Settings, SettingsFileContent, WorkingDirectory};
|
use settings::{
|
||||||
|
self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent,
|
||||||
|
WorkingDirectory,
|
||||||
|
};
|
||||||
use smol::process::Command;
|
use smol::process::Command;
|
||||||
use std::{env, ffi::OsStr, fs, panic, path::PathBuf, sync::Arc, thread, time::Duration};
|
use std::fs::OpenOptions;
|
||||||
|
use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration};
|
||||||
use terminal::terminal_container_view::{get_working_directory, TerminalContainer};
|
use terminal::terminal_container_view::{get_working_directory, TerminalContainer};
|
||||||
|
|
||||||
|
use fs::RealFs;
|
||||||
|
use settings::settings_file::{watch_keymap_file, watch_settings_file, WatchedJsonFile};
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
use workspace::{self, AppState, ItemHandle, NewFile, OpenPaths, Workspace};
|
use workspace::{self, AppState, ItemHandle, NewFile, OpenPaths, Workspace};
|
||||||
use zed::{
|
use zed::{self, build_window_options, initialize_workspace, languages, menus};
|
||||||
self, build_window_options,
|
|
||||||
fs::RealFs,
|
|
||||||
initialize_workspace, languages, menus,
|
|
||||||
settings_file::{watch_keymap_file, watch_settings_file, WatchedJsonFile},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let http = http::client();
|
let http = http::client();
|
||||||
|
@ -65,6 +65,7 @@ fn main() {
|
||||||
let themes = ThemeRegistry::new(Assets, app.font_cache());
|
let themes = ThemeRegistry::new(Assets, app.font_cache());
|
||||||
let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes);
|
let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes);
|
||||||
|
|
||||||
|
let settings_file = SettingsFile::new(&*zed::paths::SETTINGS, fs.clone());
|
||||||
let config_files = load_config_files(&app, fs.clone());
|
let config_files = load_config_files(&app, fs.clone());
|
||||||
|
|
||||||
let login_shell_env_loaded = if stdout_is_a_pty() {
|
let login_shell_env_loaded = if stdout_is_a_pty() {
|
||||||
|
@ -97,10 +98,11 @@ fn main() {
|
||||||
.spawn(languages::init(languages.clone(), cx.background().clone()));
|
.spawn(languages::init(languages.clone(), cx.background().clone()));
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
||||||
|
|
||||||
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap();
|
||||||
|
|
||||||
//Setup settings global before binding actions
|
//Setup settings global before binding actions
|
||||||
watch_settings_file(default_settings, settings_file, themes.clone(), cx);
|
cx.set_global(settings_file);
|
||||||
|
watch_settings_file(default_settings, settings_file_content, themes.clone(), cx);
|
||||||
watch_keymap_file(keymap_file, cx);
|
watch_keymap_file(keymap_file, cx);
|
||||||
|
|
||||||
context_menu::init(cx);
|
context_menu::init(cx);
|
||||||
|
@ -200,23 +202,23 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_paths() {
|
fn init_paths() {
|
||||||
fs::create_dir_all(&*zed::paths::CONFIG_DIR).expect("could not create config path");
|
std::fs::create_dir_all(&*zed::paths::CONFIG_DIR).expect("could not create config path");
|
||||||
fs::create_dir_all(&*zed::paths::LANGUAGES_DIR).expect("could not create languages path");
|
std::fs::create_dir_all(&*zed::paths::LANGUAGES_DIR).expect("could not create languages path");
|
||||||
fs::create_dir_all(&*zed::paths::DB_DIR).expect("could not create database path");
|
std::fs::create_dir_all(&*zed::paths::DB_DIR).expect("could not create database path");
|
||||||
fs::create_dir_all(&*zed::paths::LOGS_DIR).expect("could not create logs path");
|
std::fs::create_dir_all(&*zed::paths::LOGS_DIR).expect("could not create logs path");
|
||||||
|
|
||||||
// Copy setting files from legacy locations. TODO: remove this after a few releases.
|
// Copy setting files from legacy locations. TODO: remove this after a few releases.
|
||||||
thread::spawn(|| {
|
thread::spawn(|| {
|
||||||
if fs::metadata(&*zed::paths::legacy::SETTINGS).is_ok()
|
if std::fs::metadata(&*zed::paths::legacy::SETTINGS).is_ok()
|
||||||
&& fs::metadata(&*zed::paths::SETTINGS).is_err()
|
&& std::fs::metadata(&*zed::paths::SETTINGS).is_err()
|
||||||
{
|
{
|
||||||
fs::copy(&*zed::paths::legacy::SETTINGS, &*zed::paths::SETTINGS).log_err();
|
std::fs::copy(&*zed::paths::legacy::SETTINGS, &*zed::paths::SETTINGS).log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs::metadata(&*zed::paths::legacy::KEYMAP).is_ok()
|
if std::fs::metadata(&*zed::paths::legacy::KEYMAP).is_ok()
|
||||||
&& fs::metadata(&*zed::paths::KEYMAP).is_err()
|
&& std::fs::metadata(&*zed::paths::KEYMAP).is_err()
|
||||||
{
|
{
|
||||||
fs::copy(&*zed::paths::legacy::KEYMAP, &*zed::paths::KEYMAP).log_err();
|
std::fs::copy(&*zed::paths::legacy::KEYMAP, &*zed::paths::KEYMAP).log_err();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -231,9 +233,10 @@ fn init_logger() {
|
||||||
const KIB: u64 = 1024;
|
const KIB: u64 = 1024;
|
||||||
const MIB: u64 = 1024 * KIB;
|
const MIB: u64 = 1024 * KIB;
|
||||||
const MAX_LOG_BYTES: u64 = MIB;
|
const MAX_LOG_BYTES: u64 = MIB;
|
||||||
if fs::metadata(&*zed::paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
|
if std::fs::metadata(&*zed::paths::LOG)
|
||||||
|
.map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
|
||||||
{
|
{
|
||||||
let _ = fs::rename(&*zed::paths::LOG, &*zed::paths::OLD_LOG);
|
let _ = std::fs::rename(&*zed::paths::LOG, &*zed::paths::OLD_LOG);
|
||||||
}
|
}
|
||||||
|
|
||||||
let log_file = OpenOptions::new()
|
let log_file = OpenOptions::new()
|
||||||
|
@ -289,7 +292,7 @@ fn init_panic_hook(app_version: String, http: Arc<dyn HttpClient>, background: A
|
||||||
.body(body.into())?;
|
.body(body.into())?;
|
||||||
let response = http.send(request).await.context("error sending panic")?;
|
let response = http.send(request).await.context("error sending panic")?;
|
||||||
if response.status().is_success() {
|
if response.status().is_success() {
|
||||||
fs::remove_file(child_path)
|
std::fs::remove_file(child_path)
|
||||||
.context("error removing panic after sending it successfully")
|
.context("error removing panic after sending it successfully")
|
||||||
.log_err();
|
.log_err();
|
||||||
} else {
|
} else {
|
||||||
|
@ -338,7 +341,7 @@ fn init_panic_hook(app_version: String, http: Arc<dyn HttpClient>, background: A
|
||||||
};
|
};
|
||||||
|
|
||||||
let panic_filename = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
|
let panic_filename = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
|
||||||
fs::write(
|
std::fs::write(
|
||||||
zed::paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
|
zed::paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
|
||||||
&message,
|
&message,
|
||||||
)
|
)
|
||||||
|
@ -395,7 +398,7 @@ fn stdout_is_a_pty() -> bool {
|
||||||
fn collect_path_args() -> Vec<PathBuf> {
|
fn collect_path_args() -> Vec<PathBuf> {
|
||||||
env::args()
|
env::args()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.filter_map(|arg| match fs::canonicalize(arg) {
|
.filter_map(|arg| match std::fs::canonicalize(arg) {
|
||||||
Ok(path) => Some(path),
|
Ok(path) => Some(path),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!("error parsing path argument: {}", error);
|
log::error!("error parsing path argument: {}", error);
|
||||||
|
|
|
@ -2,7 +2,6 @@ mod feedback;
|
||||||
pub mod languages;
|
pub mod languages;
|
||||||
pub mod menus;
|
pub mod menus;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod settings_file;
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
|
@ -10,10 +9,11 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use breadcrumbs::Breadcrumbs;
|
use breadcrumbs::Breadcrumbs;
|
||||||
pub use client;
|
pub use client;
|
||||||
use collab_ui::CollabTitlebarItem;
|
use collab_ui::{CollabTitlebarItem, ToggleCollaborationMenu};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
pub use editor;
|
pub use editor;
|
||||||
use editor::{Editor, MultiBuffer};
|
use editor::{Editor, MultiBuffer};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
|
@ -23,7 +23,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::Rope;
|
use language::Rope;
|
||||||
pub use lsp;
|
pub use lsp;
|
||||||
pub use project::{self, fs};
|
pub use project;
|
||||||
use project_panel::ProjectPanel;
|
use project_panel::ProjectPanel;
|
||||||
use search::{BufferSearchBar, ProjectSearchBar};
|
use search::{BufferSearchBar, ProjectSearchBar};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -94,6 +94,22 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
|
||||||
cx.toggle_full_screen();
|
cx.toggle_full_screen();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
cx.add_action(
|
||||||
|
|workspace: &mut Workspace,
|
||||||
|
_: &ToggleCollaborationMenu,
|
||||||
|
cx: &mut ViewContext<Workspace>| {
|
||||||
|
if let Some(item) = workspace
|
||||||
|
.titlebar_item()
|
||||||
|
.and_then(|item| item.downcast::<CollabTitlebarItem>())
|
||||||
|
{
|
||||||
|
cx.as_mut().defer(move |cx| {
|
||||||
|
item.update(cx, |item, cx| {
|
||||||
|
item.toggle_contacts_popover(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
cx.add_global_action(quit);
|
cx.add_global_action(quit);
|
||||||
cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
|
cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
|
||||||
cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
|
cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue