diff --git a/Cargo.lock b/Cargo.lock index 1b946b4084c5bb6d7c800c593797877917dd3907..68516021b1f96dc469c51461c21b4465545c09d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,17 +705,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "cache-padded" version = "1.2.0" @@ -1577,8 +1566,13 @@ dependencies = [ "async-trait", "collections", "gpui", + "lazy_static", + "log", "parking_lot 0.11.2", - "rocksdb", + "rusqlite", + "rusqlite_migration", + "serde", + "serde_rusqlite", "tempdir", ] @@ -1901,6 +1895,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.8.0" @@ -3096,17 +3096,14 @@ dependencies = [ ] [[package]] -name = "librocksdb-sys" -version = "0.7.1+7.3.1" -source = "git+https://github.com/rust-rocksdb/rust-rocksdb?rev=39dc822dde743b2a26eb160b660e8fbdab079d49#39dc822dde743b2a26eb160b660e8fbdab079d49" +name = "libsqlite3-sys" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35" dependencies = [ - "bindgen", - "bzip2-sys", "cc", - "glob", - "libc", - "libz-sys", - "zstd-sys", + "pkg-config", + "vcpkg", ] [[package]] @@ -4254,7 +4251,6 @@ dependencies = [ "pulldown-cmark", "rand 0.8.5", "regex", - "rocksdb", "rpc", "serde", "serde_json", @@ -4763,15 +4759,6 @@ dependencies = [ "rmp", ] -[[package]] -name = "rocksdb" -version = "0.18.0" -source = "git+https://github.com/rust-rocksdb/rust-rocksdb?rev=39dc822dde743b2a26eb160b660e8fbdab079d49#39dc822dde743b2a26eb160b660e8fbdab079d49" -dependencies = [ - "libc", - "librocksdb-sys", -] - [[package]] name = "rope" version = "0.1.0" @@ -4843,6 +4830,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "serde_json", + "smallvec", +] + +[[package]] +name = "rusqlite_migration" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda44233be97aea786691f9f6f7ef230bcf905061f4012e90f4f39e6dcf31163" +dependencies = [ + "log", + "rusqlite", +] + [[package]] name = "rust-embed" version = "6.4.1" @@ -5238,6 +5250,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538b51f10ee271375cbd9caa04fa6e3e50af431a21db97caae48da92a074244a" +dependencies = [ + "rusqlite", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5844,6 +5866,7 @@ dependencies = [ "futures 0.3.24", "gpui", "itertools", + "language", "lazy_static", "libc", "mio-extras", @@ -7546,7 +7569,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.60.4" +version = "0.61.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 8d2a3fcc407aba85766a3be828a4fc394eca3f38..7e3623af98b466ec9f79b9490f2bbd64f8c455cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,6 @@ cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } -# TODO - Remove when a new version of RustRocksDB is released -rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "39dc822dde743b2a26eb160b660e8fbdab079d49" } [profile.dev] split-debuginfo = "unpacked" diff --git a/Procfile b/Procfile index a64b411ef3224a8b6bbf4596bff109b125462bf4..e1b87dd48b6eea968a4dfa24f468f5c5acb7f3c0 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -web: cd ../zed.dev && PORT=3000 npx next dev +web: cd ../zed.dev && PORT=3000 npx vercel dev collab: cd crates/collab && cargo run diff --git a/assets/settings/default.json b/assets/settings/default.json index 2ccd2c5f973b4578d62b1639c643c75d020f5a60..51aa108cd98a7bd5d16af5882b4293cca112239c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -10,6 +10,8 @@ // Whether to show the informational hover box when moving the mouse // over symbols in the editor. "hover_popover_enabled": true, + // Whether the cursor blinks in the editor. + "cursor_blink": true, // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 596e9ba9959a6cef00a145cfa153284ba86c32a3..cc788c1e482d58a9a49425e9e46adc288368f5a9 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -46,6 +46,7 @@ impl ActivityIndicator { cx: &mut ViewContext, ) -> ViewHandle { let project = workspace.project().clone(); + let auto_updater = AutoUpdater::get(cx); let this = cx.add_view(|cx: &mut ViewContext| { let mut status_events = languages.language_server_binary_statuses(); cx.spawn_weak(|this, mut cx| async move { @@ -66,11 +67,14 @@ impl ActivityIndicator { }) .detach(); cx.observe(&project, |_, _, cx| cx.notify()).detach(); + if let Some(auto_updater) = auto_updater.as_ref() { + cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); + } Self { statuses: Default::default(), project: project.clone(), - auto_updater: AutoUpdater::get(cx), + auto_updater, } }); cx.subscribe(&this, move |workspace, _, event, cx| match event { diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index d32835547c6c1ba5903eb9251fa776d5a7bc5544..efe36ccab8ae913e3b402d1f38e7d883489fd923 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -40,7 +40,7 @@ pub struct AutoUpdater { current_version: AppVersion, http_client: Arc, pending_poll: Option>, - db: Arc, + db: project::Db, server_url: String, } @@ -55,7 +55,7 @@ impl Entity for AutoUpdater { } pub fn init( - db: Arc, + db: project::Db, http_client: Arc, server_url: String, cx: &mut MutableAppContext, @@ -116,7 +116,7 @@ impl AutoUpdater { fn new( current_version: AppVersion, - db: Arc, + db: project::Db, http_client: Arc, server_url: String, ) -> Self { @@ -283,9 +283,9 @@ impl AutoUpdater { let db = self.db.clone(); cx.background().spawn(async move { if should_show { - db.write([(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY, "")])?; + db.write_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY, "")?; } else { - db.delete([(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)])?; + db.delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?; } Ok(()) }) @@ -293,8 +293,7 @@ impl AutoUpdater { fn should_show_update_notification(&self, cx: &AppContext) -> Task> { let db = self.db.clone(); - cx.background().spawn(async move { - Ok(db.read([(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)])?[0].is_some()) - }) + cx.background() + .spawn(async move { Ok(db.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?.is_some()) }) } } diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 7670a0e2391eb27834b0225d341317235e5ecf80..86946b797488cf27e008bcae202d4c81e5646688 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -10,7 +10,7 @@ use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate}; use postage::watch; use project::Project; -use std::sync::Arc; +use std::{os::unix::prelude::OsStrExt, sync::Arc}; use util::ResultExt; #[derive(Clone, Debug, PartialEq, Eq)] @@ -536,6 +536,7 @@ impl Room { id: worktree.id().to_proto(), root_name: worktree.root_name().into(), visible: worktree.is_visible(), + abs_path: worktree.abs_path().as_os_str().as_bytes().to_vec(), } }) .collect(), diff --git a/crates/chat_panel/src/chat_panel.rs b/crates/chat_panel/src/chat_panel.rs index 0c22c21c7117ba59543d87dff65ebfe143b28a7c..ccd33db3abe4b8eb42f9ac5abf612cfa947303b0 100644 --- a/crates/chat_panel/src/chat_panel.rs +++ b/crates/chat_panel/src/chat_panel.rs @@ -397,7 +397,7 @@ impl View for ChatPanel { .boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if matches!( *self.rpc.status().borrow(), client::Status::Connected { .. } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index cc6bdf627930c31f6e574e19e5f6dbb6aebdc7f4..01f54c0e146a1796701a495feccf9425ae3a2914 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -143,11 +143,16 @@ pub enum Status { Authenticating, Connecting, ConnectionError, - Connected { connection_id: ConnectionId }, + Connected { + peer_id: PeerId, + connection_id: ConnectionId, + }, ConnectionLost, Reauthenticating, Reconnecting, - ReconnectionError { next_reconnection: Instant }, + ReconnectionError { + next_reconnection: Instant, + }, } impl Status { @@ -314,6 +319,14 @@ impl Client { .map(|credentials| credentials.user_id) } + pub fn peer_id(&self) -> Option { + if let Status::Connected { peer_id, .. } = &*self.status().borrow() { + Some(*peer_id) + } else { + None + } + } + pub fn status(&self) -> watch::Receiver { self.state.read().status.1.clone() } @@ -663,6 +676,7 @@ impl Client { self.set_status(Status::Reconnecting, cx); } + let mut timeout = cx.background().timer(CONNECTION_TIMEOUT).fuse(); futures::select_biased! { connection = self.establish_connection(&credentials, cx).fuse() => { match connection { @@ -671,8 +685,14 @@ impl Client { if !read_from_keychain && IMPERSONATE_LOGIN.is_none() { write_credentials_to_keychain(&credentials, cx).log_err(); } - self.set_connection(conn, cx); - Ok(()) + + futures::select_biased! { + result = self.set_connection(conn, cx).fuse() => result, + _ = timeout => { + self.set_status(Status::ConnectionError, cx); + Err(anyhow!("timed out waiting on hello message from server")) + } + } } Err(EstablishConnectionError::Unauthorized) => { self.state.write().credentials.take(); @@ -695,21 +715,65 @@ impl Client { } } } - _ = cx.background().timer(CONNECTION_TIMEOUT).fuse() => { + _ = &mut timeout => { self.set_status(Status::ConnectionError, cx); Err(anyhow!("timed out trying to establish connection")) } } } - fn set_connection(self: &Arc, conn: Connection, cx: &AsyncAppContext) { + async fn set_connection( + self: &Arc, + conn: Connection, + cx: &AsyncAppContext, + ) -> Result<()> { let executor = cx.background(); log::info!("add connection to peer"); let (connection_id, handle_io, mut incoming) = self .peer .add_connection(conn, move |duration| executor.timer(duration)); - log::info!("set status to connected {}", connection_id); - self.set_status(Status::Connected { connection_id }, cx); + let handle_io = cx.background().spawn(handle_io); + + let peer_id = async { + log::info!("waiting for server hello"); + let message = incoming + .next() + .await + .ok_or_else(|| anyhow!("no hello message received"))?; + log::info!("got server hello"); + let hello_message_type_name = message.payload_type_name().to_string(); + let hello = message + .into_any() + .downcast::>() + .map_err(|_| { + anyhow!( + "invalid hello message received: {:?}", + hello_message_type_name + ) + })?; + Ok(PeerId(hello.payload.peer_id)) + }; + + let peer_id = match peer_id.await { + Ok(peer_id) => peer_id, + Err(error) => { + self.peer.disconnect(connection_id); + return Err(error); + } + }; + + log::info!( + "set status to connected (connection id: {}, peer id: {})", + connection_id, + peer_id + ); + self.set_status( + Status::Connected { + peer_id, + connection_id, + }, + cx, + ); cx.foreground() .spawn({ let cx = cx.clone(); @@ -807,14 +871,18 @@ impl Client { }) .detach(); - let handle_io = cx.background().spawn(handle_io); let this = self.clone(); let cx = cx.clone(); cx.foreground() .spawn(async move { match handle_io.await { Ok(()) => { - if *this.status().borrow() == (Status::Connected { connection_id }) { + if *this.status().borrow() + == (Status::Connected { + connection_id, + peer_id, + }) + { this.set_status(Status::SignedOut, &cx); } } @@ -825,6 +893,8 @@ impl Client { } }) .detach(); + + Ok(()) } fn authenticate(self: &Arc, cx: &AsyncAppContext) -> Task> { @@ -1072,7 +1142,7 @@ impl Client { self.peer.respond_with_error(receipt, error) } - pub fn start_telemetry(&self, db: Arc) { + pub fn start_telemetry(&self, db: Db) { self.telemetry.start(db); } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 0c162580d41491fb1b54a548639170688aac22c7..6829eab53150901755c1f3d89025f3076acd34f0 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -135,22 +135,16 @@ impl Telemetry { Some(self.state.lock().log_file.as_ref()?.path().to_path_buf()) } - pub fn start(self: &Arc, db: Arc) { + pub fn start(self: &Arc, db: Db) { let this = self.clone(); self.executor .spawn( async move { - let device_id = if let Some(device_id) = db - .read(["device_id"])? - .into_iter() - .flatten() - .next() - .and_then(|bytes| String::from_utf8(bytes).ok()) - { + let device_id = if let Ok(Some(device_id)) = db.read_kvp("device_id") { device_id } else { let device_id = Uuid::new_v4().to_string(); - db.write([("device_id", device_id.as_bytes())])?; + db.write_kvp("device_id", &device_id)?; device_id }; diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index ade21f02f4a366de62450309c9f5c75160d25370..3cfba3b1847c4af4655ad625840492db59249974 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -84,9 +84,19 @@ impl FakeServer { let (connection_id, io, incoming) = peer.add_test_connection(server_conn, cx.background()); cx.background().spawn(io).detach(); - let mut state = state.lock(); - state.connection_id = Some(connection_id); - state.incoming = Some(incoming); + { + let mut state = state.lock(); + state.connection_id = Some(connection_id); + state.incoming = Some(incoming); + } + peer.send( + connection_id, + proto::Hello { + peer_id: connection_id.0, + }, + ) + .unwrap(); + Ok(client_conn) }) } diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 44a70e71e92e2ccf26699bc82e9e002a4f2f1e48..cd08dd8632288d3e62adb855a5f29ed344e98322 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -8,15 +8,18 @@ use anyhow::anyhow; use call::{room, ActiveCall, ParticipantLocation, Room}; use client::{ self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection, - Credentials, EstablishConnectionError, User, UserStore, RECEIVE_TIMEOUT, + Credentials, EstablishConnectionError, PeerId, User, UserStore, RECEIVE_TIMEOUT, }; use collections::{BTreeMap, HashMap, HashSet}; use editor::{ self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset, ToggleCodeActions, Undo, }; -use fs::{FakeFs, Fs as _, LineEnding}; -use futures::{channel::mpsc, Future, StreamExt as _}; +use fs::{FakeFs, Fs as _, HomeDir, LineEnding}; +use futures::{ + channel::{mpsc, oneshot}, + Future, StreamExt as _, +}; use gpui::{ executor::{self, Deterministic}, geometry::vector::vec2f, @@ -34,7 +37,6 @@ use project::{ ProjectStore, WorktreeId, }; use rand::prelude::*; -use rpc::PeerId; use serde_json::json; use settings::{Formatter, Settings}; use sqlx::types::time::OffsetDateTime; @@ -385,7 +387,7 @@ async fn test_leaving_room_on_disconnection( ); // When user A disconnects, both client A and B clear their room on the active call. - server.disconnect_client(client_a.current_user_id(cx_a)); + server.disconnect_client(client_a.peer_id().unwrap()); cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT); active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none())); active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none())); @@ -416,7 +418,7 @@ async fn test_calls_on_multiple_connections( let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b1 = server.create_client(cx_b1, "user_b").await; - let _client_b2 = server.create_client(cx_b2, "user_b").await; + let client_b2 = server.create_client(cx_b2, "user_b").await; server .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)]) .await; @@ -468,6 +470,14 @@ async fn test_calls_on_multiple_connections( assert!(incoming_call_b1.next().await.unwrap().is_none()); assert!(incoming_call_b2.next().await.unwrap().is_none()); + // User B disconnects the client that is not on the call. Everything should be fine. + client_b1.disconnect(&cx_b1.to_async()).unwrap(); + deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); + client_b1 + .authenticate_and_connect(false, &cx_b1.to_async()) + .await + .unwrap(); + // User B hangs up, and user A calls them again. active_call_b2.update(cx_b2, |call, cx| call.hang_up(cx).unwrap()); deterministic.run_until_parked(); @@ -520,11 +530,29 @@ async fn test_calls_on_multiple_connections( assert!(incoming_call_b1.next().await.unwrap().is_some()); assert!(incoming_call_b2.next().await.unwrap().is_some()); - // User A disconnects up, causing both connections to stop ringing. - server.disconnect_client(client_a.current_user_id(cx_a)); - cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT); + // User A disconnects, causing both connections to stop ringing. + server.disconnect_client(client_a.peer_id().unwrap()); + deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); assert!(incoming_call_b1.next().await.unwrap().is_none()); assert!(incoming_call_b2.next().await.unwrap().is_none()); + + // User A reconnects automatically, then calls user B again. + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_b1.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + deterministic.run_until_parked(); + assert!(incoming_call_b1.next().await.unwrap().is_some()); + assert!(incoming_call_b2.next().await.unwrap().is_some()); + + // User B disconnects all clients, causing user A to no longer see a pending call for them. + server.forbid_connections(); + server.disconnect_client(client_b1.peer_id().unwrap()); + server.disconnect_client(client_b2.peer_id().unwrap()); + deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); + active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none())); } #[gpui::test(iterations = 10)] @@ -582,7 +610,7 @@ async fn test_share_project( .update(cx_b, |call, cx| call.accept_incoming(cx)) .await .unwrap(); - let client_b_peer_id = client_b.peer_id; + let client_b_peer_id = client_b.peer_id().unwrap(); let project_b = client_b .build_remote_project(initial_project.id, cx_b) .await; @@ -806,7 +834,7 @@ async fn test_host_disconnect( assert!(cx_b.is_window_edited(workspace_b.window_id())); // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. - server.disconnect_client(client_a.current_user_id(cx_a)); + server.disconnect_client(client_a.peer_id().unwrap()); deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); project_a .condition(cx_a, |project, _| project.collaborators().is_empty()) @@ -849,7 +877,7 @@ async fn test_host_disconnect( .unwrap(); // Drop client A's connection again. We should still unshare it successfully. - server.disconnect_client(client_a.current_user_id(cx_a)); + server.disconnect_client(client_a.peer_id().unwrap()); deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); } @@ -2150,7 +2178,7 @@ async fn test_leaving_project( // Simulate connection loss for client C and ensure client A observes client C leaving the project. client_c.wait_for_current_user(cx_c).await; - server.disconnect_client(client_c.current_user_id(cx_c)); + server.disconnect_client(client_c.peer_id().unwrap()); cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT); deterministic.run_until_parked(); project_a.read_with(cx_a, |project, _| { @@ -3038,7 +3066,7 @@ async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { assert_eq!(references[1].buffer, references[0].buffer); assert_eq!( three_buffer.file().unwrap().full_path(cx), - Path::new("three.rs") + Path::new("/root/dir-2/three.rs") ); assert_eq!(references[0].range.to_offset(two_buffer), 24..27); @@ -4313,7 +4341,7 @@ async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon // Disconnect client B, ensuring we can still access its cached channel data. server.forbid_connections(); - server.disconnect_client(client_b.current_user_id(cx_b)); + server.disconnect_client(client_b.peer_id().unwrap()); cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT); while !matches!( status_b.next().await, @@ -4476,7 +4504,7 @@ async fn test_contacts( ] ); - server.disconnect_client(client_c.current_user_id(cx_c)); + server.disconnect_client(client_c.peer_id().unwrap()); server.forbid_connections(); deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); assert_eq!( @@ -4716,7 +4744,7 @@ async fn test_contacts( ); server.forbid_connections(); - server.disconnect_client(client_a.current_user_id(cx_a)); + server.disconnect_client(client_a.peer_id().unwrap()); deterministic.advance_clock(rpc::RECEIVE_TIMEOUT); assert_eq!(contacts(&client_a, cx_a), []); assert_eq!( @@ -5626,6 +5654,7 @@ async fn test_random_collaboration( let mut clients = Vec::new(); let mut user_ids = Vec::new(); + let mut peer_ids = Vec::new(); let mut op_start_signals = Vec::new(); let mut next_entity_id = 100000; @@ -5814,6 +5843,7 @@ async fn test_random_collaboration( let op_start_signal = futures::channel::mpsc::unbounded(); user_ids.push(host_user_id); + peer_ids.push(host.peer_id().unwrap()); op_start_signals.push(op_start_signal.0); clients.push(host_cx.foreground().spawn(host.simulate_host( host_project, @@ -5831,7 +5861,7 @@ async fn test_random_collaboration( let mut operations = 0; while operations < max_operations { if operations == disconnect_host_at { - server.disconnect_client(user_ids[0]); + server.disconnect_client(peer_ids[0]); deterministic.advance_clock(RECEIVE_TIMEOUT); drop(op_start_signals); @@ -5914,6 +5944,7 @@ async fn test_random_collaboration( let op_start_signal = futures::channel::mpsc::unbounded(); user_ids.push(guest_user_id); + peer_ids.push(guest.peer_id().unwrap()); op_start_signals.push(op_start_signal.0); clients.push(guest_cx.foreground().spawn(guest.simulate_guest( guest_username.clone(), @@ -5930,10 +5961,11 @@ async fn test_random_collaboration( let guest_ix = rng.lock().gen_range(1..clients.len()); log::info!("Removing guest {}", user_ids[guest_ix]); let removed_guest_id = user_ids.remove(guest_ix); + let removed_peer_id = peer_ids.remove(guest_ix); let guest = clients.remove(guest_ix); op_start_signals.remove(guest_ix); server.forbid_connections(); - server.disconnect_client(removed_guest_id); + server.disconnect_client(removed_peer_id); deterministic.advance_clock(RECEIVE_TIMEOUT); deterministic.start_waiting(); log::info!("Waiting for guest {} to exit...", removed_guest_id); @@ -6057,8 +6089,10 @@ async fn test_random_collaboration( let host_buffer = host_project.read_with(&host_cx, |project, cx| { project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| { panic!( - "host does not have buffer for guest:{}, peer:{}, id:{}", - guest_client.username, guest_client.peer_id, buffer_id + "host does not have buffer for guest:{}, peer:{:?}, id:{}", + guest_client.username, + guest_client.peer_id(), + buffer_id ) }) }); @@ -6101,7 +6135,7 @@ struct TestServer { server: Arc, foreground: Rc, notifications: mpsc::UnboundedReceiver<()>, - connection_killers: Arc>>>, + connection_killers: Arc>>>, forbid_connections: Arc, _test_db: TestDb, } @@ -6130,6 +6164,8 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { + cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); + let mut settings = Settings::test(cx); settings.projects_online_by_default = false; cx.set_global(settings); @@ -6165,7 +6201,6 @@ impl TestServer { let db = self.app_state.db.clone(); let connection_killers = self.connection_killers.clone(); let forbid_connections = self.forbid_connections.clone(); - let (connection_id_tx, mut connection_id_rx) = mpsc::channel(16); Arc::get_mut(&mut client) .unwrap() @@ -6188,7 +6223,6 @@ impl TestServer { let connection_killers = connection_killers.clone(); let forbid_connections = forbid_connections.clone(); let client_name = client_name.clone(); - let connection_id_tx = connection_id_tx.clone(); cx.spawn(move |cx| async move { if forbid_connections.load(SeqCst) { Err(EstablishConnectionError::other(anyhow!( @@ -6197,7 +6231,7 @@ impl TestServer { } else { let (client_conn, server_conn, killed) = Connection::in_memory(cx.background()); - connection_killers.lock().insert(user_id, killed); + let (connection_id_tx, connection_id_rx) = oneshot::channel(); let user = db.get_user_by_id(user_id).await.unwrap().unwrap(); cx.background() .spawn(server.handle_connection( @@ -6208,6 +6242,10 @@ impl TestServer { cx.background(), )) .detach(); + let connection_id = connection_id_rx.await.unwrap(); + connection_killers + .lock() + .insert(PeerId(connection_id.0), killed); Ok(client_conn) } }) @@ -6239,11 +6277,9 @@ impl TestServer { .authenticate_and_connect(false, &cx.to_async()) .await .unwrap(); - let peer_id = PeerId(connection_id_rx.next().await.unwrap().0); let client = TestClient { client, - peer_id, username: name.to_string(), user_store, project_store, @@ -6255,10 +6291,10 @@ impl TestServer { client } - fn disconnect_client(&self, user_id: UserId) { + fn disconnect_client(&self, peer_id: PeerId) { self.connection_killers .lock() - .remove(&user_id) + .remove(&peer_id) .unwrap() .store(true, SeqCst); } @@ -6360,7 +6396,6 @@ impl Drop for TestServer { struct TestClient { client: Arc, username: String, - pub peer_id: PeerId, pub user_store: ModelHandle, pub project_store: ModelHandle, language_registry: Arc, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index bca9cad12f2bc45ff557fb0ad9393a85aa4ce609..8ef3d7bbfc4f527f2308004f08b51d7d4f4e392f 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -24,7 +24,7 @@ use axum::{ }; use collections::{HashMap, HashSet}; use futures::{ - channel::mpsc, + channel::{mpsc, oneshot}, future::{self, BoxFuture}, stream::FuturesUnordered, FutureExt, SinkExt, StreamExt, TryStreamExt, @@ -42,6 +42,7 @@ use std::{ marker::PhantomData, net::SocketAddr, ops::{Deref, DerefMut}, + os::unix::prelude::OsStrExt, rc::Rc, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -346,7 +347,7 @@ impl Server { connection: Connection, address: String, user: User, - mut send_connection_id: Option>, + mut send_connection_id: Option>, executor: E, ) -> impl Future> { let mut this = self.clone(); @@ -367,9 +368,11 @@ impl Server { }); tracing::info!(%user_id, %login, %connection_id, %address, "connection opened"); + this.peer.send(connection_id, proto::Hello { peer_id: connection_id.0 })?; + tracing::info!(%user_id, %login, %connection_id, %address, "sent hello message"); - if let Some(send_connection_id) = send_connection_id.as_mut() { - let _ = send_connection_id.send(connection_id).await; + if let Some(send_connection_id) = send_connection_id.take() { + let _ = send_connection_id.send(connection_id); } if !user.connected_once { @@ -476,6 +479,10 @@ impl Server { let mut room_left = None; { let mut store = self.store().await; + + #[cfg(test)] + let removed_connection = store.remove_connection(connection_id).unwrap(); + #[cfg(not(test))] let removed_connection = store.remove_connection(connection_id)?; for project in removed_connection.hosted_projects { @@ -1014,6 +1021,7 @@ impl Server { id: *id, root_name: worktree.root_name.clone(), visible: worktree.visible, + abs_path: worktree.abs_path.as_os_str().as_bytes().to_vec(), }) .collect::>(); @@ -1062,6 +1070,7 @@ impl Server { let message = proto::UpdateWorktree { project_id: project_id.to_proto(), worktree_id: *worktree_id, + abs_path: worktree.abs_path.as_os_str().as_bytes().to_vec(), root_name: worktree.root_name.clone(), updated_entries: worktree.entries.values().cloned().collect(), removed_entries: Default::default(), diff --git a/crates/collab/src/rpc/store.rs b/crates/collab/src/rpc/store.rs index 6fa84d7f10af165950cb6b3e5827f40cc648ef0d..9a2e8cbddae53af67551cf63e1658f29c58ce87f 100644 --- a/crates/collab/src/rpc/store.rs +++ b/crates/collab/src/rpc/store.rs @@ -67,6 +67,7 @@ pub struct Collaborator { #[derive(Default, Serialize)] pub struct Worktree { + pub abs_path: PathBuf, pub root_name: String, pub visible: bool, #[serde(skip)] @@ -215,11 +216,16 @@ impl Store { let connected_user = self.connected_users.get(&user_id).unwrap(); if let Some(active_call) = connected_user.active_call.as_ref() { let room_id = active_call.room_id; - let left_room = self.leave_room(room_id, connection_id)?; - result.hosted_projects = left_room.unshared_projects; - result.guest_projects = left_room.left_projects; - result.room = Some(Cow::Owned(left_room.room.into_owned())); - result.canceled_call_connection_ids = left_room.canceled_call_connection_ids; + if active_call.connection_id == Some(connection_id) { + let left_room = self.leave_room(room_id, connection_id)?; + result.hosted_projects = left_room.unshared_projects; + result.guest_projects = left_room.left_projects; + result.room = Some(Cow::Owned(left_room.room.into_owned())); + result.canceled_call_connection_ids = left_room.canceled_call_connection_ids; + } else if connected_user.connection_ids.len() == 1 { + let (room, _) = self.decline_call(room_id, connection_id)?; + result.room = Some(Cow::Owned(room.clone())); + } } let connected_user = self.connected_users.get_mut(&user_id).unwrap(); diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 702d8a9121e27b8ddaf6da7774807eed86b1b202..1279d3043748e7b8186364f926884faac0a3785b 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -526,18 +526,6 @@ impl Element for AvatarRibbon { cx.scene.push_path(path.build(self.color, None)); } - fn dispatch_event( - &mut self, - _: &gpui::Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut gpui::EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index a4ec02d2f00f20631206a3d0a2b868e4f226ae22..5165c3b1f690cc9eeeca491f4ebad7bb897be89e 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -36,7 +36,7 @@ impl View for ContactFinder { ChildView::new(self.picker.clone(), cx).boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.picker); } diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index cf8a8f8223e7ba5a0f06e548a59c4c34d3620ae8..7a51cc83ec5877e9b5485edfcbb5fc78515d6476 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -1121,13 +1121,13 @@ impl View for ContactList { .boxed() } - fn on_focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if !self.filter_editor.is_focused(cx) { cx.focus(&self.filter_editor); } } - fn on_focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if !self.filter_editor.is_focused(cx) { cx.emit(Event::Dismissed); } diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index 075255d72750bee57f8372b39570013b15d3a8a2..37280f929e7c9a5536588a4543d26f8fb8214282 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -160,7 +160,7 @@ impl View for ContactsPopover { .boxed() } - fn on_focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { match &self.child { Child::ContactList(child) => cx.focus(child), diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 7702aaaf2a414e1c6791131aedafff11d27f099a..51474be1bed9ac13a57e5c05f54561d5a31a040d 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -135,7 +135,7 @@ impl View for CommandPalette { ChildView::new(self.picker.clone(), cx).boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.picker); } diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index c28437596669ea8453a8f714c62a81657015e71a..96d99f5109d927416efe6f8b73b01f8ea09eb003 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -107,7 +107,7 @@ impl View for ContextMenu { .boxed() } - fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.reset(cx); } } @@ -315,13 +315,16 @@ impl ContextMenu { fn render_menu(&self, cx: &mut RenderContext) -> impl Element { enum Menu {} enum MenuItem {} + let style = cx.global::().theme.context_menu.clone(); + MouseEventHandler::::new(0, cx, |_, cx| { Flex::column() .with_children(self.items.iter().enumerate().map(|(ix, item)| { match item { ContextMenuItem::Item { label, action } => { let action = action.boxed_clone(); + MouseEventHandler::::new(ix, cx, |state, _| { let style = style.item.style_for(state, Some(ix) == self.selected_index); @@ -350,6 +353,7 @@ impl ContextMenu { cx.dispatch_action(Clicked); cx.dispatch_any_action(action.boxed_clone()); }) + .on_drag(MouseButton::Left, |_, _| {}) .boxed() } ContextMenuItem::Separator => Empty::new() diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index f4ed283b6e63944425857fedc99be7887ce5a7ac..1eeac03375e8cb8b40cc2ea2b710b5e927a9ff2d 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -14,8 +14,13 @@ test-support = [] collections = { path = "../collections" } anyhow = "1.0.57" async-trait = "0.1" +lazy_static = "1.4.0" +log = { version = "0.4.16", features = ["kv_unstable_serde"] } parking_lot = "0.11.1" -rocksdb = "0.18" +rusqlite = { version = "0.28.0", features = ["bundled", "serde_json"] } +rusqlite_migration = "1.0.0" +serde = { workspace = true } +serde_rusqlite = "0.31.0" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 0b630bf256147270577703d4d0df3b993d90b234..06776832b57649888b7f114f35948b86a5dee0f0 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -1,161 +1,119 @@ -use anyhow::Result; -use std::path::Path; +mod kvp; +mod migrations; + +use std::fs; +use std::path::{Path, PathBuf}; use std::sync::Arc; -pub struct Db(DbStore); +use anyhow::Result; +use log::error; +use parking_lot::Mutex; +use rusqlite::Connection; + +use migrations::MIGRATIONS; -enum DbStore { +#[derive(Clone)] +pub enum Db { + Real(Arc), Null, - Real(rocksdb::DB), +} - #[cfg(any(test, feature = "test-support"))] - Fake { - data: parking_lot::Mutex, Vec>>, - }, +pub struct RealDb { + connection: Mutex, + path: Option, } impl Db { - /// Open or create a database at the given file path. - pub fn open(path: &Path) -> Result> { - let db = rocksdb::DB::open_default(path)?; - Ok(Arc::new(Self(DbStore::Real(db)))) + /// Open or create a database at the given directory path. + pub fn open(db_dir: &Path) -> Self { + // Use 0 for now. Will implement incrementing and clearing of old db files soon TM + let current_db_dir = db_dir.join(Path::new("0")); + fs::create_dir_all(¤t_db_dir) + .expect("Should be able to create the database directory"); + let db_path = current_db_dir.join(Path::new("db.sqlite")); + + Connection::open(db_path) + .map_err(Into::into) + .and_then(|connection| Self::initialize(connection)) + .map(|connection| { + Db::Real(Arc::new(RealDb { + connection, + path: Some(db_dir.to_path_buf()), + })) + }) + .unwrap_or_else(|e| { + error!( + "Connecting to file backed db failed. Reverting to null db. {}", + e + ); + Self::Null + }) } - /// Open a null database that stores no data, for use as a fallback - /// when there is an error opening the real database. - pub fn null() -> Arc { - Arc::new(Self(DbStore::Null)) - } - - /// Open a fake database for testing. + /// Open a in memory database for testing and as a fallback. #[cfg(any(test, feature = "test-support"))] - pub fn open_fake() -> Arc { - Arc::new(Self(DbStore::Fake { - data: Default::default(), - })) + pub fn open_in_memory() -> Self { + Connection::open_in_memory() + .map_err(Into::into) + .and_then(|connection| Self::initialize(connection)) + .map(|connection| { + Db::Real(Arc::new(RealDb { + connection, + path: None, + })) + }) + .unwrap_or_else(|e| { + error!( + "Connecting to in memory db failed. Reverting to null db. {}", + e + ); + Self::Null + }) } - pub fn read(&self, keys: I) -> Result>>> - where - K: AsRef<[u8]>, - I: IntoIterator, - { - match &self.0 { - DbStore::Real(db) => db - .multi_get(keys) - .into_iter() - .map(|e| e.map_err(Into::into)) - .collect(), - - DbStore::Null => Ok(keys.into_iter().map(|_| None).collect()), - - #[cfg(any(test, feature = "test-support"))] - DbStore::Fake { data: db } => { - let db = db.lock(); - Ok(keys - .into_iter() - .map(|key| db.get(key.as_ref()).cloned()) - .collect()) - } - } - } + fn initialize(mut conn: Connection) -> Result> { + MIGRATIONS.to_latest(&mut conn)?; - pub fn delete(&self, keys: I) -> Result<()> - where - K: AsRef<[u8]>, - I: IntoIterator, - { - match &self.0 { - DbStore::Real(db) => { - let mut batch = rocksdb::WriteBatch::default(); - for key in keys { - batch.delete(key); - } - db.write(batch)?; - } + conn.pragma_update(None, "journal_mode", "WAL")?; + conn.pragma_update(None, "synchronous", "NORMAL")?; + conn.pragma_update(None, "foreign_keys", true)?; + conn.pragma_update(None, "case_sensitive_like", true)?; - DbStore::Null => {} + Ok(Mutex::new(conn)) + } - #[cfg(any(test, feature = "test-support"))] - DbStore::Fake { data: db } => { - let mut db = db.lock(); - for key in keys { - db.remove(key.as_ref()); - } - } - } - Ok(()) + pub fn persisting(&self) -> bool { + self.real().and_then(|db| db.path.as_ref()).is_some() } - pub fn write(&self, entries: I) -> Result<()> - where - K: AsRef<[u8]>, - V: AsRef<[u8]>, - I: IntoIterator, - { - match &self.0 { - DbStore::Real(db) => { - let mut batch = rocksdb::WriteBatch::default(); - for (key, value) in entries { - batch.put(key, value); - } - db.write(batch)?; - } + pub fn real(&self) -> Option<&RealDb> { + match self { + Db::Real(db) => Some(&db), + _ => None, + } + } +} - DbStore::Null => {} +impl Drop for Db { + fn drop(&mut self) { + match self { + Db::Real(real_db) => { + let lock = real_db.connection.lock(); - #[cfg(any(test, feature = "test-support"))] - DbStore::Fake { data: db } => { - let mut db = db.lock(); - for (key, value) in entries { - db.insert(key.as_ref().into(), value.as_ref().into()); - } + let _ = lock.pragma_update(None, "analysis_limit", "500"); + let _ = lock.pragma_update(None, "optimize", ""); } + Db::Null => {} } - Ok(()) } } #[cfg(test)] mod tests { - use super::*; - use tempdir::TempDir; - - #[gpui::test] - fn test_db() { - let dir = TempDir::new("db-test").unwrap(); - let fake_db = Db::open_fake(); - let real_db = Db::open(&dir.path().join("test.db")).unwrap(); - - for db in [&real_db, &fake_db] { - assert_eq!( - db.read(["key-1", "key-2", "key-3"]).unwrap(), - &[None, None, None] - ); - - db.write([("key-1", "one"), ("key-3", "three")]).unwrap(); - assert_eq!( - db.read(["key-1", "key-2", "key-3"]).unwrap(), - &[ - Some("one".as_bytes().to_vec()), - None, - Some("three".as_bytes().to_vec()) - ] - ); - - db.delete(["key-3", "key-4"]).unwrap(); - assert_eq!( - db.read(["key-1", "key-2", "key-3"]).unwrap(), - &[Some("one".as_bytes().to_vec()), None, None,] - ); - } - - drop(real_db); + use crate::migrations::MIGRATIONS; - let real_db = Db::open(&dir.path().join("test.db")).unwrap(); - assert_eq!( - real_db.read(["key-1", "key-2", "key-3"]).unwrap(), - &[Some("one".as_bytes().to_vec()), None, None,] - ); + #[test] + fn test_migrations() { + assert!(MIGRATIONS.validate().is_ok()); } } diff --git a/crates/db/src/items.rs b/crates/db/src/items.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a49cd5569768fb2614342f9e910ec10abe7f357 --- /dev/null +++ b/crates/db/src/items.rs @@ -0,0 +1,236 @@ +use std::{ffi::OsStr, os::unix::prelude::OsStrExt, path::PathBuf, sync::Arc}; + +use anyhow::Result; +use rusqlite::{ + named_params, params, + types::{FromSql, FromSqlError, FromSqlResult, ValueRef}, +}; + +use super::Db; + +pub(crate) const ITEMS_M_1: &str = " +CREATE TABLE items( + id INTEGER PRIMARY KEY, + kind TEXT +) STRICT; +CREATE TABLE item_path( + item_id INTEGER PRIMARY KEY, + path BLOB +) STRICT; +CREATE TABLE item_query( + item_id INTEGER PRIMARY KEY, + query TEXT +) STRICT; +"; + +#[derive(PartialEq, Eq, Hash, Debug)] +pub enum SerializedItemKind { + Editor, + Terminal, + ProjectSearch, + Diagnostics, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SerializedItem { + Editor(usize, PathBuf), + Terminal(usize), + ProjectSearch(usize, String), + Diagnostics(usize), +} + +impl FromSql for SerializedItemKind { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + match value { + ValueRef::Null => Err(FromSqlError::InvalidType), + ValueRef::Integer(_) => Err(FromSqlError::InvalidType), + ValueRef::Real(_) => Err(FromSqlError::InvalidType), + ValueRef::Text(bytes) => { + let str = std::str::from_utf8(bytes).map_err(|_| FromSqlError::InvalidType)?; + match str { + "Editor" => Ok(SerializedItemKind::Editor), + "Terminal" => Ok(SerializedItemKind::Terminal), + "ProjectSearch" => Ok(SerializedItemKind::ProjectSearch), + "Diagnostics" => Ok(SerializedItemKind::Diagnostics), + _ => Err(FromSqlError::InvalidType), + } + } + ValueRef::Blob(_) => Err(FromSqlError::InvalidType), + } + } +} + +impl SerializedItem { + fn kind(&self) -> SerializedItemKind { + match self { + SerializedItem::Editor(_, _) => SerializedItemKind::Editor, + SerializedItem::Terminal(_) => SerializedItemKind::Terminal, + SerializedItem::ProjectSearch(_, _) => SerializedItemKind::ProjectSearch, + SerializedItem::Diagnostics(_) => SerializedItemKind::Diagnostics, + } + } + + fn id(&self) -> usize { + match self { + SerializedItem::Editor(id, _) + | SerializedItem::Terminal(id) + | SerializedItem::ProjectSearch(id, _) + | SerializedItem::Diagnostics(id) => *id, + } + } +} + +impl Db { + fn write_item(&self, serialized_item: SerializedItem) -> Result<()> { + let mut lock = self.connection.lock(); + let tx = lock.transaction()?; + + // Serialize the item + let id = serialized_item.id(); + { + let kind = format!("{:?}", serialized_item.kind()); + + let mut stmt = + tx.prepare_cached("INSERT OR REPLACE INTO items(id, kind) VALUES ((?), (?))")?; + + stmt.execute(params![id, kind])?; + } + + // Serialize item data + match &serialized_item { + SerializedItem::Editor(_, path) => { + let mut stmt = tx.prepare_cached( + "INSERT OR REPLACE INTO item_path(item_id, path) VALUES ((?), (?))", + )?; + + let path_bytes = path.as_os_str().as_bytes(); + stmt.execute(params![id, path_bytes])?; + } + SerializedItem::ProjectSearch(_, query) => { + let mut stmt = tx.prepare_cached( + "INSERT OR REPLACE INTO item_query(item_id, query) VALUES ((?), (?))", + )?; + + stmt.execute(params![id, query])?; + } + _ => {} + } + + tx.commit()?; + + Ok(()) + } + + fn delete_item(&self, item_id: usize) -> Result<()> { + let lock = self.connection.lock(); + + let mut stmt = lock.prepare_cached( + " + DELETE FROM items WHERE id = (:id); + DELETE FROM item_path WHERE id = (:id); + DELETE FROM item_query WHERE id = (:id); + ", + )?; + + stmt.execute(named_params! {":id": item_id})?; + + Ok(()) + } + + fn take_items(&self) -> Result> { + let mut lock = self.connection.lock(); + + let tx = lock.transaction()?; + + // When working with transactions in rusqlite, need to make this kind of scope + // To make the borrow stuff work correctly. Don't know why, rust is wild. + let result = { + let mut read_stmt = tx.prepare_cached( + " + SELECT items.id, items.kind, item_path.path, item_query.query + FROM items + LEFT JOIN item_path + ON items.id = item_path.item_id + LEFT JOIN item_query + ON items.id = item_query.item_id + ORDER BY items.id + ", + )?; + + let result = read_stmt + .query_map([], |row| { + let id: usize = row.get(0)?; + let kind: SerializedItemKind = row.get(1)?; + + match kind { + SerializedItemKind::Editor => { + let buf: Vec = row.get(2)?; + let path: PathBuf = OsStr::from_bytes(&buf).into(); + + Ok(SerializedItem::Editor(id, path)) + } + SerializedItemKind::Terminal => Ok(SerializedItem::Terminal(id)), + SerializedItemKind::ProjectSearch => { + let query: Arc = row.get(3)?; + Ok(SerializedItem::ProjectSearch(id, query.to_string())) + } + SerializedItemKind::Diagnostics => Ok(SerializedItem::Diagnostics(id)), + } + })? + .collect::, rusqlite::Error>>()?; + + let mut delete_stmt = tx.prepare_cached( + "DELETE FROM items; + DELETE FROM item_path; + DELETE FROM item_query;", + )?; + + delete_stmt.execute([])?; + + result + }; + + tx.commit()?; + + Ok(result) + } +} + +#[cfg(test)] +mod test { + use anyhow::Result; + + use super::*; + + #[test] + fn test_items_round_trip() -> Result<()> { + let db = Db::open_in_memory()?; + + let mut items = vec![ + SerializedItem::Editor(0, PathBuf::from("/tmp/test.txt")), + SerializedItem::Terminal(1), + SerializedItem::ProjectSearch(2, "Test query!".to_string()), + SerializedItem::Diagnostics(3), + ]; + + for item in items.iter() { + db.write_item(item.clone())?; + } + + assert_eq!(items, db.take_items()?); + + // Check that it's empty, as expected + assert_eq!(Vec::::new(), db.take_items()?); + + for item in items.iter() { + db.write_item(item.clone())?; + } + + items.remove(2); + db.delete_item(2)?; + + assert_eq!(items, db.take_items()?); + + Ok(()) + } +} diff --git a/crates/db/src/kvp.rs b/crates/db/src/kvp.rs new file mode 100644 index 0000000000000000000000000000000000000000..534577bc79e0187ec359dd7fe43874de9a0cdd6b --- /dev/null +++ b/crates/db/src/kvp.rs @@ -0,0 +1,82 @@ +use anyhow::Result; +use rusqlite::OptionalExtension; + +use super::Db; + +pub(crate) const KVP_M_1_UP: &str = " +CREATE TABLE kv_store( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +) STRICT; +"; + +impl Db { + pub fn read_kvp(&self, key: &str) -> Result> { + self.real() + .map(|db| { + let lock = db.connection.lock(); + let mut stmt = lock.prepare_cached("SELECT value FROM kv_store WHERE key = (?)")?; + + Ok(stmt.query_row([key], |row| row.get(0)).optional()?) + }) + .unwrap_or(Ok(None)) + } + + pub fn write_kvp(&self, key: &str, value: &str) -> Result<()> { + self.real() + .map(|db| { + let lock = db.connection.lock(); + + let mut stmt = lock.prepare_cached( + "INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))", + )?; + + stmt.execute([key, value])?; + + Ok(()) + }) + .unwrap_or(Ok(())) + } + + pub fn delete_kvp(&self, key: &str) -> Result<()> { + self.real() + .map(|db| { + let lock = db.connection.lock(); + + let mut stmt = lock.prepare_cached("DELETE FROM kv_store WHERE key = (?)")?; + + stmt.execute([key])?; + + Ok(()) + }) + .unwrap_or(Ok(())) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + + #[test] + fn test_kvp() -> Result<()> { + let db = Db::open_in_memory(); + + assert_eq!(db.read_kvp("key-1")?, None); + + db.write_kvp("key-1", "one")?; + assert_eq!(db.read_kvp("key-1")?, Some("one".to_string())); + + db.write_kvp("key-1", "one-2")?; + assert_eq!(db.read_kvp("key-1")?, Some("one-2".to_string())); + + db.write_kvp("key-2", "two")?; + assert_eq!(db.read_kvp("key-2")?, Some("two".to_string())); + + db.delete_kvp("key-1")?; + assert_eq!(db.read_kvp("key-1")?, None); + + Ok(()) + } +} diff --git a/crates/db/src/migrations.rs b/crates/db/src/migrations.rs new file mode 100644 index 0000000000000000000000000000000000000000..1000543d8ddde8829320de1b0c3e4f630635d3e8 --- /dev/null +++ b/crates/db/src/migrations.rs @@ -0,0 +1,15 @@ +use rusqlite_migration::{Migrations, M}; + +// use crate::items::ITEMS_M_1; +use crate::kvp::KVP_M_1_UP; + +// This must be ordered by development time! Only ever add new migrations to the end!! +// Bad things will probably happen if you don't monotonically edit this vec!!!! +// And no re-ordering ever!!!!!!!!!! The results of these migrations are on the user's +// file system and so everything we do here is locked in _f_o_r_e_v_e_r_. +lazy_static::lazy_static! { + pub static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![ + M::up(KVP_M_1_UP), + // M::up(ITEMS_M_1), + ]); +} diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8180a6c9f61169502ac9a7114cee422da68d6329..4409fa17ad3669806ca1556a83a69d6a5e58fd87 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -99,7 +99,7 @@ impl View for ProjectDiagnosticsEditor { } } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if !self.path_states.is_empty() { cx.focus(&self.editor); } diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 7a16acae07d6dd24dd506736c5fa01861fa17dd1..31265a16972267a57b15bc5683d0e1f47c6bb656 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -4,7 +4,7 @@ use collections::HashSet; use gpui::{ elements::{MouseEventHandler, Overlay}, geometry::vector::Vector2F, - scene::DragRegionEvent, + scene::MouseDrag, CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext, View, WeakViewHandle, }; @@ -70,7 +70,7 @@ impl DragAndDrop { } pub fn dragging( - event: DragRegionEvent, + event: MouseDrag, payload: Rc, cx: &mut EventContext, render: Rc) -> ElementBox>, diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..77d10534d29a05d3a68b3f872eccc1184d01899b --- /dev/null +++ b/crates/editor/src/blink_manager.rs @@ -0,0 +1,110 @@ +use std::time::Duration; + +use gpui::{Entity, ModelContext}; +use settings::Settings; +use smol::Timer; + +pub struct BlinkManager { + blink_interval: Duration, + + blink_epoch: usize, + blinking_paused: bool, + visible: bool, + enabled: bool, +} + +impl BlinkManager { + pub fn new(blink_interval: Duration, cx: &mut ModelContext) -> Self { + let weak_handle = cx.weak_handle(); + cx.observe_global::(move |_, cx| { + if let Some(this) = weak_handle.upgrade(cx) { + // Make sure we blink the cursors if the setting is re-enabled + this.update(cx, |this, cx| this.blink_cursors(this.blink_epoch, cx)); + } + }) + .detach(); + + Self { + blink_interval, + + blink_epoch: 0, + blinking_paused: false, + visible: true, + enabled: true, + } + } + + fn next_blink_epoch(&mut self) -> usize { + self.blink_epoch += 1; + self.blink_epoch + } + + pub fn pause_blinking(&mut self, cx: &mut ModelContext) { + if !self.visible { + self.visible = true; + cx.notify(); + } + + let epoch = self.next_blink_epoch(); + let interval = self.blink_interval; + cx.spawn(|this, mut cx| { + let this = this.downgrade(); + async move { + Timer::after(interval).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx)) + } + } + }) + .detach(); + } + + fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ModelContext) { + if epoch == self.blink_epoch { + self.blinking_paused = false; + self.blink_cursors(epoch, cx); + } + } + + fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext) { + if cx.global::().cursor_blink { + if epoch == self.blink_epoch && self.enabled && !self.blinking_paused { + self.visible = !self.visible; + cx.notify(); + + let epoch = self.next_blink_epoch(); + let interval = self.blink_interval; + cx.spawn(|this, mut cx| { + let this = this.downgrade(); + async move { + Timer::after(interval).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx)); + } + } + }) + .detach(); + } + } else if !self.visible { + self.visible = true; + cx.notify(); + } + } + + pub fn enable(&mut self, cx: &mut ModelContext) { + self.enabled = true; + self.blink_cursors(self.blink_epoch, cx); + } + + pub fn disable(&mut self, _: &mut ModelContext) { + self.enabled = true; + } + + pub fn visible(&self) -> bool { + self.visible + } +} + +impl Entity for BlinkManager { + type Event = (); +} diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 3ae259f335e7353e243d03ca4b822b8741f30c48..ee07c77d0129408f7674635280b358b87a93dc7d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -157,6 +157,7 @@ pub struct BlockChunks<'a> { max_output_row: u32, } +#[derive(Clone)] pub struct BlockBufferRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, input_buffer_rows: wrap_map::WrapBufferRows<'a>, diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 663de0f80c4531e84369ecf95c8c3a64e22b11af..a2f7005e3dfa675278bd1d4080394fc3427e37ec 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -987,6 +987,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { } } +#[derive(Clone)] pub struct FoldBufferRows<'a> { cursor: Cursor<'a, Transform, (FoldPoint, Point)>, input_buffer_rows: MultiBufferRows<'a>, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 0b6713119df0729f8116c9d23958240b1a302d42..6c34fa4797f79b5f3728aa6ccf166d66a86375e4 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -62,6 +62,7 @@ pub struct WrapChunks<'a> { transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, } +#[derive(Clone)] pub struct WrapBufferRows<'a> { input_buffer_rows: fold_map::FoldBufferRows<'a>, input_buffer_row: Option, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 070fc69f7ebcfe1d974b2db2bccf5d45c19af200..07fc9655429b3bb79053a1dc0271fa0b43d0ad5d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,3 +1,4 @@ +mod blink_manager; pub mod display_map; mod element; mod highlight_matching_bracket; @@ -16,6 +17,7 @@ pub mod test; use aho_corasick::AhoCorasick; use anyhow::Result; +use blink_manager::BlinkManager; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; pub use display_map::DisplayPoint; @@ -42,11 +44,13 @@ use hover_popover::{hide_hover, HoverState}; pub use items::MAX_TAB_TITLE_LEN; pub use language::{char_kind, CharKind}; use language::{ - AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, - DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, - Selection, SelectionGoal, TransactionId, + AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, + Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, + Point, Selection, SelectionGoal, TransactionId, +}; +use link_go_to_definition::{ + hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState, }; -use link_go_to_definition::{hide_link_definition, LinkGoToDefinitionState}; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, @@ -447,12 +451,10 @@ pub struct Editor { override_text_style: Option>, project: Option>, focused: bool, - show_local_cursors: bool, + blink_manager: ModelHandle, show_local_selections: bool, show_scrollbars: bool, hide_scrollbar_task: Option>, - blink_epoch: usize, - blinking_paused: bool, mode: EditorMode, vertical_scroll_margin: f32, placeholder_text: Option>, @@ -1076,6 +1078,8 @@ impl Editor { let selections = SelectionsCollection::new(display_map.clone(), buffer.clone()); + let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); + let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1097,12 +1101,10 @@ impl Editor { scroll_top_anchor: Anchor::min(), autoscroll_request: None, focused: false, - show_local_cursors: false, + blink_manager: blink_manager.clone(), show_local_selections: true, show_scrollbars: true, hide_scrollbar_task: None, - blink_epoch: 0, - blinking_paused: false, mode, vertical_scroll_margin: 3.0, placeholder_text: None, @@ -1130,6 +1132,7 @@ impl Editor { cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), cx.observe(&display_map, Self::on_display_map_changed), + cx.observe(&blink_manager, |_, _, cx| cx.notify()), ], }; this.end_selection(cx); @@ -1478,6 +1481,7 @@ impl Editor { buffer.set_active_selections( &self.selections.disjoint_anchors(), self.selections.line_mode, + self.cursor_shape, cx, ) }); @@ -1541,7 +1545,7 @@ impl Editor { refresh_matching_bracket_highlights(self, cx); } - self.pause_cursor_blinking(cx); + self.blink_manager.update(cx, BlinkManager::pause_blinking); cx.emit(Event::SelectionsChanged { local }); cx.notify(); } @@ -6110,60 +6114,8 @@ impl Editor { highlights } - fn next_blink_epoch(&mut self) -> usize { - self.blink_epoch += 1; - self.blink_epoch - } - - fn pause_cursor_blinking(&mut self, cx: &mut ViewContext) { - if !self.focused { - return; - } - - self.show_local_cursors = true; - cx.notify(); - - let epoch = self.next_blink_epoch(); - cx.spawn(|this, mut cx| { - let this = this.downgrade(); - async move { - Timer::after(CURSOR_BLINK_INTERVAL).await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx)) - } - } - }) - .detach(); - } - - fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ViewContext) { - if epoch == self.blink_epoch { - self.blinking_paused = false; - self.blink_cursors(epoch, cx); - } - } - - fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext) { - if epoch == self.blink_epoch && self.focused && !self.blinking_paused { - self.show_local_cursors = !self.show_local_cursors; - cx.notify(); - - let epoch = self.next_blink_epoch(); - cx.spawn(|this, mut cx| { - let this = this.downgrade(); - async move { - Timer::after(CURSOR_BLINK_INTERVAL).await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx)); - } - } - }) - .detach(); - } - } - - pub fn show_local_cursors(&self) -> bool { - self.show_local_cursors && self.focused + pub fn show_local_cursors(&self, cx: &AppContext) -> bool { + self.blink_manager.read(cx).visible() && self.focused } pub fn show_scrollbars(&self) -> bool { @@ -6466,9 +6418,7 @@ impl View for Editor { } Stack::new() - .with_child( - EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed(), - ) + .with_child(EditorElement::new(self.handle.clone(), style.clone()).boxed()) .with_child(ChildView::new(&self.mouse_context_menu, cx).boxed()) .boxed() } @@ -6477,20 +6427,21 @@ impl View for Editor { "Editor" } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { let focused_event = EditorFocused(cx.handle()); cx.emit_global(focused_event); if let Some(rename) = self.pending_rename.as_ref() { cx.focus(&rename.editor); } else { self.focused = true; - self.blink_cursors(self.blink_epoch, cx); + self.blink_manager.update(cx, BlinkManager::enable); self.buffer.update(cx, |buffer, cx| { buffer.finalize_last_transaction(cx); if self.leader_replica_id.is_none() { buffer.set_active_selections( &self.selections.disjoint_anchors(), self.selections.line_mode, + self.cursor_shape, cx, ); } @@ -6498,10 +6449,11 @@ impl View for Editor { } } - fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { let blurred_event = EditorBlurred(cx.handle()); cx.emit_global(blurred_event); self.focused = false; + self.blink_manager.update(cx, BlinkManager::disable); self.buffer .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); self.hide_context_menu(cx); @@ -6510,6 +6462,44 @@ impl View for Editor { cx.notify(); } + fn modifiers_changed( + &mut self, + event: &gpui::ModifiersChangedEvent, + cx: &mut ViewContext, + ) -> bool { + let pending_selection = self.has_pending_selection(); + + if let Some(point) = self.link_go_to_definition_state.last_mouse_location.clone() { + if event.cmd && !pending_selection { + let snapshot = self.snapshot(cx); + let kind = if event.shift { + LinkDefinitionKind::Type + } else { + LinkDefinitionKind::Symbol + }; + + show_link_definition(kind, self, point, snapshot, cx); + return false; + } + } + + { + if self.link_go_to_definition_state.symbol_range.is_some() + || !self.link_go_to_definition_state.definitions.is_empty() + { + self.link_go_to_definition_state.symbol_range.take(); + self.link_go_to_definition_state.definitions.clear(); + cx.notify(); + } + + self.link_go_to_definition_state.task = None; + + self.clear_text_highlights::(cx); + } + + false + } + fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context { let mut context = Self::default_keymap_context(); let mode = match self.mode { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index afc9bdd49450227ca1f04b5d5ebe3fed7e088fc2..d18c168fde136aca3b0f6300ef763e00a29b9571 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9,14 +9,14 @@ use crate::{ HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, link_go_to_definition::{ - CmdShiftChanged, GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink, + GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink, }, mouse_context_menu::DeployMouseContextMenu, - EditorStyle, + AnchorRangeExt, EditorStyle, }; use clock::ReplicaId; use collections::{BTreeMap, HashMap}; -use git::diff::{DiffHunk, DiffHunkStatus}; +use git::diff::DiffHunkStatus; use gpui::{ color::Color, elements::*, @@ -29,13 +29,12 @@ use gpui::{ json::{self, ToJson}, platform::CursorStyle, text_layout::{self, Line, RunStyle, TextLayoutCache}, - AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, - LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, - MouseRegion, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, - WeakViewHandle, + AppContext, Axis, Border, CursorRegion, Element, ElementBox, EventContext, LayoutContext, + MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MutableAppContext, PaintContext, + Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; -use language::{Bias, DiagnosticSeverity, OffsetUtf16, Selection}; +use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Point, Selection}; use project::ProjectPath; use settings::{GitGutter, Settings}; use smallvec::SmallVec; @@ -46,10 +45,17 @@ use std::{ ops::{DerefMut, Range}, sync::Arc, }; -use theme::DiffStyle; + +#[derive(Debug)] +struct DiffHunkLayout { + visual_range: Range, + status: DiffHunkStatus, + is_folded: bool, +} struct SelectionLayout { head: DisplayPoint, + cursor_shape: CursorShape, range: Range, } @@ -57,6 +63,7 @@ impl SelectionLayout { fn new( selection: Selection, line_mode: bool, + cursor_shape: CursorShape, map: &DisplaySnapshot, ) -> Self { if line_mode { @@ -64,6 +71,7 @@ impl SelectionLayout { let point_range = map.expand_to_line(selection.range()); Self { head: selection.head().to_display_point(map), + cursor_shape, range: point_range.start.to_display_point(map) ..point_range.end.to_display_point(map), } @@ -71,6 +79,7 @@ impl SelectionLayout { let selection = selection.map(|p| p.to_display_point(map)); Self { head: selection.head(), + cursor_shape, range: selection.range(), } } @@ -81,19 +90,13 @@ impl SelectionLayout { pub struct EditorElement { view: WeakViewHandle, style: Arc, - cursor_shape: CursorShape, } impl EditorElement { - pub fn new( - view: WeakViewHandle, - style: EditorStyle, - cursor_shape: CursorShape, - ) -> Self { + pub fn new(view: WeakViewHandle, style: EditorStyle) -> Self { Self { view, style: Arc::new(style), - cursor_shape, } } @@ -408,14 +411,6 @@ impl EditorElement { true } - fn modifiers_changed(&self, event: ModifiersChangedEvent, cx: &mut EventContext) -> bool { - cx.dispatch_action(CmdShiftChanged { - cmd_down: event.cmd, - shift_down: event.shift, - }); - false - } - fn scroll( position: Vector2F, mut delta: Vector2F, @@ -525,85 +520,11 @@ impl EditorElement { layout: &mut LayoutState, cx: &mut PaintContext, ) { - struct GutterLayout { - line_height: f32, - // scroll_position: Vector2F, - scroll_top: f32, - bounds: RectF, - } - - struct DiffLayout<'a> { - buffer_row: u32, - last_diff: Option<&'a DiffHunk>, - } - - fn diff_quad( - hunk: &DiffHunk, - gutter_layout: &GutterLayout, - diff_style: &DiffStyle, - ) -> Quad { - let color = match hunk.status() { - DiffHunkStatus::Added => diff_style.inserted, - DiffHunkStatus::Modified => diff_style.modified, - - //TODO: This rendering is entirely a horrible hack - DiffHunkStatus::Removed => { - let row = hunk.buffer_range.start; - - let offset = gutter_layout.line_height / 2.; - let start_y = - row as f32 * gutter_layout.line_height + offset - gutter_layout.scroll_top; - let end_y = start_y + gutter_layout.line_height; - - let width = diff_style.removed_width_em * gutter_layout.line_height; - let highlight_origin = gutter_layout.bounds.origin() + vec2f(-width, start_y); - let highlight_size = vec2f(width * 2., end_y - start_y); - let highlight_bounds = RectF::new(highlight_origin, highlight_size); - - return Quad { - bounds: highlight_bounds, - background: Some(diff_style.deleted), - border: Border::new(0., Color::transparent_black()), - corner_radius: 1. * gutter_layout.line_height, - }; - } - }; - - let start_row = hunk.buffer_range.start; - let end_row = hunk.buffer_range.end; - - let start_y = start_row as f32 * gutter_layout.line_height - gutter_layout.scroll_top; - let end_y = end_row as f32 * gutter_layout.line_height - gutter_layout.scroll_top; - - let width = diff_style.width_em * gutter_layout.line_height; - let highlight_origin = gutter_layout.bounds.origin() + vec2f(-width, start_y); - let highlight_size = vec2f(width * 2., end_y - start_y); - let highlight_bounds = RectF::new(highlight_origin, highlight_size); - - Quad { - bounds: highlight_bounds, - background: Some(color), - border: Border::new(0., Color::transparent_black()), - corner_radius: diff_style.corner_radius * gutter_layout.line_height, - } - } + let line_height = layout.position_map.line_height; let scroll_position = layout.position_map.snapshot.scroll_position(); - let gutter_layout = { - let line_height = layout.position_map.line_height; - GutterLayout { - scroll_top: scroll_position.y() * line_height, - line_height, - bounds, - } - }; - - let mut diff_layout = DiffLayout { - buffer_row: scroll_position.y() as u32, - last_diff: None, - }; + let scroll_top = scroll_position.y() * line_height; - let diff_style = &cx.global::().theme.editor.diff.clone(); let show_gutter = matches!( &cx.global::() .git_overrides @@ -612,55 +533,104 @@ impl EditorElement { GitGutter::TrackedFiles ); - // line is `None` when there's a line wrap + if show_gutter { + Self::paint_diff_hunks(bounds, layout, cx); + } + for (ix, line) in layout.line_number_layouts.iter().enumerate() { if let Some(line) = line { let line_origin = bounds.origin() + vec2f( bounds.width() - line.width() - layout.gutter_padding, - ix as f32 * gutter_layout.line_height - - (gutter_layout.scroll_top % gutter_layout.line_height), + ix as f32 * line_height - (scroll_top % line_height), ); - line.paint(line_origin, visible_bounds, gutter_layout.line_height, cx); + line.paint(line_origin, visible_bounds, line_height, cx); + } + } - if show_gutter { - //This line starts a buffer line, so let's do the diff calculation - let new_hunk = get_hunk(diff_layout.buffer_row, &layout.diff_hunks); + if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { + let mut x = bounds.width() - layout.gutter_padding; + let mut y = *row as f32 * line_height - scroll_top; + x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; + y += (line_height - indicator.size().y()) / 2.; + indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); + } + } - let (is_ending, is_starting) = match (diff_layout.last_diff, new_hunk) { - (Some(old_hunk), Some(new_hunk)) if new_hunk == old_hunk => (false, false), - (a, b) => (a.is_some(), b.is_some()), - }; + fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { + let diff_style = &cx.global::().theme.editor.diff.clone(); + let line_height = layout.position_map.line_height; - if is_ending { - let last_hunk = diff_layout.last_diff.take().unwrap(); - cx.scene - .push_quad(diff_quad(last_hunk, &gutter_layout, diff_style)); - } + let scroll_position = layout.position_map.snapshot.scroll_position(); + let scroll_top = scroll_position.y() * line_height; + + for hunk in &layout.hunk_layouts { + let color = match (hunk.status, hunk.is_folded) { + (DiffHunkStatus::Added, false) => diff_style.inserted, + (DiffHunkStatus::Modified, false) => diff_style.modified, + + //TODO: This rendering is entirely a horrible hack + (DiffHunkStatus::Removed, false) => { + let row = hunk.visual_range.start; + + let offset = line_height / 2.; + let start_y = row as f32 * line_height - offset - scroll_top; + let end_y = start_y + line_height; + + let width = diff_style.removed_width_em * line_height; + let highlight_origin = bounds.origin() + vec2f(-width, start_y); + let highlight_size = vec2f(width * 2., end_y - start_y); + let highlight_bounds = RectF::new(highlight_origin, highlight_size); - if is_starting { - let new_hunk = new_hunk.unwrap(); - diff_layout.last_diff = Some(new_hunk); - }; + cx.scene.push_quad(Quad { + bounds: highlight_bounds, + background: Some(diff_style.deleted), + border: Border::new(0., Color::transparent_black()), + corner_radius: 1. * line_height, + }); - diff_layout.buffer_row += 1; + continue; } - } - } - // If we ran out with a diff hunk still being prepped, paint it now - if let Some(last_hunk) = diff_layout.last_diff { - cx.scene - .push_quad(diff_quad(last_hunk, &gutter_layout, diff_style)) - } + (_, true) => { + let row = hunk.visual_range.start; + let start_y = row as f32 * line_height - scroll_top; + let end_y = start_y + line_height; - if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { - let mut x = bounds.width() - layout.gutter_padding; - let mut y = *row as f32 * gutter_layout.line_height - gutter_layout.scroll_top; - x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; - y += (gutter_layout.line_height - indicator.size().y()) / 2.; - indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); + let width = diff_style.removed_width_em * line_height; + let highlight_origin = bounds.origin() + vec2f(-width, start_y); + let highlight_size = vec2f(width * 2., end_y - start_y); + let highlight_bounds = RectF::new(highlight_origin, highlight_size); + + cx.scene.push_quad(Quad { + bounds: highlight_bounds, + background: Some(diff_style.modified), + border: Border::new(0., Color::transparent_black()), + corner_radius: 1. * line_height, + }); + + continue; + } + }; + + let start_row = hunk.visual_range.start; + let end_row = hunk.visual_range.end; + + let start_y = start_row as f32 * line_height - scroll_top; + let end_y = end_row as f32 * line_height - scroll_top; + + let width = diff_style.width_em * line_height; + let highlight_origin = bounds.origin() + vec2f(-width, start_y); + let highlight_size = vec2f(width * 2., end_y - start_y); + let highlight_bounds = RectF::new(highlight_origin, highlight_size); + + cx.scene.push_quad(Quad { + bounds: highlight_bounds, + background: Some(color), + border: Border::new(0., Color::transparent_black()), + corner_radius: diff_style.corner_radius * line_height, + }); } } @@ -726,7 +696,7 @@ impl EditorElement { cx, ); - if view.show_local_cursors() || *replica_id != local_replica_id { + if view.show_local_cursors(cx) || *replica_id != local_replica_id { let cursor_position = selection.head; if layout .visible_display_row_range @@ -742,7 +712,7 @@ impl EditorElement { if block_width == 0.0 { block_width = layout.position_map.em_width; } - let block_text = if let CursorShape::Block = self.cursor_shape { + let block_text = if let CursorShape::Block = selection.cursor_shape { layout .position_map .snapshot @@ -778,7 +748,7 @@ impl EditorElement { block_width, origin: vec2f(x, y), line_height: layout.position_map.line_height, - shape: self.cursor_shape, + shape: selection.cursor_shape, block_text, }); } @@ -1122,6 +1092,75 @@ impl EditorElement { .width() } + //Folds contained in a hunk are ignored apart from shrinking visual size + //If a fold contains any hunks then that fold line is marked as modified + fn layout_git_gutters( + &self, + rows: Range, + snapshot: &EditorSnapshot, + ) -> Vec { + let buffer_snapshot = &snapshot.buffer_snapshot; + let visual_start = DisplayPoint::new(rows.start, 0).to_point(snapshot).row; + let visual_end = DisplayPoint::new(rows.end, 0).to_point(snapshot).row; + let hunks = buffer_snapshot.git_diff_hunks_in_range(visual_start..visual_end); + + let mut layouts = Vec::::new(); + + for hunk in hunks { + let hunk_start_point = Point::new(hunk.buffer_range.start, 0); + let hunk_end_point = Point::new(hunk.buffer_range.end, 0); + let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); + let hunk_end_point_sub = Point::new( + hunk.buffer_range + .end + .saturating_sub(1) + .max(hunk.buffer_range.start), + 0, + ); + + let is_removal = hunk.status() == DiffHunkStatus::Removed; + + let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); + let folds_end = Point::new(hunk.buffer_range.end + 1, 0); + let folds_range = folds_start..folds_end; + + let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| { + let fold_point_range = fold_range.to_point(buffer_snapshot); + let fold_point_range = fold_point_range.start..=fold_point_range.end; + + let folded_start = fold_point_range.contains(&hunk_start_point); + let folded_end = fold_point_range.contains(&hunk_end_point_sub); + let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub); + + (folded_start && folded_end) || (is_removal && folded_start_sub) + }); + + let visual_range = if let Some(fold) = containing_fold { + let row = fold.start.to_display_point(snapshot).row(); + row..row + } else { + let start = hunk_start_point.to_display_point(snapshot).row(); + let end = hunk_end_point.to_display_point(snapshot).row(); + start..end + }; + + let has_existing_layout = match layouts.last() { + Some(e) => visual_range == e.visual_range && e.status == hunk.status(), + None => false, + }; + + if !has_existing_layout { + layouts.push(DiffHunkLayout { + visual_range, + status: hunk.status(), + is_folded: containing_fold.is_some(), + }); + } + } + + layouts + } + fn layout_line_numbers( &self, rows: Range, @@ -1276,6 +1315,7 @@ impl EditorElement { line_height: f32, style: &EditorStyle, line_layouts: &[text_layout::Line], + include_root: bool, cx: &mut LayoutContext, ) -> (f32, Vec) { let editor = if let Some(editor) = self.view.upgrade(cx) { @@ -1379,10 +1419,11 @@ impl EditorElement { let font_size = (style.text_scale_factor * self.style.text.font_size).round(); + let path = buffer.resolve_file_path(cx, include_root); let mut filename = None; let mut parent_path = None; - if let Some(file) = buffer.file() { - let path = file.path(); + // Can't use .and_then() because `.file_name()` and `.parent()` return references :( + if let Some(path) = path { filename = path.file_name().map(|f| f.to_string_lossy().to_string()); parent_path = path.parent().map(|p| p.to_string_lossy().to_string() + "/"); @@ -1474,27 +1515,6 @@ impl EditorElement { } } -/// Get the hunk that contains buffer_line, starting from start_idx -/// Returns none if there is none found, and -fn get_hunk(buffer_line: u32, hunks: &[DiffHunk]) -> Option<&DiffHunk> { - for i in 0..hunks.len() { - // Safety: Index out of bounds is handled by the check above - let hunk = hunks.get(i).unwrap(); - if hunk.buffer_range.contains(&(buffer_line as u32)) { - return Some(hunk); - } else if hunk.status() == DiffHunkStatus::Removed && buffer_line == hunk.buffer_range.start - { - return Some(hunk); - } else if hunk.buffer_range.start > buffer_line as u32 { - // If we've passed the buffer_line, just stop - return None; - } - } - - // We reached the end of the array without finding a hunk, just return none. - return None; -} - impl Element for EditorElement { type LayoutState = LayoutState; type PaintState = (); @@ -1607,6 +1627,7 @@ impl Element for EditorElement { let mut highlighted_rows = None; let mut highlighted_ranges = Vec::new(); let mut show_scrollbars = false; + let mut include_root = false; self.update_view(cx.app, |view, cx| { let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx)); @@ -1619,7 +1640,7 @@ impl Element for EditorElement { ); let mut remote_selections = HashMap::default(); - for (replica_id, line_mode, selection) in display_map + for (replica_id, line_mode, cursor_shape, selection) in display_map .buffer_snapshot .remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone())) { @@ -1630,7 +1651,12 @@ impl Element for EditorElement { remote_selections .entry(replica_id) .or_insert(Vec::new()) - .push(SelectionLayout::new(selection, line_mode, &display_map)); + .push(SelectionLayout::new( + selection, + line_mode, + cursor_shape, + &display_map, + )); } selections.extend(remote_selections); @@ -1662,22 +1688,29 @@ impl Element for EditorElement { local_selections .into_iter() .map(|selection| { - SelectionLayout::new(selection, view.selections.line_mode, &display_map) + SelectionLayout::new( + selection, + view.selections.line_mode, + view.cursor_shape, + &display_map, + ) }) .collect(), )); } show_scrollbars = view.show_scrollbars(); + include_root = view + .project + .as_ref() + .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) + .unwrap_or_default() }); let line_number_layouts = self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); - let diff_hunks = snapshot - .buffer_snapshot - .git_diff_hunks_in_range(start_row..end_row) - .collect(); + let hunk_layouts = self.layout_git_gutters(start_row..end_row, &snapshot); let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines); @@ -1711,6 +1744,7 @@ impl Element for EditorElement { line_height, &style, &line_layouts, + include_root, cx, ); @@ -1835,7 +1869,7 @@ impl Element for EditorElement { highlighted_rows, highlighted_ranges, line_number_layouts, - diff_hunks, + hunk_layouts, blocks, selections, context_menu, @@ -1887,22 +1921,6 @@ impl Element for EditorElement { cx.scene.pop_layer(); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut LayoutState, - _: &mut (), - cx: &mut EventContext, - ) -> bool { - if let Event::ModifiersChanged(event) = event { - self.modifiers_changed(*event, cx); - } - - false - } - fn rect_for_text_range( &self, range_utf16: Range, @@ -1973,6 +1991,7 @@ pub struct LayoutState { active_rows: BTreeMap, highlighted_rows: Option>, line_number_layouts: Vec>, + hunk_layouts: Vec, blocks: Vec, highlighted_ranges: Vec<(Range, Color)>, selections: Vec<(ReplicaId, Vec)>, @@ -1980,7 +1999,6 @@ pub struct LayoutState { show_scrollbars: bool, max_row: u32, context_menu: Option<(DisplayPoint, ElementBox)>, - diff_hunks: Vec>, code_actions_indicator: Option<(u32, ElementBox)>, hover_popovers: Option<(DisplayPoint, Vec)>, } @@ -2068,20 +2086,6 @@ fn layout_line( ) } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum CursorShape { - Bar, - Block, - Underscore, - Hollow, -} - -impl Default for CursorShape { - fn default() -> Self { - CursorShape::Bar - } -} - #[derive(Debug)] pub struct Cursor { origin: Vector2F, @@ -2322,11 +2326,7 @@ mod tests { let (window_id, editor) = cx.add_window(Default::default(), |cx| { Editor::new(EditorMode::Full, buffer, None, None, cx) }); - let element = EditorElement::new( - editor.downgrade(), - editor.read(cx).style(cx), - CursorShape::Bar, - ); + let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx)); let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); @@ -2362,11 +2362,7 @@ mod tests { cx.blur(); }); - let mut element = EditorElement::new( - editor.downgrade(), - editor.read(cx).style(cx), - CursorShape::Bar, - ); + let mut element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx)); let mut scene = Scene::new(1.0); let mut presenter = cx.build_presenter(window_id, 30., Default::default()); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e6a4eebffb0cb3587761855028411182fe7aac25..47e06fc545a419e8c2a25af430340e61bd6f6a6c 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -120,6 +120,7 @@ impl FollowableItem for Editor { buffer.set_active_selections( &self.selections.disjoint_anchors(), self.selections.line_mode, + self.cursor_shape, cx, ); } @@ -531,21 +532,17 @@ impl Item for Editor { let buffer = multibuffer.buffer(buffer_id)?; let buffer = buffer.read(cx); - let filename = if let Some(file) = buffer.file() { - if file.path().file_name().is_none() - || self - .project + let filename = buffer + .snapshot() + .resolve_file_path( + cx, + self.project .as_ref() .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) - .unwrap_or_default() - { - file.full_path(cx).to_string_lossy().to_string() - } else { - file.path().to_string_lossy().to_string() - } - } else { - "untitled".to_string() - }; + .unwrap_or_default(), + ) + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| "untitled".to_string()); let mut breadcrumbs = vec![Label::new(filename, theme.breadcrumbs.text.clone()).boxed()]; breadcrumbs.extend(symbols.into_iter().map(|symbol| { diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index c8294ddb43744ccf09a016739b8d2277645b4b48..705a1b276075eda3ebbf3f42b7a6e8c0664694cc 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -19,12 +19,6 @@ pub struct UpdateGoToDefinitionLink { pub shift_held: bool, } -#[derive(Clone, PartialEq)] -pub struct CmdShiftChanged { - pub cmd_down: bool, - pub shift_down: bool, -} - #[derive(Clone, PartialEq)] pub struct GoToFetchedDefinition { pub point: DisplayPoint, @@ -39,7 +33,6 @@ impl_internal_actions!( editor, [ UpdateGoToDefinitionLink, - CmdShiftChanged, GoToFetchedDefinition, GoToFetchedTypeDefinition ] @@ -47,7 +40,6 @@ impl_internal_actions!( pub fn init(cx: &mut MutableAppContext) { cx.add_action(update_go_to_definition_link); - cx.add_action(cmd_shift_changed); cx.add_action(go_to_fetched_definition); cx.add_action(go_to_fetched_type_definition); } @@ -113,37 +105,6 @@ pub fn update_go_to_definition_link( hide_link_definition(editor, cx); } -pub fn cmd_shift_changed( - editor: &mut Editor, - &CmdShiftChanged { - cmd_down, - shift_down, - }: &CmdShiftChanged, - cx: &mut ViewContext, -) { - let pending_selection = editor.has_pending_selection(); - - if let Some(point) = editor - .link_go_to_definition_state - .last_mouse_location - .clone() - { - if cmd_down && !pending_selection { - let snapshot = editor.snapshot(cx); - let kind = if shift_down { - LinkDefinitionKind::Type - } else { - LinkDefinitionKind::Symbol - }; - - show_link_definition(kind, editor, point, snapshot, cx); - return; - } - } - - hide_link_definition(editor, cx) -} - #[derive(Debug, Clone, Copy, PartialEq)] pub enum LinkDefinitionKind { Symbol, @@ -397,6 +358,7 @@ fn go_to_fetched_definition_of_kind( #[cfg(test)] mod tests { use futures::StreamExt; + use gpui::{ModifiersChangedEvent, View}; use indoc::indoc; use lsp::request::{GotoDefinition, GotoTypeDefinition}; @@ -467,11 +429,10 @@ mod tests { // Unpress shift causes highlight to go away (normal goto-definition is not valid here) cx.update_editor(|editor, cx| { - cmd_shift_changed( - editor, - &CmdShiftChanged { - cmd_down: true, - shift_down: false, + editor.modifiers_changed( + &gpui::ModifiersChangedEvent { + cmd: true, + ..Default::default() }, cx, ); @@ -581,14 +542,7 @@ mod tests { // Unpress cmd causes highlight to go away cx.update_editor(|editor, cx| { - cmd_shift_changed( - editor, - &CmdShiftChanged { - cmd_down: false, - shift_down: false, - }, - cx, - ); + editor.modifiers_changed(&Default::default(), cx); }); // Assert no link highlights @@ -704,11 +658,10 @@ mod tests { ]))) }); cx.update_editor(|editor, cx| { - cmd_shift_changed( - editor, - &CmdShiftChanged { - cmd_down: true, - shift_down: false, + editor.modifiers_changed( + &ModifiersChangedEvent { + cmd: true, + ..Default::default() }, cx, ); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index d9bb77f1eb7dec84c1cb549c0bf03ffeee97dab2..1cff0038686f88c27816124d850eece8427ae864 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -8,7 +8,7 @@ use git::diff::DiffHunk; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ - char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, + char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, @@ -143,6 +143,7 @@ struct ExcerptSummary { text: TextSummary, } +#[derive(Clone)] pub struct MultiBufferRows<'a> { buffer_row_range: Range, excerpts: Cursor<'a, Excerpt, Point>, @@ -603,6 +604,7 @@ impl MultiBuffer { &mut self, selections: &[Selection], line_mode: bool, + cursor_shape: CursorShape, cx: &mut ModelContext, ) { let mut selections_by_buffer: HashMap>> = @@ -667,7 +669,7 @@ impl MultiBuffer { } Some(selection) })); - buffer.set_active_selections(merged_selections, line_mode, cx); + buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx); }); } } @@ -2697,7 +2699,7 @@ impl MultiBufferSnapshot { pub fn remote_selections_in_range<'a>( &'a self, range: &'a Range, - ) -> impl 'a + Iterator)> { + ) -> impl 'a + Iterator)> { let mut cursor = self.excerpts.cursor::>(); cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &()); cursor @@ -2714,7 +2716,7 @@ impl MultiBufferSnapshot { excerpt .buffer .remote_selections_in_range(query_range) - .flat_map(move |(replica_id, line_mode, selections)| { + .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| { selections.map(move |selection| { let mut start = Anchor { buffer_id: Some(excerpt.buffer_id), @@ -2736,6 +2738,7 @@ impl MultiBufferSnapshot { ( replica_id, line_mode, + cursor_shape, Selection { id: selection.id, start, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index e787e3c1a63c882398069c8efc81aa4515e826da..1a82613b84329eea6c214073185e26f886110fd2 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -53,7 +53,7 @@ impl View for FileFinder { ChildView::new(self.picker.clone(), cx).boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.picker); } diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 2061d3734bd386901a97441fa34ea71c5b33f5a6..7bd7346b4b30ef2e1a99707e194e575f6cbc2f47 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -13,6 +13,7 @@ use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; use std::cmp; use std::io::Write; +use std::ops::Deref; use std::sync::Arc; use std::{ io, @@ -92,6 +93,17 @@ impl LineEnding { } } } + +pub struct HomeDir(pub PathBuf); + +impl Deref for HomeDir { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[async_trait::async_trait] pub trait Fs: Send + Sync { async fn create_dir(&self, path: &Path) -> Result<()>; diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 803607895ac3fa97ca86fa4c2f10ff6b380ec276..900f8967d700c207080fc7ec435d5bcac2039e76 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -190,7 +190,6 @@ impl BufferDiff { } if kind == GitDiffLineType::Deletion { - *buffer_row_divergence -= 1; let end = content_offset + content_len; match &mut head_byte_range { @@ -203,6 +202,8 @@ impl BufferDiff { let row = old_row as i64 + *buffer_row_divergence; first_deletion_buffer_row = Some(row as u32); } + + *buffer_row_divergence -= 1; } } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index eddb014b63a4715e6268070ccc9f96897f0e50d5..ad1dacf7434e5bb30e7d4b04170aa0828e911b59 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -183,7 +183,7 @@ impl View for GoToLine { .named("go to line") } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { cx.focus(&self.line_editor); } } diff --git a/crates/gpui/examples/text.rs b/crates/gpui/examples/text.rs index d4e873f039c7d907e71887e5b10325fa9ba41e37..4d84fefab62e30e86f25d72f06c7638bf72894f7 100644 --- a/crates/gpui/examples/text.rs +++ b/crates/gpui/examples/text.rs @@ -101,18 +101,6 @@ impl gpui::Element for TextElement { line.paint(bounds.origin(), visible_bounds, bounds.height(), cx); } - fn dispatch_event( - &mut self, - _: &gpui::Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut gpui::EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a9529f3f9fccb67200aa944dd3509fda67432bc9..a9020cf35088a30488a8fb62ed1d0a547d37227b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -41,8 +41,8 @@ use crate::{ platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, util::post_inc, - Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, - MouseRegionId, PathPromptOptions, TextLayoutCache, + Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, KeyUpEvent, + ModifiersChangedEvent, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache, }; pub trait Entity: 'static { @@ -60,8 +60,18 @@ pub trait Entity: 'static { pub trait View: Entity + Sized { fn ui_name() -> &'static str; fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox; - fn on_focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) {} - fn on_focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) {} + fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) {} + fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) {} + fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext) -> bool { + false + } + fn key_up(&mut self, _: &KeyUpEvent, _: &mut ViewContext) -> bool { + false + } + fn modifiers_changed(&mut self, _: &ModifiersChangedEvent, _: &mut ViewContext) -> bool { + false + } + fn keymap_context(&self, _: &AppContext) -> keymap::Context { Self::default_keymap_context() } @@ -1297,7 +1307,7 @@ impl MutableAppContext { ) -> impl Iterator, SmallVec<[&Binding; 1]>)> { let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect(); - for view_id in self.parents(window_id, view_id) { + for view_id in self.ancestors(window_id, view_id) { if let Some(view) = self.views.get(&(window_id, view_id)) { let view_type = view.as_any().type_id(); if let Some(actions) = self.actions.get(&view_type) { @@ -1327,7 +1337,7 @@ impl MutableAppContext { let action_type = action.as_any().type_id(); if let Some(window_id) = self.cx.platform.key_window_id() { if let Some(focused_view_id) = self.focused_view_id(window_id) { - for view_id in self.parents(window_id, focused_view_id) { + for view_id in self.ancestors(window_id, focused_view_id) { if let Some(view) = self.views.get(&(window_id, view_id)) { let view_type = view.as_any().type_id(); if let Some(actions) = self.actions.get(&view_type) { @@ -1376,7 +1386,7 @@ impl MutableAppContext { mut visit: impl FnMut(usize, bool, &mut MutableAppContext) -> bool, ) -> bool { // List of view ids from the leaf to the root of the window - let path = self.parents(window_id, view_id).collect::>(); + let path = self.ancestors(window_id, view_id).collect::>(); // Walk down from the root to the leaf calling visit with capture_phase = true for view_id in path.iter().rev() { @@ -1397,7 +1407,7 @@ impl MutableAppContext { // Returns an iterator over all of the view ids from the passed view up to the root of the window // Includes the passed view itself - fn parents(&self, window_id: usize, mut view_id: usize) -> impl Iterator + '_ { + fn ancestors(&self, window_id: usize, mut view_id: usize) -> impl Iterator + '_ { std::iter::once(view_id) .into_iter() .chain(std::iter::from_fn(move || { @@ -1445,11 +1455,81 @@ impl MutableAppContext { self.keystroke_matcher.clear_bindings(); } + pub fn dispatch_key_down(&mut self, window_id: usize, event: &KeyDownEvent) -> bool { + if let Some(focused_view_id) = self.focused_view_id(window_id) { + for view_id in self + .ancestors(window_id, focused_view_id) + .collect::>() + { + if let Some(mut view) = self.cx.views.remove(&(window_id, view_id)) { + let handled = view.key_down(event, self, window_id, view_id); + self.cx.views.insert((window_id, view_id), view); + if handled { + return true; + } + } else { + log::error!("view {} does not exist", view_id) + } + } + } + + false + } + + pub fn dispatch_key_up(&mut self, window_id: usize, event: &KeyUpEvent) -> bool { + if let Some(focused_view_id) = self.focused_view_id(window_id) { + for view_id in self + .ancestors(window_id, focused_view_id) + .collect::>() + { + if let Some(mut view) = self.cx.views.remove(&(window_id, view_id)) { + let handled = view.key_up(event, self, window_id, view_id); + self.cx.views.insert((window_id, view_id), view); + if handled { + return true; + } + } else { + log::error!("view {} does not exist", view_id) + } + } + } + + false + } + + pub fn dispatch_modifiers_changed( + &mut self, + window_id: usize, + event: &ModifiersChangedEvent, + ) -> bool { + if let Some(focused_view_id) = self.focused_view_id(window_id) { + for view_id in self + .ancestors(window_id, focused_view_id) + .collect::>() + { + if let Some(mut view) = self.cx.views.remove(&(window_id, view_id)) { + let handled = view.modifiers_changed(event, self, window_id, view_id); + self.cx.views.insert((window_id, view_id), view); + if handled { + return true; + } + } else { + log::error!("view {} does not exist", view_id) + } + } + } + + false + } + pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: &Keystroke) -> bool { let mut pending = false; if let Some(focused_view_id) = self.focused_view_id(window_id) { - for view_id in self.parents(window_id, focused_view_id).collect::>() { + for view_id in self + .ancestors(window_id, focused_view_id) + .collect::>() + { let keymap_context = self .cx .views @@ -1580,7 +1660,7 @@ impl MutableAppContext { is_fullscreen: false, }, ); - root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx)); + root_view.update(this, |view, cx| view.focus_in(cx.handle().into(), cx)); let window = this.cx @@ -1612,7 +1692,7 @@ impl MutableAppContext { is_fullscreen: false, }, ); - root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx)); + root_view.update(this, |view, cx| view.focus_in(cx.handle().into(), cx)); let status_item = this.cx.platform.add_status_item(); this.register_platform_window(window_id, status_item); @@ -2235,12 +2315,12 @@ impl MutableAppContext { //Handle focus let focused_id = window.focused_view_id?; - for view_id in this.parents(window_id, focused_id).collect::>() { + for view_id in this.ancestors(window_id, focused_id).collect::>() { if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { if active { - view.on_focus_in(this, window_id, view_id, focused_id); + view.focus_in(this, window_id, view_id, focused_id); } else { - view.on_focus_out(this, window_id, view_id, focused_id); + view.focus_out(this, window_id, view_id, focused_id); } this.cx.views.insert((window_id, view_id), view); } @@ -2272,16 +2352,16 @@ impl MutableAppContext { }); let blurred_parents = blurred_id - .map(|blurred_id| this.parents(window_id, blurred_id).collect::>()) + .map(|blurred_id| this.ancestors(window_id, blurred_id).collect::>()) .unwrap_or_default(); let focused_parents = focused_id - .map(|focused_id| this.parents(window_id, focused_id).collect::>()) + .map(|focused_id| this.ancestors(window_id, focused_id).collect::>()) .unwrap_or_default(); if let Some(blurred_id) = blurred_id { for view_id in blurred_parents.iter().copied() { if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { - view.on_focus_out(this, window_id, view_id, blurred_id); + view.focus_out(this, window_id, view_id, blurred_id); this.cx.views.insert((window_id, view_id), view); } } @@ -2294,7 +2374,7 @@ impl MutableAppContext { if let Some(focused_id) = focused_id { for view_id in focused_parents { if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { - view.on_focus_in(this, window_id, view_id, focused_id); + view.focus_in(this, window_id, view_id, focused_id); this.cx.views.insert((window_id, view_id), view); } } @@ -2961,20 +3041,41 @@ pub trait AnyView { ) -> Option>>>; fn ui_name(&self) -> &'static str; fn render(&mut self, params: RenderParams, cx: &mut MutableAppContext) -> ElementBox; - fn on_focus_in( + fn focus_in( &mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize, focused_id: usize, ); - fn on_focus_out( + fn focus_out( &mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize, focused_id: usize, ); + fn key_down( + &mut self, + event: &KeyDownEvent, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) -> bool; + fn key_up( + &mut self, + event: &KeyUpEvent, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) -> bool; + fn modifiers_changed( + &mut self, + event: &ModifiersChangedEvent, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) -> bool; fn keymap_context(&self, cx: &AppContext) -> keymap::Context; fn debug_json(&self, cx: &AppContext) -> serde_json::Value; @@ -3040,7 +3141,7 @@ where View::render(self, &mut RenderContext::new(params, cx)) } - fn on_focus_in( + fn focus_in( &mut self, cx: &mut MutableAppContext, window_id: usize, @@ -3059,10 +3160,10 @@ where .type_id(); AnyViewHandle::new(window_id, focused_id, focused_type, cx.ref_counts.clone()) }; - View::on_focus_in(self, focused_view_handle, &mut cx); + View::focus_in(self, focused_view_handle, &mut cx); } - fn on_focus_out( + fn focus_out( &mut self, cx: &mut MutableAppContext, window_id: usize, @@ -3081,7 +3182,40 @@ where .type_id(); AnyViewHandle::new(window_id, blurred_id, blurred_type, cx.ref_counts.clone()) }; - View::on_focus_out(self, blurred_view_handle, &mut cx); + View::focus_out(self, blurred_view_handle, &mut cx); + } + + fn key_down( + &mut self, + event: &KeyDownEvent, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) -> bool { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::key_down(self, event, &mut cx) + } + + fn key_up( + &mut self, + event: &KeyUpEvent, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) -> bool { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::key_up(self, event, &mut cx) + } + + fn modifiers_changed( + &mut self, + event: &ModifiersChangedEvent, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) -> bool { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::modifiers_changed(self, event, &mut cx) } fn keymap_context(&self, cx: &AppContext) -> keymap::Context { @@ -3463,7 +3597,7 @@ impl<'a, T: View> ViewContext<'a, T> { if self.window_id != view.window_id { return false; } - self.parents(view.window_id, view.view_id) + self.ancestors(view.window_id, view.view_id) .any(|parent| parent == self.view_id) } @@ -6354,13 +6488,13 @@ mod tests { "View" } - fn on_focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { if cx.handle().id() == focused.id() { self.events.lock().push(format!("{} focused", &self.name)); } } - fn on_focus_out(&mut self, blurred: AnyViewHandle, cx: &mut ViewContext) { + fn focus_out(&mut self, blurred: AnyViewHandle, cx: &mut ViewContext) { if cx.handle().id() == blurred.id() { self.events.lock().push(format!("{} blurred", &self.name)); } diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 68c0b25f204b6f54b4aa686c356ee21af3cd3f40..9f11f09f8e9e33ebcd414e15546419ad00317ffe 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -33,8 +33,8 @@ use crate::{ }, json, presenter::MeasurementContext, - Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext, - SizeConstraint, View, + Action, DebugContext, EventContext, LayoutContext, PaintContext, RenderContext, SizeConstraint, + View, }; use core::panic; use json::ToJson; @@ -50,7 +50,6 @@ use std::{ trait AnyElement { fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F; fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext); - fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool; fn rect_for_text_range( &self, range_utf16: Range, @@ -80,16 +79,6 @@ pub trait Element { cx: &mut PaintContext, ) -> Self::PaintState; - fn dispatch_event( - &mut self, - event: &Event, - bounds: RectF, - visible_bounds: RectF, - layout: &mut Self::LayoutState, - paint: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool; - fn rect_for_text_range( &self, range_utf16: Range, @@ -303,22 +292,6 @@ impl AnyElement for Lifecycle { } } - fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool { - if let Lifecycle::PostPaint { - element, - bounds, - visible_bounds, - layout, - paint, - .. - } = self - { - element.dispatch_event(event, *bounds, *visible_bounds, layout, paint, cx) - } else { - panic!("invalid element lifecycle state"); - } - } - fn rect_for_text_range( &self, range_utf16: Range, @@ -433,10 +406,6 @@ impl ElementRc { self.element.borrow_mut().paint(origin, visible_bounds, cx); } - pub fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool { - self.element.borrow_mut().dispatch_event(event, cx) - } - pub fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index 5158b7229eda70897290fff516095ac21c7f0b8c..ec1a210f75cc7f810a840dffebd0216bbe304471 100644 --- a/crates/gpui/src/elements/align.rs +++ b/crates/gpui/src/elements/align.rs @@ -2,8 +2,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json, presenter::MeasurementContext, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, - SizeConstraint, + DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; use json::ToJson; @@ -84,18 +83,6 @@ impl Element for Align { ); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: std::ops::Range, diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 6fec2dd1dc1cfafa84c971487883a60abbc52681..31c56bb9874e9e19e340219a8e967a181dafbf56 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -56,18 +56,6 @@ where self.0(bounds, visible_bounds, cx) } - fn dispatch_event( - &mut self, - _: &crate::Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut crate::EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: std::ops::Range, diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs index 012bbe23dd05f5b542a541310b844aafe5ca0975..2e232c6197774861fd14a453e62a8efb5772ee53 100644 --- a/crates/gpui/src/elements/constrained_box.rs +++ b/crates/gpui/src/elements/constrained_box.rs @@ -7,8 +7,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json, presenter::MeasurementContext, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, - SizeConstraint, + DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; pub struct ConstrainedBox { @@ -157,18 +156,6 @@ impl Element for ConstrainedBox { self.child.paint(bounds.origin(), visible_bounds, cx); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index dc0d0bac1bda4044a6b9a3157b6734a6115b9876..8e17fdd60f2c16b50825896fdeac0674a89e8d4f 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -11,7 +11,7 @@ use crate::{ platform::CursorStyle, presenter::MeasurementContext, scene::{self, Border, CursorRegion, Quad}, - Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, + Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; use serde::Deserialize; use serde_json::json; @@ -285,18 +285,6 @@ impl Element for Container { } } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/empty.rs b/crates/gpui/src/elements/empty.rs index a7c2fc1d60ae62b1b2a77f00b14efabfb32c31e1..b0a0481c05caaf29741a0d36a2d37fc59329b6d4 100644 --- a/crates/gpui/src/elements/empty.rs +++ b/crates/gpui/src/elements/empty.rs @@ -9,7 +9,7 @@ use crate::{ presenter::MeasurementContext, DebugContext, }; -use crate::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint}; +use crate::{Element, LayoutContext, PaintContext, SizeConstraint}; #[derive(Default)] pub struct Empty { @@ -59,18 +59,6 @@ impl Element for Empty { ) -> Self::PaintState { } - fn dispatch_event( - &mut self, - _: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs index d77bbdbfb9c8e33036b6e76b444aef64833bbdbc..89e9c7c7d8997ecdb7b4ddcf0c8c65afc27a3422 100644 --- a/crates/gpui/src/elements/expanded.rs +++ b/crates/gpui/src/elements/expanded.rs @@ -4,8 +4,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json, presenter::MeasurementContext, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, - SizeConstraint, + DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; use serde_json::json; @@ -66,18 +65,6 @@ impl Element for Expanded { self.child.paint(bounds.origin(), visible_bounds, cx); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index fd37b001feb7dbc5c209a8f369cc9e24caabf040..95477c75601452f7aa26400d5cc4cb880021deb6 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -3,8 +3,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc}; use crate::{ json::{self, ToJson, Value}, presenter::MeasurementContext, - Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext, - LayoutContext, PaintContext, RenderContext, SizeConstraint, Vector2FExt, View, + Axis, DebugContext, Element, ElementBox, ElementStateHandle, LayoutContext, PaintContext, + RenderContext, SizeConstraint, Vector2FExt, View, }; use pathfinder_geometry::{ rect::RectF, @@ -259,7 +259,7 @@ impl Element for Flex { if remaining_space < 0. { let mut delta = match axis { Axis::Horizontal => { - if e.delta.x() != 0. { + if e.delta.x().abs() >= e.delta.y().abs() { e.delta.x() } else { e.delta.y() @@ -318,23 +318,6 @@ impl Element for Flex { } } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - let mut handled = false; - for child in &mut self.children { - handled = child.dispatch_event(event, cx) || handled; - } - - handled - } - fn rect_for_text_range( &self, range_utf16: Range, @@ -420,18 +403,6 @@ impl Element for FlexItem { self.child.paint(bounds.origin(), visible_bounds, cx) } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/hook.rs b/crates/gpui/src/elements/hook.rs index c620847372bed4963d49412809b4733e9515108e..b77fef4fae3d08abdfa69da2f345ef1a790d84fa 100644 --- a/crates/gpui/src/elements/hook.rs +++ b/crates/gpui/src/elements/hook.rs @@ -4,8 +4,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::json, presenter::MeasurementContext, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, - SizeConstraint, + DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; pub struct Hook { @@ -56,18 +55,6 @@ impl Element for Hook { self.child.paint(bounds.origin(), visible_bounds, cx); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index 7e231e5e293f8660b5dc60b9e8a6ebcf32264429..37cb01ace8a6c43e0add3d366d888f06b4b0225e 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -6,8 +6,7 @@ use crate::{ }, json::{json, ToJson}, presenter::MeasurementContext, - scene, Border, DebugContext, Element, Event, EventContext, ImageData, LayoutContext, - PaintContext, SizeConstraint, + scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint, }; use serde::Deserialize; use std::{ops::Range, sync::Arc}; @@ -81,18 +80,6 @@ impl Element for Image { }); } - fn dispatch_event( - &mut self, - _: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index 9b21a1b8a86e942da7d31dccf52b24d2a71d2bd2..ca317d9e114cdae9987d47e6ab073b08c90dd689 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -2,7 +2,7 @@ use crate::{ elements::*, fonts::TextStyle, geometry::{rect::RectF, vector::Vector2F}, - Action, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, + Action, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; use serde_json::json; @@ -64,18 +64,6 @@ impl Element for KeystrokeLabel { element.paint(bounds.origin(), visible_bounds, cx); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - element: &mut ElementBox, - _: &mut (), - cx: &mut EventContext, - ) -> bool { - element.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs index 18654808682bb181bd6c4557114782a649d9dda1..605a3cb7670444777f0482b59c3a066b7392bb7d 100644 --- a/crates/gpui/src/elements/label.rs +++ b/crates/gpui/src/elements/label.rs @@ -9,7 +9,7 @@ use crate::{ json::{ToJson, Value}, presenter::MeasurementContext, text_layout::{Line, RunStyle}, - DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, + DebugContext, Element, LayoutContext, PaintContext, SizeConstraint, }; use serde::Deserialize; use serde_json::json; @@ -165,18 +165,6 @@ impl Element for Label { line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx) } - fn dispatch_event( - &mut self, - _: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index d0f87590fc53065a4d27849e947e24def56f28d7..a1b4ef0c796ad3a061916b2b2a871fe26bfe4b1a 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -5,7 +5,7 @@ use crate::{ }, json::json, presenter::MeasurementContext, - DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, MouseRegion, + DebugContext, Element, ElementBox, ElementRc, EventContext, LayoutContext, MouseRegion, PaintContext, RenderContext, SizeConstraint, View, ViewContext, }; use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc}; @@ -13,7 +13,6 @@ use sum_tree::{Bias, SumTree}; pub struct List { state: ListState, - invalidated_elements: Vec, } #[derive(Clone)] @@ -82,10 +81,7 @@ struct Height(f32); impl List { pub fn new(state: ListState) -> Self { - Self { - state, - invalidated_elements: Default::default(), - } + Self { state } } } @@ -289,50 +285,6 @@ impl Element for List { cx.scene.pop_layer(); } - fn dispatch_event( - &mut self, - event: &Event, - bounds: RectF, - _: RectF, - scroll_top: &mut ListOffset, - _: &mut (), - cx: &mut EventContext, - ) -> bool { - let mut handled = false; - - let mut state = self.state.0.borrow_mut(); - let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item); - let mut cursor = state.items.cursor::(); - let mut new_items = cursor.slice(&Count(scroll_top.item_ix), Bias::Right, &()); - while let Some(item) = cursor.item() { - if item_origin.y() > bounds.max_y() { - break; - } - - if let ListItem::Rendered(element) = item { - let prev_notify_count = cx.notify_count(); - let mut element = element.clone(); - handled = element.dispatch_event(event, cx) || handled; - item_origin.set_y(item_origin.y() + element.size().y()); - if cx.notify_count() > prev_notify_count { - new_items.push(ListItem::Unrendered, &()); - self.invalidated_elements.push(element); - } else { - new_items.push(item.clone(), &()); - } - cursor.next(&()); - } else { - unreachable!(); - } - } - - new_items.push_tree(cursor.suffix(&()), &()); - drop(cursor); - state.items = new_items; - - handled - } - fn rect_for_text_range( &self, range_utf16: Range, @@ -964,18 +916,6 @@ mod tests { todo!() } - fn dispatch_event( - &mut self, - _: &Event, - _: RectF, - _: RectF, - _: &mut (), - _: &mut (), - _: &mut EventContext, - ) -> bool { - todo!() - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index ab5aeb562bbf29bb13862d1f2bc52f2a05b8a8f6..c8ba330e70324fb8c11df4469810f19b38834d2b 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -6,11 +6,10 @@ use crate::{ }, platform::CursorStyle, scene::{ - ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, - HandlerSet, HoverRegionEvent, MoveRegionEvent, ScrollWheelRegionEvent, UpOutRegionEvent, - UpRegionEvent, + CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, + MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, }, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext, + DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext, MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, }; use serde_json::json; @@ -61,10 +60,7 @@ impl MouseEventHandler { self } - pub fn on_move( - mut self, - handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static, - ) -> Self { + pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self { self.handlers = self.handlers.on_move(handler); self } @@ -72,7 +68,7 @@ impl MouseEventHandler { pub fn on_down( mut self, button: MouseButton, - handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDown, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down(button, handler); self @@ -81,7 +77,7 @@ impl MouseEventHandler { pub fn on_up( mut self, button: MouseButton, - handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseUp, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up(button, handler); self @@ -90,7 +86,7 @@ impl MouseEventHandler { pub fn on_click( mut self, button: MouseButton, - handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseClick, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_click(button, handler); self @@ -99,7 +95,7 @@ impl MouseEventHandler { pub fn on_down_out( mut self, button: MouseButton, - handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down_out(button, handler); self @@ -108,7 +104,7 @@ impl MouseEventHandler { pub fn on_up_out( mut self, button: MouseButton, - handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up_out(button, handler); self @@ -117,23 +113,20 @@ impl MouseEventHandler { pub fn on_drag( mut self, button: MouseButton, - handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDrag, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag(button, handler); self } - pub fn on_hover( - mut self, - handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, - ) -> Self { + pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self { self.handlers = self.handlers.on_hover(handler); self } pub fn on_scroll( mut self, - handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_scroll(handler); self @@ -201,18 +194,6 @@ impl Element for MouseEventHandler { self.child.paint(bounds.origin(), visible_bounds, cx); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 07442d1140c2be8f992641bf447e0ede2aa157d3..253c88f703a36abc30162053fa42b3c586738b6d 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -4,8 +4,8 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, presenter::MeasurementContext, - Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion, - PaintContext, SizeConstraint, + Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext, + SizeConstraint, }; use serde_json::json; @@ -225,18 +225,6 @@ impl Element for Overlay { cx.scene.pop_stacking_context(); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 802452790300cf94f41b39f4e103382c25a3ea86..fb5bfa4a11643dbb351a9f7138b7fdb683f43ddb 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -4,7 +4,7 @@ use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use crate::{ - geometry::rect::RectF, scene::DragRegionEvent, Axis, CursorStyle, Element, ElementBox, + geometry::rect::RectF, scene::MouseDrag, Axis, CursorStyle, Element, ElementBox, ElementStateHandle, MouseButton, MouseRegion, RenderContext, View, }; @@ -42,7 +42,7 @@ impl Side { } } - fn compute_delta(&self, e: DragRegionEvent) -> f32 { + fn compute_delta(&self, e: MouseDrag) -> f32 { if self.before_content() { self.relevant_component(e.prev_mouse_position) - self.relevant_component(e.position) } else { @@ -187,18 +187,6 @@ impl Element for Resizable { self.child.paint(bounds.origin(), visible_bounds, cx); } - fn dispatch_event( - &mut self, - event: &crate::Event, - _bounds: pathfinder_geometry::rect::RectF, - _visible_bounds: pathfinder_geometry::rect::RectF, - _layout: &mut Self::LayoutState, - _paint: &mut Self::PaintState, - cx: &mut crate::EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range_utf16: std::ops::Range, diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs index 5de53ff9e7f40712c5f36550434c6cce2058ad1a..f08ce04649a4219eba27040c7e9e43dbfebde2f8 100644 --- a/crates/gpui/src/elements/stack.rs +++ b/crates/gpui/src/elements/stack.rs @@ -4,8 +4,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::{self, json, ToJson}, presenter::MeasurementContext, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, - SizeConstraint, + DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, }; #[derive(Default)] @@ -49,23 +48,6 @@ impl Element for Stack { } } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - for child in self.children.iter_mut().rev() { - if child.dispatch_event(event, cx) { - return true; - } - } - false - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 544abb3314a526b72f674e01a213f5f4b75af334..5bd2759f7a47ecbda0bf5f2df23e5ebf87254c49 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -9,7 +9,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, presenter::MeasurementContext, - scene, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, + scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint, }; pub struct Svg { @@ -73,18 +73,6 @@ impl Element for Svg { } } - fn dispatch_event( - &mut self, - _: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 45dc2dbae3343051e8b417fc6a12393757e125f7..073e343b9c3ddeb9a0b6ed7744f33fa5c02b15c3 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -8,8 +8,7 @@ use crate::{ json::{ToJson, Value}, presenter::MeasurementContext, text_layout::{Line, RunStyle, ShapedBoundary}, - DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext, - SizeConstraint, TextLayoutCache, + DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache, }; use log::warn; use serde_json::json; @@ -178,18 +177,6 @@ impl Element for Text { } } - fn dispatch_event( - &mut self, - _: &Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - _: &mut EventContext, - ) -> bool { - false - } - fn rect_for_text_range( &self, _: Range, diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index c86230a5e158f30c5cef1716b67113a664b3a4df..f81b4af70115d71eef9c77f3c3a99ab198777164 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -186,18 +186,6 @@ impl Element for Tooltip { } } - fn dispatch_event( - &mut self, - event: &crate::Event, - _: RectF, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut crate::EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - fn rect_for_text_range( &self, range: Range, diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index c9cdbc1b2c90f76f8ac9ba3c6014d2d68e3e06f6..ca3601491ab2a507bef84fdcbcdd53bd72d209b1 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -1,4 +1,4 @@ -use super::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint}; +use super::{Element, EventContext, LayoutContext, PaintContext, SizeConstraint}; use crate::{ geometry::{ rect::RectF, @@ -6,7 +6,7 @@ use crate::{ }, json::{self, json}, presenter::MeasurementContext, - scene::ScrollWheelRegionEvent, + scene::MouseScrollWheel, ElementBox, MouseRegion, RenderContext, ScrollWheelEvent, View, }; use json::ToJson; @@ -292,7 +292,7 @@ impl Element for UniformList { MouseRegion::new::(self.view_id, 0, visible_bounds).on_scroll({ let scroll_max = layout.scroll_max; let state = self.state.clone(); - move |ScrollWheelRegionEvent { + move |MouseScrollWheel { platform_event: ScrollWheelEvent { position, @@ -324,23 +324,6 @@ impl Element for UniformList { cx.scene.pop_layer(); } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - layout: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - let mut handled = false; - for item in &mut layout.items { - handled = item.dispatch_event(event, cx) || handled; - } - - handled - } - fn rect_for_text_range( &self, range: Range, diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 48043ac9186b0701ab7d08fe86de30941ae56dda..7e4b95c9a1385fa3d3e566a8dd5bb8779775d1cd 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -11,7 +11,7 @@ pub struct KeyUpEvent { pub keystroke: Keystroke, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct ModifiersChangedEvent { pub ctrl: bool, pub alt: bool, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index d082ebd095a503e8e312530a94e934d14c0d003a..74c2dddd662c9f733ecc2a456fadc473e52de9c9 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -7,9 +7,8 @@ use crate::{ keymap::Keystroke, platform::{CursorStyle, Event}, scene::{ - ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, - HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, ScrollWheelRegionEvent, - UpOutRegionEvent, UpRegionEvent, + CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, + MouseMove, MouseScrollWheel, MouseUp, MouseUpOut, }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance, @@ -229,293 +228,283 @@ impl Presenter { event_reused: bool, cx: &mut MutableAppContext, ) -> bool { - if let Some(root_view_id) = cx.root_view_id(self.window_id) { - let mut events_to_send = Vec::new(); - let mut notified_views: HashSet = Default::default(); - - // 1. Allocate the correct set of GPUI events generated from the platform events - // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] - // -> Also moves around mouse related state - match &event { - Event::MouseDown(e) => { - // Click events are weird because they can be fired after a drag event. - // MDN says that browsers handle this by starting from 'the most - // specific ancestor element that contained both [positions]' - // So we need to store the overlapping regions on mouse down. - - // If there is already clicked_button stored, don't replace it. - if self.clicked_button.is_none() { - self.clicked_region_ids = self - .mouse_regions - .iter() - .filter_map(|(region, _)| { - if region.bounds.contains_point(e.position) { - Some(region.id()) - } else { - None - } - }) - .collect(); - - self.clicked_button = Some(e.button); - } + let mut mouse_events = SmallVec::<[_; 2]>::new(); + let mut notified_views: HashSet = Default::default(); + + // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events + // get mapped into the mouse-specific MouseEvent type. + // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] + // -> Also updates mouse-related state + match &event { + Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e), + Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e), + Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e), + Event::MouseDown(e) => { + // Click events are weird because they can be fired after a drag event. + // MDN says that browsers handle this by starting from 'the most + // specific ancestor element that contained both [positions]' + // So we need to store the overlapping regions on mouse down. + + // If there is already clicked_button stored, don't replace it. + if self.clicked_button.is_none() { + self.clicked_region_ids = self + .mouse_regions + .iter() + .filter_map(|(region, _)| { + if region.bounds.contains_point(e.position) { + Some(region.id()) + } else { + None + } + }) + .collect(); - events_to_send.push(MouseRegionEvent::Down(DownRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })); - events_to_send.push(MouseRegionEvent::DownOut(DownOutRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })); + self.clicked_button = Some(e.button); } - Event::MouseUp(e) => { - // NOTE: The order of event pushes is important! MouseUp events MUST be fired - // before click events, and so the UpRegionEvent events need to be pushed before - // ClickRegionEvents - events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })); - events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })); - events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })); - } - Event::MouseMoved( - e @ MouseMovedEvent { - position, - pressed_button, - .. - }, - ) => { - let mut style_to_assign = CursorStyle::Arrow; - for region in self.cursor_regions.iter().rev() { - if region.bounds.contains_point(*position) { - style_to_assign = region.style; - break; - } + + mouse_events.push(MouseEvent::Down(MouseDown { + region: Default::default(), + platform_event: e.clone(), + })); + mouse_events.push(MouseEvent::DownOut(MouseDownOut { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseUp(e) => { + // NOTE: The order of event pushes is important! MouseUp events MUST be fired + // before click events, and so the MouseUp events need to be pushed before + // MouseClick events. + mouse_events.push(MouseEvent::Up(MouseUp { + region: Default::default(), + platform_event: e.clone(), + })); + mouse_events.push(MouseEvent::UpOut(MouseUpOut { + region: Default::default(), + platform_event: e.clone(), + })); + mouse_events.push(MouseEvent::Click(MouseClick { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseMoved( + e @ MouseMovedEvent { + position, + pressed_button, + .. + }, + ) => { + let mut style_to_assign = CursorStyle::Arrow; + for region in self.cursor_regions.iter().rev() { + if region.bounds.contains_point(*position) { + style_to_assign = region.style; + break; } - cx.platform().set_cursor_style(style_to_assign); - - if !event_reused { - if pressed_button.is_some() { - events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent { - region: Default::default(), - prev_mouse_position: self.mouse_position, - platform_event: e.clone(), - })); - } else if let Some(clicked_button) = self.clicked_button { - // Mouse up event happened outside the current window. Simulate mouse up button event - let button_event = e.to_button_event(clicked_button); - events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { - region: Default::default(), - platform_event: button_event.clone(), - })); - events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent { - region: Default::default(), - platform_event: button_event.clone(), - })); - events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent { - region: Default::default(), - platform_event: button_event.clone(), - })); - } + } + cx.platform().set_cursor_style(style_to_assign); - events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent { + if !event_reused { + if pressed_button.is_some() { + mouse_events.push(MouseEvent::Drag(MouseDrag { region: Default::default(), + prev_mouse_position: self.mouse_position, platform_event: e.clone(), })); + } else if let Some(clicked_button) = self.clicked_button { + // Mouse up event happened outside the current window. Simulate mouse up button event + let button_event = e.to_button_event(clicked_button); + mouse_events.push(MouseEvent::Up(MouseUp { + region: Default::default(), + platform_event: button_event.clone(), + })); + mouse_events.push(MouseEvent::UpOut(MouseUpOut { + region: Default::default(), + platform_event: button_event.clone(), + })); + mouse_events.push(MouseEvent::Click(MouseClick { + region: Default::default(), + platform_event: button_event.clone(), + })); } - events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { + mouse_events.push(MouseEvent::Move(MouseMove { region: Default::default(), platform_event: e.clone(), - started: false, })); - - self.last_mouse_moved_event = Some(event.clone()); - } - Event::ScrollWheel(e) => { - events_to_send.push(MouseRegionEvent::ScrollWheel(ScrollWheelRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })) } - _ => {} - } + mouse_events.push(MouseEvent::Hover(MouseHover { + region: Default::default(), + platform_event: e.clone(), + started: false, + })); - if let Some(position) = event.position() { - self.mouse_position = position; + self.last_mouse_moved_event = Some(event.clone()); } + Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel { + region: Default::default(), + platform_event: e.clone(), + })), + } - let mut any_event_handled = false; - // 2. Process the raw mouse events into region events - for mut region_event in events_to_send { - let mut valid_regions = Vec::new(); - - // GPUI elements are arranged by depth but sibling elements can register overlapping - // mouse regions. As such, hover events are only fired on overlapping elements which - // are at the same depth as the topmost element which overlaps with the mouse. - - match ®ion_event { - MouseRegionEvent::Hover(_) => { - let mut top_most_depth = None; - let mouse_position = self.mouse_position.clone(); - for (region, depth) in self.mouse_regions.iter().rev() { - // Allow mouse regions to appear transparent to hovers - if !region.hoverable { - continue; - } + if let Some(position) = event.position() { + self.mouse_position = position; + } - let contains_mouse = region.bounds.contains_point(mouse_position); + // 2. Dispatch mouse events on regions + let mut any_event_handled = false; + for mut mouse_event in mouse_events { + let mut valid_regions = Vec::new(); + + // GPUI elements are arranged by depth but sibling elements can register overlapping + // mouse regions. As such, hover events are only fired on overlapping elements which + // are at the same depth as the topmost element which overlaps with the mouse. + match &mouse_event { + MouseEvent::Hover(_) => { + let mut top_most_depth = None; + let mouse_position = self.mouse_position.clone(); + for (region, depth) in self.mouse_regions.iter().rev() { + // Allow mouse regions to appear transparent to hovers + if !region.hoverable { + continue; + } - if contains_mouse && top_most_depth.is_none() { - top_most_depth = Some(depth); - } + let contains_mouse = region.bounds.contains_point(mouse_position); - // This unwrap relies on short circuiting boolean expressions - // The right side of the && is only executed when contains_mouse - // is true, and we know above that when contains_mouse is true - // top_most_depth is set - if contains_mouse && depth == top_most_depth.unwrap() { - //Ensure that hover entrance events aren't sent twice - if self.hovered_region_ids.insert(region.id()) { - valid_regions.push(region.clone()); - if region.notify_on_hover { - notified_views.insert(region.id().view_id()); - } - } - } else { - // Ensure that hover exit events aren't sent twice - if self.hovered_region_ids.remove(®ion.id()) { - valid_regions.push(region.clone()); - if region.notify_on_hover { - notified_views.insert(region.id().view_id()); - } - } - } + if contains_mouse && top_most_depth.is_none() { + top_most_depth = Some(depth); } - } - MouseRegionEvent::Down(_) | MouseRegionEvent::Up(_) => { - for (region, _) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(self.mouse_position) { - if region.notify_on_click { + + // This unwrap relies on short circuiting boolean expressions + // The right side of the && is only executed when contains_mouse + // is true, and we know above that when contains_mouse is true + // top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region.id()) { + valid_regions.push(region.clone()); + if region.notify_on_hover { notified_views.insert(region.id().view_id()); } - valid_regions.push(region.clone()); } - } - } - MouseRegionEvent::Click(e) => { - // Only raise click events if the released button is the same as the one stored - if self - .clicked_button - .map(|clicked_button| clicked_button == e.button) - .unwrap_or(false) - { - // Clear clicked regions and clicked button - let clicked_region_ids = - std::mem::replace(&mut self.clicked_region_ids, Default::default()); - self.clicked_button = None; - - // Find regions which still overlap with the mouse since the last MouseDown happened - for (mouse_region, _) in self.mouse_regions.iter().rev() { - if clicked_region_ids.contains(&mouse_region.id()) { - if mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); - } + } else { + // Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion.id()) { + valid_regions.push(region.clone()); + if region.notify_on_hover { + notified_views.insert(region.id().view_id()); } } } } - MouseRegionEvent::Drag(_) => { - for (mouse_region, _) in self.mouse_regions.iter().rev() { - if self.clicked_region_ids.contains(&mouse_region.id()) { - valid_regions.push(mouse_region.clone()); + } + MouseEvent::Down(_) | MouseEvent::Up(_) => { + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(self.mouse_position) { + if region.notify_on_click { + notified_views.insert(region.id().view_id()); } + valid_regions.push(region.clone()); } } - - MouseRegionEvent::UpOut(_) | MouseRegionEvent::DownOut(_) => { + } + MouseEvent::Click(e) => { + // Only raise click events if the released button is the same as the one stored + if self + .clicked_button + .map(|clicked_button| clicked_button == e.button) + .unwrap_or(false) + { + // Clear clicked regions and clicked button + let clicked_region_ids = + std::mem::replace(&mut self.clicked_region_ids, Default::default()); + self.clicked_button = None; + + // Find regions which still overlap with the mouse since the last MouseDown happened for (mouse_region, _) in self.mouse_regions.iter().rev() { - // NOT contains - if !mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); + if clicked_region_ids.contains(&mouse_region.id()) { + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } } } } - _ => { - for (mouse_region, _) in self.mouse_regions.iter().rev() { - // Contains - if mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); - } + } + MouseEvent::Drag(_) => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + if self.clicked_region_ids.contains(&mouse_region.id()) { + valid_regions.push(mouse_region.clone()); } } } - //3. Fire region events - let hovered_region_ids = self.hovered_region_ids.clone(); - for valid_region in valid_regions.into_iter() { - let mut event_cx = self.build_event_context(&mut notified_views, cx); - - region_event.set_region(valid_region.bounds); - if let MouseRegionEvent::Hover(e) = &mut region_event { - e.started = hovered_region_ids.contains(&valid_region.id()) + MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + // NOT contains + if !mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } } - // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would - // not expect a MouseRegion to be transparent to Down events if it also has a Click handler. - // This behavior can be overridden by adding a Down handler that calls cx.propogate_event - if let MouseRegionEvent::Down(e) = ®ion_event { - if valid_region - .handlers - .contains_handler(MouseRegionEvent::click_disc(), Some(e.button)) - || valid_region - .handlers - .contains_handler(MouseRegionEvent::drag_disc(), Some(e.button)) - { - event_cx.handled = true; + } + _ => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + // Contains + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); } } + } + } - if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { - event_cx.handled = true; - event_cx.with_current_view(valid_region.id().view_id(), { - let region_event = region_event.clone(); - |cx| { - callback(region_event, cx); - } - }); - } + //3. Fire region events + let hovered_region_ids = self.hovered_region_ids.clone(); + for valid_region in valid_regions.into_iter() { + let mut event_cx = self.build_event_context(&mut notified_views, cx); - any_event_handled = any_event_handled || event_cx.handled; - // For bubbling events, if the event was handled, don't continue dispatching - // This only makes sense for local events. - if event_cx.handled && region_event.is_capturable() { - break; + mouse_event.set_region(valid_region.bounds); + if let MouseEvent::Hover(e) = &mut mouse_event { + e.started = hovered_region_ids.contains(&valid_region.id()) + } + // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would + // not expect a MouseRegion to be transparent to Down events if it also has a Click handler. + // This behavior can be overridden by adding a Down handler that calls cx.propogate_event + if let MouseEvent::Down(e) = &mouse_event { + if valid_region + .handlers + .contains_handler(MouseEvent::click_disc(), Some(e.button)) + || valid_region + .handlers + .contains_handler(MouseEvent::drag_disc(), Some(e.button)) + { + event_cx.handled = true; } } - } - if !any_event_handled && !event_reused { - let mut event_cx = self.build_event_context(&mut notified_views, cx); - any_event_handled = event_cx.dispatch_event(root_view_id, &event); - } + if let Some(callback) = valid_region.handlers.get(&mouse_event.handler_key()) { + event_cx.handled = true; + event_cx.with_current_view(valid_region.id().view_id(), { + let region_event = mouse_event.clone(); + |cx| { + callback(region_event, cx); + } + }); + } - for view_id in notified_views { - cx.notify_view(self.window_id, view_id); + any_event_handled = any_event_handled || event_cx.handled; + // For bubbling events, if the event was handled, don't continue dispatching + // This only makes sense for local events. + if event_cx.handled && mouse_event.is_capturable() { + break; + } } + } - any_event_handled - } else { - false + for view_id in notified_views { + cx.notify_view(self.window_id, view_id); } + + any_event_handled } pub fn build_event_context<'a>( @@ -524,7 +513,6 @@ impl Presenter { cx: &'a mut MutableAppContext, ) -> EventContext<'a> { EventContext { - rendered_views: &mut self.rendered_views, font_cache: &self.font_cache, text_layout_cache: &self.text_layout_cache, view_stack: Default::default(), @@ -743,7 +731,6 @@ impl<'a> Deref for PaintContext<'a> { } pub struct EventContext<'a> { - rendered_views: &'a mut HashMap, pub font_cache: &'a FontCache, pub text_layout_cache: &'a TextLayoutCache, pub app: &'a mut MutableAppContext, @@ -755,17 +742,6 @@ pub struct EventContext<'a> { } impl<'a> EventContext<'a> { - fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool { - if let Some(mut element) = self.rendered_views.remove(&view_id) { - let result = - self.with_current_view(view_id, |this| element.dispatch_event(event, this)); - self.rendered_views.insert(view_id, element); - result - } else { - false - } - } - fn with_current_view(&mut self, view_id: usize, f: F) -> T where F: FnOnce(&mut Self) -> T, @@ -1028,27 +1004,6 @@ impl Element for ChildView { } } - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: RectF, - view_is_valid: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - if *view_is_valid { - cx.dispatch_event(self.view.id(), event) - } else { - log::error!( - "dispatch_event called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})", - self.view.id(), - self.view_name - ); - false - } - } - fn rect_for_text_range( &self, range_utf16: Range, diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 4ef17a3f8f5d384723ed1d94a13019bb495e4d0e..99b38a185277731a389771d531ef5b3ca9fc0187 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,5 +1,5 @@ +mod mouse_event; mod mouse_region; -mod mouse_region_event; #[cfg(debug_assertions)] use collections::HashSet; @@ -15,8 +15,8 @@ use crate::{ platform::{current::Surface, CursorStyle}, ImageData, }; +pub use mouse_event::*; pub use mouse_region::*; -pub use mouse_region_event::*; pub struct Scene { scale_factor: f32, diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7370ac75f8b5e332e320ad5cf6a95cc781828af --- /dev/null +++ b/crates/gpui/src/scene/mouse_event.rs @@ -0,0 +1,233 @@ +use std::{ + mem::{discriminant, Discriminant}, + ops::Deref, +}; + +use pathfinder_geometry::{rect::RectF, vector::Vector2F}; + +use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; + +#[derive(Debug, Default, Clone)] +pub struct MouseMove { + pub region: RectF, + pub platform_event: MouseMovedEvent, +} + +impl Deref for MouseMove { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseDrag { + pub region: RectF, + pub prev_mouse_position: Vector2F, + pub platform_event: MouseMovedEvent, +} + +impl Deref for MouseDrag { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseHover { + pub region: RectF, + pub started: bool, + pub platform_event: MouseMovedEvent, +} + +impl Deref for MouseHover { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseDown { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseDown { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseUp { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseUp { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseClick { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseClick { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseDownOut { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseDownOut { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseUpOut { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseUpOut { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default, Clone)] +pub struct MouseScrollWheel { + pub region: RectF, + pub platform_event: ScrollWheelEvent, +} + +impl Deref for MouseScrollWheel { + type Target = ScrollWheelEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Clone)] +pub enum MouseEvent { + Move(MouseMove), + Drag(MouseDrag), + Hover(MouseHover), + Down(MouseDown), + Up(MouseUp), + Click(MouseClick), + DownOut(MouseDownOut), + UpOut(MouseUpOut), + ScrollWheel(MouseScrollWheel), +} + +impl MouseEvent { + pub fn set_region(&mut self, region: RectF) { + match self { + MouseEvent::Move(r) => r.region = region, + MouseEvent::Drag(r) => r.region = region, + MouseEvent::Hover(r) => r.region = region, + MouseEvent::Down(r) => r.region = region, + MouseEvent::Up(r) => r.region = region, + MouseEvent::Click(r) => r.region = region, + MouseEvent::DownOut(r) => r.region = region, + MouseEvent::UpOut(r) => r.region = region, + MouseEvent::ScrollWheel(r) => r.region = region, + } + } + + /// When true, mouse event handlers must call cx.propagate_event() to bubble + /// the event to handlers they are painted on top of. + pub fn is_capturable(&self) -> bool { + match self { + MouseEvent::Move(_) => true, + MouseEvent::Drag(_) => true, + MouseEvent::Hover(_) => false, + MouseEvent::Down(_) => true, + MouseEvent::Up(_) => true, + MouseEvent::Click(_) => true, + MouseEvent::DownOut(_) => false, + MouseEvent::UpOut(_) => false, + MouseEvent::ScrollWheel(_) => true, + } + } +} + +impl MouseEvent { + pub fn move_disc() -> Discriminant { + discriminant(&MouseEvent::Move(Default::default())) + } + + pub fn drag_disc() -> Discriminant { + discriminant(&MouseEvent::Drag(Default::default())) + } + + pub fn hover_disc() -> Discriminant { + discriminant(&MouseEvent::Hover(Default::default())) + } + + pub fn down_disc() -> Discriminant { + discriminant(&MouseEvent::Down(Default::default())) + } + + pub fn up_disc() -> Discriminant { + discriminant(&MouseEvent::Up(Default::default())) + } + + pub fn up_out_disc() -> Discriminant { + discriminant(&MouseEvent::UpOut(Default::default())) + } + + pub fn click_disc() -> Discriminant { + discriminant(&MouseEvent::Click(Default::default())) + } + + pub fn down_out_disc() -> Discriminant { + discriminant(&MouseEvent::DownOut(Default::default())) + } + + pub fn scroll_wheel_disc() -> Discriminant { + discriminant(&MouseEvent::ScrollWheel(Default::default())) + } + + pub fn handler_key(&self) -> (Discriminant, Option) { + match self { + MouseEvent::Move(_) => (Self::move_disc(), None), + MouseEvent::Drag(e) => (Self::drag_disc(), e.pressed_button), + MouseEvent::Hover(_) => (Self::hover_disc(), None), + MouseEvent::Down(e) => (Self::down_disc(), Some(e.button)), + MouseEvent::Up(e) => (Self::up_disc(), Some(e.button)), + MouseEvent::Click(e) => (Self::click_disc(), Some(e.button)), + MouseEvent::UpOut(e) => (Self::up_out_disc(), Some(e.button)), + MouseEvent::DownOut(e) => (Self::down_out_disc(), Some(e.button)), + MouseEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), + } + } +} diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index e84508622b8d7b0130d43ebb9f20a9122f487c92..4b5217cc2dec1355d350f921fa66233d8be39591 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -7,11 +7,11 @@ use pathfinder_geometry::rect::RectF; use crate::{EventContext, MouseButton}; use super::{ - mouse_region_event::{ - ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent, - MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, + mouse_event::{ + MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, + MouseUpOut, }, - ScrollWheelRegionEvent, + MouseScrollWheel, }; #[derive(Clone)] @@ -62,7 +62,7 @@ impl MouseRegion { pub fn on_down( mut self, button: MouseButton, - handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDown, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down(button, handler); self @@ -71,7 +71,7 @@ impl MouseRegion { pub fn on_up( mut self, button: MouseButton, - handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseUp, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up(button, handler); self @@ -80,7 +80,7 @@ impl MouseRegion { pub fn on_click( mut self, button: MouseButton, - handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseClick, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_click(button, handler); self @@ -89,7 +89,7 @@ impl MouseRegion { pub fn on_down_out( mut self, button: MouseButton, - handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down_out(button, handler); self @@ -98,7 +98,7 @@ impl MouseRegion { pub fn on_up_out( mut self, button: MouseButton, - handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up_out(button, handler); self @@ -107,31 +107,25 @@ impl MouseRegion { pub fn on_drag( mut self, button: MouseButton, - handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDrag, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag(button, handler); self } - pub fn on_hover( - mut self, - handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, - ) -> Self { + pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self { self.handlers = self.handlers.on_hover(handler); self } - pub fn on_move( - mut self, - handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static, - ) -> Self { + pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self { self.handlers = self.handlers.on_move(handler); self } pub fn on_scroll( mut self, - handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_scroll(handler); self @@ -187,8 +181,8 @@ impl MouseRegionId { pub struct HandlerSet { #[allow(clippy::type_complexity)] pub set: HashMap< - (Discriminant, Option), - Rc, + (Discriminant, Option), + Rc, >, } @@ -196,68 +190,50 @@ impl HandlerSet { pub fn capture_all() -> Self { #[allow(clippy::type_complexity)] let mut set: HashMap< - (Discriminant, Option), - Rc, + (Discriminant, Option), + Rc, > = Default::default(); - set.insert((MouseRegionEvent::move_disc(), None), Rc::new(|_, _| {})); - set.insert((MouseRegionEvent::hover_disc(), None), Rc::new(|_, _| {})); + set.insert((MouseEvent::move_disc(), None), Rc::new(|_, _| {})); + set.insert((MouseEvent::hover_disc(), None), Rc::new(|_, _| {})); for button in MouseButton::all() { + set.insert((MouseEvent::drag_disc(), Some(button)), Rc::new(|_, _| {})); + set.insert((MouseEvent::down_disc(), Some(button)), Rc::new(|_, _| {})); + set.insert((MouseEvent::up_disc(), Some(button)), Rc::new(|_, _| {})); + set.insert((MouseEvent::click_disc(), Some(button)), Rc::new(|_, _| {})); set.insert( - (MouseRegionEvent::drag_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - set.insert( - (MouseRegionEvent::down_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - set.insert( - (MouseRegionEvent::up_disc(), Some(button)), + (MouseEvent::down_out_disc(), Some(button)), Rc::new(|_, _| {}), ); set.insert( - (MouseRegionEvent::click_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - set.insert( - (MouseRegionEvent::down_out_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - set.insert( - (MouseRegionEvent::up_out_disc(), Some(button)), + (MouseEvent::up_out_disc(), Some(button)), Rc::new(|_, _| {}), ); } - set.insert( - (MouseRegionEvent::scroll_wheel_disc(), None), - Rc::new(|_, _| {}), - ); + set.insert((MouseEvent::scroll_wheel_disc(), None), Rc::new(|_, _| {})); HandlerSet { set } } pub fn get( &self, - key: &(Discriminant, Option), - ) -> Option> { + key: &(Discriminant, Option), + ) -> Option> { self.set.get(key).cloned() } pub fn contains_handler( &self, - event: Discriminant, + event: Discriminant, button: Option, ) -> bool { self.set.contains_key(&(event, button)) } - pub fn on_move( - mut self, - handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static, - ) -> Self { - self.set.insert((MouseRegionEvent::move_disc(), None), + pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self { + self.set.insert((MouseEvent::move_disc(), None), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Move(e) = region_event { + if let MouseEvent::Move(e) = region_event { handler(e, cx); } else { panic!( @@ -271,11 +247,11 @@ impl HandlerSet { pub fn on_down( mut self, button: MouseButton, - handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDown, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::down_disc(), Some(button)), + self.set.insert((MouseEvent::down_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Down(e) = region_event { + if let MouseEvent::Down(e) = region_event { handler(e, cx); } else { panic!( @@ -289,11 +265,11 @@ impl HandlerSet { pub fn on_up( mut self, button: MouseButton, - handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseUp, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::up_disc(), Some(button)), + self.set.insert((MouseEvent::up_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Up(e) = region_event { + if let MouseEvent::Up(e) = region_event { handler(e, cx); } else { panic!( @@ -307,11 +283,11 @@ impl HandlerSet { pub fn on_click( mut self, button: MouseButton, - handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseClick, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::click_disc(), Some(button)), + self.set.insert((MouseEvent::click_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Click(e) = region_event { + if let MouseEvent::Click(e) = region_event { handler(e, cx); } else { panic!( @@ -325,11 +301,11 @@ impl HandlerSet { pub fn on_down_out( mut self, button: MouseButton, - handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)), + self.set.insert((MouseEvent::down_out_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::DownOut(e) = region_event { + if let MouseEvent::DownOut(e) = region_event { handler(e, cx); } else { panic!( @@ -343,11 +319,11 @@ impl HandlerSet { pub fn on_up_out( mut self, button: MouseButton, - handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)), + self.set.insert((MouseEvent::up_out_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::UpOut(e) = region_event { + if let MouseEvent::UpOut(e) = region_event { handler(e, cx); } else { panic!( @@ -361,11 +337,11 @@ impl HandlerSet { pub fn on_drag( mut self, button: MouseButton, - handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseDrag, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::drag_disc(), Some(button)), + self.set.insert((MouseEvent::drag_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Drag(e) = region_event { + if let MouseEvent::Drag(e) = region_event { handler(e, cx); } else { panic!( @@ -376,13 +352,10 @@ impl HandlerSet { self } - pub fn on_hover( - mut self, - handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, - ) -> Self { - self.set.insert((MouseRegionEvent::hover_disc(), None), + pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self { + self.set.insert((MouseEvent::hover_disc(), None), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Hover(e) = region_event { + if let MouseEvent::Hover(e) = region_event { handler(e, cx); } else { panic!( @@ -395,11 +368,11 @@ impl HandlerSet { pub fn on_scroll( mut self, - handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static, + handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseRegionEvent::scroll_wheel_disc(), None), + self.set.insert((MouseEvent::scroll_wheel_disc(), None), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::ScrollWheel(e) = region_event { + if let MouseEvent::ScrollWheel(e) = region_event { handler(e, cx); } else { panic!( diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs deleted file mode 100644 index 4d89cd5b6f06a9aafd98d168a0c1defbd55fd801..0000000000000000000000000000000000000000 --- a/crates/gpui/src/scene/mouse_region_event.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::{ - mem::{discriminant, Discriminant}, - ops::Deref, -}; - -use pathfinder_geometry::{rect::RectF, vector::Vector2F}; - -use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; - -#[derive(Debug, Default, Clone)] -pub struct MoveRegionEvent { - pub region: RectF, - pub platform_event: MouseMovedEvent, -} - -impl Deref for MoveRegionEvent { - type Target = MouseMovedEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct DragRegionEvent { - pub region: RectF, - pub prev_mouse_position: Vector2F, - pub platform_event: MouseMovedEvent, -} - -impl Deref for DragRegionEvent { - type Target = MouseMovedEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct HoverRegionEvent { - pub region: RectF, - pub started: bool, - pub platform_event: MouseMovedEvent, -} - -impl Deref for HoverRegionEvent { - type Target = MouseMovedEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct DownRegionEvent { - pub region: RectF, - pub platform_event: MouseButtonEvent, -} - -impl Deref for DownRegionEvent { - type Target = MouseButtonEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct UpRegionEvent { - pub region: RectF, - pub platform_event: MouseButtonEvent, -} - -impl Deref for UpRegionEvent { - type Target = MouseButtonEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct ClickRegionEvent { - pub region: RectF, - pub platform_event: MouseButtonEvent, -} - -impl Deref for ClickRegionEvent { - type Target = MouseButtonEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct DownOutRegionEvent { - pub region: RectF, - pub platform_event: MouseButtonEvent, -} - -impl Deref for DownOutRegionEvent { - type Target = MouseButtonEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct UpOutRegionEvent { - pub region: RectF, - pub platform_event: MouseButtonEvent, -} - -impl Deref for UpOutRegionEvent { - type Target = MouseButtonEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default, Clone)] -pub struct ScrollWheelRegionEvent { - pub region: RectF, - pub platform_event: ScrollWheelEvent, -} - -impl Deref for ScrollWheelRegionEvent { - type Target = ScrollWheelEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Clone)] -pub enum MouseRegionEvent { - Move(MoveRegionEvent), - Drag(DragRegionEvent), - Hover(HoverRegionEvent), - Down(DownRegionEvent), - Up(UpRegionEvent), - Click(ClickRegionEvent), - DownOut(DownOutRegionEvent), - UpOut(UpOutRegionEvent), - ScrollWheel(ScrollWheelRegionEvent), -} - -impl MouseRegionEvent { - pub fn set_region(&mut self, region: RectF) { - match self { - MouseRegionEvent::Move(r) => r.region = region, - MouseRegionEvent::Drag(r) => r.region = region, - MouseRegionEvent::Hover(r) => r.region = region, - MouseRegionEvent::Down(r) => r.region = region, - MouseRegionEvent::Up(r) => r.region = region, - MouseRegionEvent::Click(r) => r.region = region, - MouseRegionEvent::DownOut(r) => r.region = region, - MouseRegionEvent::UpOut(r) => r.region = region, - MouseRegionEvent::ScrollWheel(r) => r.region = region, - } - } - - /// When true, mouse event handlers must call cx.propagate_event() to bubble - /// the event to handlers they are painted on top of. - pub fn is_capturable(&self) -> bool { - match self { - MouseRegionEvent::Move(_) => true, - MouseRegionEvent::Drag(_) => true, - MouseRegionEvent::Hover(_) => false, - MouseRegionEvent::Down(_) => true, - MouseRegionEvent::Up(_) => true, - MouseRegionEvent::Click(_) => true, - MouseRegionEvent::DownOut(_) => false, - MouseRegionEvent::UpOut(_) => false, - MouseRegionEvent::ScrollWheel(_) => true, - } - } -} - -impl MouseRegionEvent { - pub fn move_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Move(Default::default())) - } - - pub fn drag_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Drag(Default::default())) - } - - pub fn hover_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Hover(Default::default())) - } - - pub fn down_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Down(Default::default())) - } - - pub fn up_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Up(Default::default())) - } - - pub fn up_out_disc() -> Discriminant { - discriminant(&MouseRegionEvent::UpOut(Default::default())) - } - - pub fn click_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Click(Default::default())) - } - - pub fn down_out_disc() -> Discriminant { - discriminant(&MouseRegionEvent::DownOut(Default::default())) - } - - pub fn scroll_wheel_disc() -> Discriminant { - discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) - } - - pub fn handler_key(&self) -> (Discriminant, Option) { - match self { - MouseRegionEvent::Move(_) => (Self::move_disc(), None), - MouseRegionEvent::Drag(e) => (Self::drag_disc(), e.pressed_button), - MouseRegionEvent::Hover(_) => (Self::hover_disc(), None), - MouseRegionEvent::Down(e) => (Self::down_disc(), Some(e.button)), - MouseRegionEvent::Up(e) => (Self::up_disc(), Some(e.button)), - MouseRegionEvent::Click(e) => (Self::click_disc(), Some(e.button)), - MouseRegionEvent::UpOut(e) => (Self::up_out_disc(), Some(e.button)), - MouseRegionEvent::DownOut(e) => (Self::down_out_disc(), Some(e.button)), - MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), - } - } -} diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 274777b81cbf5531288f81e03523f515b53a0eda..5a75f1a56f53c4a621679649ee37fb7fafdb7611 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -111,9 +111,19 @@ pub enum IndentKind { Tab, } +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +pub enum CursorShape { + #[default] + Bar, + Block, + Underscore, + Hollow, +} + #[derive(Clone, Debug)] struct SelectionSet { line_mode: bool, + cursor_shape: CursorShape, selections: Arc<[Selection]>, lamport_timestamp: clock::Lamport, } @@ -161,6 +171,7 @@ pub enum Operation { selections: Arc<[Selection]>, lamport_timestamp: clock::Lamport, line_mode: bool, + cursor_shape: CursorShape, }, UpdateCompletionTriggers { triggers: Vec, @@ -395,6 +406,7 @@ impl Buffer { selections: set.selections.clone(), lamport_timestamp: set.lamport_timestamp, line_mode: set.line_mode, + cursor_shape: set.cursor_shape, }) })); operations.push(proto::serialize_operation(&Operation::UpdateDiagnostics { @@ -1227,6 +1239,7 @@ impl Buffer { &mut self, selections: Arc<[Selection]>, line_mode: bool, + cursor_shape: CursorShape, cx: &mut ModelContext, ) { let lamport_timestamp = self.text.lamport_clock.tick(); @@ -1236,6 +1249,7 @@ impl Buffer { selections: selections.clone(), lamport_timestamp, line_mode, + cursor_shape, }, ); self.send_operation( @@ -1243,13 +1257,14 @@ impl Buffer { selections, line_mode, lamport_timestamp, + cursor_shape, }, cx, ); } pub fn remove_active_selections(&mut self, cx: &mut ModelContext) { - self.set_active_selections(Arc::from([]), false, cx); + self.set_active_selections(Arc::from([]), false, Default::default(), cx); } pub fn set_text(&mut self, text: T, cx: &mut ModelContext) -> Option @@ -1474,6 +1489,7 @@ impl Buffer { selections, lamport_timestamp, line_mode, + cursor_shape, } => { if let Some(set) = self.remote_selections.get(&lamport_timestamp.replica_id) { if set.lamport_timestamp > lamport_timestamp { @@ -1487,6 +1503,7 @@ impl Buffer { selections, lamport_timestamp, line_mode, + cursor_shape, }, ); self.text.lamport_clock.observe(lamport_timestamp); @@ -2236,6 +2253,7 @@ impl BufferSnapshot { Item = ( ReplicaId, bool, + CursorShape, impl Iterator> + '_, ), > + '_ { @@ -2259,6 +2277,7 @@ impl BufferSnapshot { ( *replica_id, set.line_mode, + set.cursor_shape, set.selections[start_ix..end_ix].iter(), ) }) @@ -2315,6 +2334,18 @@ impl BufferSnapshot { self.file.as_deref() } + pub fn resolve_file_path(&self, cx: &AppContext, include_root: bool) -> Option { + if let Some(file) = self.file() { + if file.path().file_name().is_none() || include_root { + Some(file.full_path(cx)) + } else { + Some(file.path().to_path_buf()) + } + } else { + None + } + } + pub fn file_update_count(&self) -> usize { self.file_update_count } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 0f3ab50f4adf2daae6828479cf4cd5879615fddb..f1b51f7e021368d93a99f9addd9d223a7e3934ab 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1283,7 +1283,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { selections ); active_selections.insert(replica_id, selections.clone()); - buffer.set_active_selections(selections, false, cx); + buffer.set_active_selections(selections, false, Default::default(), cx); }); mutation_count -= 1; } @@ -1448,7 +1448,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { let buffer = buffer.read(cx).snapshot(); let actual_remote_selections = buffer .remote_selections_in_range(Anchor::MIN..Anchor::MAX) - .map(|(replica_id, _, selections)| (replica_id, selections.collect::>())) + .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::>())) .collect::>(); let expected_remote_selections = active_selections .iter() diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 9e3ee7d46b17790cb0af121dade9edea27eb301f..f93d99f76b02a86a0267d2355a7c85cccda60b70 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,5 +1,6 @@ use crate::{ - diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, Diagnostic, Language, + diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, + Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -52,11 +53,13 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation { selections, line_mode, lamport_timestamp, + cursor_shape, } => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections { replica_id: lamport_timestamp.replica_id as u32, lamport_timestamp: lamport_timestamp.value, selections: serialize_selections(selections), line_mode: *line_mode, + cursor_shape: serialize_cursor_shape(cursor_shape) as i32, }), crate::Operation::UpdateDiagnostics { diagnostics, @@ -125,6 +128,24 @@ pub fn serialize_selection(selection: &Selection) -> proto::Selection { } } +pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape { + match cursor_shape { + CursorShape::Bar => proto::CursorShape::CursorBar, + CursorShape::Block => proto::CursorShape::CursorBlock, + CursorShape::Underscore => proto::CursorShape::CursorUnderscore, + CursorShape::Hollow => proto::CursorShape::CursorHollow, + } +} + +pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape { + match cursor_shape { + proto::CursorShape::CursorBar => CursorShape::Bar, + proto::CursorShape::CursorBlock => CursorShape::Block, + proto::CursorShape::CursorUnderscore => CursorShape::Underscore, + proto::CursorShape::CursorHollow => CursorShape::Hollow, + } +} + pub fn serialize_diagnostics<'a>( diagnostics: impl IntoIterator>, ) -> Vec { @@ -223,6 +244,10 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index a677ab5b675a57bd4731bd760401ccfe1fafb248..cee5388800ab3a84c749dfc96cdbf371b551d61c 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -52,7 +52,7 @@ impl View for OutlineView { ChildView::new(self.picker.clone(), cx).boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.picker); } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 30ad7827efce472959d3c833fc492bd1d45d8c3a..0945f6771bc6d640fb05a7df6233cbc6d71d71e7 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -116,7 +116,7 @@ impl View for Picker { cx } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.query_editor); } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 4e6dc09e4e45cc8528c8382221f8e487e32e9c15..76c60f9556235605e59a62cc5bd8c70aa9aaff1b 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -52,7 +52,6 @@ similar = "1.3" smol = "1.2.5" thiserror = "1.0.29" toml = "0.5" -rocksdb = "0.18" [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f964726c4ceb3e250bd662acca72ecd90d2710dc..f05b7e6728a3273f77705daa69a28941e7d198c1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -583,7 +583,10 @@ impl Project { cx: &mut gpui::TestAppContext, ) -> ModelHandle { if !cx.read(|cx| cx.has_global::()) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + cx.update(|cx| { + cx.set_global(Settings::test(cx)); + cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())) + }); } let languages = Arc::new(LanguageRegistry::test()); diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 1b0294c4d12ed83ab2586a5ffd5ee3f53089faa0..a9ac5f4411469e6466ded12b6762526dbc783658 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2164,6 +2164,7 @@ async fn test_rescan_and_remote_updates( proto::WorktreeMetadata { id: initial_snapshot.id().to_proto(), root_name: initial_snapshot.root_name().into(), + abs_path: initial_snapshot.abs_path().as_os_str().as_bytes().to_vec(), visible: true, }, rpc.clone(), diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 383c9ac35b34c38d02e208cef1cb2b2c5a61e45a..9f0ebe32f7a10af0e1b547fd4ae0e51ce2d78134 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -5,8 +5,8 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; -use fs::LineEnding; use fs::{repository::GitRepository, Fs}; +use fs::{HomeDir, LineEnding}; use futures::{ channel::{ mpsc::{self, UnboundedSender}, @@ -87,6 +87,7 @@ pub struct RemoteWorktree { #[derive(Clone)] pub struct Snapshot { id: WorktreeId, + abs_path: Arc, root_name: String, root_char_bag: CharBag, entries_by_path: SumTree, @@ -118,7 +119,6 @@ impl std::fmt::Debug for GitRepositoryEntry { } pub struct LocalSnapshot { - abs_path: Arc, ignores_by_parent_abs_path: HashMap, (Arc, usize)>, git_repositories: Vec, removed_entry_ids: HashMap, @@ -130,7 +130,6 @@ pub struct LocalSnapshot { impl Clone for LocalSnapshot { fn clone(&self) -> Self { Self { - abs_path: self.abs_path.clone(), ignores_by_parent_abs_path: self.ignores_by_parent_abs_path.clone(), git_repositories: self.git_repositories.iter().cloned().collect(), removed_entry_ids: self.removed_entry_ids.clone(), @@ -221,8 +220,11 @@ impl Worktree { .collect(); let root_name = worktree.root_name.clone(); let visible = worktree.visible; + + let abs_path = PathBuf::from(OsString::from_vec(worktree.abs_path)); let snapshot = Snapshot { id: WorktreeId(remote_id as usize), + abs_path: Arc::from(abs_path.deref()), root_name, root_char_bag, entries_by_path: Default::default(), @@ -372,6 +374,13 @@ impl Worktree { Self::Remote(worktree) => worktree.poll_snapshot(cx), }; } + + pub fn abs_path(&self) -> Arc { + match self { + Worktree::Local(worktree) => worktree.abs_path.clone(), + Worktree::Remote(worktree) => worktree.abs_path.clone(), + } + } } impl LocalWorktree { @@ -402,13 +411,13 @@ impl LocalWorktree { watch::channel_with(ScanState::Initializing); let tree = cx.add_model(move |cx: &mut ModelContext| { let mut snapshot = LocalSnapshot { - abs_path, ignores_by_parent_abs_path: Default::default(), git_repositories: Default::default(), removed_entry_ids: Default::default(), next_entry_id, snapshot: Snapshot { id: WorktreeId::from_usize(cx.model_id()), + abs_path, root_name: root_name.clone(), root_char_bag, entries_by_path: Default::default(), @@ -647,6 +656,7 @@ impl LocalWorktree { id: self.id().to_proto(), root_name: self.root_name().to_string(), visible: self.visible, + abs_path: self.abs_path().as_os_str().as_bytes().to_vec(), } } @@ -980,6 +990,7 @@ impl LocalWorktree { let update = proto::UpdateWorktree { project_id, worktree_id, + abs_path: snapshot.abs_path().as_os_str().as_bytes().to_vec(), root_name: snapshot.root_name().to_string(), updated_entries: snapshot .entries_by_path @@ -1389,6 +1400,7 @@ impl LocalSnapshot { proto::UpdateWorktree { project_id, worktree_id: self.id().to_proto(), + abs_path: self.abs_path().as_os_str().as_bytes().to_vec(), root_name, updated_entries: self.entries_by_path.iter().map(Into::into).collect(), removed_entries: Default::default(), @@ -1456,6 +1468,7 @@ impl LocalSnapshot { proto::UpdateWorktree { project_id, worktree_id, + abs_path: self.abs_path().as_os_str().as_bytes().to_vec(), root_name: self.root_name().to_string(), updated_entries, removed_entries, @@ -1839,10 +1852,25 @@ impl language::File for File { fn full_path(&self, cx: &AppContext) -> PathBuf { let mut full_path = PathBuf::new(); - full_path.push(self.worktree.read(cx).root_name()); + let worktree = self.worktree.read(cx); + + if worktree.is_visible() { + full_path.push(worktree.root_name()); + } else { + let path = worktree.abs_path(); + + if worktree.is_local() && path.starts_with(cx.global::().as_path()) { + full_path.push("~"); + full_path.push(path.strip_prefix(cx.global::().as_path()).unwrap()); + } else { + full_path.push(path) + } + } + if self.path.components().next().is_some() { full_path.push(&self.path); } + full_path } @@ -3455,7 +3483,6 @@ mod tests { let fs = Arc::new(RealFs); let next_entry_id = Arc::new(AtomicUsize::new(0)); let mut initial_snapshot = LocalSnapshot { - abs_path: root_dir.path().into(), removed_entry_ids: Default::default(), ignores_by_parent_abs_path: Default::default(), git_repositories: Default::default(), @@ -3464,6 +3491,7 @@ mod tests { id: WorktreeId::from_usize(0), entries_by_path: Default::default(), entries_by_id: Default::default(), + abs_path: root_dir.path().into(), root_name: Default::default(), root_char_bag: Default::default(), scan_id: 0, diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index a81231b98f38395c7ed67cef227fec407807b112..e4a251de00cdfc5de619c6e8d2beb305fadd1ea1 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -51,7 +51,7 @@ impl View for ProjectSymbolsView { ChildView::new(self.picker.clone(), cx).boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.picker); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3f6bf6149e72fe8ecd442d4ab01749918bdef97e..ded708370d3f64d00c478661911e34e37fa8dd98 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -6,125 +6,130 @@ message Envelope { optional uint32 responding_to = 2; optional uint32 original_sender_id = 3; oneof payload { - Ack ack = 4; - Error error = 5; - Ping ping = 6; - Test test = 7; + Hello hello = 4; + Ack ack = 5; + Error error = 6; + Ping ping = 7; + Test test = 8; - CreateRoom create_room = 8; - CreateRoomResponse create_room_response = 9; - JoinRoom join_room = 10; - JoinRoomResponse join_room_response = 11; - LeaveRoom leave_room = 12; - Call call = 13; - IncomingCall incoming_call = 14; - CallCanceled call_canceled = 15; - CancelCall cancel_call = 16; - DeclineCall decline_call = 17; - UpdateParticipantLocation update_participant_location = 18; - RoomUpdated room_updated = 19; - - ShareProject share_project = 20; - ShareProjectResponse share_project_response = 21; - UnshareProject unshare_project = 22; - JoinProject join_project = 23; - JoinProjectResponse join_project_response = 24; - LeaveProject leave_project = 25; - AddProjectCollaborator add_project_collaborator = 26; - RemoveProjectCollaborator remove_project_collaborator = 27; - - GetDefinition get_definition = 28; - GetDefinitionResponse get_definition_response = 29; - GetTypeDefinition get_type_definition = 30; - GetTypeDefinitionResponse get_type_definition_response = 31; - GetReferences get_references = 32; - GetReferencesResponse get_references_response = 33; - GetDocumentHighlights get_document_highlights = 34; - GetDocumentHighlightsResponse get_document_highlights_response = 35; - GetProjectSymbols get_project_symbols = 36; - GetProjectSymbolsResponse get_project_symbols_response = 37; - OpenBufferForSymbol open_buffer_for_symbol = 38; - OpenBufferForSymbolResponse open_buffer_for_symbol_response = 39; - - UpdateProject update_project = 40; - RegisterProjectActivity register_project_activity = 41; - UpdateWorktree update_worktree = 42; - UpdateWorktreeExtensions update_worktree_extensions = 43; - - CreateProjectEntry create_project_entry = 44; - RenameProjectEntry rename_project_entry = 45; - CopyProjectEntry copy_project_entry = 46; - DeleteProjectEntry delete_project_entry = 47; - ProjectEntryResponse project_entry_response = 48; - - UpdateDiagnosticSummary update_diagnostic_summary = 49; - StartLanguageServer start_language_server = 50; - UpdateLanguageServer update_language_server = 51; - - OpenBufferById open_buffer_by_id = 52; - OpenBufferByPath open_buffer_by_path = 53; - OpenBufferResponse open_buffer_response = 54; - CreateBufferForPeer create_buffer_for_peer = 55; - UpdateBuffer update_buffer = 56; - UpdateBufferFile update_buffer_file = 57; - SaveBuffer save_buffer = 58; - BufferSaved buffer_saved = 59; - BufferReloaded buffer_reloaded = 60; - ReloadBuffers reload_buffers = 61; - ReloadBuffersResponse reload_buffers_response = 62; - FormatBuffers format_buffers = 63; - FormatBuffersResponse format_buffers_response = 64; - GetCompletions get_completions = 65; - GetCompletionsResponse get_completions_response = 66; - ApplyCompletionAdditionalEdits apply_completion_additional_edits = 67; - ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 68; - GetCodeActions get_code_actions = 69; - GetCodeActionsResponse get_code_actions_response = 70; - GetHover get_hover = 71; - GetHoverResponse get_hover_response = 72; - ApplyCodeAction apply_code_action = 73; - ApplyCodeActionResponse apply_code_action_response = 74; - PrepareRename prepare_rename = 75; - PrepareRenameResponse prepare_rename_response = 76; - PerformRename perform_rename = 77; - PerformRenameResponse perform_rename_response = 78; - SearchProject search_project = 79; - SearchProjectResponse search_project_response = 80; - - GetChannels get_channels = 81; - GetChannelsResponse get_channels_response = 82; - JoinChannel join_channel = 83; - JoinChannelResponse join_channel_response = 84; - LeaveChannel leave_channel = 85; - SendChannelMessage send_channel_message = 86; - SendChannelMessageResponse send_channel_message_response = 87; - ChannelMessageSent channel_message_sent = 88; - GetChannelMessages get_channel_messages = 89; - GetChannelMessagesResponse get_channel_messages_response = 90; - - UpdateContacts update_contacts = 91; - UpdateInviteInfo update_invite_info = 92; - ShowContacts show_contacts = 93; - - GetUsers get_users = 94; - FuzzySearchUsers fuzzy_search_users = 95; - UsersResponse users_response = 96; - RequestContact request_contact = 97; - RespondToContactRequest respond_to_contact_request = 98; - RemoveContact remove_contact = 99; - - Follow follow = 100; - FollowResponse follow_response = 101; - UpdateFollowers update_followers = 102; - Unfollow unfollow = 103; - GetPrivateUserInfo get_private_user_info = 104; - GetPrivateUserInfoResponse get_private_user_info_response = 105; - UpdateDiffBase update_diff_base = 106; + CreateRoom create_room = 9; + CreateRoomResponse create_room_response = 10; + JoinRoom join_room = 11; + JoinRoomResponse join_room_response = 12; + LeaveRoom leave_room = 13; + Call call = 14; + IncomingCall incoming_call = 15; + CallCanceled call_canceled = 16; + CancelCall cancel_call = 17; + DeclineCall decline_call = 18; + UpdateParticipantLocation update_participant_location = 19; + RoomUpdated room_updated = 20; + + ShareProject share_project = 21; + ShareProjectResponse share_project_response = 22; + UnshareProject unshare_project = 23; + JoinProject join_project = 24; + JoinProjectResponse join_project_response = 25; + LeaveProject leave_project = 26; + AddProjectCollaborator add_project_collaborator = 27; + RemoveProjectCollaborator remove_project_collaborator = 28; + + GetDefinition get_definition = 29; + GetDefinitionResponse get_definition_response = 30; + GetTypeDefinition get_type_definition = 31; + GetTypeDefinitionResponse get_type_definition_response = 32; + GetReferences get_references = 33; + GetReferencesResponse get_references_response = 34; + GetDocumentHighlights get_document_highlights = 35; + GetDocumentHighlightsResponse get_document_highlights_response = 36; + GetProjectSymbols get_project_symbols = 37; + GetProjectSymbolsResponse get_project_symbols_response = 38; + OpenBufferForSymbol open_buffer_for_symbol = 39; + OpenBufferForSymbolResponse open_buffer_for_symbol_response = 40; + + UpdateProject update_project = 41; + RegisterProjectActivity register_project_activity = 42; + UpdateWorktree update_worktree = 43; + UpdateWorktreeExtensions update_worktree_extensions = 44; + + CreateProjectEntry create_project_entry = 45; + RenameProjectEntry rename_project_entry = 46; + CopyProjectEntry copy_project_entry = 47; + DeleteProjectEntry delete_project_entry = 48; + ProjectEntryResponse project_entry_response = 49; + + UpdateDiagnosticSummary update_diagnostic_summary = 50; + StartLanguageServer start_language_server = 51; + UpdateLanguageServer update_language_server = 52; + + OpenBufferById open_buffer_by_id = 53; + OpenBufferByPath open_buffer_by_path = 54; + OpenBufferResponse open_buffer_response = 55; + CreateBufferForPeer create_buffer_for_peer = 56; + UpdateBuffer update_buffer = 57; + UpdateBufferFile update_buffer_file = 58; + SaveBuffer save_buffer = 59; + BufferSaved buffer_saved = 60; + BufferReloaded buffer_reloaded = 61; + ReloadBuffers reload_buffers = 62; + ReloadBuffersResponse reload_buffers_response = 63; + FormatBuffers format_buffers = 64; + FormatBuffersResponse format_buffers_response = 65; + GetCompletions get_completions = 66; + GetCompletionsResponse get_completions_response = 67; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 68; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 69; + GetCodeActions get_code_actions = 70; + GetCodeActionsResponse get_code_actions_response = 71; + GetHover get_hover = 72; + GetHoverResponse get_hover_response = 73; + ApplyCodeAction apply_code_action = 74; + ApplyCodeActionResponse apply_code_action_response = 75; + PrepareRename prepare_rename = 76; + PrepareRenameResponse prepare_rename_response = 77; + PerformRename perform_rename = 78; + PerformRenameResponse perform_rename_response = 79; + SearchProject search_project = 80; + SearchProjectResponse search_project_response = 81; + + GetChannels get_channels = 82; + GetChannelsResponse get_channels_response = 83; + JoinChannel join_channel = 84; + JoinChannelResponse join_channel_response = 85; + LeaveChannel leave_channel = 86; + SendChannelMessage send_channel_message = 87; + SendChannelMessageResponse send_channel_message_response = 88; + ChannelMessageSent channel_message_sent = 89; + GetChannelMessages get_channel_messages = 90; + GetChannelMessagesResponse get_channel_messages_response = 91; + + UpdateContacts update_contacts = 92; + UpdateInviteInfo update_invite_info = 93; + ShowContacts show_contacts = 94; + + GetUsers get_users = 95; + FuzzySearchUsers fuzzy_search_users = 96; + UsersResponse users_response = 97; + RequestContact request_contact = 98; + RespondToContactRequest respond_to_contact_request = 99; + RemoveContact remove_contact = 100; + + Follow follow = 101; + FollowResponse follow_response = 102; + UpdateFollowers update_followers = 103; + Unfollow unfollow = 104; + GetPrivateUserInfo get_private_user_info = 105; + GetPrivateUserInfoResponse get_private_user_info_response = 106; + UpdateDiffBase update_diff_base = 107; } } // Messages +message Hello { + uint32 peer_id = 1; +} + message Ping {} message Ack {} @@ -275,6 +280,7 @@ message UpdateWorktree { repeated uint64 removed_entries = 5; uint64 scan_id = 6; bool is_last_update = 7; + bytes abs_path = 8; } message UpdateWorktreeExtensions { @@ -917,6 +923,7 @@ message SelectionSet { repeated Selection selections = 2; uint32 lamport_timestamp = 3; bool line_mode = 4; + CursorShape cursor_shape = 5; } message Selection { @@ -926,6 +933,13 @@ message Selection { bool reversed = 4; } +enum CursorShape { + CursorBar = 0; + CursorBlock = 1; + CursorUnderscore = 2; + CursorHollow = 3; +} + message Anchor { uint32 replica_id = 1; uint32 local_timestamp = 2; @@ -991,6 +1005,7 @@ message Operation { uint32 lamport_timestamp = 2; repeated Selection selections = 3; bool line_mode = 4; + CursorShape cursor_shape = 5; } message UpdateCompletionTriggers { @@ -1061,6 +1076,7 @@ message WorktreeMetadata { uint64 id = 1; string root_name = 2; bool visible = 3; + bytes abs_path = 4; } message UpdateDiffBase { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 069fde4e59638d25673b6c517c6e9b7c776d8a38..11bbaaf5ffcbdeff96906033b1cacae6f62e48f0 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -121,6 +121,7 @@ messages!( (GetProjectSymbols, Background), (GetProjectSymbolsResponse, Background), (GetUsers, Foreground), + (Hello, Foreground), (IncomingCall, Foreground), (UsersResponse, Foreground), (JoinChannel, Foreground), @@ -435,6 +436,7 @@ pub fn split_worktree_update( project_id: message.project_id, worktree_id: message.worktree_id, root_name: message.root_name.clone(), + abs_path: message.abs_path.clone(), updated_entries, removed_entries: mem::take(&mut message.removed_entries), scan_id: message.scan_id, diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 5fb9ca79a2c51e7930eb88bdb96d2c949eb3a5f0..c11caab108ea722d5abba800a1c836d05cbfcd4e 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 35; +pub const PROTOCOL_VERSION: u32 = 38; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a43f3eb4867c7c9b1e27f1db278009cce26a6fcf..cd7a74ce8e3233f0d5b4404a3bc597ae4271b6a6 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -82,7 +82,7 @@ impl View for BufferSearchBar { "BufferSearchBar" } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.query_editor); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index eb5bf7d699480e9a240f93718f169f1979d0549f..8eb8e110e460972ada29f41f38150d5f8c08785f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -195,7 +195,7 @@ impl View for ProjectSearchView { } } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { let handle = cx.weak_handle(); cx.update_global(|state: &mut ActiveSearches, cx| { state diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 63bc5962fa0bba24e6cc0875793f96643f17cfe4..ee389c7a0efc3d3f6dc36ef526225dec0e4ec448 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -28,6 +28,7 @@ pub struct Settings { pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, pub buffer_font_size: f32, + pub cursor_blink: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, pub vim_mode: bool, @@ -234,6 +235,8 @@ pub struct SettingsFileContent { #[serde(default)] pub buffer_font_size: Option, #[serde(default)] + pub cursor_blink: Option, + #[serde(default)] pub hover_popover_enabled: Option, #[serde(default)] pub show_completions_on_input: Option, @@ -292,6 +295,7 @@ impl Settings { .unwrap(), buffer_font_size: defaults.buffer_font_size.unwrap(), default_buffer_font_size: defaults.buffer_font_size.unwrap(), + cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), projects_online_by_default: defaults.projects_online_by_default.unwrap(), @@ -346,6 +350,7 @@ impl Settings { ); merge(&mut self.buffer_font_size, data.buffer_font_size); merge(&mut self.default_buffer_font_size, data.buffer_font_size); + merge(&mut self.cursor_blink, data.cursor_blink); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); merge( &mut self.show_completions_on_input, @@ -436,6 +441,7 @@ impl Settings { buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., default_buffer_font_size: 14., + cursor_blink: true, hover_popover_enabled: true, show_completions_on_input: true, vim_mode: false, diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index a0b5231501228d699631e4146b2acb3051656903..efd6a739679178fd54d55f6e840df53d8f5a3165 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -8,16 +8,17 @@ path = "src/terminal.rs" doctest = false [dependencies] -alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" } -procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } +context_menu = { path = "../context_menu" } editor = { path = "../editor" } -util = { path = "../util" } +language = { path = "../language" } gpui = { path = "../gpui" } -theme = { path = "../theme" } +project = { path = "../project" } settings = { path = "../settings" } +theme = { path = "../theme" } +util = { path = "../util" } workspace = { path = "../workspace" } -project = { path = "../project" } -context_menu = { path = "../context_menu" } +alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" } +procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } smallvec = { version = "1.6", features = ["union"] } smol = "1.2.5" mio-extras = "2.0.6" diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 1616540cff65aaa245551ef7a2da37f7da2b4613..2254eea5af0965351c61adc7e0fe1e7baab40097 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -6,7 +6,7 @@ use alacritty_terminal::grid::Dimensions; /// with modifications for our circumstances use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side}; use alacritty_terminal::term::TermMode; -use gpui::scene::ScrollWheelRegionEvent; +use gpui::scene::MouseScrollWheel; use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; use crate::TerminalSize; @@ -115,7 +115,7 @@ impl MouseButton { pub fn scroll_report( point: Point, scroll_lines: i32, - e: &ScrollWheelRegionEvent, + e: &MouseScrollWheel, mode: TermMode, ) -> Option>> { if mode.intersects(TermMode::MOUSE_MODE) { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5485fb50ca2a94ad43d6e5dab5e7423be4946e3c..a18b90e56ddeb3a9644fe18d83b963381caa7a12 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -53,7 +53,7 @@ use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - scene::{DownRegionEvent, DragRegionEvent, ScrollWheelRegionEvent, UpRegionEvent}, + scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp}, ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, MutableAppContext, Task, }; @@ -971,7 +971,7 @@ impl Terminal { } } - pub fn mouse_drag(&mut self, e: DragRegionEvent, origin: Vector2F) { + pub fn mouse_drag(&mut self, e: MouseDrag, origin: Vector2F) { let position = e.position.sub(origin); self.last_mouse_position = Some(position); @@ -997,7 +997,7 @@ impl Terminal { } } - fn drag_line_delta(&mut self, e: DragRegionEvent) -> Option { + fn drag_line_delta(&mut self, e: MouseDrag) -> Option { //TODO: Why do these need to be doubled? Probably the same problem that the IME has let top = e.region.origin_y() + (self.last_content.size.line_height * 2.); let bottom = e.region.lower_left().y() - (self.last_content.size.line_height * 2.); @@ -1011,67 +1011,46 @@ impl Terminal { Some(scroll_delta) } - pub fn mouse_down(&mut self, e: &DownRegionEvent, origin: Vector2F) { + pub fn mouse_down(&mut self, e: &MouseDown, origin: Vector2F) { let position = e.position.sub(origin); let point = grid_point( position, self.last_content.size, self.last_content.display_offset, ); - // let side = mouse_side(position, self.last_content.size); if self.mouse_mode(e.shift) { if let Some(bytes) = mouse_button_report(point, e, true, self.last_content.mode) { self.pty_tx.notify(bytes); } } else if e.button == MouseButton::Left { - self.left_click(e, origin) - } - } - - pub fn left_click(&mut self, e: &DownRegionEvent, origin: Vector2F) { - let position = e.position.sub(origin); - if !self.mouse_mode(e.shift) { - //Hyperlinks - { - let mouse_cell_index = content_index_for_mouse(position, &self.last_content); - if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { - open_uri(link.uri()).log_err(); - } else { - self.events - .push_back(InternalEvent::FindHyperlink(position, true)); - } - } + let position = e.position.sub(origin); + let point = grid_point( + position, + self.last_content.size, + self.last_content.display_offset, + ); + let side = mouse_side(position, self.last_content.size); - // Selections - { - let point = grid_point( - position, - self.last_content.size, - self.last_content.display_offset, - ); - let side = mouse_side(position, self.last_content.size); - - let selection_type = match e.click_count { - 0 => return, //This is a release - 1 => Some(SelectionType::Simple), - 2 => Some(SelectionType::Semantic), - 3 => Some(SelectionType::Lines), - _ => None, - }; + let selection_type = match e.click_count { + 0 => return, //This is a release + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; - let selection = selection_type - .map(|selection_type| Selection::new(selection_type, point, side)); + let selection = + selection_type.map(|selection_type| Selection::new(selection_type, point, side)); - if let Some(sel) = selection { - self.events - .push_back(InternalEvent::SetSelection(Some((sel, point)))); - } + if let Some(sel) = selection { + self.events + .push_back(InternalEvent::SetSelection(Some((sel, point)))); } } } - pub fn mouse_up(&mut self, e: &UpRegionEvent, origin: Vector2F, cx: &mut ModelContext) { + pub fn mouse_up(&mut self, e: &MouseUp, origin: Vector2F, cx: &mut ModelContext) { let settings = cx.global::(); let copy_on_select = settings .terminal_overrides @@ -1094,8 +1073,21 @@ impl Terminal { if let Some(bytes) = mouse_button_report(point, e, false, self.last_content.mode) { self.pty_tx.notify(bytes); } - } else if e.button == MouseButton::Left && copy_on_select { - self.copy(); + } else { + if e.button == MouseButton::Left && copy_on_select { + self.copy(); + } + + //Hyperlinks + if self.selection_phase == SelectionPhase::Ended { + let mouse_cell_index = content_index_for_mouse(position, &self.last_content); + if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { + open_uri(link.uri()).log_err(); + } else { + self.events + .push_back(InternalEvent::FindHyperlink(position, true)); + } + } } self.selection_phase = SelectionPhase::Ended; @@ -1103,7 +1095,7 @@ impl Terminal { } ///Scroll the terminal - pub fn scroll_wheel(&mut self, e: ScrollWheelRegionEvent, origin: Vector2F) { + pub fn scroll_wheel(&mut self, e: MouseScrollWheel, origin: Vector2F) { let mouse_mode = self.mouse_mode(e.shift); if let Some(scroll_lines) = self.determine_scroll_lines(&e, mouse_mode) { @@ -1142,11 +1134,7 @@ impl Terminal { self.hyperlink_from_position(self.last_mouse_position); } - fn determine_scroll_lines( - &mut self, - e: &ScrollWheelRegionEvent, - mouse_mode: bool, - ) -> Option { + fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option { let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER }; match e.phase { diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index 5cad16774d9f54ab2f173fb44678527bffaf55a5..d56631fd4f2fac5776a807f7bc657addd5347478 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -174,7 +174,7 @@ impl View for TerminalContainer { } } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(self.content.handle()); } diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index df745dae464459f0d53439b264e91036353a7f44..7dfe9c785d34e685d96013e20bb8dc872e5c7e62 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -4,7 +4,7 @@ use alacritty_terminal::{ index::Point, term::{cell::Flags, TermMode}, }; -use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; +use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, elements::{Empty, Overlay}, @@ -15,11 +15,11 @@ use gpui::{ }, serde_json::json, text_layout::{Line, RunStyle}, - Element, ElementBox, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, - MouseRegion, PaintContext, Quad, SizeConstraint, TextLayoutCache, WeakModelHandle, - WeakViewHandle, + Element, ElementBox, EventContext, FontCache, ModelContext, MouseButton, MouseRegion, + PaintContext, Quad, SizeConstraint, TextLayoutCache, WeakModelHandle, WeakViewHandle, }; use itertools::Itertools; +use language::CursorShape; use ordered_float::OrderedFloat; use settings::Settings; use theme::TerminalStyle; @@ -799,46 +799,6 @@ impl Element for TerminalElement { }); } - fn dispatch_event( - &mut self, - event: &gpui::Event, - _bounds: gpui::geometry::rect::RectF, - _visible_bounds: gpui::geometry::rect::RectF, - _layout: &mut Self::LayoutState, - _paint: &mut Self::PaintState, - cx: &mut gpui::EventContext, - ) -> bool { - if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = event { - if !cx.is_parent_view_focused() { - return false; - } - - if let Some(view) = self.view.upgrade(cx.app) { - view.update(cx.app, |view, cx| { - view.clear_bel(cx); - view.pause_cursor_blinking(cx); - }) - } - - self.terminal - .upgrade(cx.app) - .map(|model_handle| { - model_handle.update(cx.app, |term, cx| { - term.try_keystroke( - keystroke, - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), - ) - }) - }) - .unwrap_or(false) - } else { - false - } - } - fn metadata(&self) -> Option<&dyn std::any::Any> { None } diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index 732c0a717edddab1d42532252692aa57c814477c..f67fa5bb928188eb6536a94e616c8c83e74ea960 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -38,18 +38,7 @@ pub struct SendKeystroke(String); actions!( terminal, - [ - Up, - Down, - CtrlC, - Escape, - Enter, - Clear, - Copy, - Paste, - ShowCharacterPalette, - SearchTest - ] + [Clear, Copy, Paste, ShowCharacterPalette, SearchTest] ); impl_actions!(terminal, [SendText, SendKeystroke]); @@ -343,20 +332,35 @@ impl View for TerminalView { .boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.has_new_content = false; self.terminal.read(cx).focus_in(); self.blink_cursors(self.blink_epoch, cx); cx.notify(); } - fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.terminal.update(cx, |terminal, _| { terminal.focus_out(); }); cx.notify(); } + fn key_down(&mut self, event: &gpui::KeyDownEvent, cx: &mut ViewContext) -> bool { + self.clear_bel(cx); + self.pause_cursor_blinking(cx); + + self.terminal.update(cx, |term, cx| { + term.try_keystroke( + &event.keystroke, + cx.global::() + .terminal_overrides + .option_as_meta + .unwrap_or(false), + ) + }) + } + //IME stuff fn selected_text_range(&self, cx: &AppContext) -> Option> { if self diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 3236120857e229514eef37c251fa42aca8ec8b37..1caeae75f597050f145b8ebc36aa3a0b827261fc 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -266,7 +266,7 @@ impl View for ThemeSelector { ChildView::new(self.picker.clone(), cx).boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.picker); } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index fef0da209996289ac1ac11171951918fe03ae9ee..b5acb50e7c04e945660187438096aa84be6405dc 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -1,5 +1,5 @@ -use editor::CursorShape; use gpui::keymap::Context; +use language::CursorShape; use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 81bafcf3e27f7f850aa89863c1c8f404a3e9b3f8..ce3a7e2366ae8279e877d94bf8e48bdcca6394b2 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -12,8 +12,9 @@ mod visual; use collections::HashMap; use command_palette::CommandPaletteFilter; -use editor::{Bias, Cancel, CursorShape, Editor}; +use editor::{Bias, Cancel, Editor}; use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle}; +use language::CursorShape; use serde::Deserialize; use settings::Settings; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index b7ae7f2ba0b867dd7b57b9c9ed77fd2a9077ef29..882b501d2e59c3414589713d17c6752a9f69351c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1485,7 +1485,7 @@ impl View for Pane { .named("pane") } - fn on_focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { if let Some(active_item) = self.active_item() { if cx.is_self_focused() { // Pane was focused directly. We need to either focus a view inside the active item, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5ec76f51301392be5672bc6eec29823db0968ba3..7f84e58c030fe295b3b79c72534d4fd1a4b0ad9c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -927,6 +927,9 @@ impl From<&dyn NotificationHandle> for AnyViewHandle { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut MutableAppContext) -> Arc { + use fs::HomeDir; + + cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); let settings = Settings::test(cx); cx.set_global(settings); @@ -2665,7 +2668,7 @@ impl View for Workspace { .named("workspace") } - fn on_focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { + fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { cx.focus(&self.active_pane); } else { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index abf4c218718fcbf55b6c6ec03e23f2962e362af6..e35bfabe109e55dabf49423206badeeeef2425a0 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.60.4" +version = "0.61.0" [lib] name = "zed" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a921bc2680271f195aa985b751fc98f45df0a9e9..072866445fdbfc7de0bcc2a376e90437f1d4f320 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -23,7 +23,7 @@ use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; use parking_lot::Mutex; -use project::{Fs, ProjectStore}; +use project::{Fs, HomeDir, ProjectStore}; use serde_json::json; use settings::{ self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, @@ -52,11 +52,9 @@ fn main() { .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); init_panic_hook(app_version, http.clone(), app.background()); - let db = app.background().spawn(async move { - project::Db::open(&*zed::paths::DB) - .log_err() - .unwrap_or_else(project::Db::null) - }); + let db = app + .background() + .spawn(async move { project::Db::open(&*zed::paths::DB_DIR) }); load_embedded_fonts(&app); @@ -99,6 +97,8 @@ fn main() { let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); + cx.set_global(HomeDir(zed::paths::HOME.to_path_buf())); + //Setup settings global before binding actions cx.set_global(SettingsFile::new( &*zed::paths::SETTINGS, diff --git a/crates/zed/src/paths.rs b/crates/zed/src/paths.rs index d6d99288c771f5260d5cd452fc97a04f15c87969..8698d6891e18e7e3e960a210ec7b3f23e308da72 100644 --- a/crates/zed/src/paths.rs +++ b/crates/zed/src/paths.rs @@ -1,12 +1,11 @@ use std::path::PathBuf; lazy_static::lazy_static! { - static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); + pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed"); pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); - pub static ref DB: PathBuf = DB_DIR.join("zed.db"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt"); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ef0c84909a28e229945917a3daa8c2c730323f1d..3319aebd09953d89e44a89c0961cd275f7dd6915 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -63,6 +63,7 @@ actions!( DecreaseBufferFontSize, ResetBufferFontSize, InstallCommandLineInterface, + ResetDatabase, ] ); diff --git a/script/changes-since-last-release b/script/changes-since-last-release index 5d0f94db1269fb5670f228b97af4ca2fcf8a662c..23a65143b26ec4ac71d28edbf68a7174f57897fd 100755 --- a/script/changes-since-last-release +++ b/script/changes-since-last-release @@ -67,7 +67,7 @@ async function main() { console.log(" URL: ", webURL); // If the pull request contains a 'closes' line, print the closed issue. - const fixesMatch = pullRequest.body.match(FIXES_REGEX); + const fixesMatch = (pullRequest.body || '').match(FIXES_REGEX); if (fixesMatch) { const fixedIssueURL = fixesMatch[2]; console.log(" Issue: ", fixedIssueURL);