From 1044b7d7d2b81e6e311b08284edbd7e336b92b95 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 Aug 2022 16:15:53 -0700 Subject: [PATCH 01/74] checkpoint, no idea what's happening --- crates/gpui/src/platform/mac/window.rs | 6 +++++- crates/terminal/src/mappings/keys.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 908efd451c..0aba30dd6d 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -292,6 +292,7 @@ struct WindowState { previous_modifiers_changed_event: Option, } +#[derive(Debug)] struct InsertText { replacement_range: Option>, text: String, @@ -742,6 +743,8 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let function_is_held; window_state_borrow.pending_key_down = match event { Event::KeyDown(event) => { + dbg!(&event); + let keydown = event.keystroke.clone(); // Ignore events from held-down keys after some of the initially-pressed keys // were released. @@ -752,12 +755,12 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: } else { window_state_borrow.last_fresh_keydown = Some(keydown); } - function_is_held = event.keystroke.function; Some((event, None)) } _ => return NO, }; + drop(window_state_borrow); if !function_is_held { @@ -782,6 +785,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: } if !handled { + println!("not handling, {:?}", insert_text); if let Some(insert) = insert_text { handled = true; with_input_handler(this, |input_handler| { diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 215bfe1ad9..6389bf6a10 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -243,7 +243,7 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { } } - None + Some(keystroke.key.to_string()) } /// Code Modifiers From e2200109e61ee3f821aa746463e25cf3cb25c58d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 Aug 2022 15:47:56 -0700 Subject: [PATCH 02/74] Make clippy happy --- crates/gpui/src/platform/mac/window.rs | 4 ---- crates/terminal/src/mappings/keys.rs | 2 +- styles/package-lock.json | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 35eacb6f68..60b079c266 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -301,7 +301,6 @@ struct WindowState { previous_modifiers_changed_event: Option, } -#[derive(Debug)] struct InsertText { replacement_range: Option>, text: String, @@ -757,8 +756,6 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let function_is_held; window_state_borrow.pending_key_down = match event { Event::KeyDown(event) => { - dbg!(&event); - let keydown = event.keystroke.clone(); // Ignore events from held-down keys after some of the initially-pressed keys // were released. @@ -799,7 +796,6 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: } if !handled { - println!("not handling, {:?}", insert_text); if let Some(insert) = insert_text { handled = true; with_input_handler(this, |input_handler| { diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index b71aa0d312..e07a324998 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -243,7 +243,7 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { } } - Some(keystroke.key.to_string()) + None } /// Code Modifiers diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c8496..5499f1852c 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From 45c7c32a0a0f76bc5a150975f356db559b0c2001 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 Aug 2022 19:29:52 -0700 Subject: [PATCH 03/74] Test --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31cfdcbd7f..2cda4fb3a7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml) -Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true. +Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true. ## Development tips From 683953083271e457b69344d5825627af798183d6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 Aug 2022 19:32:47 -0700 Subject: [PATCH 04/74] remove clippy :( --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d07b26ef8..cef9497074 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,6 @@ jobs: run: | rustup set profile minimal rustup update stable - rustup component add clippy rustup target add wasm32-wasi - name: Install Node @@ -40,14 +39,6 @@ jobs: uses: actions/checkout@v2 with: clean: false - - - name: Run clippy - run: > - cargo clippy --workspace -- - -Dwarnings - -Aclippy::reversed_empty_ranges - -Aclippy::missing_safety_doc - -Aclippy::let_unit_value - name: Run tests run: cargo test --workspace --no-fail-fast From 7b71554b58615298b18fcc220331f36101ac54d4 Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Thu, 11 Aug 2022 13:52:19 -0400 Subject: [PATCH 05/74] Don't scroll to cursor when performing file save --- crates/editor/src/items.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index cb7632d5e2..58bbda2e4d 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -397,7 +397,7 @@ impl Item for Editor { let buffers = buffer.read(cx).all_buffers(); let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse(); let format = project.update(cx, |project, cx| project.format(buffers, true, cx)); - cx.spawn(|this, mut cx| async move { + cx.spawn(|_, mut cx| async move { let transaction = futures::select_biased! { _ = timeout => { log::warn!("timed out waiting for formatting"); @@ -406,9 +406,6 @@ impl Item for Editor { transaction = format.log_err().fuse() => transaction, }; - this.update(&mut cx, |editor, cx| { - editor.request_autoscroll(Autoscroll::Fit, cx) - }); buffer .update(&mut cx, |buffer, cx| { if let Some(transaction) = transaction { From b94366ab90d69ab9b16b6d5cd6d3e4e43c0e8a78 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Aug 2022 11:53:37 +0200 Subject: [PATCH 06/74] Add `zed::ToggleFullScreen` bound to `ctrl-cmd-f` --- assets/keymaps/default.json | 1 + crates/gpui/src/app.rs | 9 +++++++++ crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/window.rs | 12 ++++++++++++ crates/gpui/src/platform/test.rs | 2 ++ crates/zed/src/zed.rs | 6 ++++++ 6 files changed, 31 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 2782dd7d6a..3fc3a8128b 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -28,6 +28,7 @@ "cmd-h": "zed::Hide", "alt-cmd-h": "zed::HideOthers", "cmd-m": "zed::Minimize", + "ctrl-cmd-f": "zed::ToggleFullScreen", "cmd-n": "workspace::NewFile", "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9071f98dfe..accb4d43c1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1319,6 +1319,11 @@ impl MutableAppContext { window.zoom(); } + pub fn toggle_window_full_screen(&self, window_id: usize) { + let (_, window) = &self.presenters_and_platform_windows[&window_id]; + window.toggle_full_screen(); + } + fn prompt( &self, window_id: usize, @@ -3683,6 +3688,10 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.zoom_window(self.window_id) } + pub fn toggle_full_screen(&self) { + self.app.toggle_window_full_screen(self.window_id) + } + pub fn prompt( &self, level: PromptLevel, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b5fcfe8f0d..38e8348ac3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -123,6 +123,7 @@ pub trait Window: WindowContext { fn show_character_palette(&self); fn minimize(&self); fn zoom(&self); + fn toggle_full_screen(&self); } pub trait WindowContext { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 60b079c266..b597dfb778 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -601,6 +601,18 @@ impl platform::Window for Window { }) .detach(); } + + fn toggle_full_screen(&self) { + let this = self.0.borrow(); + let window = this.native_window; + this.executor + .spawn(async move { + unsafe { + window.toggleFullScreen_(nil); + } + }) + .detach(); + } } impl platform::WindowContext for Window { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index f75cff78e8..519ba931d1 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -294,6 +294,8 @@ impl super::Window for Window { fn minimize(&self) {} fn zoom(&self) {} + + fn toggle_full_screen(&self) {} } pub fn platform() -> Platform { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3a5dd18c1c..9976f7a033 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -51,6 +51,7 @@ actions!( ShowAll, Minimize, Zoom, + ToggleFullScreen, Quit, DebugElements, OpenSettings, @@ -88,6 +89,11 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.zoom_window(); }, ); + cx.add_action( + |_: &mut Workspace, _: &ToggleFullScreen, cx: &mut ViewContext| { + cx.toggle_full_screen(); + }, + ); cx.add_global_action(quit); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { From 545f12362c6c85bf58d32e58f081a1286e6bf77f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Aug 2022 14:53:42 +0200 Subject: [PATCH 07/74] Rename `{project_panel,contacts_panel}::Toggle` to `ToggleFocus` --- assets/keymaps/default.json | 4 ++-- crates/contacts_panel/src/contacts_panel.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/zed/src/menus.rs | 4 ++-- crates/zed/src/zed.rs | 8 ++++++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 3fc3a8128b..6f34facc42 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -308,7 +308,7 @@ "cmd-p": "file_finder::Toggle", "cmd-shift-p": "command_palette::Toggle", "cmd-shift-m": "diagnostics::Deploy", - "cmd-shift-e": "project_panel::Toggle", + "cmd-shift-e": "project_panel::ToggleFocus", "cmd-alt-s": "workspace::SaveAll" } }, @@ -393,7 +393,7 @@ { "context": "Workspace", "bindings": { - "cmd-shift-c": "contacts_panel::Toggle", + "cmd-shift-c": "contacts_panel::ToggleFocus", "cmd-shift-b": "workspace::ToggleRightSidebar" } }, diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 9198efceba..d97810f106 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -26,7 +26,7 @@ use std::{ops::DerefMut, sync::Arc}; use theme::IconButton; use workspace::{sidebar::SidebarItem, JoinProject, ToggleProjectOnline, Workspace}; -actions!(contacts_panel, [Toggle]); +actions!(contacts_panel, [ToggleFocus]); impl_actions!( contacts_panel, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 702c750ea2..057e0da0c4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -111,7 +111,7 @@ actions!( Paste, Delete, Rename, - Toggle + ToggleFocus ] ); impl_internal_actions!(project_panel, [Open, ToggleExpanded, DeployContextMenu]); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 71ab1d14e5..3a34166ba6 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -242,11 +242,11 @@ pub fn menus() -> Vec> { MenuItem::Separator, MenuItem::Action { name: "Project Panel", - action: Box::new(project_panel::Toggle), + action: Box::new(project_panel::ToggleFocus), }, MenuItem::Action { name: "Contacts Panel", - action: Box::new(contacts_panel::Toggle), + action: Box::new(contacts_panel::ToggleFocus), }, MenuItem::Action { name: "Command Palette", diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9976f7a033..e924812718 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -201,12 +201,16 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }, ); cx.add_action( - |workspace: &mut Workspace, _: &project_panel::Toggle, cx: &mut ViewContext| { + |workspace: &mut Workspace, + _: &project_panel::ToggleFocus, + cx: &mut ViewContext| { workspace.toggle_sidebar_item_focus(Side::Left, 0, cx); }, ); cx.add_action( - |workspace: &mut Workspace, _: &contacts_panel::Toggle, cx: &mut ViewContext| { + |workspace: &mut Workspace, + _: &contacts_panel::ToggleFocus, + cx: &mut ViewContext| { workspace.toggle_sidebar_item_focus(Side::Right, 0, cx); }, ); From 4fcba083ba4c689b0c66766313fd9099672b9520 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Aug 2022 15:14:42 +0200 Subject: [PATCH 08/74] Autoscroll to filename editor when creating or renaming entry Also, autoscroll when a filename editor goes off-screen and the user types or changes selections. --- crates/project_panel/src/project_panel.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 057e0da0c4..dff51fe2bb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -161,7 +161,7 @@ impl ProjectPanel { { this.expand_entry(worktree_id, *entry_id, cx); this.update_visible_entries(Some((worktree_id, *entry_id)), cx); - this.autoscroll(); + this.autoscroll(cx); cx.notify(); } } @@ -185,6 +185,13 @@ impl ProjectPanel { ) }); + cx.subscribe(&filename_editor, |this, _, event, cx| match event { + editor::Event::BufferEdited | editor::Event::SelectionsChanged { .. } => { + this.autoscroll(cx); + } + _ => {} + }) + .detach(); cx.observe_focus(&filename_editor, |this, _, is_focused, cx| { if !is_focused && this @@ -391,7 +398,7 @@ impl ProjectPanel { worktree_id: *worktree_id, entry_id: worktree_entries[entry_ix].id, }); - self.autoscroll(); + self.autoscroll(cx); cx.notify(); } else { self.select_first(cx); @@ -559,6 +566,7 @@ impl ProjectPanel { .update(cx, |editor, cx| editor.clear(cx)); cx.focus(&self.filename_editor); self.update_visible_entries(Some((worktree_id, NEW_ENTRY_ID)), cx); + self.autoscroll(cx); cx.notify(); } } @@ -588,6 +596,7 @@ impl ProjectPanel { }); cx.focus(&self.filename_editor); self.update_visible_entries(None, cx); + self.autoscroll(cx); cx.notify(); } } @@ -636,7 +645,7 @@ impl ProjectPanel { worktree_id: *worktree_id, entry_id: entry.id, }); - self.autoscroll(); + self.autoscroll(cx); cx.notify(); } } @@ -658,15 +667,16 @@ impl ProjectPanel { worktree_id, entry_id: root_entry.id, }); - self.autoscroll(); + self.autoscroll(cx); cx.notify(); } } } - fn autoscroll(&mut self) { + fn autoscroll(&mut self, cx: &mut ViewContext) { if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) { self.list.scroll_to(ScrollTarget::Show(index)); + cx.notify(); } } From 776095caf04e4ecb2c868aa6c115d5199225c608 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Aug 2022 13:25:31 -0700 Subject: [PATCH 09/74] Add bootstrap script, avoid hard-coding zed team members --- .../20210527024318_initial_schema.sql | 6 -- crates/collab/src/bin/seed.rs | 85 ++++++++++--------- script/bootstrap | 13 +++ script/seed-db | 4 +- script/sqlx | 2 +- 5 files changed, 62 insertions(+), 48 deletions(-) create mode 100755 script/bootstrap diff --git a/crates/collab/migrations/20210527024318_initial_schema.sql b/crates/collab/migrations/20210527024318_initial_schema.sql index 5882d2de39..4b06531848 100644 --- a/crates/collab/migrations/20210527024318_initial_schema.sql +++ b/crates/collab/migrations/20210527024318_initial_schema.sql @@ -18,9 +18,3 @@ CREATE TABLE IF NOT EXISTS "signups" ( "email_address" VARCHAR, "about" TEXT ); - -INSERT INTO users (github_login, admin) -VALUES - ('nathansobo', true), - ('maxbrunsfeld', true), - ('as-cii', true); diff --git a/crates/collab/src/bin/seed.rs b/crates/collab/src/bin/seed.rs index 4a3dabfc71..dba7d14939 100644 --- a/crates/collab/src/bin/seed.rs +++ b/crates/collab/src/bin/seed.rs @@ -1,8 +1,7 @@ -use clap::Parser; use collab::{Error, Result}; use db::{Db, PostgresDb, UserId}; use rand::prelude::*; -use serde::Deserialize; +use serde::{de::DeserializeOwned, Deserialize}; use std::fmt::Write; use time::{Duration, OffsetDateTime}; @@ -10,62 +9,52 @@ use time::{Duration, OffsetDateTime}; #[path = "../db.rs"] mod db; -#[derive(Parser)] -struct Args { - /// Seed users from GitHub. - #[clap(short, long)] - github_users: bool, -} - #[derive(Debug, Deserialize)] struct GitHubUser { id: usize, login: String, + email: Option, } #[tokio::main] async fn main() { - let args = Args::parse(); let mut rng = StdRng::from_entropy(); let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var"); let db = PostgresDb::new(&database_url, 5) .await .expect("failed to connect to postgres database"); + let github_token = std::env::var("GITHUB_TOKEN").expect("missing GITHUB_TOKEN env var"); + let client = reqwest::Client::new(); - let mut zed_users = vec![ - ("nathansobo".to_string(), Some("nathan@zed.dev")), - ("maxbrunsfeld".to_string(), Some("max@zed.dev")), - ("as-cii".to_string(), Some("antonio@zed.dev")), - ("iamnbutler".to_string(), Some("nate@zed.dev")), - ("gibusu".to_string(), Some("greg@zed.dev")), - ("Kethku".to_string(), Some("keith@zed.dev")), - ]; + let current_user = + fetch_github::(&client, &github_token, "https://api.github.com/user").await; + let staff_users = fetch_github::>( + &client, + &github_token, + "https://api.github.com/orgs/zed-industries/teams/staff/members", + ) + .await; - if args.github_users { - let github_token = std::env::var("GITHUB_TOKEN").expect("missing GITHUB_TOKEN env var"); - let client = reqwest::Client::new(); + let mut zed_users = Vec::new(); + zed_users.push((current_user, true)); + zed_users.extend(staff_users.into_iter().map(|user| (user, true))); + + let user_count = db + .get_all_users(0, 200) + .await + .expect("failed to load users from db") + .len(); + if user_count < 100 { let mut last_user_id = None; - for page in 0..20 { - println!("Downloading users from GitHub, page {}", page); + for _ in 0..10 { let mut uri = "https://api.github.com/users?per_page=100".to_string(); if let Some(last_user_id) = last_user_id { write!(&mut uri, "&since={}", last_user_id).unwrap(); } - let response = client - .get(uri) - .bearer_auth(&github_token) - .header("user-agent", "zed") - .send() - .await - .expect("failed to fetch github users"); - let users = response - .json::>() - .await - .expect("failed to deserialize github user"); - zed_users.extend(users.iter().map(|user| (user.login.clone(), None))); - + let users = fetch_github::>(&client, &github_token, &uri).await; if let Some(last_user) = users.last() { last_user_id = Some(last_user.id); + zed_users.extend(users.into_iter().map(|user| (user, false))); } else { break; } @@ -73,16 +62,16 @@ async fn main() { } let mut zed_user_ids = Vec::::new(); - for (zed_user, email) in zed_users { + for (github_user, admin) in zed_users { if let Some(user) = db - .get_user_by_github_login(&zed_user) + .get_user_by_github_login(&github_user.login) .await .expect("failed to fetch user") { zed_user_ids.push(user.id); } else { zed_user_ids.push( - db.create_user(&zed_user, email, true) + db.create_user(&github_user.login, github_user.email.as_deref(), admin) .await .expect("failed to insert user"), ); @@ -140,3 +129,21 @@ async fn main() { .expect("failed to insert channel membership"); } } + +async fn fetch_github( + client: &reqwest::Client, + access_token: &str, + url: &str, +) -> T { + let response = client + .get(url) + .bearer_auth(&access_token) + .header("user-agent", "zed") + .send() + .await + .expect(&format!("failed to fetch '{}'", url)); + response + .json() + .await + .expect(&format!("failed to deserialize github user from '{}'", url)) +} diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 0000000000..4f6b9cc70d --- /dev/null +++ b/script/bootstrap @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +echo "installing foreman..." +which foreman > /dev/null || brew install foreman + +echo "creating database..." +script/sqlx database create + +echo "migrating database..." +script/sqlx migrate run + +echo "seeding database..." +script/seed-db diff --git a/script/seed-db b/script/seed-db index 2b12e0f480..6bb0f96933 100755 --- a/script/seed-db +++ b/script/seed-db @@ -4,6 +4,6 @@ set -e cd crates/collab # Export contents of .env.toml -eval "$(cargo run --bin dotenv)" +eval "$(cargo run --quiet --bin dotenv)" -cargo run --package=collab --features seed-support --bin seed -- $@ +cargo run --quiet --package=collab --features seed-support --bin seed -- $@ diff --git a/script/sqlx b/script/sqlx index 2241f4fa9a..cf2fa8d405 100755 --- a/script/sqlx +++ b/script/sqlx @@ -8,7 +8,7 @@ set -e cd crates/collab # Export contents of .env.toml -eval "$(cargo run --bin dotenv)" +eval "$(cargo run --quiet --bin dotenv)" # Run sqlx command sqlx $@ From e14f2b3d9fbf6cf420964c6746213327bcdc4133 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 14:30:16 -0700 Subject: [PATCH 10/74] Removed old cargo params --- .cargo/config.toml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index cf5c88d091..0000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -[target.'cfg(all())'] -rustflags = [ - "-Dwarnings", - "-Aclippy::reversed_empty_ranges", - "-Aclippy::missing_safety_doc", - "-Aclippy::let_unit_value", -] From 51b98d548b406f2db201ba359dc6e805f0263ee9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Aug 2022 15:23:12 -0700 Subject: [PATCH 11/74] Ensure a deterministic order to project activity summaries --- crates/collab/src/db.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 98a8fc3a9b..eeb598413e 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -709,7 +709,7 @@ impl Db for PostgresDb { user_durations.user_id = project_durations.user_id AND user_durations.user_id = users.id AND project_durations.project_id = project_collaborators.project_id - ORDER BY total_duration DESC, user_id ASC + ORDER BY total_duration DESC, user_id ASC, project_id ASC "; let mut rows = sqlx::query_as::<_, (UserId, String, ProjectId, i64, i64)>(query) From 761b211e891873f8eca492ba77e6fdc826170399 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 15:59:17 -0700 Subject: [PATCH 12/74] Added focus-in and focus-out behavior to terminal --- crates/terminal/src/connected_el.rs | 13 +++++++++++-- crates/terminal/src/connected_view.rs | 17 +++++++++++++++-- crates/terminal/src/terminal.rs | 19 ++++++++++++------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index ab08efc1b3..5f917c7f4e 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -200,6 +200,7 @@ pub struct TerminalEl { terminal: WeakModelHandle, view: WeakViewHandle, modal: bool, + focused: bool, } impl TerminalEl { @@ -207,11 +208,13 @@ impl TerminalEl { view: WeakViewHandle, terminal: WeakModelHandle, modal: bool, + focused: bool, ) -> TerminalEl { TerminalEl { view, terminal, modal, + focused, } } @@ -660,12 +663,18 @@ impl Element for TerminalEl { TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( move |(cursor_position, block_width)| { + let (shape, color) = if self.focused { + (CursorShape::Block, terminal_theme.colors.cursor) + } else { + (CursorShape::Underscore, terminal_theme.colors.foreground) + }; + Cursor::new( cursor_position, block_width, dimensions.line_height, - terminal_theme.colors.cursor, - CursorShape::Block, + color, + shape, Some(cursor_text), ) }, diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 1da8aad610..9e909d5bcc 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -181,9 +181,15 @@ impl View for ConnectedView { fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { let terminal_handle = self.terminal.clone().downgrade(); + let self_id = cx.view_id(); + let focused = cx + .focused_view_id(cx.window_id()) + .filter(|view_id| *view_id == self_id) + .is_some(); + Stack::new() .with_child( - TerminalEl::new(cx.handle(), terminal_handle, self.modal) + TerminalEl::new(cx.handle(), terminal_handle, self.modal, focused) .contained() .boxed(), ) @@ -191,8 +197,15 @@ impl View for ConnectedView { .boxed() } - fn on_focus_in(&mut self, _: AnyViewHandle, _cx: &mut ViewContext) { + fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.has_new_content = false; + self.terminal.read(cx).focus_in(); + cx.notify(); + } + + fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + self.terminal.read(cx).focus_out(); + cx.notify(); } fn selected_text_range(&self, cx: &AppContext) -> Option> { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f31c07423e..af1c763f52 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -601,18 +601,23 @@ impl Terminal { f(content, cursor_text) } - // fn estimate_utilization(last_processed: usize) -> f32 { - // let buffer_utilization = (last_processed as f32 / (READ_BUFFER_SIZE as f32)).clamp(0., 1.); - - // //Scale result to bias low, then high - // buffer_utilization * buffer_utilization - // } - ///Scroll the terminal pub fn scroll(&mut self, scroll: Scroll) { self.events.push(InternalEvent::Scroll(scroll)); } + pub fn focus_in(&self) { + if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { + self.notify_pty("\x1b[I".to_string()); + } + } + + pub fn focus_out(&self) { + if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { + self.notify_pty("\x1b[O".to_string()); + } + } + pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { let selection_type = match clicks { 0 => return, //This is a release From a24e5b128cfb706e4fa12f564a001997f8e8c6b9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 16:05:03 -0700 Subject: [PATCH 13/74] Fixed text rendering color based on focus --- crates/terminal/src/connected_el.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 5f917c7f4e..69fc5f581f 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -647,6 +647,13 @@ impl Element for TerminalEl { let cursor_point = DisplayCursor::from(cursor.point, display_offset); let cursor_text = { let str_trxt = cursor_text.to_string(); + + let color = if self.focused { + terminal_theme.colors.background + } else { + terminal_theme.colors.foreground + }; + cx.text_layout_cache.layout_str( &str_trxt, text_style.font_size, @@ -654,7 +661,7 @@ impl Element for TerminalEl { str_trxt.len(), RunStyle { font_id: text_style.font_id, - color: terminal_theme.colors.background, + color, underline: Default::default(), }, )], From 7de8228efdf8778392e8b71fd35f45513d72595c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Aug 2022 16:15:20 -0700 Subject: [PATCH 14/74] Clear stale go-to-def link when resizing buffer font --- crates/editor/src/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e8d3ad4650..2fd3c06538 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -42,7 +42,7 @@ use language::{ DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, }; -use link_go_to_definition::LinkGoToDefinitionState; +use link_go_to_definition::{hide_link_definition, LinkGoToDefinitionState}; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, @@ -6010,6 +6010,7 @@ impl View for Editor { if let Some(editor) = handle.upgrade(cx) { editor.update(cx, |editor, cx| { hide_hover(editor, cx); + hide_link_definition(editor, cx); }) } }); From 7b3a7727c64797fa449ad4bd3667953812d9761e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 17:00:44 -0700 Subject: [PATCH 15/74] Basic cursor blinking :) --- crates/terminal/src/connected_el.rs | 94 ++++++++++++++++----------- crates/terminal/src/connected_view.rs | 66 +++++++++++++++++++ crates/terminal/src/terminal.rs | 1 + 3 files changed, 123 insertions(+), 38 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 69fc5f581f..392783f02d 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -643,49 +643,64 @@ impl Element for TerminalEl { ); //Layout cursor + //TODO: This logic can be a lot better + let show_cursor = if let Some(view_handle) = self.view.upgrade(cx) { + if view_handle.read(cx).show_cursor() { + false + } else { + true + } + } else { + true + }; + let cursor = { - let cursor_point = DisplayCursor::from(cursor.point, display_offset); - let cursor_text = { - let str_trxt = cursor_text.to_string(); + if show_cursor { + None + } else { + let cursor_point = DisplayCursor::from(cursor.point, display_offset); + let cursor_text = { + let str_trxt = cursor_text.to_string(); - let color = if self.focused { - terminal_theme.colors.background - } else { - terminal_theme.colors.foreground - }; - - cx.text_layout_cache.layout_str( - &str_trxt, - text_style.font_size, - &[( - str_trxt.len(), - RunStyle { - font_id: text_style.font_id, - color, - underline: Default::default(), - }, - )], - ) - }; - - TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( - move |(cursor_position, block_width)| { - let (shape, color) = if self.focused { - (CursorShape::Block, terminal_theme.colors.cursor) + let color = if self.focused { + terminal_theme.colors.background } else { - (CursorShape::Underscore, terminal_theme.colors.foreground) + terminal_theme.colors.foreground }; - Cursor::new( - cursor_position, - block_width, - dimensions.line_height, - color, - shape, - Some(cursor_text), + cx.text_layout_cache.layout_str( + &str_trxt, + text_style.font_size, + &[( + str_trxt.len(), + RunStyle { + font_id: text_style.font_id, + color, + underline: Default::default(), + }, + )], ) - }, - ) + }; + + TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( + move |(cursor_position, block_width)| { + let (shape, color) = if self.focused { + (CursorShape::Block, terminal_theme.colors.cursor) + } else { + (CursorShape::Underscore, terminal_theme.colors.foreground) + }; + + Cursor::new( + cursor_position, + block_width, + dimensions.line_height, + color, + shape, + Some(cursor_text), + ) + }, + ) + } }; //Done! @@ -818,7 +833,10 @@ impl Element for TerminalEl { //TODO Talk to keith about how to catch events emitted from an element. if let Some(view) = self.view.upgrade(cx.app) { - view.update(cx.app, |view, cx| view.clear_bel(cx)) + view.update(cx.app, |view, cx| { + view.clear_bel(cx); + view.pause_cursor_blinking(cx); + }) } self.terminal diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 9e909d5bcc..924dece2c2 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use alacritty_terminal::term::TermMode; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ @@ -9,10 +11,13 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle, }; +use smol::Timer; use workspace::pane; use crate::{connected_el::TerminalEl, Event, Terminal}; +const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); + ///Event to transmit the scroll from the element to the view #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); @@ -51,6 +56,9 @@ pub struct ConnectedView { // Only for styling purposes. Doesn't effect behavior modal: bool, context_menu: ViewHandle, + show_cursor: bool, + blinking_paused: bool, + blink_epoch: usize, } impl ConnectedView { @@ -83,6 +91,9 @@ impl ConnectedView { has_bell: false, modal, context_menu: cx.add_view(ContextMenu::new), + show_cursor: true, + blinking_paused: false, + blink_epoch: 0, } } @@ -120,6 +131,59 @@ impl ConnectedView { cx.notify(); } + //Following code copied from editor cursor + pub fn show_cursor(&self) -> bool { + self.blinking_paused || self.show_cursor + } + + fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext) { + if epoch == self.blink_epoch && !self.blinking_paused { + self.show_cursor = !self.show_cursor; + 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 pause_cursor_blinking(&mut self, cx: &mut ViewContext) { + self.show_cursor = 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 next_blink_epoch(&mut self) -> usize { + self.blink_epoch += 1; + self.blink_epoch + } + + 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); + } + } + ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.copy()) @@ -200,6 +264,7 @@ impl View for ConnectedView { fn on_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(); } @@ -208,6 +273,7 @@ impl View for ConnectedView { cx.notify(); } + //IME stuff fn selected_text_range(&self, cx: &AppContext) -> Option> { if self .terminal diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index af1c763f52..0c1d29a248 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -287,6 +287,7 @@ impl TerminalBuilder { setup_env(&config); //Spawn a task so the Alacritty EventLoop can communicate with us in a view context + //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... let term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); From 754a130e5935e72a9217e70734a26501094508f9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Aug 2022 17:30:41 -0700 Subject: [PATCH 16/74] Clear go-to-def link when deactivating the window --- crates/editor/src/items.rs | 8 +++++- crates/editor/src/link_go_to_definition.rs | 31 ++++++++++++++++++++-- crates/workspace/src/workspace.rs | 29 +++++++++++++------- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 58bbda2e4d..04d649f910 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,5 +1,6 @@ use crate::{ - Anchor, Autoscroll, Editor, Event, ExcerptId, MultiBuffer, NavigationData, ToPoint as _, + link_go_to_definition::hide_link_definition, Anchor, Autoscroll, Editor, Event, ExcerptId, + MultiBuffer, NavigationData, ToPoint as _, }; use anyhow::{anyhow, Result}; use futures::FutureExt; @@ -376,6 +377,11 @@ impl Item for Editor { self.push_to_nav_history(selection.head(), None, cx); } + fn workspace_deactivated(&mut self, cx: &mut ViewContext) { + hide_link_definition(self, cx); + self.link_go_to_definition_state.last_mouse_location = None; + } + fn is_dirty(&self, cx: &AppContext) -> bool { self.buffer().read(cx).read(cx).is_dirty() } diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index c1544306d6..6eb38df758 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -52,7 +52,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(go_to_fetched_type_definition); } -#[derive(Default)] +#[derive(Debug, Default)] pub struct LinkGoToDefinitionState { pub last_mouse_location: Option, pub symbol_range: Option>, @@ -706,7 +706,34 @@ mod tests { fn do_work() { «test»(); } "}); - // Moving within symbol range doesn't re-request + // Deactivating the window dismisses the highlight + cx.update_workspace(|workspace, cx| { + workspace.on_window_activation_changed(false, cx); + }); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + + // Moving the mouse restores the highlights. + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + &UpdateGoToDefinitionLink { + point: Some(hover_point), + cmd_held: true, + shift_held: false, + }, + cx, + ); + }); + cx.foreground().run_until_parked(); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { «test»(); } + "}); + + // Moving again within the same symbol range doesn't re-request let hover_point = cx.display_point(indoc! {" fn test() { do_work(); } fn do_work() { tesˇt(); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 40934f4ed7..1abf6a54fc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -261,6 +261,7 @@ pub struct AppState { pub trait Item: View { fn deactivated(&mut self, _: &mut ViewContext) {} + fn workspace_deactivated(&mut self, _: &mut ViewContext) {} fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { false } @@ -433,6 +434,7 @@ pub trait ItemHandle: 'static + fmt::Debug { cx: &mut ViewContext, ); fn deactivated(&self, cx: &mut MutableAppContext); + fn workspace_deactivated(&self, cx: &mut MutableAppContext); fn navigate(&self, data: Box, cx: &mut MutableAppContext) -> bool; fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; @@ -629,6 +631,10 @@ impl ItemHandle for ViewHandle { self.update(cx, |this, cx| this.deactivated(cx)); } + fn workspace_deactivated(&self, cx: &mut MutableAppContext) { + self.update(cx, |this, cx| this.workspace_deactivated(cx)); + } + fn navigate(&self, data: Box, cx: &mut MutableAppContext) -> bool { self.update(cx, |this, cx| this.navigate(data, cx)) } @@ -2383,18 +2389,21 @@ impl Workspace { None } - fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { - if !active - && matches!( - cx.global::().autosave, - Autosave::OnWindowChange | Autosave::OnFocusChange - ) - { + pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { + if !active { for pane in &self.panes { pane.update(cx, |pane, cx| { - for item in pane.items() { - Pane::autosave_item(item.as_ref(), self.project.clone(), cx) - .detach_and_log_err(cx); + if let Some(item) = pane.active_item() { + item.workspace_deactivated(cx); + } + if matches!( + cx.global::().autosave, + Autosave::OnWindowChange | Autosave::OnFocusChange + ) { + for item in pane.items() { + Pane::autosave_item(item.as_ref(), self.project.clone(), cx) + .detach_and_log_err(cx); + } } }); } From bba51c3ae6df6fc03b41fa95f85a38bf059cc19f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 18:05:07 -0700 Subject: [PATCH 17/74] Added cursor blink and settings --- assets/settings/default.json | 13 +++++++ crates/settings/src/settings.rs | 16 ++++++++ crates/terminal/src/connected_el.rs | 56 ++++++++++++++++++++------- crates/terminal/src/connected_view.rs | 14 +++++-- crates/terminal/src/terminal.rs | 22 ++++++++--- crates/terminal/src/terminal_view.rs | 9 ++++- 6 files changed, 104 insertions(+), 26 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 43b4be512f..c6f08f4e56 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,6 +102,19 @@ // // "working_directory": "current_project_directory", + //Set the cursor blinking behavior in the terminal. + //May take 4 values: + // 1. Never blink the cursor, ignoring the terminal mode + // "blinking": "never", + // 2. Default the cursor blink to off, but allow the terminal to + // turn blinking on + // "blinking": "off", + // 3. Default the cursor blink to on, but allow the terminal to + // turn blinking off + // "blinking": "on", + // 4. Always blink the cursor, ignoring the terminal mode + // "blinking": "always", + "blinking": "on", //Any key-value pairs added to this list will be added to the terminal's //enviroment. Use `:` to seperate multiple values. "env": { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index ce1fa09d99..9c1a17a462 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -83,6 +83,22 @@ pub struct TerminalSettings { pub font_size: Option, pub font_family: Option, pub env: Option>, + pub blinking: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TerminalBlink { + Never, + On, + Off, + Always, +} + +impl Default for TerminalBlink { + fn default() -> Self { + TerminalBlink::On + } } #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 392783f02d..eb9eb32b81 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -21,7 +21,7 @@ use gpui::{ }; use itertools::Itertools; use ordered_float::OrderedFloat; -use settings::Settings; +use settings::{Settings, TerminalBlink}; use theme::TerminalStyle; use util::ResultExt; @@ -201,6 +201,7 @@ pub struct TerminalEl { view: WeakViewHandle, modal: bool, focused: bool, + blink_state: bool, } impl TerminalEl { @@ -209,12 +210,14 @@ impl TerminalEl { terminal: WeakModelHandle, modal: bool, focused: bool, + blink_state: bool, ) -> TerminalEl { TerminalEl { view, terminal, modal, focused, + blink_state, } } @@ -568,6 +571,33 @@ impl TerminalEl { (point, side) } + + pub fn should_show_cursor( + settings: Option, + blinking_on: bool, + focused: bool, + blink_show: bool, + ) -> bool { + if !focused { + true + } else { + match settings { + Some(setting) => match setting { + TerminalBlink::Never => true, + TerminalBlink::On | TerminalBlink::Off if blinking_on => blink_show, + TerminalBlink::On | TerminalBlink::Off /*if !blinking_on */ => true, + TerminalBlink::Always => focused && blink_show, + }, + None => { + if blinking_on { + blink_show + } else { + false + } + } + } + } + } } impl Element for TerminalEl { @@ -580,6 +610,7 @@ impl Element for TerminalEl { cx: &mut gpui::LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let settings = cx.global::(); + let blink_settings = settings.terminal_overrides.blinking.clone(); let font_cache = cx.font_cache(); //Setup layout information @@ -598,13 +629,13 @@ impl Element for TerminalEl { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text) = self + let (cells, selection, cursor, display_offset, cursor_text, blink_mode) = self .terminal .upgrade(cx) .unwrap() .update(cx.app, |terminal, mcx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text| { + terminal.render_lock(mcx, |content, cursor_text, style| { let mut cells = vec![]; cells.extend( content @@ -628,6 +659,7 @@ impl Element for TerminalEl { content.cursor, content.display_offset, cursor_text, + style, ) }) }); @@ -643,19 +675,13 @@ impl Element for TerminalEl { ); //Layout cursor - //TODO: This logic can be a lot better - let show_cursor = if let Some(view_handle) = self.view.upgrade(cx) { - if view_handle.read(cx).show_cursor() { - false - } else { - true - } - } else { - true - }; - let cursor = { - if show_cursor { + if !TerminalEl::should_show_cursor( + blink_settings, + blink_mode, + self.focused, + self.blink_state, + ) { None } else { let cursor_point = DisplayCursor::from(cursor.point, display_offset); diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 924dece2c2..6f16ac9bcd 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -132,7 +132,7 @@ impl ConnectedView { } //Following code copied from editor cursor - pub fn show_cursor(&self) -> bool { + pub fn blink_show(&self) -> bool { self.blinking_paused || self.show_cursor } @@ -253,9 +253,15 @@ impl View for ConnectedView { Stack::new() .with_child( - TerminalEl::new(cx.handle(), terminal_handle, self.modal, focused) - .contained() - .boxed(), + TerminalEl::new( + cx.handle(), + terminal_handle, + self.modal, + focused, + self.blink_show(), + ) + .contained() + .boxed(), ) .with_child(ChildView::new(&self.context_menu).boxed()) .boxed() diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 0c1d29a248..0debf4fa91 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -25,7 +25,7 @@ use futures::{ }; use modal::deploy_modal; -use settings::{Settings, Shell}; +use settings::{Settings, Shell, TerminalBlink}; use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; use thiserror::Error; @@ -254,6 +254,7 @@ impl TerminalBuilder { shell: Option, env: Option>, initial_size: TerminalSize, + blink_settings: Option, ) -> Result { let pty_config = { let alac_shell = shell.clone().and_then(|shell| match shell { @@ -290,7 +291,18 @@ impl TerminalBuilder { //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... - let term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); + let mut term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); + + //Start off blinking if we need to + match blink_settings { + Some(setting) => match setting { + TerminalBlink::On | TerminalBlink::Always => { + term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) + } + _ => {} + }, + None => term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor), + } let term = Arc::new(FairMutex::new(term)); //Setup the pty... @@ -322,7 +334,7 @@ impl TerminalBuilder { //And connect them together let event_loop = EventLoop::new( term.clone(), - ZedListener(events_tx), + ZedListener(events_tx.clone()), pty, pty_config.hold, false, @@ -583,7 +595,7 @@ impl Terminal { pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where - F: FnOnce(RenderableContent, char) -> T, + F: FnOnce(RenderableContent, char, bool) -> T, { let m = self.term.clone(); //Arc clone let mut term = m.lock(); @@ -599,7 +611,7 @@ impl Terminal { let cursor_text = term.grid()[content.cursor.point].c; - f(content, cursor_text) + f(content, cursor_text, term.cursor_style().blinking) } ///Scroll the terminal diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index ed63217178..a997ebf631 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -94,8 +94,13 @@ impl TerminalView { let shell = settings.terminal_overrides.shell.clone(); let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap. - let content = match TerminalBuilder::new(working_directory.clone(), shell, envs, size_info) - { + let content = match TerminalBuilder::new( + working_directory.clone(), + shell, + envs, + size_info, + settings.terminal_overrides.blinking.clone(), + ) { Ok(terminal) => { let terminal = cx.add_model(|cx| terminal.subscribe(cx)); let view = cx.add_view(|cx| ConnectedView::from_terminal(terminal, modal, cx)); From b9c73127b43e631b267a3789ddc2c67d4b274253 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 18:27:26 -0700 Subject: [PATCH 18/74] Added a hollow mode to the cursor rendering code, for terminal lost focus --- crates/editor/src/element.rs | 46 +++++++++++++++++++++++++++-- crates/terminal/src/connected_el.rs | 6 ++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index da1e78179a..6140731579 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1753,6 +1753,7 @@ pub enum CursorShape { Bar, Block, Underscore, + Hollow, } impl Default for CursorShape { @@ -1808,8 +1809,19 @@ impl Cursor { self.origin + origin + Vector2F::new(0.0, self.line_height - 2.0), vec2f(self.block_width, 2.0), ), + CursorShape::Hollow => RectF::new( + self.origin + origin + Vector2F::new(0.0, self.line_height - 1.0), + vec2f(self.block_width, 1.0), + ), }; + //Draw text under the hollow block if need be + if matches!(self.shape, CursorShape::Hollow) { + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin + origin, bounds, self.line_height, cx); + } + } + cx.scene.push_quad(Quad { bounds, background: Some(self.color), @@ -1817,8 +1829,38 @@ impl Cursor { corner_radius: 0., }); - if let Some(block_text) = &self.block_text { - block_text.paint(self.origin + origin, bounds, self.line_height, cx); + if matches!(self.shape, CursorShape::Hollow) { + //Top + cx.scene.push_quad(Quad { + bounds: RectF::new( + self.origin + origin + Vector2F::new(0.0, -1.0), + vec2f(self.block_width + 1., 1.0), + ), + background: Some(self.color), + border: Border::new(0., Color::black()), + corner_radius: 0., + }); + //Left + cx.scene.push_quad(Quad { + bounds: RectF::new(self.origin + origin, vec2f(1.0, self.line_height)), + background: Some(self.color), + border: Border::new(0., Color::black()), + corner_radius: 0., + }); + //Right + cx.scene.push_quad(Quad { + bounds: RectF::new( + self.origin + origin + vec2f(self.block_width, 0.), + vec2f(1.0, self.line_height), + ), + background: Some(self.color), + border: Border::new(0., Color::black()), + corner_radius: 0., + }); + } else { + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin + origin, bounds, self.line_height, cx); + } } } } diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index eb9eb32b81..d017aad65b 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -635,7 +635,7 @@ impl Element for TerminalEl { .unwrap() .update(cx.app, |terminal, mcx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text, style| { + terminal.render_lock(mcx, |content, cursor_text, blink_mode| { let mut cells = vec![]; cells.extend( content @@ -659,7 +659,7 @@ impl Element for TerminalEl { content.cursor, content.display_offset, cursor_text, - style, + blink_mode, ) }) }); @@ -713,7 +713,7 @@ impl Element for TerminalEl { let (shape, color) = if self.focused { (CursorShape::Block, terminal_theme.colors.cursor) } else { - (CursorShape::Underscore, terminal_theme.colors.foreground) + (CursorShape::Hollow, terminal_theme.colors.foreground) }; Cursor::new( From ea09ef2c1a657ec086e8ce5aa68c372222cf7b37 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 Aug 2022 12:58:53 -0700 Subject: [PATCH 19/74] remove http-auth-basic dependency --- Cargo.lock | 48 ++++++++++++++-------------------------- crates/zed/Cargo.toml | 1 - styles/package-lock.json | 1 + 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4256d24c9d..f5a8effc8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ version = "0.17.0-dev" source = "git+https://github.com/zed-industries/alacritty?rev=4e1f0c6177975a040b37f942dfb0e723e46a9971#4e1f0c6177975a040b37f942dfb0e723e46a9971" dependencies = [ "alacritty_config_derive", - "base64 0.13.0", + "base64", "bitflags", "dirs 4.0.0", "libc", @@ -430,7 +430,7 @@ checksum = "c2cc6e8e8c993cb61a005fab8c1e5093a29199b7253b05a6883999312935c1ff" dependencies = [ "async-trait", "axum-core", - "base64 0.13.0", + "base64", "bitflags", "bytes", "futures-util", @@ -507,12 +507,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -984,7 +978,7 @@ dependencies = [ "async-tungstenite", "axum", "axum-extra", - "base64 0.13.0", + "base64", "clap 3.2.8", "client", "collections", @@ -2287,7 +2281,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" dependencies = [ - "base64 0.13.0", + "base64", "bitflags", "bytes", "headers-core", @@ -2387,15 +2381,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-auth-basic" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df69b6a68474b935f436fb9c84139f32de4f7759810090d1a3a5e592553f7ee0" -dependencies = [ - "base64 0.12.3", -] - [[package]] name = "http-body" version = "0.4.5" @@ -3588,7 +3573,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64 0.13.0", + "base64", "once_cell", "regex", ] @@ -3690,7 +3675,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" dependencies = [ - "base64 0.13.0", + "base64", "indexmap", "line-wrap", "serde", @@ -4251,7 +4236,7 @@ version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" dependencies = [ - "base64 0.13.0", + "base64", "bytes", "encoding_rs", "futures-core", @@ -4347,7 +4332,7 @@ dependencies = [ "anyhow", "async-lock", "async-tungstenite", - "base64 0.13.0", + "base64", "clock", "collections", "ctor", @@ -4466,7 +4451,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.0", + "base64", "log", "ring", "sct 0.6.1", @@ -4491,7 +4476,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" dependencies = [ - "base64 0.13.0", + "base64", ] [[package]] @@ -5103,7 +5088,7 @@ checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093" dependencies = [ "ahash", "atoi", - "base64 0.13.0", + "base64", "bitflags", "byteorder", "bytes", @@ -5694,7 +5679,7 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64 0.13.0", + "base64", "bytes", "futures-core", "futures-util", @@ -5983,7 +5968,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ - "base64 0.13.0", + "base64", "byteorder", "bytes", "http", @@ -6002,7 +5987,7 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ - "base64 0.13.0", + "base64", "byteorder", "bytes", "http", @@ -6142,7 +6127,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" dependencies = [ - "base64 0.13.0", + "base64", "data-url", "flate2", "fontdb", @@ -6493,7 +6478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743a9f142d93318262d7e1fe329394ff2e8f86a1df45ae5e4f0eedba215ca5ce" dependencies = [ "anyhow", - "base64 0.13.0", + "base64", "bincode", "directories-next", "file-per-thread-logger", @@ -7028,7 +7013,6 @@ dependencies = [ "fuzzy", "go_to_line", "gpui", - "http-auth-basic", "ignore", "image", "indexmap", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 0eb2aff7c4..611099ccdb 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -64,7 +64,6 @@ dirs = "3.0" easy-parallel = "3.1.0" env_logger = "0.9" futures = "0.3" -http-auth-basic = "0.1.3" ignore = "0.4" image = "0.23" indexmap = "1.6.2" diff --git a/styles/package-lock.json b/styles/package-lock.json index 5499f1852c..582f1c8496 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From 18dc12218fb7157aa7a4175416000cd509639483 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 16 Aug 2022 13:01:35 -0700 Subject: [PATCH 20/74] Begin work on IME changes --- crates/gpui/src/platform/mac/window.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index b597dfb778..c36cdf7fe6 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -278,6 +278,18 @@ unsafe fn build_classes() { pub struct Window(Rc>); +///Used to track what the IME does when we send it a keystroke. +///This is only used to handle the case where the IME mysteriously +///swallows certain keys. +/// +///Basically a direct copy of the approach that WezTerm uses in: +///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs +enum ImeState { + Continue, + Acted, + None, +} + struct WindowState { id: usize, native_window: id, @@ -299,6 +311,10 @@ struct WindowState { layer: id, traffic_light_position: Option, previous_modifiers_changed_event: Option, + //State tracking what the IME did after the last request + ime_state: ImeState, + //Retains the last IME keystroke that was in the 'Acted' state + last_ime_key: Option, } struct InsertText { From e4eead64779f8c72927b08f54cf67c445ea2e3e6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 Aug 2022 17:44:58 -0700 Subject: [PATCH 21/74] Fix crash when attempting to show file prompt while using Japanese keyboard layout --- crates/gpui/src/app.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index accb4d43c1..6da4111a76 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -344,7 +344,14 @@ impl WindowInputHandler { where F: FnOnce(&dyn AnyView, &AppContext) -> T, { - let app = self.app.borrow(); + // Input-related application hooks are sometimes called by the OS during + // a call to a window-manipulation API, like prompting the user for file + // paths. In that case, the AppContext will already be borrowed, so any + // InputHandler methods need to fail gracefully. + // + // See https://github.com/zed-industries/feedback/issues/444 + let app = self.app.try_borrow().ok()?; + let view_id = app.focused_view_id(self.window_id)?; let view = app.cx.views.get(&(self.window_id, view_id))?; let result = f(view.as_ref(), &app); @@ -355,7 +362,7 @@ impl WindowInputHandler { where F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T, { - let mut app = self.app.borrow_mut(); + let mut app = self.app.try_borrow_mut().ok()?; app.update(|app| { let view_id = app.focused_view_id(self.window_id)?; let mut view = app.cx.views.remove(&(self.window_id, view_id))?; From 94ba282e76064e727fa170f260db7896080fcd05 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 16 Aug 2022 22:12:06 -0700 Subject: [PATCH 22/74] Checkpoint, alt-n l is very broken. Bisecting. --- crates/gpui/src/platform/mac/window.rs | 86 ++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c36cdf7fe6..507b04bd1a 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -313,8 +313,8 @@ struct WindowState { previous_modifiers_changed_event: Option, //State tracking what the IME did after the last request ime_state: ImeState, - //Retains the last IME keystroke that was in the 'Acted' state - last_ime_key: Option, + //Retains the last IME Text + ime_text: Option, } struct InsertText { @@ -411,6 +411,8 @@ impl Window { layer, traffic_light_position: options.traffic_light_position, previous_modifiers_changed_event: None, + ime_state: ImeState::None, + ime_text: None, }))); (*native_window).set_ivar( @@ -774,6 +776,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut window_state_borrow = window_state.as_ref().borrow_mut(); let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; + + println!("Handle key event! {:?}", event); + if let Some(event) = event { if key_equivalent { window_state_borrow.performed_key_equivalent = true; @@ -811,7 +816,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut handled = false; let mut window_state_borrow = window_state.borrow_mut(); + let ime_text = window_state_borrow.ime_text.clone(); if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() { + let is_held = event.is_held; if let Some(mut callback) = window_state_borrow.event_callback.take() { drop(window_state_borrow); @@ -830,6 +837,18 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: input_handler .replace_text_in_range(insert.replacement_range, &insert.text) }); + } else if !is_composing && is_held { + if let Some(last_insert_text) = ime_text { + //MacOS IME is a bit funky, and even when you've told it there's nothing to + //inter it will still swallow certain keys (e.g. 'f', 'j') and not others + //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal + with_input_handler(this, |input_handler| { + if input_handler.selected_text_range().is_none() { + handled = true; + input_handler.replace_text_in_range(None, &last_insert_text) + } + }); + } } } @@ -1180,11 +1199,24 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS .unwrap(); let replacement_range = replacement_range.to_range(); + window_state.borrow_mut().ime_text = Some(text.to_string()); + window_state.borrow_mut().ime_state = ImeState::Acted; + + //Conceptually incorrect let is_composing = with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() .is_some(); + println!( + "Insert text, is_composing {}, text {}, have pending key down? {:?}", + is_composing, + &text, + &pending_key_down.is_some() + ); + + //Handle key event is going to be returned to soon! Make sure it + //has the new text from the IME match pending_key_down { None | Some(_) if is_composing || text.chars().count() > 1 => { with_input_handler(this, |input_handler| { @@ -1202,6 +1234,30 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS _ => unreachable!(), } + + // if let Some(mut pending_key_down) = pending_key_down { + // pending_key_down.1 = Some(InsertText { + // replacement_range, + // text: text.to_string(), + // }); + // window_state.borrow_mut().pending_key_down = Some(pending_key_down); + + // //Modifier key combos (˜ˆ) (success / failure) + // //Press-and-hold replacements ((hold i) 1 -> î) (success / failure) + // //Pop down chinese composition menu + // //Handwriting menu + // //Emoji Picker (😤) + + // //Regression -> alt-i l only causes 'l' to be placed, instead of ˆl + // // + // } + // //If no pending key down, then handle_key_event isn't going to + // //insert the text for us. Do it ourselves + // else { + // with_input_handler(this, |input_handler| { + // input_handler.replace_text_in_range(replacement_range, text) + // }); + // } } } @@ -1213,7 +1269,8 @@ extern "C" fn set_marked_text( replacement_range: NSRange, ) { unsafe { - get_window_state(this).borrow_mut().pending_key_down.take(); + let window_state = get_window_state(this); + window_state.borrow_mut().pending_key_down.take(); let is_attributed_string: BOOL = msg_send![text, isKindOfClass: [class!(NSAttributedString)]]; @@ -1228,6 +1285,11 @@ extern "C" fn set_marked_text( .to_str() .unwrap(); + window_state.borrow_mut().ime_state = ImeState::Acted; + window_state.borrow_mut().ime_text = Some(text.to_string()); + + println!("set_marked_text({selected_range:?}, {replacement_range:?}, {text:?})"); + with_input_handler(this, |input_handler| { input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range); }); @@ -1235,6 +1297,15 @@ extern "C" fn set_marked_text( } extern "C" fn unmark_text(this: &Object, _: Sel) { + unsafe { + let state = get_window_state(this); + let mut borrow = state.borrow_mut(); + borrow.ime_state = ImeState::Acted; + borrow.ime_text.take(); + } + + println!("unmark_text()"); + with_input_handler(this, |input_handler| input_handler.unmark_text()); } @@ -1261,7 +1332,14 @@ extern "C" fn attributed_substring_for_proposed_range( .unwrap_or(nil) } -extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {} +extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) { + unsafe { + let state = get_window_state(this); + let mut borrow = state.borrow_mut(); + borrow.ime_state = ImeState::Continue; + borrow.ime_text.take(); + } +} async fn synthetic_drag( window_state: Weak>, From aa75fbe56c04f1b6f54089b326046d0fce6b7068 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 16 Aug 2022 22:36:24 -0700 Subject: [PATCH 23/74] Fix panic on main --- crates/gpui/src/platform/mac/window.rs | 27 +++++++++++--------------- styles/package-lock.json | 1 - 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index b597dfb778..83dd9d6e20 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1169,22 +1169,17 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS .flatten() .is_some(); - match pending_key_down { - None | Some(_) if is_composing || text.chars().count() > 1 => { - with_input_handler(this, |input_handler| { - input_handler.replace_text_in_range(replacement_range, text) - }); - } - - Some(mut pending_key_down) => { - pending_key_down.1 = Some(InsertText { - replacement_range, - text: text.to_string(), - }); - window_state.borrow_mut().pending_key_down = Some(pending_key_down); - } - - _ => unreachable!(), + if is_composing || text.chars().count() > 1 || pending_key_down.is_none() { + with_input_handler(this, |input_handler| { + input_handler.replace_text_in_range(replacement_range, text) + }); + } else { + let mut pending_key_down = pending_key_down.unwrap(); + pending_key_down.1 = Some(InsertText { + replacement_range, + text: text.to_string(), + }); + window_state.borrow_mut().pending_key_down = Some(pending_key_down); } } } diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c8496..5499f1852c 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From 1f82c0c8bf2bb90cf46bf4d7952bc3d427acb27e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 16 Aug 2022 23:00:09 -0700 Subject: [PATCH 24/74] Repeat key problems fixed. --- crates/gpui/src/platform/mac/window.rs | 38 -------------------------- 1 file changed, 38 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 8c31f686cb..5db530b9cf 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -777,8 +777,6 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; - println!("Handle key event! {:?}", event); - if let Some(event) = event { if key_equivalent { window_state_borrow.performed_key_equivalent = true; @@ -1202,19 +1200,11 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS window_state.borrow_mut().ime_text = Some(text.to_string()); window_state.borrow_mut().ime_state = ImeState::Acted; - //Conceptually incorrect let is_composing = with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() .is_some(); - println!( - "Insert text, is_composing {}, text {}, have pending key down? {:?}", - is_composing, - &text, - &pending_key_down.is_some() - ); - if is_composing || text.chars().count() > 1 || pending_key_down.is_none() { with_input_handler(this, |input_handler| { input_handler.replace_text_in_range(replacement_range, text) @@ -1227,30 +1217,6 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS }); window_state.borrow_mut().pending_key_down = Some(pending_key_down); } - - // if let Some(mut pending_key_down) = pending_key_down { - // pending_key_down.1 = Some(InsertText { - // replacement_range, - // text: text.to_string(), - // }); - // window_state.borrow_mut().pending_key_down = Some(pending_key_down); - - // //Modifier key combos (˜ˆ) (success / failure) - // //Press-and-hold replacements ((hold i) 1 -> î) (success / failure) - // //Pop down chinese composition menu - // //Handwriting menu - // //Emoji Picker (😤) - - // //Regression -> alt-i l only causes 'l' to be placed, instead of ˆl - // // - // } - // //If no pending key down, then handle_key_event isn't going to - // //insert the text for us. Do it ourselves - // else { - // with_input_handler(this, |input_handler| { - // input_handler.replace_text_in_range(replacement_range, text) - // }); - // } } } @@ -1281,8 +1247,6 @@ extern "C" fn set_marked_text( window_state.borrow_mut().ime_state = ImeState::Acted; window_state.borrow_mut().ime_text = Some(text.to_string()); - println!("set_marked_text({selected_range:?}, {replacement_range:?}, {text:?})"); - with_input_handler(this, |input_handler| { input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range); }); @@ -1297,8 +1261,6 @@ extern "C" fn unmark_text(this: &Object, _: Sel) { borrow.ime_text.take(); } - println!("unmark_text()"); - with_input_handler(this, |input_handler| input_handler.unmark_text()); } From bf7b3150e4f7cac308aff12281ae7085b8465dec Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 16 Aug 2022 23:10:05 -0700 Subject: [PATCH 25/74] Added show character palette. Need to position correctly. --- assets/keymaps/default.json | 2 ++ crates/terminal/src/connected_view.rs | 17 ++++++++++++++++- crates/terminal/src/mappings/keys.rs | 1 - 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6f34facc42..b40a076c49 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -420,6 +420,7 @@ "enter": "terminal::Enter", "ctrl-c": "terminal::CtrlC", // Useful terminal actions: + "ctrl-cmd-space": "terminal::ShowCharacterPalette", "cmd-c": "terminal::Copy", "cmd-v": "terminal::Paste", "cmd-k": "terminal::Clear" @@ -428,6 +429,7 @@ { "context": "ModalTerminal", "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", "shift-escape": "terminal::DeployModal" } } diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 6f16ac9bcd..905c27d2b9 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -29,7 +29,17 @@ pub struct DeployContextMenu { actions!( terminal, - [Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,] + [ + Up, + Down, + CtrlC, + Escape, + Enter, + Clear, + Copy, + Paste, + ShowCharacterPalette + ] ); impl_internal_actions!(project_panel, [DeployContextMenu]); @@ -45,6 +55,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(ConnectedView::copy); cx.add_action(ConnectedView::paste); cx.add_action(ConnectedView::clear); + cx.add_action(ConnectedView::show_character_palette); } ///A terminal view, maintains the PTY's file handles and communicates with the terminal @@ -126,6 +137,10 @@ impl ConnectedView { cx.notify(); } + fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { + cx.show_character_palette(); + } + fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.clear()); cx.notify(); diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index e07a324998..002759d78d 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -53,7 +53,6 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { // Manual Bindings including modifiers let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) { //Basic special keys - ("space", Modifiers::None) => Some(" ".to_string()), ("tab", Modifiers::None) => Some("\x09".to_string()), ("escape", Modifiers::None) => Some("\x1b".to_string()), ("enter", Modifiers::None) => Some("\x0d".to_string()), From 5ad746e53f2326ddef644cf9773775b58a41ae17 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 16 Aug 2022 23:10:49 -0700 Subject: [PATCH 26/74] Added alt screen mode check --- crates/terminal/src/connected_view.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 905c27d2b9..8de728b638 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -138,7 +138,14 @@ impl ConnectedView { } fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { - cx.show_character_palette(); + if self + .terminal + .read(cx) + .last_mode + .contains(TermMode::ALT_SCREEN) + { + cx.show_character_palette(); + } } fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { From ab236a6008a388e16a6c360ea76e16cfcae82d71 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Aug 2022 11:26:05 +0200 Subject: [PATCH 27/74] Fix divergence bug in undo/redo As part of #1405, we changed the way we performed undo and redo to support combining transactions that were not temporally adjacent for IME purposes. We introduced a bug with that release that caused divergence when performing undo: the bug was caused by only changing the visibility of fragments whose insertion id was contained in the undo operation. However, an undo operation also affects deletions which we were mistakenly not considering. Randomized tests caught this but I guess we didn't run enough of them. --- crates/language/src/proto.rs | 2 -- crates/rpc/proto/zed.proto | 3 +-- crates/text/src/text.rs | 12 ++---------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 047cef3ab6..df1e0e0959 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -39,7 +39,6 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { local_timestamp: undo.id.value, lamport_timestamp: lamport_timestamp.value, version: serialize_version(&undo.version), - transaction_version: serialize_version(&undo.transaction_version), counts: undo .counts .iter() @@ -199,7 +198,6 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { ) }) .collect(), - transaction_version: deserialize_version(undo.transaction_version), }, }), proto::operation::Variant::UpdateSelections(message) => { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index f52815a8be..3da7f62ac7 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -901,8 +901,7 @@ message Operation { uint32 local_timestamp = 2; uint32 lamport_timestamp = 3; repeated VectorClockEntry version = 4; - repeated VectorClockEntry transaction_version = 6; - repeated UndoCount counts = 7; + repeated UndoCount counts = 5; } message UpdateSelections { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index ff78f4a14b..1f2e4e7c7a 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -512,7 +512,6 @@ pub struct EditOperation { pub struct UndoOperation { pub id: clock::Local, pub counts: HashMap, - pub transaction_version: clock::Global, pub version: clock::Global, } @@ -1109,14 +1108,8 @@ impl Buffer { let mut fragment = fragment.clone(); let fragment_was_visible = fragment.visible; - if fragment.was_visible(&undo.transaction_version, &self.undo_map) - || undo - .counts - .contains_key(&fragment.insertion_timestamp.local()) - { - fragment.visible = fragment.is_visible(&self.undo_map); - fragment.max_undos.observe(undo.id); - } + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.id); let old_start = old_fragments.start().1; let new_start = new_fragments.summary().text.visible; @@ -1297,7 +1290,6 @@ impl Buffer { id: self.local_clock.tick(), version: self.version(), counts, - transaction_version: transaction.start, }; self.apply_undo(&undo)?; let operation = Operation::Undo { From 9726d1f049161149ef67fb9354469a9139f38b5f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Aug 2022 11:31:48 +0200 Subject: [PATCH 28/74] Bump protocol version --- crates/rpc/src/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index c4017015f9..b3c7aa0046 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 = 29; +pub const PROTOCOL_VERSION: u32 = 30; From 75c9b90c767aabe37112b82748cc35e6897a0e51 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Aug 2022 11:53:51 +0200 Subject: [PATCH 29/74] Add failing unit test for buffer opening cancellation --- crates/collab/src/integration_tests.rs | 46 ++++++++++++++++++++++++++ crates/gpui/src/executor.rs | 21 +++++++----- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 2327aa6aad..905aa328f2 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -1390,6 +1390,52 @@ async fn test_leaving_worktree_while_opening_buffer( .await; } +#[gpui::test(iterations = 10)] +async fn test_canceling_buffer_opening( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + + 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_b = server.create_client(cx_b, "user_b").await; + server + .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + client_a + .fs + .insert_tree( + "/dir", + json!({ + "a.txt": "abc", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; + let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; + + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .await + .unwrap(); + + // Open a buffer as client B but cancel after a random amount of time. + let buffer_b = project_b.update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx)); + deterministic.simulate_random_delay().await; + drop(buffer_b); + + // Try opening the same buffer again as client B, and ensure we can + // still do it despite the cancellation above. + let buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx)) + .await + .unwrap(); + buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc")); +} + #[gpui::test(iterations = 10)] async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 539d582e5b..980da91167 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -381,6 +381,17 @@ impl Deterministic { state.forbid_parking = true; state.rng = StdRng::seed_from_u64(state.seed); } + + pub async fn simulate_random_delay(&self) { + use rand::prelude::*; + use smol::future::yield_now; + if self.state.lock().rng.gen_bool(0.2) { + let yields = self.state.lock().rng.gen_range(1..=10); + for _ in 0..yields { + yield_now().await; + } + } + } } impl Drop for Timer { @@ -662,17 +673,9 @@ impl Background { #[cfg(any(test, feature = "test-support"))] pub async fn simulate_random_delay(&self) { - use rand::prelude::*; - use smol::future::yield_now; - match self { Self::Deterministic { executor, .. } => { - if executor.state.lock().rng.gen_bool(0.2) { - let yields = executor.state.lock().rng.gen_range(1..=10); - for _ in 0..yields { - yield_now().await; - } - } + executor.simulate_random_delay().await; } _ => { panic!("this method can only be called on a deterministic executor") From 9c9bf07e40af81432125665acb4d3b091149bfcb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Aug 2022 11:10:09 +0200 Subject: [PATCH 30/74] Create buffers for remote collaborators out of band Previously, we would use `Project::serialize_buffer_for_peer` and `Project::deserialize_buffer` respectively in the host and in the guest to create a new buffer or just send its ID if the host thought the buffer had already been sent. These methods would be called as part of other methods, such as `Project::open_buffer_by_id` or `Project::open_buffer_for_symbol`. However, if any of the tasks driving the futures that eventually called `Project::deserialize_buffer` were dropped after the host responded with the buffer state but (crucially) before the guest deserialized it and registered it, there could be a situation where the host thought the guest had the buffer (thus sending them just the buffer id) and the guest would wait indefinitely. Given how crucial this interaction is, this commit switches to creating remote buffers for peers out of band. The host will push buffers to guests, who will always refer to buffers via IDs and wait for the host to send them, as opposed to including the buffer's payload as part of some other operation. --- crates/collab/src/rpc.rs | 13 +++ crates/language/src/buffer.rs | 6 +- crates/language/src/proto.rs | 2 +- crates/project/src/lsp_command.rs | 29 ++--- crates/project/src/project.rs | 185 +++++++++++++++--------------- crates/rpc/proto/zed.proto | 117 +++++++++---------- crates/rpc/src/proto.rs | 2 + 7 files changed, 183 insertions(+), 171 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 4164424a2b..dab7df3e67 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -187,6 +187,7 @@ impl Server { .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) + .add_message_handler(Server::create_buffer_for_peer) .add_request_handler(Server::update_buffer) .add_message_handler(Server::update_buffer_file) .add_message_handler(Server::buffer_reloaded) @@ -1186,6 +1187,18 @@ impl Server { Ok(()) } + async fn create_buffer_for_peer( + self: Arc, + request: TypedEnvelope, + ) -> Result<()> { + self.peer.forward_send( + request.sender_id, + ConnectionId(request.payload.peer_id), + request.payload, + )?; + Ok(()) + } + async fn update_buffer( self: Arc, request: TypedEnvelope, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index c8180e0a8f..7c616762d8 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -358,7 +358,7 @@ impl Buffer { pub fn from_proto( replica_id: ReplicaId, - message: proto::BufferState, + message: proto::Buffer, file: Option>, cx: &mut ModelContext, ) -> Result { @@ -406,7 +406,7 @@ impl Buffer { Ok(this) } - pub fn to_proto(&self) -> proto::BufferState { + pub fn to_proto(&self) -> proto::Buffer { let mut operations = self .text .history() @@ -414,7 +414,7 @@ impl Buffer { .chain(self.deferred_ops.iter().map(proto::serialize_operation)) .collect::>(); operations.sort_unstable_by_key(proto::lamport_timestamp_for_operation); - proto::BufferState { + proto::Buffer { id: self.remote_id(), file: self.file.as_ref().map(|f| f.to_proto()), base_text: self.base_text().to_string(), diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index df1e0e0959..c01973b1c6 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -9,7 +9,7 @@ use rpc::proto; use std::{ops::Range, sync::Arc}; use text::*; -pub use proto::{Buffer, BufferState, LineEnding, SelectionSet}; +pub use proto::{Buffer, LineEnding, SelectionSet}; pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding { match message { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 7737a4df9e..37f6e76340 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -522,11 +522,10 @@ async fn location_links_from_proto( for link in proto_links { let origin = match link.origin { Some(origin) => { - let buffer = origin - .buffer - .ok_or_else(|| anyhow!("missing origin buffer"))?; let buffer = project - .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + .update(&mut cx, |this, cx| { + this.wait_for_buffer(origin.buffer_id, cx) + }) .await?; let start = origin .start @@ -548,9 +547,10 @@ async fn location_links_from_proto( }; let target = link.target.ok_or_else(|| anyhow!("missing target"))?; - let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?; let buffer = project - .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + .update(&mut cx, |this, cx| { + this.wait_for_buffer(target.buffer_id, cx) + }) .await?; let start = target .start @@ -664,19 +664,19 @@ fn location_links_to_proto( .into_iter() .map(|definition| { let origin = definition.origin.map(|origin| { - let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx); + let buffer_id = project.create_buffer_for_peer(&origin.buffer, peer_id, cx); proto::Location { start: Some(serialize_anchor(&origin.range.start)), end: Some(serialize_anchor(&origin.range.end)), - buffer: Some(buffer), + buffer_id, } }); - let buffer = project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx); + let buffer_id = project.create_buffer_for_peer(&definition.target.buffer, peer_id, cx); let target = proto::Location { start: Some(serialize_anchor(&definition.target.range.start)), end: Some(serialize_anchor(&definition.target.range.end)), - buffer: Some(buffer), + buffer_id, }; proto::LocationLink { @@ -792,11 +792,11 @@ impl LspCommand for GetReferences { let locations = response .into_iter() .map(|definition| { - let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx); + let buffer_id = project.create_buffer_for_peer(&definition.buffer, peer_id, cx); proto::Location { start: Some(serialize_anchor(&definition.range.start)), end: Some(serialize_anchor(&definition.range.end)), - buffer: Some(buffer), + buffer_id, } }) .collect(); @@ -812,9 +812,10 @@ impl LspCommand for GetReferences { ) -> Result> { let mut locations = Vec::new(); for location in message.locations { - let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?; let target_buffer = project - .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + .update(&mut cx, |this, cx| { + this.wait_for_buffer(location.buffer_id, cx) + }) .await?; let start = location .start diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 31d18f4fa8..0f762f822f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -112,7 +112,7 @@ pub struct Project { collaborators: HashMap, client_subscriptions: Vec, _subscriptions: Vec, - opened_buffer: (Rc>>, watch::Receiver<()>), + opened_buffer: (watch::Sender<()>, watch::Receiver<()>), shared_buffers: HashMap>, #[allow(clippy::type_complexity)] loading_buffers: HashMap< @@ -375,6 +375,7 @@ impl Project { client.add_model_message_handler(Self::handle_update_project); client.add_model_message_handler(Self::handle_unregister_project); client.add_model_message_handler(Self::handle_project_unshared); + client.add_model_message_handler(Self::handle_create_buffer_for_peer); client.add_model_message_handler(Self::handle_update_buffer_file); client.add_model_message_handler(Self::handle_update_buffer); client.add_model_message_handler(Self::handle_update_diagnostic_summary); @@ -454,7 +455,6 @@ impl Project { let handle = cx.weak_handle(); project_store.update(cx, |store, cx| store.add_project(handle, cx)); - let (opened_buffer_tx, opened_buffer_rx) = watch::channel(); Self { worktrees: Default::default(), collaborators: Default::default(), @@ -472,7 +472,7 @@ impl Project { _maintain_remote_id, _maintain_online_status, }, - opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx), + opened_buffer: watch::channel(), client_subscriptions: Vec::new(), _subscriptions: vec![cx.observe_global::(Self::on_settings_changed)], _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), @@ -540,7 +540,6 @@ impl Project { worktrees.push(worktree); } - let (opened_buffer_tx, opened_buffer_rx) = watch::channel(); let this = cx.add_model(|cx: &mut ModelContext| { let handle = cx.weak_handle(); project_store.update(cx, |store, cx| store.add_project(handle, cx)); @@ -548,7 +547,7 @@ impl Project { let mut this = Self { worktrees: Vec::new(), loading_buffers: Default::default(), - opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx), + opened_buffer: watch::channel(), shared_buffers: Default::default(), loading_local_worktrees: Default::default(), active_entry: None, @@ -1624,9 +1623,10 @@ impl Project { path: path_string, }) .await?; - let buffer = response.buffer.ok_or_else(|| anyhow!("missing buffer"))?; - this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) - .await + this.update(&mut cx, |this, cx| { + this.wait_for_buffer(response.buffer_id, cx) + }) + .await }) } @@ -1684,11 +1684,8 @@ impl Project { .client .request(proto::OpenBufferById { project_id, id }); cx.spawn(|this, mut cx| async move { - let buffer = request - .await? - .buffer - .ok_or_else(|| anyhow!("invalid buffer"))?; - this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + let buffer_id = request.await?.buffer_id; + this.update(&mut cx, |this, cx| this.wait_for_buffer(buffer_id, cx)) .await }) } else { @@ -1800,6 +1797,7 @@ impl Project { }) .detach(); + *self.opened_buffer.0.borrow_mut() = (); Ok(()) } @@ -3476,9 +3474,10 @@ impl Project { }); cx.spawn(|this, mut cx| async move { let response = request.await?; - let buffer = response.buffer.ok_or_else(|| anyhow!("invalid buffer"))?; - this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) - .await + this.update(&mut cx, |this, cx| { + this.wait_for_buffer(response.buffer_id, cx) + }) + .await }) } else { Task::ready(Err(anyhow!("project does not have a remote id"))) @@ -4294,9 +4293,10 @@ impl Project { let response = request.await?; let mut result = HashMap::default(); for location in response.locations { - let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?; let target_buffer = this - .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + .update(&mut cx, |this, cx| { + this.wait_for_buffer(location.buffer_id, cx) + }) .await?; let start = location .start @@ -5107,6 +5107,36 @@ impl Project { }) } + async fn handle_create_buffer_for_peer( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result<()> { + this.update(&mut cx, |this, cx| { + let mut buffer = envelope + .payload + .buffer + .ok_or_else(|| anyhow!("invalid buffer"))?; + let mut buffer_file = None; + if let Some(file) = buffer.file.take() { + let worktree_id = WorktreeId::from_proto(file.worktree_id); + let worktree = this + .worktree_for_id(worktree_id, cx) + .ok_or_else(|| anyhow!("no worktree found for id {}", file.worktree_id))?; + buffer_file = Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?) + as Arc); + } + + let buffer = cx.add_model(|cx| { + Buffer::from_proto(this.replica_id(), buffer, buffer_file, cx).unwrap() + }); + this.register_buffer(&buffer, cx)?; + + Ok(()) + }) + } + async fn handle_update_buffer_file( this: ModelHandle, envelope: TypedEnvelope, @@ -5448,9 +5478,9 @@ impl Project { for range in ranges { let start = serialize_anchor(&range.start); let end = serialize_anchor(&range.end); - let buffer = this.serialize_buffer_for_peer(&buffer, peer_id, cx); + let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx); locations.push(proto::Location { - buffer: Some(buffer), + buffer_id, start: Some(start), end: Some(end), }); @@ -5487,9 +5517,9 @@ impl Project { .await?; Ok(proto::OpenBufferForSymbolResponse { - buffer: Some(this.update(&mut cx, |this, cx| { - this.serialize_buffer_for_peer(&buffer, peer_id, cx) - })), + buffer_id: this.update(&mut cx, |this, cx| { + this.create_buffer_for_peer(&buffer, peer_id, cx) + }), }) } @@ -5515,7 +5545,7 @@ impl Project { .await?; this.update(&mut cx, |this, cx| { Ok(proto::OpenBufferResponse { - buffer: Some(this.serialize_buffer_for_peer(&buffer, peer_id, cx)), + buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx), }) }) } @@ -5541,7 +5571,7 @@ impl Project { let buffer = open_buffer.await?; this.update(&mut cx, |this, cx| { Ok(proto::OpenBufferResponse { - buffer: Some(this.serialize_buffer_for_peer(&buffer, peer_id, cx)), + buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx), }) }) } @@ -5553,13 +5583,13 @@ impl Project { cx: &AppContext, ) -> proto::ProjectTransaction { let mut serialized_transaction = proto::ProjectTransaction { - buffers: Default::default(), + buffer_ids: Default::default(), transactions: Default::default(), }; for (buffer, transaction) in project_transaction.0 { serialized_transaction - .buffers - .push(self.serialize_buffer_for_peer(&buffer, peer_id, cx)); + .buffer_ids + .push(self.create_buffer_for_peer(&buffer, peer_id, cx)); serialized_transaction .transactions .push(language::proto::serialize_transaction(&transaction)); @@ -5575,9 +5605,10 @@ impl Project { ) -> Task> { cx.spawn(|this, mut cx| async move { let mut project_transaction = ProjectTransaction::default(); - for (buffer, transaction) in message.buffers.into_iter().zip(message.transactions) { + for (buffer_id, transaction) in message.buffer_ids.into_iter().zip(message.transactions) + { let buffer = this - .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + .update(&mut cx, |this, cx| this.wait_for_buffer(buffer_id, cx)) .await?; let transaction = language::proto::deserialize_transaction(transaction)?; project_transaction.0.insert(buffer, transaction); @@ -5601,81 +5632,51 @@ impl Project { }) } - fn serialize_buffer_for_peer( + fn create_buffer_for_peer( &mut self, buffer: &ModelHandle, peer_id: PeerId, cx: &AppContext, - ) -> proto::Buffer { + ) -> u64 { let buffer_id = buffer.read(cx).remote_id(); - let shared_buffers = self.shared_buffers.entry(peer_id).or_default(); - if shared_buffers.insert(buffer_id) { - proto::Buffer { - variant: Some(proto::buffer::Variant::State(buffer.read(cx).to_proto())), - } - } else { - proto::Buffer { - variant: Some(proto::buffer::Variant::Id(buffer_id)), + if let Some(project_id) = self.remote_id() { + let shared_buffers = self.shared_buffers.entry(peer_id).or_default(); + if shared_buffers.insert(buffer_id) { + self.client + .send(proto::CreateBufferForPeer { + project_id, + peer_id: peer_id.0, + buffer: Some(buffer.read(cx).to_proto()), + }) + .log_err(); } } + + buffer_id } - fn deserialize_buffer( - &mut self, - buffer: proto::Buffer, + fn wait_for_buffer( + &self, + id: u64, cx: &mut ModelContext, ) -> Task>> { - let replica_id = self.replica_id(); - - let opened_buffer_tx = self.opened_buffer.0.clone(); let mut opened_buffer_rx = self.opened_buffer.1.clone(); - cx.spawn(|this, mut cx| async move { - match buffer.variant.ok_or_else(|| anyhow!("missing buffer"))? { - proto::buffer::Variant::Id(id) => { - let buffer = loop { - let buffer = this.read_with(&cx, |this, cx| { - this.opened_buffers - .get(&id) - .and_then(|buffer| buffer.upgrade(cx)) - }); - if let Some(buffer) = buffer { - break buffer; - } - opened_buffer_rx - .next() - .await - .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?; - }; - Ok(buffer) + cx.spawn(|this, cx| async move { + let buffer = loop { + let buffer = this.read_with(&cx, |this, cx| { + this.opened_buffers + .get(&id) + .and_then(|buffer| buffer.upgrade(cx)) + }); + if let Some(buffer) = buffer { + break buffer; } - proto::buffer::Variant::State(mut buffer) => { - let mut buffer_worktree = None; - let mut buffer_file = None; - if let Some(file) = buffer.file.take() { - this.read_with(&cx, |this, cx| { - let worktree_id = WorktreeId::from_proto(file.worktree_id); - let worktree = - this.worktree_for_id(worktree_id, cx).ok_or_else(|| { - anyhow!("no worktree found for id {}", file.worktree_id) - })?; - buffer_file = - Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?) - as Arc); - buffer_worktree = Some(worktree); - Ok::<_, anyhow::Error>(()) - })?; - } - - let buffer = cx.add_model(|cx| { - Buffer::from_proto(replica_id, buffer, buffer_file, cx).unwrap() - }); - - this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))?; - - *opened_buffer_tx.borrow_mut().borrow_mut() = (); - Ok(buffer) - } - } + opened_buffer_rx + .next() + .await + .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?; + }; + Ok(buffer) }) } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3da7f62ac7..c803ff85b5 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -55,58 +55,59 @@ message Envelope { OpenBufferById open_buffer_by_id = 44; OpenBufferByPath open_buffer_by_path = 45; OpenBufferResponse open_buffer_response = 46; - UpdateBuffer update_buffer = 47; - UpdateBufferFile update_buffer_file = 48; - SaveBuffer save_buffer = 49; - BufferSaved buffer_saved = 50; - BufferReloaded buffer_reloaded = 51; - ReloadBuffers reload_buffers = 52; - ReloadBuffersResponse reload_buffers_response = 53; - FormatBuffers format_buffers = 54; - FormatBuffersResponse format_buffers_response = 55; - GetCompletions get_completions = 56; - GetCompletionsResponse get_completions_response = 57; - ApplyCompletionAdditionalEdits apply_completion_additional_edits = 58; - ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 59; - GetCodeActions get_code_actions = 60; - GetCodeActionsResponse get_code_actions_response = 61; - GetHover get_hover = 62; - GetHoverResponse get_hover_response = 63; - ApplyCodeAction apply_code_action = 64; - ApplyCodeActionResponse apply_code_action_response = 65; - PrepareRename prepare_rename = 66; - PrepareRenameResponse prepare_rename_response = 67; - PerformRename perform_rename = 68; - PerformRenameResponse perform_rename_response = 69; - SearchProject search_project = 70; - SearchProjectResponse search_project_response = 71; + CreateBufferForPeer create_buffer_for_peer = 47; + UpdateBuffer update_buffer = 48; + UpdateBufferFile update_buffer_file = 49; + SaveBuffer save_buffer = 50; + BufferSaved buffer_saved = 51; + BufferReloaded buffer_reloaded = 52; + ReloadBuffers reload_buffers = 53; + ReloadBuffersResponse reload_buffers_response = 54; + FormatBuffers format_buffers = 55; + FormatBuffersResponse format_buffers_response = 56; + GetCompletions get_completions = 57; + GetCompletionsResponse get_completions_response = 58; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 59; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 60; + GetCodeActions get_code_actions = 61; + GetCodeActionsResponse get_code_actions_response = 62; + GetHover get_hover = 63; + GetHoverResponse get_hover_response = 64; + ApplyCodeAction apply_code_action = 65; + ApplyCodeActionResponse apply_code_action_response = 66; + PrepareRename prepare_rename = 67; + PrepareRenameResponse prepare_rename_response = 68; + PerformRename perform_rename = 69; + PerformRenameResponse perform_rename_response = 70; + SearchProject search_project = 71; + SearchProjectResponse search_project_response = 72; - GetChannels get_channels = 72; - GetChannelsResponse get_channels_response = 73; - JoinChannel join_channel = 74; - JoinChannelResponse join_channel_response = 75; - LeaveChannel leave_channel = 76; - SendChannelMessage send_channel_message = 77; - SendChannelMessageResponse send_channel_message_response = 78; - ChannelMessageSent channel_message_sent = 79; - GetChannelMessages get_channel_messages = 80; - GetChannelMessagesResponse get_channel_messages_response = 81; + GetChannels get_channels = 73; + GetChannelsResponse get_channels_response = 74; + JoinChannel join_channel = 75; + JoinChannelResponse join_channel_response = 76; + LeaveChannel leave_channel = 77; + SendChannelMessage send_channel_message = 78; + SendChannelMessageResponse send_channel_message_response = 79; + ChannelMessageSent channel_message_sent = 80; + GetChannelMessages get_channel_messages = 81; + GetChannelMessagesResponse get_channel_messages_response = 82; - UpdateContacts update_contacts = 82; - UpdateInviteInfo update_invite_info = 83; - ShowContacts show_contacts = 84; + UpdateContacts update_contacts = 83; + UpdateInviteInfo update_invite_info = 84; + ShowContacts show_contacts = 85; - GetUsers get_users = 85; - FuzzySearchUsers fuzzy_search_users = 86; - UsersResponse users_response = 87; - RequestContact request_contact = 88; - RespondToContactRequest respond_to_contact_request = 89; - RemoveContact remove_contact = 90; + GetUsers get_users = 86; + FuzzySearchUsers fuzzy_search_users = 87; + UsersResponse users_response = 88; + RequestContact request_contact = 89; + RespondToContactRequest respond_to_contact_request = 90; + RemoveContact remove_contact = 91; - Follow follow = 91; - FollowResponse follow_response = 92; - UpdateFollowers update_followers = 93; - Unfollow unfollow = 94; + Follow follow = 92; + FollowResponse follow_response = 93; + UpdateFollowers update_followers = 94; + Unfollow unfollow = 95; } } @@ -299,7 +300,7 @@ message GetDocumentHighlightsResponse { } message Location { - Buffer buffer = 1; + uint64 buffer_id = 1; Anchor start = 2; Anchor end = 3; } @@ -348,7 +349,7 @@ message OpenBufferForSymbol { } message OpenBufferForSymbolResponse { - Buffer buffer = 1; + uint64 buffer_id = 1; } message OpenBufferByPath { @@ -363,12 +364,13 @@ message OpenBufferById { } message OpenBufferResponse { - Buffer buffer = 1; + uint64 buffer_id = 1; } -message CloseBuffer { +message CreateBufferForPeer { uint64 project_id = 1; - uint64 buffer_id = 2; + uint32 peer_id = 2; + Buffer buffer = 3; } message UpdateBuffer { @@ -539,7 +541,7 @@ message CodeAction { } message ProjectTransaction { - repeated Buffer buffers = 1; + repeated uint64 buffer_ids = 1; repeated Transaction transactions = 2; } @@ -807,13 +809,6 @@ message Entry { } message Buffer { - oneof variant { - uint64 id = 1; - BufferState state = 2; - } -} - -message BufferState { uint64 id = 1; optional File file = 2; string base_text = 3; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 8cd65d34f1..2ba3fa18ba 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -86,6 +86,7 @@ messages!( (RemoveContact, Foreground), (ChannelMessageSent, Foreground), (CopyProjectEntry, Foreground), + (CreateBufferForPeer, Foreground), (CreateProjectEntry, Foreground), (DeleteProjectEntry, Foreground), (Error, Foreground), @@ -222,6 +223,7 @@ entity_messages!( BufferReloaded, BufferSaved, CopyProjectEntry, + CreateBufferForPeer, CreateProjectEntry, DeleteProjectEntry, Follow, From b60277cad62de6746754199ad25e3037f0c6572a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Aug 2022 12:10:32 +0200 Subject: [PATCH 31/74] v0.51.0 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5a8effc8c..9e5e723d63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6981,7 +6981,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.50.0" +version = "0.51.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 611099ccdb..c3e785ab10 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.50.0" +version = "0.51.0" [lib] name = "zed" From 443432606eac8f4a975cbf7cc403c4701bb88e0d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Aug 2022 09:33:48 -0600 Subject: [PATCH 32/74] Fix focus regression in contact finder Co-Authored-By: Antonio Scandurra --- crates/contacts_panel/src/contact_finder.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/contacts_panel/src/contact_finder.rs b/crates/contacts_panel/src/contact_finder.rs index d7c096aaa9..1831c1ba72 100644 --- a/crates/contacts_panel/src/contact_finder.rs +++ b/crates/contacts_panel/src/contact_finder.rs @@ -43,7 +43,9 @@ impl View for ContactFinder { } fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - cx.focus(&self.picker); + if cx.is_self_focused() { + cx.focus(&self.picker); + } } } From d3904cd961be932ec2015e2a2f65b329e5fe5512 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Aug 2022 09:47:19 -0600 Subject: [PATCH 33/74] v0.51.1 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- styles/package-lock.json | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e5e723d63..b08fb287ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6981,7 +6981,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.51.0" +version = "0.51.1" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c3e785ab10..ffb0945b25 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.51.0" +version = "0.51.1" [lib] name = "zed" diff --git a/styles/package-lock.json b/styles/package-lock.json index 5499f1852c..582f1c8496 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From acce0042f90474263314180a71181c9e004a5392 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 Aug 2022 14:41:28 -0700 Subject: [PATCH 34/74] Fixed blink problems --- assets/settings/default.json | 11 ++--- crates/editor/src/element.rs | 61 +++++++-------------------- crates/settings/src/settings.rs | 5 +-- crates/terminal/src/connected_el.rs | 60 ++++++-------------------- crates/terminal/src/connected_view.rs | 53 +++++++++++++++++++---- crates/terminal/src/terminal.rs | 21 ++++----- 6 files changed, 89 insertions(+), 122 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index c6f08f4e56..144a34ba14 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -106,15 +106,12 @@ //May take 4 values: // 1. Never blink the cursor, ignoring the terminal mode // "blinking": "never", - // 2. Default the cursor blink to off, but allow the terminal to - // turn blinking on - // "blinking": "off", - // 3. Default the cursor blink to on, but allow the terminal to + // 2. Default the cursor blink to on, but allow the terminal to // turn blinking off - // "blinking": "on", - // 4. Always blink the cursor, ignoring the terminal mode + // "blinking": "terminal_controlled", + // 3. Always blink the cursor, ignoring the terminal mode // "blinking": "always", - "blinking": "on", + "blinking": "terminal_controlled", //Any key-value pairs added to this list will be added to the terminal's //enviroment. Use `:` to seperate multiple values. "env": { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6140731579..a688479047 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1801,7 +1801,7 @@ impl Cursor { pub fn paint(&self, origin: Vector2F, cx: &mut PaintContext) { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)), - CursorShape::Block => RectF::new( + CursorShape::Block | CursorShape::Hollow => RectF::new( self.origin + origin, vec2f(self.block_width, self.line_height), ), @@ -1809,58 +1809,27 @@ impl Cursor { self.origin + origin + Vector2F::new(0.0, self.line_height - 2.0), vec2f(self.block_width, 2.0), ), - CursorShape::Hollow => RectF::new( - self.origin + origin + Vector2F::new(0.0, self.line_height - 1.0), - vec2f(self.block_width, 1.0), - ), }; - //Draw text under the hollow block if need be + //Draw background or border quad if matches!(self.shape, CursorShape::Hollow) { - if let Some(block_text) = &self.block_text { - block_text.paint(self.origin + origin, bounds, self.line_height, cx); - } - } - - cx.scene.push_quad(Quad { - bounds, - background: Some(self.color), - border: Border::new(0., Color::black()), - corner_radius: 0., - }); - - if matches!(self.shape, CursorShape::Hollow) { - //Top cx.scene.push_quad(Quad { - bounds: RectF::new( - self.origin + origin + Vector2F::new(0.0, -1.0), - vec2f(self.block_width + 1., 1.0), - ), - background: Some(self.color), - border: Border::new(0., Color::black()), - corner_radius: 0., - }); - //Left - cx.scene.push_quad(Quad { - bounds: RectF::new(self.origin + origin, vec2f(1.0, self.line_height)), - background: Some(self.color), - border: Border::new(0., Color::black()), - corner_radius: 0., - }); - //Right - cx.scene.push_quad(Quad { - bounds: RectF::new( - self.origin + origin + vec2f(self.block_width, 0.), - vec2f(1.0, self.line_height), - ), - background: Some(self.color), - border: Border::new(0., Color::black()), + bounds, + background: None, + border: Border::all(1., self.color), corner_radius: 0., }); } else { - if let Some(block_text) = &self.block_text { - block_text.paint(self.origin + origin, bounds, self.line_height, cx); - } + cx.scene.push_quad(Quad { + bounds, + background: Some(self.color), + border: Default::default(), + corner_radius: 0., + }); + } + + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin + origin, bounds, self.line_height, cx); } } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9c1a17a462..124d5c292a 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -90,14 +90,13 @@ pub struct TerminalSettings { #[serde(rename_all = "snake_case")] pub enum TerminalBlink { Never, - On, - Off, + TerminalControlled, Always, } impl Default for TerminalBlink { fn default() -> Self { - TerminalBlink::On + TerminalBlink::TerminalControlled } } diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index d017aad65b..b472a1c78f 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -21,7 +21,7 @@ use gpui::{ }; use itertools::Itertools; use ordered_float::OrderedFloat; -use settings::{Settings, TerminalBlink}; +use settings::Settings; use theme::TerminalStyle; use util::ResultExt; @@ -201,7 +201,7 @@ pub struct TerminalEl { view: WeakViewHandle, modal: bool, focused: bool, - blink_state: bool, + cursor_visible: bool, } impl TerminalEl { @@ -210,14 +210,14 @@ impl TerminalEl { terminal: WeakModelHandle, modal: bool, focused: bool, - blink_state: bool, + cursor_visible: bool, ) -> TerminalEl { TerminalEl { view, terminal, modal, focused, - blink_state, + cursor_visible, } } @@ -571,33 +571,6 @@ impl TerminalEl { (point, side) } - - pub fn should_show_cursor( - settings: Option, - blinking_on: bool, - focused: bool, - blink_show: bool, - ) -> bool { - if !focused { - true - } else { - match settings { - Some(setting) => match setting { - TerminalBlink::Never => true, - TerminalBlink::On | TerminalBlink::Off if blinking_on => blink_show, - TerminalBlink::On | TerminalBlink::Off /*if !blinking_on */ => true, - TerminalBlink::Always => focused && blink_show, - }, - None => { - if blinking_on { - blink_show - } else { - false - } - } - } - } - } } impl Element for TerminalEl { @@ -610,7 +583,6 @@ impl Element for TerminalEl { cx: &mut gpui::LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let settings = cx.global::(); - let blink_settings = settings.terminal_overrides.blinking.clone(); let font_cache = cx.font_cache(); //Setup layout information @@ -629,13 +601,13 @@ impl Element for TerminalEl { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text, blink_mode) = self + let (cells, selection, cursor, display_offset, cursor_text) = self .terminal .upgrade(cx) .unwrap() .update(cx.app, |terminal, mcx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text, blink_mode| { + terminal.render_lock(mcx, |content, cursor_text| { let mut cells = vec![]; cells.extend( content @@ -659,7 +631,6 @@ impl Element for TerminalEl { content.cursor, content.display_offset, cursor_text, - blink_mode, ) }) }); @@ -676,14 +647,7 @@ impl Element for TerminalEl { //Layout cursor let cursor = { - if !TerminalEl::should_show_cursor( - blink_settings, - blink_mode, - self.focused, - self.blink_state, - ) { - None - } else { + if self.cursor_visible { let cursor_point = DisplayCursor::from(cursor.point, display_offset); let cursor_text = { let str_trxt = cursor_text.to_string(); @@ -710,22 +674,24 @@ impl Element for TerminalEl { TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( move |(cursor_position, block_width)| { - let (shape, color) = if self.focused { - (CursorShape::Block, terminal_theme.colors.cursor) + let shape = if self.focused { + CursorShape::Block } else { - (CursorShape::Hollow, terminal_theme.colors.foreground) + CursorShape::Hollow }; Cursor::new( cursor_position, block_width, dimensions.line_height, - color, + terminal_theme.colors.cursor, shape, Some(cursor_text), ) }, ) + } else { + None } }; diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 8de728b638..be272d9119 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -11,6 +11,7 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle, }; +use settings::{Settings, TerminalBlink}; use smol::Timer; use workspace::pane; @@ -67,7 +68,8 @@ pub struct ConnectedView { // Only for styling purposes. Doesn't effect behavior modal: bool, context_menu: ViewHandle, - show_cursor: bool, + blink_state: bool, + blinking_on: bool, blinking_paused: bool, blink_epoch: usize, } @@ -91,7 +93,7 @@ impl ConnectedView { this.has_bell = true; cx.emit(Event::Wakeup); } - + Event::BlinkChanged => this.blinking_on = !this.blinking_on, _ => cx.emit(*event), }) .detach(); @@ -102,7 +104,8 @@ impl ConnectedView { has_bell: false, modal, context_menu: cx.add_view(ContextMenu::new), - show_cursor: true, + blink_state: true, + blinking_on: false, blinking_paused: false, blink_epoch: 0, } @@ -153,14 +156,46 @@ impl ConnectedView { cx.notify(); } - //Following code copied from editor cursor - pub fn blink_show(&self) -> bool { - self.blinking_paused || self.show_cursor + //2 -> Character palette shows up! But it's incorrectly positioned + + pub fn should_show_cursor( + &self, + focused: bool, + cx: &mut gpui::RenderContext<'_, Self>, + ) -> bool { + //Don't blink the cursor when not focused, blinking is disabled, or paused + if !focused + || !self.blinking_on + || self.blinking_paused + || self + .terminal + .read(cx) + .last_mode + .contains(TermMode::ALT_SCREEN) + { + return true; + } + + let setting = { + let settings = cx.global::(); + settings + .terminal_overrides + .blinking + .clone() + .unwrap_or(TerminalBlink::TerminalControlled) + }; + + match setting { + //If the user requested to never blink, don't blink it. + TerminalBlink::Never => true, + //If the terminal is controlling it, check terminal mode + TerminalBlink::TerminalControlled | TerminalBlink::Always => self.blink_state, + } } fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext) { if epoch == self.blink_epoch && !self.blinking_paused { - self.show_cursor = !self.show_cursor; + self.blink_state = !self.blink_state; cx.notify(); let epoch = self.next_blink_epoch(); @@ -178,7 +213,7 @@ impl ConnectedView { } pub fn pause_cursor_blinking(&mut self, cx: &mut ViewContext) { - self.show_cursor = true; + self.blink_state = true; cx.notify(); let epoch = self.next_blink_epoch(); @@ -280,7 +315,7 @@ impl View for ConnectedView { terminal_handle, self.modal, focused, - self.blink_show(), + self.should_show_cursor(focused, cx), ) .contained() .boxed(), diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 0debf4fa91..8b574f8597 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -19,6 +19,8 @@ use alacritty_terminal::{ }; use anyhow::{bail, Result}; +//When you type a key, scroll does not happen in terminal !!!TODO + use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, FutureExt, @@ -62,6 +64,7 @@ pub enum Event { CloseTerminal, Bell, Wakeup, + BlinkChanged, } #[derive(Clone, Debug)] @@ -295,14 +298,12 @@ impl TerminalBuilder { //Start off blinking if we need to match blink_settings { - Some(setting) => match setting { - TerminalBlink::On | TerminalBlink::Always => { - term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) - } - _ => {} - }, - None => term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor), + None | Some(TerminalBlink::TerminalControlled) | Some(TerminalBlink::Always) => { + term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) + } + _ => {} } + let term = Arc::new(FairMutex::new(term)); //Setup the pty... @@ -479,7 +480,7 @@ impl Terminal { self.notify_pty(format(self.cur_size.into())) } AlacTermEvent::CursorBlinkingChange => { - //TODO whatever state we need to set to get the cursor blinking + cx.emit(Event::BlinkChanged); } AlacTermEvent::Bell => { cx.emit(Event::Bell); @@ -595,7 +596,7 @@ impl Terminal { pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where - F: FnOnce(RenderableContent, char, bool) -> T, + F: FnOnce(RenderableContent, char) -> T, { let m = self.term.clone(); //Arc clone let mut term = m.lock(); @@ -611,7 +612,7 @@ impl Terminal { let cursor_text = term.grid()[content.cursor.point].c; - f(content, cursor_text, term.cursor_style().blinking) + f(content, cursor_text) } ///Scroll the terminal From 06f9516d3127a4540347255568afa82fe8a22bb3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Aug 2022 14:59:17 +0200 Subject: [PATCH 35/74] Fix crash when closing a window while in full-screen mode This commit delays closing the native window to the next tick to avoid borrowing either `WindowState` or `MutableAppContext` twice. --- crates/gpui/src/platform/mac/window.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 83dd9d6e20..aed8bd92b6 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -458,9 +458,15 @@ impl Window { impl Drop for Window { fn drop(&mut self) { - unsafe { - self.0.as_ref().borrow().native_window.close(); - } + let this = self.0.borrow(); + let window = this.native_window; + this.executor + .spawn(async move { + unsafe { + window.close(); + } + }) + .detach(); } } From 6652d4186469a469c9ab44fbd4c7fd42c6bd0bac Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 11:28:18 -0700 Subject: [PATCH 36/74] Fixed cursor blinking, added other cursor shape rendering --- assets/settings/default.json | 8 +- crates/gpui/src/platform/mac/window.rs | 1 - crates/settings/src/settings.rs | 4 +- crates/terminal/src/connected_el.rs | 103 +++++++++++++------------ crates/terminal/src/connected_view.rs | 12 +-- crates/terminal/src/terminal.rs | 7 +- 6 files changed, 70 insertions(+), 65 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 144a34ba14..739b34d743 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -105,12 +105,12 @@ //Set the cursor blinking behavior in the terminal. //May take 4 values: // 1. Never blink the cursor, ignoring the terminal mode - // "blinking": "never", - // 2. Default the cursor blink to on, but allow the terminal to - // turn blinking off + // "blinking": "off", + // 2. Default the cursor blink to off, but allow the terminal to + // set blinking // "blinking": "terminal_controlled", // 3. Always blink the cursor, ignoring the terminal mode - // "blinking": "always", + // "blinking": "on", "blinking": "terminal_controlled", //Any key-value pairs added to this list will be added to the terminal's //enviroment. Use `:` to seperate multiple values. diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 5db530b9cf..503f2b87c5 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1159,7 +1159,6 @@ extern "C" fn first_rect_for_character_range( let window = get_window_state(this).borrow().native_window; NSView::frame(window) }; - with_input_handler(this, |input_handler| { input_handler.rect_for_range(range.to_range()?) }) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 124d5c292a..9defb6f410 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -89,9 +89,9 @@ pub struct TerminalSettings { #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TerminalBlink { - Never, + Off, TerminalControlled, - Always, + On, } impl Default for TerminalBlink { diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index b472a1c78f..230c802503 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -645,54 +645,59 @@ impl Element for TerminalEl { selection, ); - //Layout cursor - let cursor = { - if self.cursor_visible { - let cursor_point = DisplayCursor::from(cursor.point, display_offset); - let cursor_text = { - let str_trxt = cursor_text.to_string(); + //Layout cursor. Rectangle is used for IME, so we should lay it out even + //if we don't end up showing it. + let cursor = if let alacritty_terminal::ansi::CursorShape::Hidden = cursor.shape { + None + } else { + let cursor_point = DisplayCursor::from(cursor.point, display_offset); + let cursor_text = { + let str_trxt = cursor_text.to_string(); - let color = if self.focused { - terminal_theme.colors.background - } else { - terminal_theme.colors.foreground - }; - - cx.text_layout_cache.layout_str( - &str_trxt, - text_style.font_size, - &[( - str_trxt.len(), - RunStyle { - font_id: text_style.font_id, - color, - underline: Default::default(), - }, - )], - ) + let color = if self.focused { + terminal_theme.colors.background + } else { + terminal_theme.colors.foreground }; - TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( - move |(cursor_position, block_width)| { - let shape = if self.focused { - CursorShape::Block - } else { - CursorShape::Hollow - }; - - Cursor::new( - cursor_position, - block_width, - dimensions.line_height, - terminal_theme.colors.cursor, - shape, - Some(cursor_text), - ) - }, + cx.text_layout_cache.layout_str( + &str_trxt, + text_style.font_size, + &[( + str_trxt.len(), + RunStyle { + font_id: text_style.font_id, + color, + underline: Default::default(), + }, + )], ) - } else { - None - } + }; + + TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( + move |(cursor_position, block_width)| { + let shape = match cursor.shape { + alacritty_terminal::ansi::CursorShape::Block if !self.focused => { + CursorShape::Hollow + } + alacritty_terminal::ansi::CursorShape::Block => CursorShape::Block, + alacritty_terminal::ansi::CursorShape::Underline => CursorShape::Underscore, + alacritty_terminal::ansi::CursorShape::Beam => CursorShape::Bar, + alacritty_terminal::ansi::CursorShape::HollowBlock => CursorShape::Hollow, + //This case is handled in the wrapping if + alacritty_terminal::ansi::CursorShape::Hidden => CursorShape::Block, + }; + + Cursor::new( + cursor_position, + block_width, + dimensions.line_height, + terminal_theme.colors.cursor, + shape, + Some(cursor_text), + ) + }, + ) }; //Done! @@ -783,10 +788,12 @@ impl Element for TerminalEl { }); //Draw cursor - if let Some(cursor) = &layout.cursor { - cx.paint_layer(clip_bounds, |cx| { - cursor.paint(origin, cx); - }) + if self.cursor_visible { + if let Some(cursor) = &layout.cursor { + cx.paint_layer(clip_bounds, |cx| { + cursor.paint(origin, cx); + }) + } } }); } diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index be272d9119..d4bbe7e0b5 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -141,13 +141,17 @@ impl ConnectedView { } fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { - if self + if !self .terminal .read(cx) .last_mode .contains(TermMode::ALT_SCREEN) { cx.show_character_palette(); + } else { + self.terminal + .read(cx) + .try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap()); } } @@ -156,8 +160,6 @@ impl ConnectedView { cx.notify(); } - //2 -> Character palette shows up! But it's incorrectly positioned - pub fn should_show_cursor( &self, focused: bool, @@ -187,9 +189,9 @@ impl ConnectedView { match setting { //If the user requested to never blink, don't blink it. - TerminalBlink::Never => true, + TerminalBlink::Off => true, //If the terminal is controlling it, check terminal mode - TerminalBlink::TerminalControlled | TerminalBlink::Always => self.blink_state, + TerminalBlink::TerminalControlled | TerminalBlink::On => self.blink_state, } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8b574f8597..628020d3bc 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -297,11 +297,8 @@ impl TerminalBuilder { let mut term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); //Start off blinking if we need to - match blink_settings { - None | Some(TerminalBlink::TerminalControlled) | Some(TerminalBlink::Always) => { - term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) - } - _ => {} + if let Some(TerminalBlink::On) = blink_settings { + term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) } let term = Arc::new(FairMutex::new(term)); From d545e59b4953c210880d638981db6e634cf70a72 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 12:57:19 -0700 Subject: [PATCH 37/74] Finished IME for now --- crates/editor/src/element.rs | 4 ++++ crates/terminal/src/connected_el.rs | 20 +++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a688479047..c935df7d07 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1832,6 +1832,10 @@ impl Cursor { block_text.paint(self.origin + origin, bounds, self.line_height, cx); } } + + pub fn shape(&self) -> CursorShape { + self.shape + } } #[derive(Debug)] diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 230c802503..f21727af66 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -1,5 +1,5 @@ use alacritty_terminal::{ - ansi::{Color as AnsiColor, Color::Named, NamedColor}, + ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, grid::{Dimensions, Scroll}, index::{Column as GridCol, Line as GridLine, Point, Side}, selection::SelectionRange, @@ -647,7 +647,7 @@ impl Element for TerminalEl { //Layout cursor. Rectangle is used for IME, so we should lay it out even //if we don't end up showing it. - let cursor = if let alacritty_terminal::ansi::CursorShape::Hidden = cursor.shape { + let cursor = if let AlacCursorShape::Hidden = cursor.shape { None } else { let cursor_point = DisplayCursor::from(cursor.point, display_offset); @@ -677,15 +677,13 @@ impl Element for TerminalEl { TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( move |(cursor_position, block_width)| { let shape = match cursor.shape { - alacritty_terminal::ansi::CursorShape::Block if !self.focused => { - CursorShape::Hollow - } - alacritty_terminal::ansi::CursorShape::Block => CursorShape::Block, - alacritty_terminal::ansi::CursorShape::Underline => CursorShape::Underscore, - alacritty_terminal::ansi::CursorShape::Beam => CursorShape::Bar, - alacritty_terminal::ansi::CursorShape::HollowBlock => CursorShape::Hollow, - //This case is handled in the wrapping if - alacritty_terminal::ansi::CursorShape::Hidden => CursorShape::Block, + AlacCursorShape::Block if !self.focused => CursorShape::Hollow, + AlacCursorShape::Block => CursorShape::Block, + AlacCursorShape::Underline => CursorShape::Underscore, + AlacCursorShape::Beam => CursorShape::Bar, + AlacCursorShape::HollowBlock => CursorShape::Hollow, + //This case is handled in the if wrapping the whole cursor layout + AlacCursorShape::Hidden => unreachable!(), }; Cursor::new( From aa98e5c3f7c08087d8742473909dcc8807a96ce1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 13:22:48 -0700 Subject: [PATCH 38/74] Fixed scrolling on text input --- crates/terminal/README.md | 15 ++++++++-- crates/terminal/src/connected_view.rs | 42 ++++++++++++++------------- crates/terminal/src/terminal.rs | 33 ++++++++++----------- 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/crates/terminal/README.md b/crates/terminal/README.md index 99ab583ebb..cdfdaffe85 100644 --- a/crates/terminal/README.md +++ b/crates/terminal/README.md @@ -4,6 +4,17 @@ This crate is split into two conceptual halves: - The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here. - Everything else. These other files integrate the `Terminal` struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file. -Terminals are created externally, and so can fail in unexpected ways However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split `Terminal` instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context. -The TerminalView struct abstracts over failed and successful terminals, and provides a standardized way of instantiating an always-successful view of a terminal. +ttys are created externally, and so can fail in unexpected ways. However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split tty instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context. + +The TerminalView struct abstracts over failed and successful terminals, passing focus through to the associated view and allowing clients to build a terminal without worrying about errors. + +#Input + +There are currently 3 distinct paths for getting keystrokes to the terminal: + +1. Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the `try_keystroke()` method on Terminal + +2. GPU Action handlers. GPUI clobbers a few vital keys by adding bindings to them in the global context. These keys are synthesized and then dispatched through the same `try_keystroke()` API as the above mappings + +3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`. diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index d4bbe7e0b5..5b521748bb 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -149,9 +149,9 @@ impl ConnectedView { { cx.show_character_palette(); } else { - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap()) + }); } } @@ -258,41 +258,41 @@ impl ConnectedView { ///Synthesize the keyboard event corresponding to 'up' fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("up").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("up").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'down' fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("down").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("down").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'ctrl-c' fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("ctrl-c").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'escape' fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("escape").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("escape").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'enter' fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("enter").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("enter").unwrap()) + }); } } @@ -358,8 +358,10 @@ impl View for ConnectedView { text: &str, cx: &mut ViewContext, ) { - self.terminal - .update(cx, |terminal, _| terminal.write_to_pty(text.into())); + self.terminal.update(cx, |terminal, _| { + terminal.write_to_pty(text.into()); + terminal.scroll(alacritty_terminal::grid::Scroll::Bottom); + }); } fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 628020d3bc..f550688edc 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -467,14 +467,14 @@ impl Terminal { AlacTermEvent::ClipboardStore(_, data) => { cx.write_to_clipboard(ClipboardItem::new(data.to_string())) } - AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format( + AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty(format( &cx.read_from_clipboard() .map(|ci| ci.text().to_string()) .unwrap_or_else(|| "".to_string()), )), - AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()), + AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()), AlacTermEvent::TextAreaSizeRequest(format) => { - self.notify_pty(format(self.cur_size.into())) + self.write_to_pty(format(self.cur_size.into())) } AlacTermEvent::CursorBlinkingChange => { cx.emit(Event::BlinkChanged); @@ -517,7 +517,7 @@ impl Terminal { let term_style = &cx.global::().theme.terminal; to_alac_rgb(get_color_at_index(index, &term_style.colors)) }); - self.notify_pty(format(color)) + self.write_to_pty(format(color)) } } InternalEvent::Resize(new_size) => { @@ -528,7 +528,7 @@ impl Terminal { term.resize(*new_size); } InternalEvent::Clear => { - self.notify_pty("\x0c".to_string()); + self.write_to_pty("\x0c".to_string()); term.clear_screen(ClearMode::Saved); } InternalEvent::Scroll(scroll) => term.scroll_display(*scroll), @@ -548,12 +548,8 @@ impl Terminal { } } - pub fn notify_pty(&self, txt: String) { - self.pty_tx.notify(txt.into_bytes()); - } - ///Write the Input payload to the tty. - pub fn write_to_pty(&mut self, input: String) { + pub fn write_to_pty(&self, input: String) { self.pty_tx.notify(input.into_bytes()); } @@ -566,10 +562,11 @@ impl Terminal { self.events.push(InternalEvent::Clear) } - pub fn try_keystroke(&self, keystroke: &Keystroke) -> bool { + pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { let esc = to_esc_str(keystroke, &self.last_mode); if let Some(esc) = esc { - self.notify_pty(esc); + self.write_to_pty(esc); + self.scroll(Scroll::Bottom); true } else { false @@ -579,11 +576,11 @@ impl Terminal { ///Paste text into the terminal pub fn paste(&self, text: &str) { if self.last_mode.contains(TermMode::BRACKETED_PASTE) { - self.notify_pty("\x1b[200~".to_string()); - self.notify_pty(text.replace('\x1b', "")); - self.notify_pty("\x1b[201~".to_string()); + self.write_to_pty("\x1b[200~".to_string()); + self.write_to_pty(text.replace('\x1b', "")); + self.write_to_pty("\x1b[201~".to_string()); } else { - self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r")); + self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r")); } } @@ -619,13 +616,13 @@ impl Terminal { pub fn focus_in(&self) { if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { - self.notify_pty("\x1b[I".to_string()); + self.write_to_pty("\x1b[I".to_string()); } } pub fn focus_out(&self) { if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { - self.notify_pty("\x1b[O".to_string()); + self.write_to_pty("\x1b[O".to_string()); } } From 2c4ea7f01d97752f13a5ae6816ee772a98548a59 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 13:24:00 -0700 Subject: [PATCH 39/74] Remove todo --- crates/terminal/src/terminal.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f550688edc..5254ffbe7f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -19,8 +19,6 @@ use alacritty_terminal::{ }; use anyhow::{bail, Result}; -//When you type a key, scroll does not happen in terminal !!!TODO - use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, FutureExt, From a0d0c84eee7cee4f2eac2ee7196d47cc95e450c1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 19:16:42 -0700 Subject: [PATCH 40/74] Begin mouse mode --- crates/terminal/src/terminal.rs | 69 +++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5254ffbe7f..bca67919ae 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -607,11 +607,6 @@ impl Terminal { f(content, cursor_text) } - ///Scroll the terminal - pub fn scroll(&mut self, scroll: Scroll) { - self.events.push(InternalEvent::Scroll(scroll)); - } - pub fn focus_in(&self) { if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { self.write_to_pty("\x1b[I".to_string()); @@ -624,34 +619,60 @@ impl Terminal { } } + ///Scroll the terminal + pub fn scroll(&mut self, scroll: Scroll) { + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } + + self.events.push(InternalEvent::Scroll(scroll)); + } + pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { - let selection_type = match clicks { - 0 => return, //This is a release - 1 => Some(SelectionType::Simple), - 2 => Some(SelectionType::Semantic), - 3 => Some(SelectionType::Lines), - _ => None, - }; + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } else { + let selection_type = match clicks { + 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)); - self.events.push(InternalEvent::SetSelection(selection)); + self.events.push(InternalEvent::SetSelection(selection)); + } + } + + pub fn mouse_move(&mut self, point: Point, side: Direction, clicks: usize) { + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } } pub fn drag(&mut self, point: Point, side: Direction) { - self.events - .push(InternalEvent::UpdateSelection((point, side))); + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } else { + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } } - ///TODO: Check if the mouse_down-then-click assumption holds, so this code works as expected pub fn mouse_down(&mut self, point: Point, side: Direction) { - self.events - .push(InternalEvent::SetSelection(Some(Selection::new( - SelectionType::Simple, - point, - side, - )))); + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } else { + self.events + .push(InternalEvent::SetSelection(Some(Selection::new( + SelectionType::Simple, + point, + side, + )))); + } } } From b61e9a940ea560b0d99da3737e53ec086cf7de6d Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Thu, 18 Aug 2022 11:46:11 -0400 Subject: [PATCH 41/74] Avoid triggering goto-definition links while with a pending selection Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 12 ++--- crates/editor/src/element.rs | 50 +++++++++++++---- crates/editor/src/link_go_to_definition.rs | 63 +++++++++++++--------- 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2fd3c06538..9e5a6329bf 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1789,15 +1789,15 @@ impl Editor { cx.notify(); } - pub fn are_selections_empty(&self) -> bool { - let pending_empty = match self.selections.pending_anchor() { - Some(Selection { start, end, .. }) => start == end, - None => true, + pub fn has_pending_nonempty_selection(&self) -> bool { + let pending_nonempty_selection = match self.selections.pending_anchor() { + Some(Selection { start, end, .. }) => start != end, + None => false, }; - pending_empty && self.columnar_selection_tail.is_none() + pending_nonempty_selection || self.columnar_selection_tail.is_some() } - pub fn is_selecting(&self) -> bool { + pub fn has_pending_selection(&self) -> bool { self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some() } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6140731579..0c899cd421 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -179,14 +179,14 @@ impl EditorElement { cx: &mut EventContext, ) -> bool { let view = self.view(cx.app.as_ref()); - let end_selection = view.is_selecting(); - let selections_empty = view.are_selections_empty(); + let end_selection = view.has_pending_selection(); + let pending_nonempty_selections = view.has_pending_nonempty_selection(); if end_selection { cx.dispatch_action(Select(SelectPhase::End)); } - if selections_empty && cmd && paint.text_bounds.contains_point(position) { + if !pending_nonempty_selections && cmd && paint.text_bounds.contains_point(position) { let (point, target_point) = paint.point_for_position(&self.snapshot(cx), layout, position); @@ -206,14 +206,38 @@ impl EditorElement { fn mouse_dragged( &self, - position: Vector2F, + MouseMovedEvent { + cmd, + shift, + position, + .. + }: MouseMovedEvent, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, ) -> bool { - let view = self.view(cx.app.as_ref()); + // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed + // Don't trigger hover popover if mouse is hovering over context menu + let point = if paint.text_bounds.contains_point(position) { + let (point, target_point) = + paint.point_for_position(&self.snapshot(cx), layout, position); + if point == target_point { + Some(point) + } else { + None + } + } else { + None + }; - if view.is_selecting() { + cx.dispatch_action(UpdateGoToDefinitionLink { + point, + cmd_held: cmd, + shift_held: shift, + }); + + let view = self.view(cx.app); + if view.has_pending_selection() { let rect = paint.text_bounds; let mut scroll_delta = Vector2F::zero(); @@ -250,8 +274,11 @@ impl EditorElement { scroll_position: (snapshot.scroll_position() + scroll_delta) .clamp(Vector2F::zero(), layout.scroll_max), })); + + cx.dispatch_action(HoverAt { point }); true } else { + cx.dispatch_action(HoverAt { point }); false } } @@ -1572,11 +1599,12 @@ impl Element for EditorElement { .. }) => self.mouse_up(position, cmd, shift, layout, paint, cx), - Event::MouseMoved(MouseMovedEvent { - pressed_button: Some(MouseButton::Left), - position, - .. - }) => self.mouse_dragged(*position, layout, paint, cx), + Event::MouseMoved( + event @ MouseMovedEvent { + pressed_button: Some(MouseButton::Left), + .. + }, + ) => self.mouse_dragged(*event, layout, paint, cx), Event::ScrollWheel(ScrollWheelEvent { position, diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 6eb38df758..79b2e950cb 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -70,6 +70,8 @@ pub fn update_go_to_definition_link( }: &UpdateGoToDefinitionLink, cx: &mut ViewContext, ) { + let pending_nonempty_selection = editor.has_pending_nonempty_selection(); + // Store new mouse point as an anchor let snapshot = editor.snapshot(cx); let point = point.map(|point| { @@ -89,6 +91,12 @@ pub fn update_go_to_definition_link( } editor.link_go_to_definition_state.last_mouse_location = point.clone(); + + if pending_nonempty_selection { + hide_link_definition(editor, cx); + return; + } + if cmd_held { if let Some(point) = point { let kind = if shift_held { @@ -113,12 +121,14 @@ pub fn cmd_shift_changed( }: &CmdShiftChanged, cx: &mut ViewContext, ) { + let pending_nonempty_selection = editor.has_pending_nonempty_selection(); + if let Some(point) = editor .link_go_to_definition_state .last_mouse_location .clone() { - if cmd_down { + if cmd_down && !pending_nonempty_selection { let snapshot = editor.snapshot(cx); let kind = if shift_down { LinkDefinitionKind::Type @@ -127,10 +137,11 @@ pub fn cmd_shift_changed( }; show_link_definition(kind, editor, point, snapshot, cx); - } else { - hide_link_definition(editor, cx) + return; } } + + hide_link_definition(editor, cx) } #[derive(Debug, Clone, Copy, PartialEq)] @@ -243,28 +254,32 @@ pub fn show_link_definition( this.link_go_to_definition_state.definitions = definitions.clone(); let buffer_snapshot = buffer.read(cx).snapshot(); + // Only show highlight if there exists a definition to jump to that doesn't contain // the current location. - if definitions.iter().any(|definition| { - let target = &definition.target; - if target.buffer == buffer { - let range = &target.range; - // Expand range by one character as lsp definition ranges include positions adjacent - // but not contained by the symbol range - let start = buffer_snapshot.clip_offset( - range.start.to_offset(&buffer_snapshot).saturating_sub(1), - Bias::Left, - ); - let end = buffer_snapshot.clip_offset( - range.end.to_offset(&buffer_snapshot) + 1, - Bias::Right, - ); - let offset = buffer_position.to_offset(&buffer_snapshot); - !(start <= offset && end >= offset) - } else { - true - } - }) { + let any_definition_does_not_contain_current_location = + definitions.iter().any(|definition| { + let target = &definition.target; + if target.buffer == buffer { + let range = &target.range; + // Expand range by one character as lsp definition ranges include positions adjacent + // but not contained by the symbol range + let start = buffer_snapshot.clip_offset( + range.start.to_offset(&buffer_snapshot).saturating_sub(1), + Bias::Left, + ); + let end = buffer_snapshot.clip_offset( + range.end.to_offset(&buffer_snapshot) + 1, + Bias::Right, + ); + let offset = buffer_position.to_offset(&buffer_snapshot); + !(start <= offset && end >= offset) + } else { + true + } + }); + + if any_definition_does_not_contain_current_location { // If no symbol range returned from language server, use the surrounding word. let highlight_range = symbol_range.unwrap_or_else(|| { let snapshot = &snapshot.buffer_snapshot; @@ -280,7 +295,7 @@ pub fn show_link_definition( vec![highlight_range], style, cx, - ) + ); } else { hide_link_definition(this, cx); } From b8b951deabaa3c02c96b0d60833abb944cca7001 Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Wed, 17 Aug 2022 18:11:54 -0400 Subject: [PATCH 42/74] Clear last-mouse-moved pressed button when that button gets a mouse-up This fixes an annoying issue where if the last mouse moved event was during a drag it would never trigger mouse cursor changes until next mouse move reset it. It makes sense to continue to not change the cursor while the button is pressed so instead this tracks when the mouse button is released in order to update the mouse move event --- crates/gpui/src/platform/event.rs | 2 +- crates/gpui/src/presenter.rs | 96 +++++++++++++++++-------------- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 6ac75926be..9a84b8ef2f 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -64,7 +64,7 @@ impl Default for MouseButton { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct MouseButtonEvent { pub button: MouseButton, pub position: Vector2F, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 023e013d13..3e6c97a208 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -31,7 +31,7 @@ pub struct Presenter { font_cache: Arc, text_layout_cache: TextLayoutCache, asset_cache: Arc, - last_mouse_moved_event: Option, + last_mouse_moved_event: Option, hovered_region_ids: HashSet, clicked_region: Option, right_clicked_region: Option, @@ -268,15 +268,27 @@ impl Presenter { } } } - Event::MouseUp(e @ MouseButtonEvent { position, .. }) => { + + &Event::MouseUp( + e @ MouseButtonEvent { + position, button, .. + }, + ) => { self.prev_drag_position.take(); if let Some(region) = self.clicked_region.take() { invalidated_views.push(region.view_id); - if region.bounds.contains_point(*position) { + if region.bounds.contains_point(position) { clicked_region = Some((region, MouseRegionEvent::Click(e.clone()))); } } + + if let Some(moved) = &mut self.last_mouse_moved_event { + if moved.pressed_button == Some(button) { + moved.pressed_button = None; + } + } } + Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => { if let Some((clicked_region, prev_drag_position)) = self .clicked_region @@ -290,13 +302,17 @@ impl Presenter { *prev_drag_position = *position; } - self.last_mouse_moved_event = Some(event.clone()); + self.last_mouse_moved_event = Some(e.clone()); } + _ => {} } - let (mut handled, mut event_cx) = - self.handle_hover_events(&event, &mut invalidated_views, cx); + let (mut handled, mut event_cx) = if let Event::MouseMoved(e) = &event { + self.handle_hover_events(e, &mut invalidated_views, cx) + } else { + (false, self.build_event_context(cx)) + }; for (handler, view_id, region_event) in mouse_down_out_handlers { event_cx.with_current_view(view_id, |event_cx| handler(region_event, event_cx)) @@ -353,51 +369,45 @@ impl Presenter { fn handle_hover_events<'a>( &'a mut self, - event: &Event, + e @ MouseMovedEvent { + position, + pressed_button, + .. + }: &MouseMovedEvent, invalidated_views: &mut Vec, cx: &'a mut MutableAppContext, ) -> (bool, EventContext<'a>) { let mut hover_regions = Vec::new(); - if let Event::MouseMoved( - e @ MouseMovedEvent { - position, - pressed_button, - .. - }, - ) = event - { - if pressed_button.is_none() { - 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); - let mut hover_depth = None; - for (region, depth) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(*position) - && hover_depth.map_or(true, |hover_depth| hover_depth == *depth) - { - hover_depth = Some(*depth); - if let Some(region_id) = region.id() { - if !self.hovered_region_ids.contains(®ion_id) { - invalidated_views.push(region.view_id); - hover_regions - .push((region.clone(), MouseRegionEvent::Hover(true, *e))); - self.hovered_region_ids.insert(region_id); - } - } - } else if let Some(region_id) = region.id() { - if self.hovered_region_ids.contains(®ion_id) { + if pressed_button.is_none() { + 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); + + let mut hover_depth = None; + for (region, depth) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(*position) + && hover_depth.map_or(true, |hover_depth| hover_depth == *depth) + { + hover_depth = Some(*depth); + if let Some(region_id) = region.id() { + if !self.hovered_region_ids.contains(®ion_id) { invalidated_views.push(region.view_id); - hover_regions - .push((region.clone(), MouseRegionEvent::Hover(false, *e))); - self.hovered_region_ids.remove(®ion_id); + hover_regions.push((region.clone(), MouseRegionEvent::Hover(true, *e))); + self.hovered_region_ids.insert(region_id); } } + } else if let Some(region_id) = region.id() { + if self.hovered_region_ids.contains(®ion_id) { + invalidated_views.push(region.view_id); + hover_regions.push((region.clone(), MouseRegionEvent::Hover(false, *e))); + self.hovered_region_ids.remove(®ion_id); + } } } } From 8583320e9bc33fbf0f093c704e9adf82acdd9aff Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Thu, 18 Aug 2022 18:33:37 -0400 Subject: [PATCH 43/74] Add test for pending selection influence on go-to links Co-authored-by: Max Brunsfeld --- crates/editor/src/link_go_to_definition.rs | 54 ++++++++++++++++++++++ crates/editor/src/test.rs | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 79b2e950cb..6af985171a 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -821,5 +821,59 @@ mod tests { fn test() { do_work(); } fn «do_workˇ»() { test(); } "}); + + // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens + // 2. Selection is completed, hovering + let hover_point = cx.display_point(indoc! {" + fn test() { do_wˇork(); } + fn do_work() { test(); } + "}); + let target_range = cx.lsp_range(indoc! {" + fn test() { do_work(); } + fn «do_work»() { test(); } + "}); + let mut requests = cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: None, + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); + + // create a pending selection + let selection_range = cx.ranges(indoc! {" + fn «test() { do_w»ork(); } + fn do_work() { test(); } + "})[0] + .clone(); + cx.update_editor(|editor, cx| { + let snapshot = editor.buffer().read(cx).snapshot(cx); + let anchor_range = snapshot.anchor_before(selection_range.start) + ..snapshot.anchor_after(selection_range.end); + editor.change_selections(Some(crate::Autoscroll::Fit), cx, |s| { + s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) + }); + }); + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + &UpdateGoToDefinitionLink { + point: Some(hover_point), + cmd_held: true, + shift_held: false, + }, + cx, + ); + }); + cx.foreground().run_until_parked(); + assert!(requests.try_next().is_err()); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + cx.foreground().run_until_parked(); } } diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 5f6528db1c..43e50829f5 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -183,7 +183,7 @@ impl<'a> EditorTestContext<'a> { } } - fn ranges(&self, marked_text: &str) -> Vec> { + pub fn ranges(&self, marked_text: &str) -> Vec> { let (unmarked_text, ranges) = marked_text_ranges(marked_text, false); assert_eq!(self.buffer_text(), unmarked_text); ranges From 37ca7a66589851872831e3edd79eda9d6d301817 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 17:37:24 -0700 Subject: [PATCH 44/74] Half way done with mouse reporting --- crates/gpui/src/presenter.rs | 21 +++ crates/gpui/src/scene/mouse_region.rs | 26 +++ crates/terminal/src/connected_el.rs | 181 +++++++++++++------ crates/terminal/src/mappings/keys.rs | 1 + crates/terminal/src/mappings/mod.rs | 1 + crates/terminal/src/mappings/mouse.rs | 248 ++++++++++++++++++++++++++ crates/terminal/src/terminal.rs | 116 +++++------- styles/package-lock.json | 1 - 8 files changed, 468 insertions(+), 127 deletions(-) create mode 100644 crates/terminal/src/mappings/mouse.rs diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 023e013d13..8e5a303086 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -235,6 +235,7 @@ impl Presenter { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut invalidated_views = Vec::new(); let mut mouse_down_out_handlers = Vec::new(); + let mut mouse_moved_region = None; let mut mouse_down_region = None; let mut clicked_region = None; let mut dragged_region = None; @@ -290,6 +291,15 @@ impl Presenter { *prev_drag_position = *position; } + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(*position) { + invalidated_views.push(region.view_id); + mouse_moved_region = + Some((region.clone(), MouseRegionEvent::Move(e.clone()))); + break; + } + } + self.last_mouse_moved_event = Some(event.clone()); } _ => {} @@ -313,6 +323,17 @@ impl Presenter { } } + if let Some((move_moved_region, region_event)) = mouse_moved_region { + handled = true; + if let Some(mouse_moved_callback) = + move_moved_region.handlers.get(®ion_event.handler_key()) + { + event_cx.with_current_view(move_moved_region.view_id, |event_cx| { + mouse_moved_callback(region_event, event_cx); + }) + } + } + if let Some((clicked_region, region_event)) = clicked_region { handled = true; if let Some(click_callback) = diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 704567450a..ba7173b4a1 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -1,6 +1,7 @@ use std::{any::TypeId, mem::Discriminant, rc::Rc}; use collections::HashMap; + use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; @@ -97,6 +98,14 @@ impl MouseRegion { self.handlers = self.handlers.on_hover(handler); self } + + pub fn on_move( + mut self, + handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move(handler); + self + } } #[derive(Copy, Clone, Eq, PartialEq, Hash)] @@ -267,6 +276,23 @@ impl HandlerSet { })); self } + + pub fn on_move( + mut self, + handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::move_disc(), None), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Move(move_event)= region_event { + handler(move_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}", + region_event); + } + })); + self + } } #[derive(Debug)] diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index f21727af66..48f58762b0 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -1,9 +1,12 @@ use alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, grid::{Dimensions, Scroll}, - index::{Column as GridCol, Line as GridLine, Point, Side}, + index::{Column as GridCol, Direction, Line as GridLine, Point, Side}, selection::SelectionRange, - term::cell::{Cell, Flags}, + term::{ + cell::{Cell, Flags}, + TermMode, + }, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ @@ -16,8 +19,9 @@ use gpui::{ }, json::json, text_layout::{Line, RunStyle}, - Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, - PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle, + Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent, + MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, + WeakViewHandle, }; use itertools::Itertools; use ordered_float::OrderedFloat; @@ -413,6 +417,32 @@ impl TerminalEl { } } + fn generic_button_handler( + connection: WeakModelHandle, + origin: Vector2F, + cur_size: TerminalSize, + display_offset: usize, + f: impl Fn(&mut Terminal, Point, Direction, MouseButtonEvent, &mut ModelContext), + ) -> impl Fn(MouseButtonEvent, &mut EventContext) { + move |event, cx| { + cx.focus_parent_view(); + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + event.position, + origin, + cur_size, + display_offset, + ); + + f(terminal, point, side, event, cx); + + cx.notify(); + }) + } + } + } + fn attach_mouse_handlers( &self, origin: Vector2F, @@ -422,77 +452,116 @@ impl TerminalEl { display_offset: usize, cx: &mut PaintContext, ) { - let mouse_down_connection = self.terminal; - let click_connection = self.terminal; - let drag_connection = self.terminal; + let connection = self.terminal; cx.scene.push_mouse_region( MouseRegion::new(view_id, None, visible_bounds) - .on_down( - MouseButton::Left, - move |MouseButtonEvent { position, .. }, cx| { - if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) { + .on_move(move |event, cx| { + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { let (point, side) = TerminalEl::mouse_to_cell_data( - position, + event.position, origin, cur_size, display_offset, ); - terminal.mouse_down(point, side); + terminal.mouse_move(point, side, &event); cx.notify(); }) } - }, + } + }) + .on_down( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, _e, _cx| { + terminal.mouse_down(point, side); + }, + ), ) + .on_down( + MouseButton::Right, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, _e, _cx| { + terminal.mouse_down(point, side); + }, + ), + ) + .on_down( + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, _e, _cx| { + terminal.mouse_down(point, side); + }, + ), + ) + //TODO .on_click( MouseButton::Left, - move |MouseButtonEvent { - position, - click_count, - .. - }, - cx| { - cx.focus_parent_view(); - if let Some(conn_handle) = click_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); - - terminal.click(point, side, click_count); - - cx.notify(); - }); - } - }, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, e, _cx| { + terminal.click(point, side, e.click_count); + }, + ), + ) + .on_click( + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, e, _cx| { + terminal.click(point, side, e.click_count); + }, + ), ) .on_click( MouseButton::Right, - move |MouseButtonEvent { position, .. }, cx| { - cx.dispatch_action(DeployContextMenu { position }); - }, - ) - .on_drag( - MouseButton::Left, - move |_, MouseMovedEvent { position, .. }, cx| { - if let Some(conn_handle) = drag_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); + move |e @ MouseButtonEvent { position, .. }, cx| { + //Attempt to check the mode + if let Some(conn_handle) = connection.upgrade(cx.app) { + let handled = conn_handle.update(cx.app, |terminal, _cx| { + //Finally, we can check the mode! + if terminal.last_mode.intersects(TermMode::MOUSE_MODE) { + let (point, side) = TerminalEl::mouse_to_cell_data( + position, + origin, + cur_size, + display_offset, + ); - terminal.drag(point, side); - - cx.notify() + terminal.click(point, side, e.click_count); + true + } else { + false + } }); + + //If I put this up by the true, then we're in the wrong 'cx' + if !handled { + cx.dispatch_action(DeployContextMenu { position }); + } + } else { + cx.dispatch_action(DeployContextMenu { position }); } }, ), @@ -811,12 +880,12 @@ impl Element for TerminalEl { }) => visible_bounds .contains_point(*position) .then(|| { - let vertical_scroll = + let scroll_lines = (delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER; if let Some(terminal) = self.terminal.upgrade(cx.app) { terminal.update(cx.app, |term, _| { - term.scroll(Scroll::Delta(vertical_scroll.round() as i32)) + term.scroll(Scroll::Delta(scroll_lines.round() as i32)) }); } diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 002759d78d..da730e6296 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -1,3 +1,4 @@ +/// The mappings defined in this file where created from reading the alacritty source use alacritty_terminal::term::TermMode; use gpui::keymap::Keystroke; diff --git a/crates/terminal/src/mappings/mod.rs b/crates/terminal/src/mappings/mod.rs index cde6c337ea..d58dd27f96 100644 --- a/crates/terminal/src/mappings/mod.rs +++ b/crates/terminal/src/mappings/mod.rs @@ -1,2 +1,3 @@ pub mod colors; pub mod keys; +pub mod mouse; diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs new file mode 100644 index 0000000000..dec67b68f7 --- /dev/null +++ b/crates/terminal/src/mappings/mouse.rs @@ -0,0 +1,248 @@ +/// Most of the code, and specifically the constants, in this are copied from Alacritty, +/// with modifications for our circumstances +use alacritty_terminal::{index::Point, term::TermMode}; +use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; + +pub struct Modifiers { + ctrl: bool, + shift: bool, + alt: bool, +} + +impl Modifiers { + pub fn from_moved(e: &MouseMovedEvent) -> Self { + Modifiers { + ctrl: e.ctrl, + shift: e.shift, + alt: e.alt, + } + } + + pub fn from_button(e: &MouseButtonEvent) -> Self { + Modifiers { + ctrl: e.ctrl, + shift: e.shift, + alt: e.alt, + } + } + + //TODO: Determine if I should add modifiers into the ScrollWheelEvent type + pub fn from_scroll() -> Self { + Modifiers { + ctrl: false, + shift: false, + alt: false, + } + } +} + +pub enum MouseFormat { + SGR, + Normal(bool), +} + +impl MouseFormat { + pub fn from_mode(mode: TermMode) -> Self { + if mode.contains(TermMode::SGR_MOUSE) { + MouseFormat::SGR + } else if mode.contains(TermMode::UTF8_MOUSE) { + MouseFormat::Normal(true) + } else { + MouseFormat::Normal(false) + } + } +} + +pub enum MouseButton { + LeftButton = 0, + MiddleButton = 1, + RightButton = 2, + LeftMove = 32, + MiddleMove = 33, + RightMove = 34, + NoneMove = 35, + ScrollUp = 64, + ScrollDown = 65, + Other = 99, +} + +impl MouseButton { + pub fn from_move(e: &MouseMovedEvent) -> Self { + match e.pressed_button { + Some(b) => match b { + gpui::MouseButton::Left => MouseButton::LeftMove, + gpui::MouseButton::Middle => MouseButton::MiddleMove, + gpui::MouseButton::Right => MouseButton::RightMove, + gpui::MouseButton::Navigate(_) => MouseButton::Other, + }, + None => MouseButton::NoneMove, + } + } + + pub fn from_button(e: &MouseButtonEvent) -> Self { + match e.button { + gpui::MouseButton::Left => MouseButton::LeftButton, + gpui::MouseButton::Right => MouseButton::MiddleButton, + gpui::MouseButton::Middle => MouseButton::RightButton, + gpui::MouseButton::Navigate(_) => MouseButton::Other, + } + } + + pub fn from_scroll(e: &ScrollWheelEvent) -> Self { + if e.delta.y() > 0. { + MouseButton::ScrollUp + } else { + MouseButton::ScrollDown + } + } + + pub fn is_other(&self) -> bool { + match self { + MouseButton::Other => true, + _ => false, + } + } +} + +pub fn scroll_report( + point: Point, + scroll_lines: i32, + e: &ScrollWheelEvent, + mode: TermMode, +) -> Option>> { + if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { + if let Some(report) = mouse_report( + point, + MouseButton::from_scroll(e), + true, + Modifiers::from_scroll(), + MouseFormat::from_mode(mode), + ) { + let mut res = vec![]; + for _ in 0..scroll_lines.abs() { + res.push(report.clone()); + } + return Some(res); + } + } + + None +} + +pub fn mouse_button_report( + point: Point, + e: &MouseButtonEvent, + pressed: bool, + mode: TermMode, +) -> Option> { + let button = MouseButton::from_button(e); + if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) { + mouse_report( + point, + button, + pressed, + Modifiers::from_button(e), + MouseFormat::from_mode(mode), + ) + } else { + None + } +} + +pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option> { + let button = MouseButton::from_move(e); + if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { + mouse_report( + point, + button, + true, + Modifiers::from_moved(e), + MouseFormat::from_mode(mode), + ) + } else { + None + } +} + +///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode +fn mouse_report( + point: Point, + button: MouseButton, + pressed: bool, + modifiers: Modifiers, + format: MouseFormat, +) -> Option> { + if point.line < 0 { + return None; + } + + let mut mods = 0; + if modifiers.shift { + mods += 4; + } + if modifiers.alt { + mods += 8; + } + if modifiers.ctrl { + mods += 16; + } + + match format { + MouseFormat::SGR => { + Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes()) + } + MouseFormat::Normal(utf8) => { + if pressed { + normal_mouse_report(point, button as u8 + mods, utf8) + } else { + normal_mouse_report(point, 3 + mods, utf8) + } + } + } +} + +fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option> { + let Point { line, column } = point; + let max_point = if utf8 { 2015 } else { 223 }; + + if line >= max_point || column >= max_point { + return None; + } + + let mut msg = vec![b'\x1b', b'[', b'M', 32 + button]; + + let mouse_pos_encode = |pos: usize| -> Vec { + let pos = 32 + 1 + pos; + let first = 0xC0 + pos / 64; + let second = 0x80 + (pos & 63); + vec![first as u8, second as u8] + }; + + if utf8 && column >= 95 { + msg.append(&mut mouse_pos_encode(column.0)); + } else { + msg.push(32 + 1 + column.0 as u8); + } + + if utf8 && line >= 95 { + msg.append(&mut mouse_pos_encode(line.0 as usize)); + } else { + msg.push(32 + 1 + line.0 as u8); + } + + Some(msg) +} + +fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String { + let c = if pressed { 'M' } else { 'm' }; + + let msg = format!( + "\x1b[<{};{};{}{}", + button, + point.column + 1, + point.line + 1, + c + ); + + msg +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index bca67919ae..1522795195 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -24,6 +24,7 @@ use futures::{ FutureExt, }; +use mappings::mouse::mouse_moved_report; use modal::deploy_modal; use settings::{Settings, Shell, TerminalBlink}; use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; @@ -32,7 +33,7 @@ use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - ClipboardItem, Entity, ModelContext, MutableAppContext, + ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext, }; use crate::mappings::{ @@ -49,11 +50,9 @@ pub fn init(cx: &mut MutableAppContext) { } const DEBUG_TERMINAL_WIDTH: f32 = 500.; -const DEBUG_TERMINAL_HEIGHT: f32 = 30.; //This needs to be wide enough that the CI & a local dev's prompt can fill the whole space. +const DEBUG_TERMINAL_HEIGHT: f32 = 30.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; -// const MAX_FRAME_RATE: f32 = 60.; -// const BACK_BUFFER_SIZE: usize = 5000; ///Upward flowing events, for changing the title and such #[derive(Clone, Copy, Debug)] @@ -348,7 +347,7 @@ impl TerminalBuilder { default_title: shell_txt, last_mode: TermMode::NONE, cur_size: initial_size, - // utilization: 0., + last_mouse: None, }; Ok(TerminalBuilder { @@ -406,27 +405,6 @@ impl TerminalBuilder { }) .detach(); - // //Render loop - // cx.spawn_weak(|this, mut cx| async move { - // loop { - // let utilization = match this.upgrade(&cx) { - // Some(this) => this.update(&mut cx, |this, cx| { - // cx.notify(); - // this.utilization() - // }), - // None => break, - // }; - - // let utilization = (1. - utilization).clamp(0.1, 1.); - // let delay = cx.background().timer(Duration::from_secs_f32( - // 1.0 / (Terminal::default_fps() * utilization), - // )); - - // delay.await; - // } - // }) - // .detach(); - self.terminal } } @@ -439,19 +417,10 @@ pub struct Terminal { title: String, cur_size: TerminalSize, last_mode: TermMode, - //Percentage, between 0 and 1 - // utilization: f32, + last_mouse: Option<(Point, Direction)>, } impl Terminal { - // fn default_fps() -> f32 { - // MAX_FRAME_RATE - // } - - // fn utilization(&self) -> f32 { - // self.utilization - // } - fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext) { match event { AlacTermEvent::Title(title) => { @@ -494,12 +463,6 @@ impl Terminal { } } - // fn process_events(&mut self, events: Vec, cx: &mut ModelContext) { - // for event in events.into_iter() { - // self.process_event(&event, cx); - // } - // } - ///Takes events from Alacritty and translates them to behavior on this view fn process_terminal_event( &mut self, @@ -507,7 +470,6 @@ impl Terminal { term: &mut Term, cx: &mut ModelContext, ) { - // TODO: Handle is_self_focused in subscription on terminal view match event { InternalEvent::TermEvent(term_event) => { if let AlacTermEvent::ColorRequest(index, format) = term_event { @@ -619,13 +581,46 @@ impl Terminal { } } - ///Scroll the terminal - pub fn scroll(&mut self, scroll: Scroll) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE + pub fn mouse_changed(&mut self, point: Point, side: Direction) -> bool { + match self.last_mouse { + Some((old_point, old_side)) => { + if old_point == point && old_side == side { + false + } else { + self.last_mouse = Some((point, side)); + true + } + } + None => { + self.last_mouse = Some((point, side)); + true + } } + } - self.events.push(InternalEvent::Scroll(scroll)); + /// Handle a mouse move, this is mutually exclusive with drag. + pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { + if self.mouse_changed(point, side) { + if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { + self.pty_tx.notify(bytes); + } + } else if matches!(e.pressed_button, Some(gpui::MouseButton::Left)) { + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } + } + + pub fn mouse_down(&mut self, point: Point, side: Direction) { + if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { + //TODE: MOUSE MODE + } else { + self.events + .push(InternalEvent::SetSelection(Some(Selection::new( + SelectionType::Simple, + point, + side, + )))); + } } pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { @@ -647,32 +642,13 @@ impl Terminal { } } - pub fn mouse_move(&mut self, point: Point, side: Direction, clicks: usize) { + ///Scroll the terminal + pub fn scroll(&mut self, scroll: Scroll) { if self.last_mode.intersects(TermMode::MOUSE_MODE) { //TODE: MOUSE MODE } - } - pub fn drag(&mut self, point: Point, side: Direction) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } else { - self.events - .push(InternalEvent::UpdateSelection((point, side))); - } - } - - pub fn mouse_down(&mut self, point: Point, side: Direction) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } else { - self.events - .push(InternalEvent::SetSelection(Some(Selection::new( - SelectionType::Simple, - point, - side, - )))); - } + self.events.push(InternalEvent::Scroll(scroll)); } } diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c8496..5499f1852c 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From d15ba8efb4261d481f0cf383bd4f078efde1d827 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Aug 2022 09:17:43 -0700 Subject: [PATCH 45/74] Revert "Merge pull request #1481 from zed-industries/trigger-search-automatically" This reverts commit ae8dd1e3fd0f2ab6d38a1387479cb7806264a7e7, reversing changes made to 96d5dcadf1981c135be3788b92d18b598c9a2cb0. --- crates/search/src/project_search.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 0b30a0bd36..6566722ce5 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -450,7 +450,6 @@ impl ProjectSearchView { search.update(cx, |search, cx| { if let Some(query) = query { search.set_query(&query, cx); - search.search(cx); } search.focus_query_editor(cx) }); From 6e67448420bb2366ee4706d2b28aad857af61a41 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 Aug 2022 09:20:26 -0700 Subject: [PATCH 46/74] 0.52.0 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b08fb287ea..680e40a7f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6981,7 +6981,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.51.1" +version = "0.52.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ffb0945b25..aa31130d1e 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.51.1" +version = "0.52.0" [lib] name = "zed" From 04600d73fc85ec4ebede797aa09b90ea7d7e940d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 09:50:26 -0700 Subject: [PATCH 47/74] Moved back to the drag handler, selections work again --- crates/terminal/README.md | 5 +++- crates/terminal/src/connected_el.rs | 18 +++++++++++++- crates/terminal/src/connected_view.rs | 6 ++--- crates/terminal/src/terminal.rs | 34 ++++++++++++++++----------- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/crates/terminal/README.md b/crates/terminal/README.md index cdfdaffe85..272212a538 100644 --- a/crates/terminal/README.md +++ b/crates/terminal/README.md @@ -10,7 +10,7 @@ The TerminalView struct abstracts over failed and successful terminals, passing #Input -There are currently 3 distinct paths for getting keystrokes to the terminal: +There are currently many distinct paths for getting keystrokes to the terminal: 1. Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the `try_keystroke()` method on Terminal @@ -18,3 +18,6 @@ There are currently 3 distinct paths for getting keystrokes to the terminal: 3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`. +4. Pasted text has a seperate pathway. + +Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the `.input()` API (which try_keystroke uses) so we have consistent input handling across the terminal \ No newline at end of file diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 48f58762b0..020c71f421 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -473,6 +473,22 @@ impl TerminalEl { } } }) + .on_drag(MouseButton::Left, move |_prev, event, cx| { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + event.position, + origin, + cur_size, + display_offset, + ); + + terminal.mouse_drag(point, side); + + cx.notify(); + }) + } + }) .on_down( MouseButton::Left, TerminalEl::generic_button_handler( @@ -696,7 +712,7 @@ impl Element for TerminalEl { ( cells, - content.selection, + dbg!(content.selection), content.cursor, content.display_offset, cursor_text, diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 5b521748bb..703a54ba91 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -251,7 +251,8 @@ impl ConnectedView { ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { if let Some(item) = cx.read_from_clipboard() { - self.terminal.read(cx).paste(item.text()); + self.terminal + .update(cx, |terminal, _cx| terminal.paste(item.text())); } } @@ -359,8 +360,7 @@ impl View for ConnectedView { cx: &mut ViewContext, ) { self.terminal.update(cx, |terminal, _| { - terminal.write_to_pty(text.into()); - terminal.scroll(alacritty_terminal::grid::Scroll::Bottom); + terminal.input(text.into()); }); } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 1522795195..37bf85e78f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -508,8 +508,14 @@ impl Terminal { } } + pub fn input(&mut self, input: String) { + self.scroll(Scroll::Bottom); + self.events.push(InternalEvent::SetSelection(None)); + self.write_to_pty(input); + } + ///Write the Input payload to the tty. - pub fn write_to_pty(&self, input: String) { + fn write_to_pty(&self, input: String) { self.pty_tx.notify(input.into_bytes()); } @@ -525,8 +531,7 @@ impl Terminal { pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { let esc = to_esc_str(keystroke, &self.last_mode); if let Some(esc) = esc { - self.write_to_pty(esc); - self.scroll(Scroll::Bottom); + self.input(esc); true } else { false @@ -534,14 +539,13 @@ impl Terminal { } ///Paste text into the terminal - pub fn paste(&self, text: &str) { - if self.last_mode.contains(TermMode::BRACKETED_PASTE) { - self.write_to_pty("\x1b[200~".to_string()); - self.write_to_pty(text.replace('\x1b', "")); - self.write_to_pty("\x1b[201~".to_string()); + pub fn paste(&mut self, text: &str) { + let paste_text = if self.last_mode.contains(TermMode::BRACKETED_PASTE) { + format!("{}{}{}", "\x1b[200~", text.replace('\x1b', ""), "\x1b[201~") } else { - self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r")); - } + text.replace("\r\n", "\r").replace('\n', "\r") + }; + self.input(paste_text) } pub fn copy(&mut self) { @@ -598,18 +602,20 @@ impl Terminal { } } - /// Handle a mouse move, this is mutually exclusive with drag. + /// Handle a mouse move pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { if self.mouse_changed(point, side) { if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { self.pty_tx.notify(bytes); } - } else if matches!(e.pressed_button, Some(gpui::MouseButton::Left)) { - self.events - .push(InternalEvent::UpdateSelection((point, side))); } } + pub fn mouse_drag(&mut self, point: Point, side: Direction) { + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } + pub fn mouse_down(&mut self, point: Point, side: Direction) { if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { //TODE: MOUSE MODE From a806634b822f914969e49424ad6c29875d7dc3b7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 11:41:17 -0700 Subject: [PATCH 48/74] Handlers attached, things are looking good 2 go --- crates/terminal/src/connected_el.rs | 271 +++++++------------------- crates/terminal/src/mappings/mouse.rs | 120 +++++++++--- crates/terminal/src/terminal.rs | 129 +++++++++--- 3 files changed, 270 insertions(+), 250 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 020c71f421..e241cb5822 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -1,26 +1,22 @@ use alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, - grid::{Dimensions, Scroll}, - index::{Column as GridCol, Direction, Line as GridLine, Point, Side}, + grid::Dimensions, + index::Point, selection::SelectionRange, - term::{ - cell::{Cell, Flags}, - TermMode, - }, + term::cell::{Cell, Flags}, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, - elements::*, fonts::{Properties, Style::Italic, TextStyle, Underline, Weight}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json::json, + serde_json::json, text_layout::{Line, RunStyle}, - Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent, - MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, + Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, + MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle, WeakViewHandle, }; use itertools::Itertools; @@ -29,12 +25,11 @@ use settings::Settings; use theme::TerminalStyle; use util::ResultExt; +use std::fmt::Debug; use std::{ - cmp::min, mem, ops::{Deref, Range}, }; -use std::{fmt::Debug, ops::Sub}; use crate::{ connected_view::{ConnectedView, DeployContextMenu}, @@ -42,11 +37,6 @@ use crate::{ Terminal, TerminalSize, }; -///Scrolling is unbearably sluggish by default. Alacritty supports a configurable -///Scroll multiplier that is set to 3 by default. This will be removed when I -///Implement scroll bars. -pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; - ///The information generated during layout that is nescessary for painting pub struct LayoutState { cells: Vec, @@ -56,7 +46,6 @@ pub struct LayoutState { background_color: Color, selection_color: Color, size: TerminalSize, - display_offset: usize, } #[derive(Debug)] @@ -420,22 +409,13 @@ impl TerminalEl { fn generic_button_handler( connection: WeakModelHandle, origin: Vector2F, - cur_size: TerminalSize, - display_offset: usize, - f: impl Fn(&mut Terminal, Point, Direction, MouseButtonEvent, &mut ModelContext), + f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext), ) -> impl Fn(MouseButtonEvent, &mut EventContext) { move |event, cx| { cx.focus_parent_view(); if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - event.position, - origin, - cur_size, - display_offset, - ); - - f(terminal, point, side, event, cx); + f(terminal, origin, event, cx); cx.notify(); }) @@ -448,8 +428,6 @@ impl TerminalEl { origin: Vector2F, view_id: usize, visible_bounds: RectF, - cur_size: TerminalSize, - display_offset: usize, cx: &mut PaintContext, ) { let connection = self.terminal; @@ -459,34 +437,20 @@ impl TerminalEl { if cx.is_parent_view_focused() { if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - event.position, - origin, - cur_size, - display_offset, - ); - - terminal.mouse_move(point, side, &event); - + terminal.mouse_move(&event, origin); cx.notify(); }) } } }) .on_drag(MouseButton::Left, move |_prev, event, cx| { - if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - event.position, - origin, - cur_size, - display_offset, - ); - - terminal.mouse_drag(point, side); - - cx.notify(); - }) + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + terminal.mouse_drag(event, origin); + cx.notify(); + }) + } } }) .on_down( @@ -494,10 +458,8 @@ impl TerminalEl { TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, _e, _cx| { - terminal.mouse_down(point, side); + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); }, ), ) @@ -506,10 +468,8 @@ impl TerminalEl { TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, _e, _cx| { - terminal.mouse_down(point, side); + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); }, ), ) @@ -518,65 +478,61 @@ impl TerminalEl { TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, _e, _cx| { - terminal.mouse_down(point, side); + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ) + .on_up( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + .on_up( + MouseButton::Right, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + .on_up( + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); }, ), ) - //TODO .on_click( MouseButton::Left, TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, e, _cx| { - terminal.click(point, side, e.click_count); - }, - ), - ) - .on_click( - MouseButton::Middle, - TerminalEl::generic_button_handler( - connection, - origin, - cur_size, - display_offset, - move |terminal, point, side, e, _cx| { - terminal.click(point, side, e.click_count); + move |terminal, origin, e, _cx| { + terminal.left_click(&e, origin); }, ), ) .on_click( MouseButton::Right, move |e @ MouseButtonEvent { position, .. }, cx| { - //Attempt to check the mode - if let Some(conn_handle) = connection.upgrade(cx.app) { - let handled = conn_handle.update(cx.app, |terminal, _cx| { - //Finally, we can check the mode! - if terminal.last_mode.intersects(TermMode::MOUSE_MODE) { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); - - terminal.click(point, side, e.click_count); - true - } else { - false - } - }); - - //If I put this up by the true, then we're in the wrong 'cx' - if !handled { - cx.dispatch_action(DeployContextMenu { position }); - } + let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) } else { + //If we can't get the model handle, probably can't deploy the context menu + true + }; + if !mouse_mode { cx.dispatch_action(DeployContextMenu { position }); } }, @@ -615,47 +571,6 @@ impl TerminalEl { underline: Default::default(), } } - - pub fn mouse_to_cell_data( - pos: Vector2F, - origin: Vector2F, - cur_size: TerminalSize, - display_offset: usize, - ) -> (Point, alacritty_terminal::index::Direction) { - let pos = pos.sub(origin); - let point = { - let col = pos.x() / cur_size.cell_width; //TODO: underflow... - let col = min(GridCol(col as usize), cur_size.last_column()); - - let line = pos.y() / cur_size.line_height; - let line = min(line as i32, cur_size.bottommost_line().0); - - Point::new(GridLine(line - display_offset as i32), col) - }; - - //Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() - let side = { - let x = pos.0.x() as usize; - let cell_x = - x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize; - let half_cell_width = (cur_size.cell_width / 2.0) as usize; - - let additional_padding = - (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; - let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; - //Width: Pixels or columns? - if cell_x > half_cell_width - // Edge case when mouse leaves the window. - || x as f32 >= end_of_grid - { - Side::Right - } else { - Side::Left - } - }; - - (point, side) - } } impl Element for TerminalEl { @@ -712,7 +627,7 @@ impl Element for TerminalEl { ( cells, - dbg!(content.selection), + content.selection, content.cursor, content.display_offset, cursor_text, @@ -794,7 +709,6 @@ impl Element for TerminalEl { size: dimensions, rects, highlights, - display_offset, }, ) } @@ -813,14 +727,7 @@ impl Element for TerminalEl { let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse - self.attach_mouse_handlers( - origin, - self.view.id(), - visible_bounds, - layout.size, - layout.display_offset, - cx, - ); + self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, cx); cx.paint_layer(clip_bounds, |cx| { //Start with a background color @@ -884,28 +791,22 @@ impl Element for TerminalEl { fn dispatch_event( &mut self, event: &gpui::Event, - _bounds: gpui::geometry::rect::RectF, + bounds: gpui::geometry::rect::RectF, visible_bounds: gpui::geometry::rect::RectF, layout: &mut Self::LayoutState, _paint: &mut Self::PaintState, cx: &mut gpui::EventContext, ) -> bool { match event { - Event::ScrollWheel(ScrollWheelEvent { - delta, position, .. - }) => visible_bounds - .contains_point(*position) + Event::ScrollWheel(e) => visible_bounds + .contains_point(e.position) .then(|| { - let scroll_lines = - (delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER; + let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); if let Some(terminal) = self.terminal.upgrade(cx.app) { - terminal.update(cx.app, |term, _| { - term.scroll(Scroll::Delta(scroll_lines.round() as i32)) - }); + terminal.update(cx.app, |term, _| term.scroll(e, origin)); + cx.notify(); } - - cx.notify(); }) .is_some(), Event::KeyDown(KeyDownEvent { keystroke, .. }) => { @@ -913,7 +814,6 @@ impl Element for TerminalEl { return false; } - //TODO Talk to keith about how to catch events emitted from an element. if let Some(view) = self.view.upgrade(cx.app) { view.update(cx.app, |view, cx| { view.clear_bel(cx); @@ -969,36 +869,3 @@ impl Element for TerminalEl { Some(layout.cursor.as_ref()?.bounding_rect(origin)) } } - -mod test { - - #[test] - fn test_mouse_to_selection() { - let term_width = 100.; - let term_height = 200.; - let cell_width = 10.; - let line_height = 20.; - let mouse_pos_x = 100.; //Window relative - let mouse_pos_y = 100.; //Window relative - let origin_x = 10.; - let origin_y = 20.; - - let cur_size = crate::connected_el::TerminalSize::new( - line_height, - cell_width, - gpui::geometry::vector::vec2f(term_width, term_height), - ); - - let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); - let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in - let (point, _) = - crate::connected_el::TerminalEl::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); - assert_eq!( - point, - alacritty_terminal::index::Point::new( - alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), - alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), - ) - ); - } -} diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index dec67b68f7..7c524ed2f9 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -1,16 +1,23 @@ +use std::cmp::min; +use std::iter::repeat; + +use alacritty_terminal::grid::Dimensions; /// Most of the code, and specifically the constants, in this are copied from Alacritty, /// with modifications for our circumstances -use alacritty_terminal::{index::Point, term::TermMode}; -use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; +use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side}; +use alacritty_terminal::term::TermMode; +use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; -pub struct Modifiers { +use crate::TerminalSize; + +struct Modifiers { ctrl: bool, shift: bool, alt: bool, } impl Modifiers { - pub fn from_moved(e: &MouseMovedEvent) -> Self { + fn from_moved(e: &MouseMovedEvent) -> Self { Modifiers { ctrl: e.ctrl, shift: e.shift, @@ -18,7 +25,7 @@ impl Modifiers { } } - pub fn from_button(e: &MouseButtonEvent) -> Self { + fn from_button(e: &MouseButtonEvent) -> Self { Modifiers { ctrl: e.ctrl, shift: e.shift, @@ -27,7 +34,7 @@ impl Modifiers { } //TODO: Determine if I should add modifiers into the ScrollWheelEvent type - pub fn from_scroll() -> Self { + fn from_scroll() -> Self { Modifiers { ctrl: false, shift: false, @@ -36,13 +43,13 @@ impl Modifiers { } } -pub enum MouseFormat { +enum MouseFormat { SGR, Normal(bool), } impl MouseFormat { - pub fn from_mode(mode: TermMode) -> Self { + fn from_mode(mode: TermMode) -> Self { if mode.contains(TermMode::SGR_MOUSE) { MouseFormat::SGR } else if mode.contains(TermMode::UTF8_MOUSE) { @@ -53,7 +60,7 @@ impl MouseFormat { } } -pub enum MouseButton { +enum MouseButton { LeftButton = 0, MiddleButton = 1, RightButton = 2, @@ -67,7 +74,7 @@ pub enum MouseButton { } impl MouseButton { - pub fn from_move(e: &MouseMovedEvent) -> Self { + fn from_move(e: &MouseMovedEvent) -> Self { match e.pressed_button { Some(b) => match b { gpui::MouseButton::Left => MouseButton::LeftMove, @@ -79,7 +86,7 @@ impl MouseButton { } } - pub fn from_button(e: &MouseButtonEvent) -> Self { + fn from_button(e: &MouseButtonEvent) -> Self { match e.button { gpui::MouseButton::Left => MouseButton::LeftButton, gpui::MouseButton::Right => MouseButton::MiddleButton, @@ -88,7 +95,7 @@ impl MouseButton { } } - pub fn from_scroll(e: &ScrollWheelEvent) -> Self { + fn from_scroll(e: &ScrollWheelEvent) -> Self { if e.delta.y() > 0. { MouseButton::ScrollUp } else { @@ -96,7 +103,7 @@ impl MouseButton { } } - pub fn is_other(&self) -> bool { + fn is_other(&self) -> bool { match self { MouseButton::Other => true, _ => false, @@ -109,24 +116,31 @@ pub fn scroll_report( scroll_lines: i32, e: &ScrollWheelEvent, mode: TermMode, -) -> Option>> { +) -> Option>> { if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { - if let Some(report) = mouse_report( + mouse_report( point, MouseButton::from_scroll(e), true, Modifiers::from_scroll(), MouseFormat::from_mode(mode), - ) { - let mut res = vec![]; - for _ in 0..scroll_lines.abs() { - res.push(report.clone()); - } - return Some(res); - } + ) + .map(|report| repeat(report).take(scroll_lines as usize)) + } else { + None } +} - None +pub fn alt_scroll(scroll_lines: i32) -> Vec { + let cmd = if scroll_lines > 0 { b'A' } else { b'B' }; + + let mut content = Vec::with_capacity(scroll_lines as usize * 3); + for _ in 0..scroll_lines { + content.push(0x1b); + content.push(b'O'); + content.push(cmd); + } + content } pub fn mouse_button_report( @@ -164,6 +178,31 @@ pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> } } +pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::index::Direction { + let x = pos.0.x() as usize; + let cell_x = x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize; + let half_cell_width = (cur_size.cell_width / 2.0) as usize; + let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; + let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; + //Width: Pixels or columns? + if cell_x > half_cell_width + // Edge case when mouse leaves the window. + || x as f32 >= end_of_grid + { + Side::Right + } else { + Side::Left + } +} + +pub fn mouse_point(pos: Vector2F, cur_size: TerminalSize, display_offset: usize) -> Point { + let col = pos.x() / cur_size.cell_width; + let col = min(GridCol(col as usize), cur_size.last_column()); + let line = pos.y() / cur_size.line_height; + let line = min(line as i32, cur_size.bottommost_line().0); + Point::new(GridLine(line - display_offset as i32), col) +} + ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode fn mouse_report( point: Point, @@ -246,3 +285,38 @@ fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String { msg } + +#[cfg(test)] +mod test { + use crate::mappings::mouse::mouse_point; + + #[test] + fn test_mouse_to_selection() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = crate::TerminalSize::new( + line_height, + cell_width, + gpui::geometry::vector::vec2f(term_width, term_height), + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let mouse_pos = mouse_pos - origin; + let point = mouse_point(mouse_pos, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 37bf85e78f..ff313478c5 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -24,16 +24,19 @@ use futures::{ FutureExt, }; -use mappings::mouse::mouse_moved_report; +use mappings::mouse::{ + alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report, +}; use modal::deploy_modal; use settings::{Settings, Shell, TerminalBlink}; -use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; +use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration}; use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext, + ClipboardItem, Entity, ModelContext, MouseButtonEvent, MouseMovedEvent, MutableAppContext, + ScrollWheelEvent, }; use crate::mappings::{ @@ -49,6 +52,11 @@ pub fn init(cx: &mut MutableAppContext) { connected_view::init(cx); } +///Scrolling is unbearably sluggish by default. Alacritty supports a configurable +///Scroll multiplier that is set to 3 by default. This will be removed when I +///Implement scroll bars. +pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; + const DEBUG_TERMINAL_WIDTH: f32 = 500.; const DEBUG_TERMINAL_HEIGHT: f32 = 30.; const DEBUG_CELL_WIDTH: f32 = 5.; @@ -348,6 +356,7 @@ impl TerminalBuilder { last_mode: TermMode::NONE, cur_size: initial_size, last_mouse: None, + last_offset: 0, }; Ok(TerminalBuilder { @@ -417,6 +426,7 @@ pub struct Terminal { title: String, cur_size: TerminalSize, last_mode: TermMode, + last_offset: usize, last_mouse: Option<(Point, Direction)>, } @@ -509,7 +519,7 @@ impl Terminal { } pub fn input(&mut self, input: String) { - self.scroll(Scroll::Bottom); + self.events.push(InternalEvent::Scroll(Scroll::Bottom)); self.events.push(InternalEvent::SetSelection(None)); self.write_to_pty(input); } @@ -563,11 +573,12 @@ impl Terminal { self.process_terminal_event(&e, &mut term, cx) } - // self.utilization = Self::estimate_utilization(term.take_last_processed_bytes()); self.last_mode = *term.mode(); let content = term.renderable_content(); + self.last_offset = content.display_offset; + let cursor_text = term.grid()[content.cursor.point].c; f(content, cursor_text) @@ -602,23 +613,45 @@ impl Terminal { } } - /// Handle a mouse move - pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { - if self.mouse_changed(point, side) { + pub fn mouse_mode(&self, shift: bool) -> bool { + self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift + } + + pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) { + let position = e.position.sub(origin); + + let point = mouse_point(position, self.cur_size, self.last_offset); + let side = mouse_side(position, self.cur_size); + + if self.mouse_changed(point, side) && self.mouse_mode(e.shift) { if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { self.pty_tx.notify(bytes); } } } - pub fn mouse_drag(&mut self, point: Point, side: Direction) { - self.events - .push(InternalEvent::UpdateSelection((point, side))); + pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F) { + let position = e.position.sub(origin); + + if !self.mouse_mode(e.shift) { + let point = mouse_point(position, self.cur_size, self.last_offset); + let side = mouse_side(position, self.cur_size); + + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } } - pub fn mouse_down(&mut self, point: Point, side: Direction) { - if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { - //TODE: MOUSE MODE + pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) { + let position = e.position.sub(origin); + + let point = mouse_point(position, self.cur_size, self.last_offset); + let side = mouse_side(position, self.cur_size); + + if self.mouse_mode(e.shift) { + if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) { + self.pty_tx.notify(bytes); + } } else { self.events .push(InternalEvent::SetSelection(Some(Selection::new( @@ -629,11 +662,15 @@ impl Terminal { } } - pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } else { - let selection_type = match clicks { + pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) { + let position = e.position.sub(origin); + //TODO: Alt-click cursor position + + if !self.mouse_mode(e.shift) { + let point = mouse_point(position, self.cur_size, self.last_offset); + let side = mouse_side(position, self.cur_size); + + let selection_type = match e.click_count { 0 => return, //This is a release 1 => Some(SelectionType::Simple), 2 => Some(SelectionType::Semantic), @@ -648,13 +685,55 @@ impl Terminal { } } - ///Scroll the terminal - pub fn scroll(&mut self, scroll: Scroll) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } + pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) { + let position = e.position.sub(origin); - self.events.push(InternalEvent::Scroll(scroll)); + if self.mouse_mode(e.shift) { + let point = mouse_point(position, self.cur_size, self.last_offset); + + if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) { + self.pty_tx.notify(bytes); + } + } else { + // Seems pretty standard to automatically copy on mouse_up for terminals, + // so let's do that here + self.copy(); + } + } + + ///Scroll the terminal + pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) { + if self.mouse_mode(false) { + //TODO: Currently this only sends the current scroll reports as they come in. Alacritty + //Sends the *entire* scroll delta on *every* scroll event, only resetting it when + //The scroll enters 'TouchPhase::Started'. Do I need to replicate this? + //This would be consistent with a scroll model based on 'distance from origin'... + let scroll_lines = (scroll.delta.y() / self.cur_size.line_height) as i32; + let point = mouse_point(scroll.position.sub(origin), self.cur_size, self.last_offset); + + if let Some(scrolls) = scroll_report(point, scroll_lines as i32, scroll, self.last_mode) + { + for scroll in scrolls { + self.pty_tx.notify(scroll); + } + }; + } else if self + .last_mode + .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) + { + //TODO: See above TODO, also applies here. + let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) + / self.cur_size.line_height) as i32; + + self.pty_tx.notify(alt_scroll(scroll_lines)) + } else { + let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) + / self.cur_size.line_height) as i32; + if scroll_lines != 0 { + let scroll = Scroll::Delta(scroll_lines); + self.events.push(InternalEvent::Scroll(scroll)); + } + } } } From efd3247ce4b79386e6bc06e7dcae93754d7fe875 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 12:10:12 -0700 Subject: [PATCH 49/74] Added modifiers to scroll wheel eevent --- crates/editor/src/element.rs | 1 + crates/gpui/src/elements/flex.rs | 1 + crates/gpui/src/elements/list.rs | 1 + crates/gpui/src/elements/uniform_list.rs | 1 + crates/gpui/src/platform/event.rs | 4 ++++ crates/gpui/src/platform/mac/event.rs | 6 ++++++ crates/terminal/src/terminal.rs | 3 ++- 7 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c935df7d07..1881d14842 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1582,6 +1582,7 @@ impl Element for EditorElement { position, delta, precise, + .. }) => self.scroll(*position, *delta, *precise, layout, paint, cx), &Event::ModifiersChanged(event) => self.modifiers_changed(event, cx), diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 1d577344c6..798fb3e8c0 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -293,6 +293,7 @@ impl Element for Flex { position, delta, precise, + .. }) = event { if *remaining_space < 0. && bounds.contains_point(position) { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e0ca47b598..d3c15c4e2b 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -316,6 +316,7 @@ impl Element for List { position, delta, precise, + .. }) = event { if bounds.contains_point(*position) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index bf9a0a4e30..103cb00d8c 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -315,6 +315,7 @@ impl Element for UniformList { position, delta, precise, + .. }) = event { if bounds.contains_point(*position) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 6ac75926be..010fc9a733 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -24,6 +24,10 @@ pub struct ScrollWheelEvent { pub position: Vector2F, pub delta: Vector2F, pub precise: bool, + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, } #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index a53e009589..c6f838b431 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -148,6 +148,8 @@ impl Event { }) } NSEventType::NSScrollWheel => window_height.map(|window_height| { + let modifiers = native_event.modifierFlags(); + Self::ScrollWheel(ScrollWheelEvent { position: vec2f( native_event.locationInWindow().x as f32, @@ -158,6 +160,10 @@ impl Event { native_event.scrollingDeltaY() as f32, ), precise: native_event.hasPreciseScrollingDeltas() == YES, + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), }) }), NSEventType::NSLeftMouseDragged diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ff313478c5..ed77b00707 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -703,7 +703,7 @@ impl Terminal { ///Scroll the terminal pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) { - if self.mouse_mode(false) { + if self.mouse_mode(scroll.shift) { //TODO: Currently this only sends the current scroll reports as they come in. Alacritty //Sends the *entire* scroll delta on *every* scroll event, only resetting it when //The scroll enters 'TouchPhase::Started'. Do I need to replicate this? @@ -720,6 +720,7 @@ impl Terminal { } else if self .last_mode .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) + && !scroll.shift { //TODO: See above TODO, also applies here. let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) From a279f83cd643063408d957f043b931318ea57355 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 13:03:17 -0700 Subject: [PATCH 50/74] Finished majority of mouse work. Need to add a setting and change up mouse handlers, and it'll be done. --- crates/terminal/src/terminal.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ed77b00707..14e368b29d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -664,7 +664,6 @@ impl Terminal { pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) { let position = e.position.sub(origin); - //TODO: Alt-click cursor position if !self.mouse_mode(e.shift) { let point = mouse_point(position, self.cur_size, self.last_offset); From 55be05b05c0e9606cfcd82ed850f390c38f33baf Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Fri, 19 Aug 2022 11:28:03 -0400 Subject: [PATCH 51/74] Toggle window zoom/maximize when double clicking titlebar --- crates/workspace/src/workspace.rs | 56 ++++++++++++++++++------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1abf6a54fc..eb697b92cc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1870,33 +1870,41 @@ impl Workspace { }; ConstrainedBox::new( - Container::new( - Stack::new() - .with_child( - Label::new(worktree_root_names, theme.workspace.titlebar.title.clone()) - .aligned() - .left() - .boxed(), - ) - .with_child( - Align::new( - Flex::row() - .with_children(self.render_collaborators(theme, cx)) - .with_children(self.render_current_user( - self.user_store.read(cx).current_user().as_ref(), - replica_id, - theme, - cx, - )) - .with_children(self.render_connection_status(cx)) + MouseEventHandler::new::(0, cx, |_, cx| { + Container::new( + Stack::new() + .with_child( + Label::new(worktree_root_names, theme.workspace.titlebar.title.clone()) + .aligned() + .left() .boxed(), ) - .right() + .with_child( + Align::new( + Flex::row() + .with_children(self.render_collaborators(theme, cx)) + .with_children(self.render_current_user( + self.user_store.read(cx).current_user().as_ref(), + replica_id, + theme, + cx, + )) + .with_children(self.render_connection_status(cx)) + .boxed(), + ) + .right() + .boxed(), + ) .boxed(), - ) - .boxed(), - ) - .with_style(container_theme) + ) + .with_style(container_theme) + .boxed() + }) + .on_click(MouseButton::Left, |event, cx| { + if event.click_count == 2 { + cx.zoom_window(cx.window_id()); + } + }) .boxed(), ) .with_height(theme.workspace.titlebar.height) From cfbda00cc4283f44b36ea78dacf2f3070a1d9968 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 13:54:25 -0700 Subject: [PATCH 52/74] Finished mouse compatability --- assets/settings/default.json | 62 ++++++++++++++++----------- crates/settings/src/settings.rs | 14 ++++++ crates/terminal/src/mappings/mouse.rs | 4 +- crates/terminal/src/terminal.rs | 11 ++++- crates/terminal/src/terminal_view.rs | 16 ++++++- 5 files changed, 77 insertions(+), 30 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 739b34d743..8ea515e7a9 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,27 +102,37 @@ // // "working_directory": "current_project_directory", - //Set the cursor blinking behavior in the terminal. - //May take 4 values: - // 1. Never blink the cursor, ignoring the terminal mode - // "blinking": "off", - // 2. Default the cursor blink to off, but allow the terminal to - // set blinking - // "blinking": "terminal_controlled", - // 3. Always blink the cursor, ignoring the terminal mode - // "blinking": "on", + // Set the cursor blinking behavior in the terminal. + // May take 4 values: + // 1. Never blink the cursor, ignoring the terminal mode + // "blinking": "off", + // 2. Default the cursor blink to off, but allow the terminal to + // set blinking + // "blinking": "terminal_controlled", + // 3. Always blink the cursor, ignoring the terminal mode + // "blinking": "on", "blinking": "terminal_controlled", - //Any key-value pairs added to this list will be added to the terminal's - //enviroment. Use `:` to seperate multiple values. + // Set whether Alternate Scroll mode (code: ?1007) is active by default. + // Alternate Scroll mode converts mouse scroll events into up / down key + // presses when in the alternate screen (e.g. when running applications + // like vim or less). The terminal can still set and unset this mode. + // May take 2 values: + // 1. Default alternate scroll mode to on + // "alternate_scroll": "on", + // 2. Default alternate scroll mode to off + // "alternate_scroll": "off", + "alternate_scroll": "off", + // Any key-value pairs added to this list will be added to the terminal's + // enviroment. Use `:` to seperate multiple values. "env": { - //"KEY": "value1:value2" + // "KEY": "value1:value2" } - //Set the terminal's font size. If this option is not included, - //the terminal will default to matching the buffer's font size. - //"font_size": "15" - //Set the terminal's font family. If this option is not included, - //the terminal will default to matching the buffer's font family. - //"font_family": "Zed Mono" + // Set the terminal's font size. If this option is not included, + // the terminal will default to matching the buffer's font size. + // "font_size": "15" + // Set the terminal's font family. If this option is not included, + // the terminal will default to matching the buffer's font family. + // "font_family": "Zed Mono" }, // Different settings for specific languages. "languages": { @@ -155,15 +165,15 @@ "tab_size": 2 } }, - //LSP Specific settings. + // LSP Specific settings. "lsp": { - //Specify the LSP name as a key here. - //As of 8/10/22, supported LSPs are: - //pyright - //gopls - //rust-analyzer - //typescript-language-server - //vscode-json-languageserver + // Specify the LSP name as a key here. + // As of 8/10/22, supported LSPs are: + // pyright + // gopls + // rust-analyzer + // typescript-language-server + // vscode-json-languageserver // "rust_analyzer": { // //These initialization options are merged into Zed's defaults // "initialization_options": { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9defb6f410..2b97cb0b10 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -84,6 +84,7 @@ pub struct TerminalSettings { pub font_family: Option, pub env: Option>, pub blinking: Option, + pub alternate_scroll: Option, } #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] @@ -114,6 +115,19 @@ impl Default for Shell { } } +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AlternateScroll { + On, + Off, +} + +impl Default for AlternateScroll { + fn default() -> Self { + AlternateScroll::On + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectory { diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 7c524ed2f9..9cd1768b47 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -134,8 +134,8 @@ pub fn scroll_report( pub fn alt_scroll(scroll_lines: i32) -> Vec { let cmd = if scroll_lines > 0 { b'A' } else { b'B' }; - let mut content = Vec::with_capacity(scroll_lines as usize * 3); - for _ in 0..scroll_lines { + let mut content = Vec::with_capacity(scroll_lines.abs() as usize * 3); + for _ in 0..scroll_lines.abs() { content.push(0x1b); content.push(b'O'); content.push(cmd); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 14e368b29d..a3aa0f0601 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -28,7 +28,7 @@ use mappings::mouse::{ alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report, }; use modal::deploy_modal; -use settings::{Settings, Shell, TerminalBlink}; +use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration}; use thiserror::Error; @@ -263,6 +263,7 @@ impl TerminalBuilder { env: Option>, initial_size: TerminalSize, blink_settings: Option, + alternate_scroll: &AlternateScroll, ) -> Result { let pty_config = { let alac_shell = shell.clone().and_then(|shell| match shell { @@ -306,6 +307,14 @@ impl TerminalBuilder { term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) } + //Start alternate_scroll if we need to + if let AlternateScroll::On = alternate_scroll { + term.set_mode(alacritty_terminal::ansi::Mode::AlternateScroll) + } else { + //Alacritty turns it on by default, so we need to turn it off. + term.unset_mode(alacritty_terminal::ansi::Mode::AlternateScroll) + } + let term = Arc::new(FairMutex::new(term)); //Setup the pty... diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index a997ebf631..8041eafb9c 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -10,7 +10,7 @@ use workspace::{Item, Workspace}; use crate::TerminalSize; use project::{LocalWorktree, Project, ProjectPath}; -use settings::{Settings, WorkingDirectory}; +use settings::{AlternateScroll, Settings, WorkingDirectory}; use smallvec::SmallVec; use std::path::{Path, PathBuf}; @@ -94,12 +94,26 @@ impl TerminalView { let shell = settings.terminal_overrides.shell.clone(); let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap. + //TODO: move this pattern to settings + let scroll = settings + .terminal_overrides + .alternate_scroll + .as_ref() + .unwrap_or( + settings + .terminal_defaults + .alternate_scroll + .as_ref() + .unwrap_or_else(|| &AlternateScroll::On), + ); + let content = match TerminalBuilder::new( working_directory.clone(), shell, envs, size_info, settings.terminal_overrides.blinking.clone(), + scroll, ) { Ok(terminal) => { let terminal = cx.add_model(|cx| terminal.subscribe(cx)); From 40a8c26080575a59190611567722cab9c8168953 Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Fri, 19 Aug 2022 17:25:35 -0400 Subject: [PATCH 53/74] Include empty selections in updating link logic on cmd/shift changed Prevents an issue where pressing cmd while the mouse button is down would create a link which would fire on mouse up if the selection was still empty --- crates/editor/src/link_go_to_definition.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 6af985171a..6b23a04b67 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -121,14 +121,14 @@ pub fn cmd_shift_changed( }: &CmdShiftChanged, cx: &mut ViewContext, ) { - let pending_nonempty_selection = editor.has_pending_nonempty_selection(); + 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_nonempty_selection { + if cmd_down && !pending_selection { let snapshot = editor.snapshot(cx); let kind = if shift_down { LinkDefinitionKind::Type From 1de68a724c22b144fb14d76ffb67bb32078822db Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 15:11:49 -0700 Subject: [PATCH 54/74] Debugged mouse reporting for now. Remaining bugs are GPUI level bugs --- crates/gpui/src/presenter.rs | 21 +++ crates/terminal/src/connected_el.rs | 244 ++++++++++++++------------ crates/terminal/src/mappings/mouse.rs | 28 +-- crates/terminal/src/terminal.rs | 11 +- 4 files changed, 179 insertions(+), 125 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 0075581d36..6836d157fe 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -237,6 +237,7 @@ impl Presenter { let mut mouse_down_out_handlers = Vec::new(); let mut mouse_moved_region = None; let mut mouse_down_region = None; + let mut mouse_up_region = None; let mut clicked_region = None; let mut dragged_region = None; @@ -283,6 +284,15 @@ impl Presenter { } } + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(position) { + invalidated_views.push(region.view_id); + mouse_up_region = + Some((region.clone(), MouseRegionEvent::Up(e.clone()))); + break; + } + } + if let Some(moved) = &mut self.last_mouse_moved_event { if moved.pressed_button == Some(button) { moved.pressed_button = None; @@ -350,6 +360,17 @@ impl Presenter { } } + if let Some((mouse_up_region, region_event)) = mouse_up_region { + handled = true; + if let Some(mouse_up_callback) = + mouse_up_region.handlers.get(®ion_event.handler_key()) + { + event_cx.with_current_view(mouse_up_region.view_id, |event_cx| { + mouse_up_callback(region_event, event_cx); + }) + } + } + if let Some((clicked_region, region_event)) = clicked_region { handled = true; if let Some(click_callback) = diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index e241cb5822..bc80796aae 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -3,7 +3,10 @@ use alacritty_terminal::{ grid::Dimensions, index::Point, selection::SelectionRange, - term::cell::{Cell, Flags}, + term::{ + cell::{Cell, Flags}, + TermMode, + }, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ @@ -46,6 +49,7 @@ pub struct LayoutState { background_color: Color, selection_color: Color, size: TerminalSize, + mode: TermMode, } #[derive(Debug)] @@ -428,116 +432,137 @@ impl TerminalEl { origin: Vector2F, view_id: usize, visible_bounds: RectF, + mode: TermMode, cx: &mut PaintContext, ) { let connection = self.terminal; - cx.scene.push_mouse_region( - MouseRegion::new(view_id, None, visible_bounds) - .on_move(move |event, cx| { - if cx.is_parent_view_focused() { - if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - terminal.mouse_move(&event, origin); - cx.notify(); - }) - } - } - }) - .on_drag(MouseButton::Left, move |_prev, event, cx| { - if cx.is_parent_view_focused() { - if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - terminal.mouse_drag(event, origin); - cx.notify(); - }) - } - } - }) - .on_down( - MouseButton::Left, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_down( - MouseButton::Right, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_down( - MouseButton::Middle, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_up( - MouseButton::Left, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_up(&e, origin); - }, - ), - ) - .on_up( - MouseButton::Right, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_up(&e, origin); - }, - ), - ) - .on_up( - MouseButton::Middle, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_up(&e, origin); - }, - ), - ) - .on_click( - MouseButton::Left, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.left_click(&e, origin); - }, - ), - ) - .on_click( - MouseButton::Right, - move |e @ MouseButtonEvent { position, .. }, cx| { - let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) - } else { - //If we can't get the model handle, probably can't deploy the context menu - true - }; - if !mouse_mode { - cx.dispatch_action(DeployContextMenu { position }); - } + + let mut region = MouseRegion::new(view_id, None, visible_bounds); + + //Terminal Emulator controlled behavior: + region = region + //Start selections + .on_down( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); }, ), - ); + ) + //Update drag selections + .on_drag(MouseButton::Left, move |_prev, event, cx| { + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + terminal.mouse_drag(event, origin); + cx.notify(); + }) + } + } + }) + //Copy on up behavior + .on_up( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + //Handle click based selections + .on_click( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.left_click(&e, origin); + }, + ), + ) + //Context menu + .on_click( + MouseButton::Right, + move |e @ MouseButtonEvent { position, .. }, cx| { + let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) + } else { + //If we can't get the model handle, probably can't deploy the context menu + true + }; + if !mouse_mode { + cx.dispatch_action(DeployContextMenu { position }); + } + }, + ) + //This handles both drag mode and mouse motion mode + //Mouse Move TODO + //This cannot be done conditionally for unknown reasons. Pending drag and drop rework. + //This also does not fire on right-mouse-down-move events wild. + .on_move(move |event, cx| { + dbg!(event); + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + terminal.mouse_move(&event, origin); + cx.notify(); + }) + } + } + }); + + if mode.contains(TermMode::MOUSE_MODE) { + region = region + .on_down( + MouseButton::Right, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ) + .on_down( + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ) + .on_up( + MouseButton::Right, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + .on_up( + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + } + + //TODO: Mouse drag isn't correct + //TODO: Nor is mouse motion. Move events aren't happening?? + cx.scene.push_mouse_region(region); } ///Configures a text style from the current settings. @@ -601,7 +626,7 @@ impl Element for TerminalEl { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text) = self + let (cells, selection, cursor, display_offset, cursor_text, mode) = self .terminal .upgrade(cx) .unwrap() @@ -624,13 +649,13 @@ impl Element for TerminalEl { cell: ic.cell.clone(), }), ); - ( cells, content.selection, content.cursor, content.display_offset, cursor_text, + content.mode, ) }) }); @@ -709,6 +734,7 @@ impl Element for TerminalEl { size: dimensions, rects, highlights, + mode, }, ) } @@ -727,7 +753,7 @@ impl Element for TerminalEl { let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse - self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, cx); + self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx); cx.paint_layer(clip_bounds, |cx| { //Start with a background color diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 9cd1768b47..236f954c74 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -1,4 +1,4 @@ -use std::cmp::min; +use std::cmp::{max, min}; use std::iter::repeat; use alacritty_terminal::grid::Dimensions; @@ -60,6 +60,7 @@ impl MouseFormat { } } +#[derive(Debug)] enum MouseButton { LeftButton = 0, MiddleButton = 1, @@ -117,7 +118,7 @@ pub fn scroll_report( e: &ScrollWheelEvent, mode: TermMode, ) -> Option>> { - if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { + if mode.intersects(TermMode::MOUSE_MODE) { mouse_report( point, MouseButton::from_scroll(e), @@ -125,7 +126,7 @@ pub fn scroll_report( Modifiers::from_scroll(), MouseFormat::from_mode(mode), ) - .map(|report| repeat(report).take(scroll_lines as usize)) + .map(|report| repeat(report).take(max(scroll_lines, 1) as usize)) } else { None } @@ -165,14 +166,21 @@ pub fn mouse_button_report( pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option> { let button = MouseButton::from_move(e); + dbg!(&button); + if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { - mouse_report( - point, - button, - true, - Modifiers::from_moved(e), - MouseFormat::from_mode(mode), - ) + //Only drags are reported in drag mode, so block NoneMove. + if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, MouseButton::NoneMove) { + None + } else { + mouse_report( + point, + button, + true, + Modifiers::from_moved(e), + MouseFormat::from_mode(mode), + ) + } } else { None } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a3aa0f0601..ea3774a586 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -35,8 +35,8 @@ use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - ClipboardItem, Entity, ModelContext, MouseButtonEvent, MouseMovedEvent, MutableAppContext, - ScrollWheelEvent, + ClipboardItem, Entity, ModelContext, MouseButton, MouseButtonEvent, MouseMovedEvent, + MutableAppContext, ScrollWheelEvent, }; use crate::mappings::{ @@ -627,6 +627,7 @@ impl Terminal { } pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) { + dbg!("term mouse_move"); let position = e.position.sub(origin); let point = mouse_point(position, self.cur_size, self.last_offset); @@ -653,7 +654,6 @@ impl Terminal { pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) { let position = e.position.sub(origin); - let point = mouse_point(position, self.cur_size, self.last_offset); let side = mouse_side(position, self.cur_size); @@ -661,7 +661,7 @@ impl Terminal { if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) { self.pty_tx.notify(bytes); } - } else { + } else if e.button == MouseButton::Left { self.events .push(InternalEvent::SetSelection(Some(Selection::new( SelectionType::Simple, @@ -695,14 +695,13 @@ impl Terminal { pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) { let position = e.position.sub(origin); - if self.mouse_mode(e.shift) { let point = mouse_point(position, self.cur_size, self.last_offset); if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) { self.pty_tx.notify(bytes); } - } else { + } else if e.button == MouseButton::Left { // Seems pretty standard to automatically copy on mouse_up for terminals, // so let's do that here self.copy(); From 399de91f558d2c03559db7aeb74733183210c340 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 16:27:27 -0700 Subject: [PATCH 55/74] Removed debugs, added keymap contexts to terminal --- crates/terminal/src/connected_el.rs | 1 - crates/terminal/src/connected_view.rs | 79 ++++++++++++++++++++++++++- crates/terminal/src/mappings/mouse.rs | 1 - crates/terminal/src/terminal.rs | 1 - 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index bc80796aae..7e4cbd1e82 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -505,7 +505,6 @@ impl TerminalEl { //This cannot be done conditionally for unknown reasons. Pending drag and drop rework. //This also does not fire on right-mouse-down-move events wild. .on_move(move |event, cx| { - dbg!(event); if cx.is_parent_view_focused() { if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 703a54ba91..639f5f5d8e 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -39,7 +39,7 @@ actions!( Clear, Copy, Paste, - ShowCharacterPalette + ShowCharacterPalette, ] ); impl_internal_actions!(project_panel, [DeployContextMenu]); @@ -364,11 +364,86 @@ impl View for ConnectedView { }); } - fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { + fn keymap_context(&self, cx: &gpui::AppContext) -> gpui::keymap::Context { let mut context = Self::default_keymap_context(); if self.modal { context.set.insert("ModalTerminal".into()); } + let mode = self.terminal.read(cx).last_mode; + context.map.insert( + "screen".to_string(), + (if mode.contains(TermMode::ALT_SCREEN) { + "alt" + } else { + "normal" + }) + .to_string(), + ); + + if mode.contains(TermMode::APP_CURSOR) { + context.set.insert("DECCKM".to_string()); + } + if mode.contains(TermMode::APP_KEYPAD) { + context.set.insert("DECPAM".to_string()); + } + //Note the ! here + if !mode.contains(TermMode::APP_KEYPAD) { + context.set.insert("DECPNM".to_string()); + } + if mode.contains(TermMode::SHOW_CURSOR) { + context.set.insert("DECTCEM".to_string()); + } + if mode.contains(TermMode::LINE_WRAP) { + context.set.insert("DECAWM".to_string()); + } + if mode.contains(TermMode::ORIGIN) { + context.set.insert("DECOM".to_string()); + } + if mode.contains(TermMode::INSERT) { + context.set.insert("IRM".to_string()); + } + //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html + if mode.contains(TermMode::LINE_FEED_NEW_LINE) { + context.set.insert("LNM".to_string()); + } + if mode.contains(TermMode::FOCUS_IN_OUT) { + context.set.insert("report_focus".to_string()); + } + if mode.contains(TermMode::ALTERNATE_SCROLL) { + context.set.insert("alternate_scroll".to_string()); + } + if mode.contains(TermMode::BRACKETED_PASTE) { + context.set.insert("bracketed_paste".to_string()); + } + if mode.intersects(TermMode::MOUSE_MODE) { + context.set.insert("any_mouse_reporting".to_string()); + } + { + let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) { + "click" + } else if mode.contains(TermMode::MOUSE_DRAG) { + "drag" + } else if mode.contains(TermMode::MOUSE_MOTION) { + "motion" + } else { + "off" + }; + context + .map + .insert("mouse_reporting".to_string(), mouse_reporting.to_string()); + } + { + let format = if mode.contains(TermMode::SGR_MOUSE) { + "sgr" + } else if mode.contains(TermMode::UTF8_MOUSE) { + "utf8" + } else { + "normal" + }; + context + .map + .insert("mouse_format".to_string(), format.to_string()); + } context } } diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 236f954c74..83d408e75f 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -166,7 +166,6 @@ pub fn mouse_button_report( pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option> { let button = MouseButton::from_move(e); - dbg!(&button); if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { //Only drags are reported in drag mode, so block NoneMove. diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ea3774a586..25e952d591 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -627,7 +627,6 @@ impl Terminal { } pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) { - dbg!("term mouse_move"); let position = e.position.sub(origin); let point = mouse_point(position, self.cur_size, self.last_offset); From c42bf1c50b13022b81472b8a7370d06f4d03bd56 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 17:19:35 -0700 Subject: [PATCH 56/74] Fixed bug in mouse handler attaching --- crates/terminal/src/connected_el.rs | 34 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 7e4cbd1e82..e0117192fd 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -499,23 +499,10 @@ impl TerminalEl { cx.dispatch_action(DeployContextMenu { position }); } }, - ) - //This handles both drag mode and mouse motion mode - //Mouse Move TODO - //This cannot be done conditionally for unknown reasons. Pending drag and drop rework. - //This also does not fire on right-mouse-down-move events wild. - .on_move(move |event, cx| { - if cx.is_parent_view_focused() { - if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - terminal.mouse_move(&event, origin); - cx.notify(); - }) - } - } - }); + ); - if mode.contains(TermMode::MOUSE_MODE) { + //All mouse modes need the extra click handlers + if mode.intersects(TermMode::MOUSE_MODE) { region = region .on_down( MouseButton::Right, @@ -558,6 +545,21 @@ impl TerminalEl { ), ) } + //Mouse move manages both dragging and motion events + if mode.intersects(TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION) { + region = region + //This does not fire on right-mouse-down-move events wild. + .on_move(move |event, cx| { + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + terminal.mouse_move(&event, origin); + cx.notify(); + }) + } + } + }) + } //TODO: Mouse drag isn't correct //TODO: Nor is mouse motion. Move events aren't happening?? From 3ffe760ed344ef6ba02bb648f6d6bef61577c312 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 17:20:54 -0700 Subject: [PATCH 57/74] Removed extra todos --- crates/terminal/src/connected_el.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index e0117192fd..20a97b53d3 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -439,9 +439,9 @@ impl TerminalEl { let mut region = MouseRegion::new(view_id, None, visible_bounds); - //Terminal Emulator controlled behavior: + // Terminal Emulator controlled behavior: region = region - //Start selections + // Start selections .on_down( MouseButton::Left, TerminalEl::generic_button_handler( @@ -452,7 +452,7 @@ impl TerminalEl { }, ), ) - //Update drag selections + // Update drag selections .on_drag(MouseButton::Left, move |_prev, event, cx| { if cx.is_parent_view_focused() { if let Some(conn_handle) = connection.upgrade(cx.app) { @@ -463,7 +463,7 @@ impl TerminalEl { } } }) - //Copy on up behavior + // Copy on up behavior .on_up( MouseButton::Left, TerminalEl::generic_button_handler( @@ -474,7 +474,7 @@ impl TerminalEl { }, ), ) - //Handle click based selections + // Handle click based selections .on_click( MouseButton::Left, TerminalEl::generic_button_handler( @@ -485,14 +485,14 @@ impl TerminalEl { }, ), ) - //Context menu + // Context menu .on_click( MouseButton::Right, move |e @ MouseButtonEvent { position, .. }, cx| { let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) } else { - //If we can't get the model handle, probably can't deploy the context menu + // If we can't get the model handle, probably can't deploy the context menu true }; if !mouse_mode { @@ -501,7 +501,8 @@ impl TerminalEl { }, ); - //All mouse modes need the extra click handlers + // Mouse mode handlers: + // All mouse modes need the extra click handlers if mode.intersects(TermMode::MOUSE_MODE) { region = region .on_down( @@ -548,7 +549,7 @@ impl TerminalEl { //Mouse move manages both dragging and motion events if mode.intersects(TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION) { region = region - //This does not fire on right-mouse-down-move events wild. + //TODO: This does not fire on right-mouse-down-move events. .on_move(move |event, cx| { if cx.is_parent_view_focused() { if let Some(conn_handle) = connection.upgrade(cx.app) { @@ -561,8 +562,6 @@ impl TerminalEl { }) } - //TODO: Mouse drag isn't correct - //TODO: Nor is mouse motion. Move events aren't happening?? cx.scene.push_mouse_region(region); } From d00a482bddae94cd49d77350a63589be8e612b31 Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Sun, 21 Aug 2022 13:19:27 -0400 Subject: [PATCH 58/74] Only extend selection with shift without other modifiers --- crates/editor/src/element.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ee46544df3..a2fe64f7a4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -110,13 +110,17 @@ impl EditorElement { self.update_view(cx, |view, cx| view.snapshot(cx)) } - #[allow(clippy::too_many_arguments)] fn mouse_down( &self, - position: Vector2F, - alt: bool, - shift: bool, - mut click_count: usize, + MouseButtonEvent { + position, + ctrl, + alt, + shift, + cmd, + mut click_count, + .. + }: MouseButtonEvent, layout: &mut LayoutState, paint: &mut PaintState, cx: &mut EventContext, @@ -135,7 +139,7 @@ impl EditorElement { position, goal_column: target_position.column(), })); - } else if shift { + } else if shift && !ctrl && !alt && !cmd { cx.dispatch_action(Select(SelectPhase::Extend { position, click_count, @@ -1576,14 +1580,12 @@ impl Element for EditorElement { } match event { - &Event::MouseDown(MouseButtonEvent { - button: MouseButton::Left, - position, - alt, - shift, - click_count, - .. - }) => self.mouse_down(position, alt, shift, click_count, layout, paint, cx), + &Event::MouseDown( + event @ MouseButtonEvent { + button: MouseButton::Left, + .. + }, + ) => self.mouse_down(event, layout, paint, cx), &Event::MouseDown(MouseButtonEvent { button: MouseButton::Right, From 327aae3bf2e84073933234cb59a7e0bcf0523baf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Aug 2022 12:05:08 +0200 Subject: [PATCH 59/74] Start adding support for Elixir --- Cargo.lock | 10 ++ assets/settings/default.json | 3 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/elixir/brackets.scm | 5 + crates/zed/src/languages/elixir/config.toml | 11 ++ .../zed/src/languages/elixir/highlights.scm | 155 ++++++++++++++++++ crates/zed/src/languages/elixir/indents.scm | 8 + crates/zed/src/languages/elixir/outline.scm | 0 9 files changed, 194 insertions(+) create mode 100644 crates/zed/src/languages/elixir/brackets.scm create mode 100644 crates/zed/src/languages/elixir/config.toml create mode 100644 crates/zed/src/languages/elixir/highlights.scm create mode 100644 crates/zed/src/languages/elixir/indents.scm create mode 100644 crates/zed/src/languages/elixir/outline.scm diff --git a/Cargo.lock b/Cargo.lock index 680e40a7f9..513862d2ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5868,6 +5868,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-elixir" +version = "0.19.0" +source = "git+https://github.com/elixir-lang/tree-sitter-elixir?rev=05e3631c6a0701c1fa518b0fee7be95a2ceef5e2#05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-go" version = "0.19.1" @@ -7056,6 +7065,7 @@ dependencies = [ "tree-sitter", "tree-sitter-c", "tree-sitter-cpp", + "tree-sitter-elixir", "tree-sitter-go", "tree-sitter-json 0.20.0", "tree-sitter-markdown", diff --git a/assets/settings/default.json b/assets/settings/default.json index 8ea515e7a9..95ba08c9d5 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -145,6 +145,9 @@ "C++": { "tab_size": 2 }, + "Elixir": { + "tab_size": 2 + }, "Go": { "tab_size": 4, "hard_tabs": true diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index aa31130d1e..153e87c2dd 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -91,6 +91,7 @@ toml = "0.5" tree-sitter = "0.20" tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" +tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" } tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" } tree-sitter-rust = "0.20.1" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 8dc20bdbd1..953e3aaaca 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -45,6 +45,7 @@ pub async fn init(languages: Arc, _executor: Arc) tree_sitter_cpp::language(), Some(CachedLspAdapter::new(c::CLspAdapter).await), ), + ("elixir", tree_sitter_elixir::language(), None), ( "go", tree_sitter_go::language(), diff --git a/crates/zed/src/languages/elixir/brackets.scm b/crates/zed/src/languages/elixir/brackets.scm new file mode 100644 index 0000000000..a28683378f --- /dev/null +++ b/crates/zed/src/languages/elixir/brackets.scm @@ -0,0 +1,5 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) +("\"" @open "\"" @close) +("do" @open "end" @close) \ No newline at end of file diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml new file mode 100644 index 0000000000..def2e4d238 --- /dev/null +++ b/crates/zed/src/languages/elixir/config.toml @@ -0,0 +1,11 @@ +name = "Elixir" +path_suffixes = ["ex", "exs"] +line_comment = "# " +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, + { start = "do", end = " end", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/elixir/highlights.scm b/crates/zed/src/languages/elixir/highlights.scm new file mode 100644 index 0000000000..9324990391 --- /dev/null +++ b/crates/zed/src/languages/elixir/highlights.scm @@ -0,0 +1,155 @@ +["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword + +(unary_operator + operator: "@" @comment.doc + operand: (call + target: (identifier) @comment.doc.__attribute__ + (arguments + [ + (string) @comment.doc + (charlist) @comment.doc + (sigil + quoted_start: _ @comment.doc + quoted_end: _ @comment.doc) @comment.doc + (boolean) @comment.doc + ])) + (#match? @comment.doc.__attribute__ "^(moduledoc|typedoc|doc)$")) + +(unary_operator + operator: "&" + operand: (integer) @operator) + +(operator_identifier) @operator + +(unary_operator + operator: _ @operator) + +(binary_operator + operator: _ @operator) + +(dot + operator: _ @operator) + +(stab_clause + operator: _ @operator) + +[ + (boolean) + (nil) +] @constant + +[ + (integer) + (float) +] @number + +(alias) @module + +(call + target: (dot + left: (atom) @module)) + +(char) @constant + +(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded + +(escape_sequence) @string.escape + +[ + (atom) + (quoted_atom) + (keyword) + (quoted_keyword) +] @string.special.symbol + +[ + (string) + (charlist) +] @string + +(sigil + (sigil_name) @__name__ + quoted_start: _ @string + quoted_end: _ @string + (#match? @__name__ "^[sS]$")) @string + +(sigil + (sigil_name) @__name__ + quoted_start: _ @string.regex + quoted_end: _ @string.regex + (#match? @__name__ "^[rR]$")) @string.regex + +(sigil + (sigil_name) @__name__ + quoted_start: _ @string.special + quoted_end: _ @string.special) @string.special + +(call + target: [ + (identifier) @function + (dot + right: (identifier) @function) + ]) + +(call + target: (identifier) @keyword + (arguments + [ + (identifier) @function + (binary_operator + left: (identifier) @function + operator: "when") + ]) + (#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$")) + +(call + target: (identifier) @keyword + (arguments + (binary_operator + operator: "|>" + right: (identifier))) + (#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$")) + +(binary_operator + operator: "|>" + right: (identifier) @function) + +(call + target: (identifier) @keyword + (#match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$")) + +(call + target: (identifier) @keyword + (#match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$")) + +( + (identifier) @constant.builtin + (#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$") +) + +( + (identifier) @comment.unused + (#match? @comment.unused "^_") +) + +(comment) @comment + +[ + "%" +] @punctuation + +[ + "," + ";" +] @punctuation.delimiter + +[ + "(" + ")" + "[" + "]" + "{" + "}" + "<<" + ">>" +] @punctuation.bracket diff --git a/crates/zed/src/languages/elixir/indents.scm b/crates/zed/src/languages/elixir/indents.scm new file mode 100644 index 0000000000..e4139841fc --- /dev/null +++ b/crates/zed/src/languages/elixir/indents.scm @@ -0,0 +1,8 @@ +[ + (call) +] @indent + +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent +(_ "do" "end" @end) @indent diff --git a/crates/zed/src/languages/elixir/outline.scm b/crates/zed/src/languages/elixir/outline.scm new file mode 100644 index 0000000000..e69de29bb2 From 15429efba9a1449eb994bef07bb566bab619cac0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Aug 2022 15:26:50 +0200 Subject: [PATCH 60/74] Add outline support for Elixir --- crates/zed/src/languages/elixir/outline.scm | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/zed/src/languages/elixir/outline.scm b/crates/zed/src/languages/elixir/outline.scm index e69de29bb2..985c8ffdca 100644 --- a/crates/zed/src/languages/elixir/outline.scm +++ b/crates/zed/src/languages/elixir/outline.scm @@ -0,0 +1,16 @@ +(call + target: (identifier) @context + (arguments (alias) @name) + (#match? @context "^(defmodule|defprotocol)$")) @item + +(call + target: (identifier) @context + (arguments + [ + (identifier) @name + (call target: (identifier) @name) + (binary_operator + left: (call target: (identifier) @name) + operator: "when") + ]) + (#match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item From 107ea5d05858d6e6814cf251a55d59d2daeacbe4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Aug 2022 16:03:26 +0200 Subject: [PATCH 61/74] Start on Elixir LSP integration --- crates/zed/src/languages.rs | 7 +- crates/zed/src/languages/elixir.rs | 111 +++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 crates/zed/src/languages/elixir.rs diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 953e3aaaca..857783062a 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -5,6 +5,7 @@ use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; mod c; +mod elixir; mod go; mod installation; mod json; @@ -45,7 +46,11 @@ pub async fn init(languages: Arc, _executor: Arc) tree_sitter_cpp::language(), Some(CachedLspAdapter::new(c::CLspAdapter).await), ), - ("elixir", tree_sitter_elixir::language(), None), + ( + "elixir", + tree_sitter_elixir::language(), + Some(CachedLspAdapter::new(elixir::ElixirLspAdapter).await), + ), ( "go", tree_sitter_go::language(), diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs new file mode 100644 index 0000000000..32abbec4c6 --- /dev/null +++ b/crates/zed/src/languages/elixir.rs @@ -0,0 +1,111 @@ +use super::installation::{latest_github_release, GitHubLspBinaryVersion}; +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use client::http::HttpClient; +use futures::StreamExt; +pub use language::*; +use smol::fs::{self, File}; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::ResultExt; + +pub struct ElixirLspAdapter; + +#[async_trait] +impl LspAdapter for ElixirLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("elixir-ls".into()) + } + + async fn fetch_latest_server_version( + &self, + http: Arc, + ) -> Result> { + let release = latest_github_release("elixir-lsp/elixir-ls", http).await?; + let asset_name = "elixir-ls.zip"; + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + }; + Ok(Box::new(version) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + http: Arc, + container_dir: PathBuf, + ) -> Result { + let version = version.downcast::().unwrap(); + let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name)); + let version_dir = container_dir.join(format!("elixir-ls_{}", version.name)); + let binary_path = version_dir.join("language_server.sh"); + + if fs::metadata(&binary_path).await.is_err() { + let mut response = http + .get(&version.url, Default::default(), true) + .await + .context("error downloading release")?; + let mut file = File::create(&zip_path) + .await + .with_context(|| format!("failed to create file {}", zip_path.display()))?; + if !response.status().is_success() { + Err(anyhow!( + "download failed with status {}", + response.status().to_string() + ))?; + } + futures::io::copy(response.body_mut(), &mut file).await?; + + fs::create_dir_all(&version_dir) + .await + .with_context(|| format!("failed to create directory {}", version_dir.display()))?; + let unzip_status = smol::process::Command::new("unzip") + .arg(&zip_path) + .arg("-d") + .arg(&version_dir) + .output() + .await? + .status; + if !unzip_status.success() { + Err(anyhow!("failed to unzip clangd archive"))?; + } + + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + while let Some(entry) = entries.next().await { + if let Some(entry) = entry.log_err() { + let entry_path = entry.path(); + if entry_path.as_path() != version_dir { + if let Ok(metadata) = fs::metadata(&entry_path).await { + if metadata.is_file() { + fs::remove_file(&entry_path).await.log_err(); + } else { + fs::remove_dir_all(&entry_path).await.log_err(); + } + } + } + } + } + } + } + + Ok(binary_path) + } + + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + last.ok_or_else(|| anyhow!("no cached binary")) + })() + .await + .log_err() + } +} From cd62e60eba6b4dbb69955f128ecdaa9303c6fa3a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 Aug 2022 11:43:26 +0200 Subject: [PATCH 62/74] Implement `ElixirLspAdapter::label_for_completion` --- crates/zed/src/languages/elixir.rs | 54 +++++++++++++++++++ .../zed/src/languages/elixir/highlights.scm | 4 +- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 32abbec4c6..c6c7c8d161 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; pub use language::*; +use lsp::CompletionItemKind; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -108,4 +109,57 @@ impl LspAdapter for ElixirLspAdapter { .await .log_err() } + + async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + language: &Language, + ) -> Option { + match completion.kind.zip(completion.detail.as_ref()) { + Some((_, detail)) if detail.starts_with("(function)") => { + let text = detail.strip_prefix("(function) ")?; + let filter_range = 0..text.find('(').unwrap_or(text.len()); + let source = Rope::from(format!("def {text}").as_str()); + let runs = language.highlight_text(&source, 4..4 + text.len()); + return Some(CodeLabel { + text: text.to_string(), + runs, + filter_range, + }); + } + Some((_, detail)) if detail.starts_with("(macro)") => { + let text = detail.strip_prefix("(macro) ")?; + let filter_range = 0..text.find('(').unwrap_or(text.len()); + let source = Rope::from(format!("defmacro {text}").as_str()); + let runs = language.highlight_text(&source, 9..9 + text.len()); + return Some(CodeLabel { + text: text.to_string(), + runs, + filter_range, + }); + } + Some(( + CompletionItemKind::MODULE + | CompletionItemKind::INTERFACE + | CompletionItemKind::STRUCT, + _, + )) => { + let filter_range = 0..completion + .label + .find(" (") + .unwrap_or(completion.label.len()); + let text = &completion.label[filter_range.clone()]; + let source = Rope::from(format!("defmodule {text}").as_str()); + let runs = language.highlight_text(&source, 10..10 + text.len()); + return Some(CodeLabel { + text: completion.label.clone(), + runs, + filter_range, + }); + } + _ => {} + } + + None + } } diff --git a/crates/zed/src/languages/elixir/highlights.scm b/crates/zed/src/languages/elixir/highlights.scm index 9324990391..5c256f341c 100644 --- a/crates/zed/src/languages/elixir/highlights.scm +++ b/crates/zed/src/languages/elixir/highlights.scm @@ -43,11 +43,11 @@ (float) ] @number -(alias) @module +(alias) @type (call target: (dot - left: (atom) @module)) + left: (atom) @type)) (char) @constant From 13c2021aef36751de2874bab561926e0816ae79d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 Aug 2022 12:32:58 +0200 Subject: [PATCH 63/74] Default to language that started LSP when querying project symbols --- crates/project/src/project.rs | 53 +++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0f762f822f..dcab44e373 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -202,6 +202,7 @@ pub enum Event { pub enum LanguageServerState { Starting(Task>>), Running { + language: Arc, adapter: Arc, server: Arc, }, @@ -1969,7 +1970,7 @@ impl Project { uri: lsp::Url::from_file_path(abs_path).unwrap(), }; - for (_, server) in self.language_servers_for_worktree(worktree_id) { + for (_, _, server) in self.language_servers_for_worktree(worktree_id) { server .notify::( lsp::DidSaveTextDocumentParams { @@ -2004,15 +2005,18 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, &Arc)> { + ) -> impl Iterator, &Arc, &Arc)> { self.language_server_ids .iter() .filter_map(move |((language_server_worktree_id, _), id)| { if *language_server_worktree_id == worktree_id { - if let Some(LanguageServerState::Running { adapter, server }) = - self.language_servers.get(id) + if let Some(LanguageServerState::Running { + adapter, + language, + server, + }) = self.language_servers.get(id) { - return Some((adapter, server)); + return Some((adapter, language, server)); } } None @@ -2282,6 +2286,7 @@ impl Project { server_id, LanguageServerState::Running { adapter: adapter.clone(), + language, server: language_server.clone(), }, ); @@ -3314,10 +3319,14 @@ impl Project { .worktree_for_id(worktree_id, cx) .and_then(|worktree| worktree.read(cx).as_local()) { - if let Some(LanguageServerState::Running { adapter, server }) = - self.language_servers.get(server_id) + if let Some(LanguageServerState::Running { + adapter, + language, + server, + }) = self.language_servers.get(server_id) { let adapter = adapter.clone(); + let language = language.clone(); let worktree_abs_path = worktree.abs_path().clone(); requests.push( server @@ -3331,6 +3340,7 @@ impl Project { .map(move |response| { ( adapter, + language, worktree_id, worktree_abs_path, response.unwrap_or_default(), @@ -3350,7 +3360,14 @@ impl Project { }; let symbols = this.read_with(&cx, |this, cx| { let mut symbols = Vec::new(); - for (adapter, source_worktree_id, worktree_abs_path, response) in responses { + for ( + adapter, + adapter_language, + source_worktree_id, + worktree_abs_path, + response, + ) in responses + { symbols.extend(response.into_iter().flatten().filter_map(|lsp_symbol| { let abs_path = lsp_symbol.location.uri.to_file_path().ok()?; let mut worktree_id = source_worktree_id; @@ -3369,16 +3386,15 @@ impl Project { path: path.into(), }; let signature = this.symbol_signature(&project_path); - let language = this.languages.select_language(&project_path.path); + let language = this + .languages + .select_language(&project_path.path) + .unwrap_or(adapter_language.clone()); let language_server_name = adapter.name.clone(); Some(async move { - let label = if let Some(language) = language { - language - .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) - .await - } else { - None - }; + let label = language + .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) + .await; Symbol { language_server_name, @@ -5940,8 +5956,9 @@ impl Project { let key = (worktree_id, name); if let Some(server_id) = self.language_server_ids.get(&key) { - if let Some(LanguageServerState::Running { adapter, server }) = - self.language_servers.get(server_id) + if let Some(LanguageServerState::Running { + adapter, server, .. + }) = self.language_servers.get(server_id) { return Some((adapter, server)); } From 559dad893ff783c0ebe3f9b0a8590440b35ad61d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 Aug 2022 12:33:28 +0200 Subject: [PATCH 64/74] Implement `ElixirLspAdapter::label_for_symbol` --- crates/zed/src/languages/elixir.rs | 34 ++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index c6c7c8d161..4959338522 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; pub use language::*; -use lsp::CompletionItemKind; +use lsp::{CompletionItemKind, SymbolKind}; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -139,7 +139,8 @@ impl LspAdapter for ElixirLspAdapter { }); } Some(( - CompletionItemKind::MODULE + CompletionItemKind::CLASS + | CompletionItemKind::MODULE | CompletionItemKind::INTERFACE | CompletionItemKind::STRUCT, _, @@ -162,4 +163,33 @@ impl LspAdapter for ElixirLspAdapter { None } + + async fn label_for_symbol( + &self, + name: &str, + kind: SymbolKind, + language: &Language, + ) -> Option { + let (text, filter_range, display_range) = match kind { + SymbolKind::METHOD | SymbolKind::FUNCTION => { + let text = format!("def {}", name); + let filter_range = 4..4 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => { + let text = format!("defmodule {}", name); + let filter_range = 10..10 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + _ => return None, + }; + + Some(CodeLabel { + runs: language.highlight_text(&text.as_str().into(), display_range.clone()), + text: text[display_range].to_string(), + filter_range, + }) + } } From 92f0e4fd744cacae9ccb46dd95e020b5ecd3fc4b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 Aug 2022 14:57:44 +0200 Subject: [PATCH 65/74] Don't auto-complete `do...end` Implementing this properly is a bit finicky, and it's unclear this is even expected, e.g. VS Code and Atom don't auto-complete do/end for Ruby, although VS Code does auto-complete do/end for Elixir. We can add it if it's something the Elixir folks want us to implement. --- crates/zed/src/languages/elixir/config.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml index def2e4d238..4e1af93943 100644 --- a/crates/zed/src/languages/elixir/config.toml +++ b/crates/zed/src/languages/elixir/config.toml @@ -6,6 +6,5 @@ brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false }, - { start = "do", end = " end", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false } ] From 72ccb52eb09232025ee0c53a38cb36bd1d32a421 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 22 Aug 2022 15:05:11 +0200 Subject: [PATCH 66/74] :lipstick: --- crates/zed/src/languages/elixir/brackets.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/languages/elixir/brackets.scm b/crates/zed/src/languages/elixir/brackets.scm index a28683378f..d8713187e2 100644 --- a/crates/zed/src/languages/elixir/brackets.scm +++ b/crates/zed/src/languages/elixir/brackets.scm @@ -2,4 +2,4 @@ ("[" @open "]" @close) ("{" @open "}" @close) ("\"" @open "\"" @close) -("do" @open "end" @close) \ No newline at end of file +("do" @open "end" @close) From 9b6116e1e2acd2e0c95d2bf2c93a23c24e6f066d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 13:11:23 -0700 Subject: [PATCH 67/74] Basic feature flag implementation --- assets/keymaps/default.json | 7 ------- crates/settings/src/keymap_file.rs | 14 ++++++++------ crates/settings/src/settings.rs | 12 +++++++++++- crates/terminal/src/terminal.rs | 5 ++++- crates/zed/src/main.rs | 9 ++++++--- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index b40a076c49..7b059652fa 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -425,12 +425,5 @@ "cmd-v": "terminal::Paste", "cmd-k": "terminal::Clear" } - }, - { - "context": "ModalTerminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "shift-escape": "terminal::DeployModal" - } } ] \ No newline at end of file diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index c1d5db32bb..e588239210 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -10,6 +10,7 @@ use schemars::{ }; use serde::Deserialize; use serde_json::{value::RawValue, Value}; +use util::ResultExt; #[derive(Deserialize, Default, Clone, JsonSchema)] #[serde(transparent)] @@ -56,26 +57,27 @@ impl KeymapFileContent { for KeymapBlock { context, bindings } in self.0 { let bindings = bindings .into_iter() - .map(|(keystroke, action)| { + .filter_map(|(keystroke, action)| { let action = action.0.get(); // This is a workaround for a limitation in serde: serde-rs/json#497 // We want to deserialize the action data as a `RawValue` so that we can // deserialize the action itself dynamically directly from the JSON // string. But `RawValue` currently does not work inside of an untagged enum. - let action = if action.starts_with('[') { - let ActionWithData(name, data) = serde_json::from_str(action)?; + if action.starts_with('[') { + let ActionWithData(name, data) = serde_json::from_str(action).log_err()?; cx.deserialize_action(&name, Some(data.get())) } else { - let name = serde_json::from_str(action)?; + let name = serde_json::from_str(action).log_err()?; cx.deserialize_action(name, None) } .with_context(|| { format!( "invalid binding value for keystroke {keystroke}, context {context:?}" ) - })?; - Binding::load(&keystroke, action, context.as_deref()) + }) + .log_err() + .map(|action| Binding::load(&keystroke, action, context.as_deref())) }) .collect::>>()?; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 2b97cb0b10..360ff1ac08 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -20,6 +20,7 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { + pub nightly: FeatureFlags, pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, @@ -38,6 +39,11 @@ pub struct Settings { pub theme: Arc, } +#[derive(Copy, Clone, Debug, Default, Deserialize, JsonSchema)] +pub struct FeatureFlags { + pub modal_terminal: bool, +} + #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct EditorSettings { pub tab_size: Option, @@ -139,6 +145,8 @@ pub enum WorkingDirectory { #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct SettingsFileContent { + #[serde(default)] + pub nightly: Option, #[serde(default)] pub projects_online_by_default: Option, #[serde(default)] @@ -189,6 +197,7 @@ impl Settings { .unwrap(); Self { + nightly: FeatureFlags::default(), buffer_font_family: font_cache .load_family(&[defaults.buffer_font_family.as_ref().unwrap()]) .unwrap(), @@ -247,7 +256,7 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - + merge(&mut self.nightly, data.nightly); // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { font_cache.load_family(&[terminal_font]).log_err(); @@ -308,6 +317,7 @@ impl Settings { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { + nightly: FeatureFlags::default(), buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., default_buffer_font_size: 14., diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 25e952d591..c8e7396a6f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -46,7 +46,10 @@ use crate::mappings::{ ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { - cx.add_action(deploy_modal); + let settings = cx.global::(); + if settings.nightly.modal_terminal { + cx.add_action(deploy_modal); + } terminal_view::init(cx); connected_view::init(cx); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index aa2f0b7ada..2f8bc73c9f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -95,6 +95,12 @@ fn main() { .spawn(languages::init(languages.clone(), cx.background().clone())); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); + let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); + + //Make sure to load settings before initialization, so we know what features to toggle + watch_settings_file(default_settings, settings_file, themes.clone(), cx); + watch_keymap_file(keymap_file, cx); + context_menu::init(cx); project::Project::init(&client); client::Channel::init(&client); @@ -114,10 +120,7 @@ fn main() { terminal::init(cx); let db = cx.background().block(db); - let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); - watch_settings_file(default_settings, settings_file, themes.clone(), cx); - watch_keymap_file(keymap_file, cx); cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); From 7efeb0662a3a5d618fd44f92a8c470d67d8d54fc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 13:27:36 -0700 Subject: [PATCH 68/74] Changed name to experiments --- crates/settings/src/settings.rs | 8 ++++---- crates/terminal/src/terminal.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 360ff1ac08..a520dbaf67 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -20,7 +20,7 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { - pub nightly: FeatureFlags, + pub experiments: FeatureFlags, pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, @@ -197,7 +197,7 @@ impl Settings { .unwrap(); Self { - nightly: FeatureFlags::default(), + experiments: FeatureFlags::default(), buffer_font_family: font_cache .load_family(&[defaults.buffer_font_family.as_ref().unwrap()]) .unwrap(), @@ -256,7 +256,7 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - merge(&mut self.nightly, data.nightly); + merge(&mut self.experiments, data.nightly); // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { font_cache.load_family(&[terminal_font]).log_err(); @@ -317,7 +317,7 @@ impl Settings { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { - nightly: FeatureFlags::default(), + experiments: FeatureFlags::default(), buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., default_buffer_font_size: 14., diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c8e7396a6f..668af56145 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -47,7 +47,7 @@ use crate::mappings::{ ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { let settings = cx.global::(); - if settings.nightly.modal_terminal { + if settings.experiments.modal_terminal { cx.add_action(deploy_modal); } From 0450380e97a4274a76b6b11373fd76520079c04a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 13:38:23 -0700 Subject: [PATCH 69/74] Fixed name issue --- crates/settings/src/settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index a520dbaf67..ad904f034a 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -146,7 +146,7 @@ pub enum WorkingDirectory { #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct SettingsFileContent { #[serde(default)] - pub nightly: Option, + pub experiments: Option, #[serde(default)] pub projects_online_by_default: Option, #[serde(default)] @@ -256,7 +256,7 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - merge(&mut self.experiments, data.nightly); + merge(&mut self.experiments, data.experiments); // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { font_cache.load_family(&[terminal_font]).log_err(); From b6785c5624206a803ff74fd4d217deb5b9ea70bc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 13:54:18 -0700 Subject: [PATCH 70/74] Revert "Basic feature flag implementation" --- assets/keymaps/default.json | 7 +++++++ crates/settings/src/keymap_file.rs | 14 ++++++-------- crates/settings/src/settings.rs | 12 +----------- crates/terminal/src/terminal.rs | 5 +---- crates/zed/src/main.rs | 9 +++------ 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 7b059652fa..b40a076c49 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -425,5 +425,12 @@ "cmd-v": "terminal::Paste", "cmd-k": "terminal::Clear" } + }, + { + "context": "ModalTerminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "shift-escape": "terminal::DeployModal" + } } ] \ No newline at end of file diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index e588239210..c1d5db32bb 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -10,7 +10,6 @@ use schemars::{ }; use serde::Deserialize; use serde_json::{value::RawValue, Value}; -use util::ResultExt; #[derive(Deserialize, Default, Clone, JsonSchema)] #[serde(transparent)] @@ -57,27 +56,26 @@ impl KeymapFileContent { for KeymapBlock { context, bindings } in self.0 { let bindings = bindings .into_iter() - .filter_map(|(keystroke, action)| { + .map(|(keystroke, action)| { let action = action.0.get(); // This is a workaround for a limitation in serde: serde-rs/json#497 // We want to deserialize the action data as a `RawValue` so that we can // deserialize the action itself dynamically directly from the JSON // string. But `RawValue` currently does not work inside of an untagged enum. - if action.starts_with('[') { - let ActionWithData(name, data) = serde_json::from_str(action).log_err()?; + let action = if action.starts_with('[') { + let ActionWithData(name, data) = serde_json::from_str(action)?; cx.deserialize_action(&name, Some(data.get())) } else { - let name = serde_json::from_str(action).log_err()?; + let name = serde_json::from_str(action)?; cx.deserialize_action(name, None) } .with_context(|| { format!( "invalid binding value for keystroke {keystroke}, context {context:?}" ) - }) - .log_err() - .map(|action| Binding::load(&keystroke, action, context.as_deref())) + })?; + Binding::load(&keystroke, action, context.as_deref()) }) .collect::>>()?; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index ad904f034a..2b97cb0b10 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -20,7 +20,6 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { - pub experiments: FeatureFlags, pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, @@ -39,11 +38,6 @@ pub struct Settings { pub theme: Arc, } -#[derive(Copy, Clone, Debug, Default, Deserialize, JsonSchema)] -pub struct FeatureFlags { - pub modal_terminal: bool, -} - #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct EditorSettings { pub tab_size: Option, @@ -145,8 +139,6 @@ pub enum WorkingDirectory { #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct SettingsFileContent { - #[serde(default)] - pub experiments: Option, #[serde(default)] pub projects_online_by_default: Option, #[serde(default)] @@ -197,7 +189,6 @@ impl Settings { .unwrap(); Self { - experiments: FeatureFlags::default(), buffer_font_family: font_cache .load_family(&[defaults.buffer_font_family.as_ref().unwrap()]) .unwrap(), @@ -256,7 +247,7 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - merge(&mut self.experiments, data.experiments); + // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { font_cache.load_family(&[terminal_font]).log_err(); @@ -317,7 +308,6 @@ impl Settings { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { - experiments: FeatureFlags::default(), buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., default_buffer_font_size: 14., diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 668af56145..25e952d591 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -46,10 +46,7 @@ use crate::mappings::{ ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { - let settings = cx.global::(); - if settings.experiments.modal_terminal { - cx.add_action(deploy_modal); - } + cx.add_action(deploy_modal); terminal_view::init(cx); connected_view::init(cx); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2f8bc73c9f..aa2f0b7ada 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -95,12 +95,6 @@ fn main() { .spawn(languages::init(languages.clone(), cx.background().clone())); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); - - //Make sure to load settings before initialization, so we know what features to toggle - watch_settings_file(default_settings, settings_file, themes.clone(), cx); - watch_keymap_file(keymap_file, cx); - context_menu::init(cx); project::Project::init(&client); client::Channel::init(&client); @@ -120,7 +114,10 @@ fn main() { terminal::init(cx); let db = cx.background().block(db); + let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); + watch_settings_file(default_settings, settings_file, themes.clone(), cx); + watch_keymap_file(keymap_file, cx); cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); From 0a40cc0370154e2327969a89ae4b1e8e5f33d87a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 14:49:01 -0700 Subject: [PATCH 71/74] Added experimental keymaps support --- assets/keymaps/default.json | 7 ------ .../keymaps/experiments/modal_terminal.json | 9 +++++++ crates/settings/src/keymap_file.rs | 20 +++++++++------- crates/settings/src/settings.rs | 24 +++++++++++++++++++ crates/terminal/src/terminal.rs | 5 +++- crates/zed/src/main.rs | 9 +++---- 6 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 assets/keymaps/experiments/modal_terminal.json diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index b40a076c49..7b059652fa 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -425,12 +425,5 @@ "cmd-v": "terminal::Paste", "cmd-k": "terminal::Clear" } - }, - { - "context": "ModalTerminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "shift-escape": "terminal::DeployModal" - } } ] \ No newline at end of file diff --git a/assets/keymaps/experiments/modal_terminal.json b/assets/keymaps/experiments/modal_terminal.json new file mode 100644 index 0000000000..427f3e1a70 --- /dev/null +++ b/assets/keymaps/experiments/modal_terminal.json @@ -0,0 +1,9 @@ +[ + { + "context": "ModalTerminal", + "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", + "shift-escape": "terminal::DeployModal" + } + } +] \ No newline at end of file diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index c1d5db32bb..c7cff92721 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use crate::parse_json_with_comments; +use crate::{parse_json_with_comments, Settings}; use anyhow::{Context, Result}; use assets::Assets; use collections::BTreeMap; @@ -10,6 +10,7 @@ use schemars::{ }; use serde::Deserialize; use serde_json::{value::RawValue, Value}; +use util::ResultExt; #[derive(Deserialize, Default, Clone, JsonSchema)] #[serde(transparent)] @@ -41,7 +42,9 @@ struct ActionWithData(Box, Box); impl KeymapFileContent { pub fn load_defaults(cx: &mut MutableAppContext) { - for path in ["keymaps/default.json", "keymaps/vim.json"] { + let mut paths = vec!["keymaps/default.json", "keymaps/vim.json"]; + paths.extend(cx.global::().experiments.keymap_files()); + for path in paths { Self::load(path, cx).unwrap(); } } @@ -56,26 +59,27 @@ impl KeymapFileContent { for KeymapBlock { context, bindings } in self.0 { let bindings = bindings .into_iter() - .map(|(keystroke, action)| { + .filter_map(|(keystroke, action)| { let action = action.0.get(); // This is a workaround for a limitation in serde: serde-rs/json#497 // We want to deserialize the action data as a `RawValue` so that we can // deserialize the action itself dynamically directly from the JSON // string. But `RawValue` currently does not work inside of an untagged enum. - let action = if action.starts_with('[') { - let ActionWithData(name, data) = serde_json::from_str(action)?; + if action.starts_with('[') { + let ActionWithData(name, data) = serde_json::from_str(action).log_err()?; cx.deserialize_action(&name, Some(data.get())) } else { - let name = serde_json::from_str(action)?; + let name = serde_json::from_str(action).log_err()?; cx.deserialize_action(name, None) } .with_context(|| { format!( "invalid binding value for keystroke {keystroke}, context {context:?}" ) - })?; - Binding::load(&keystroke, action, context.as_deref()) + }) + .log_err() + .map(|action| Binding::load(&keystroke, action, context.as_deref())) }) .collect::>>()?; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 2b97cb0b10..1ac1bf0cc3 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -20,6 +20,7 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { + pub experiments: FeatureFlags, pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, @@ -38,6 +39,25 @@ pub struct Settings { pub theme: Arc, } +#[derive(Copy, Clone, Debug, Default, Deserialize, JsonSchema)] +pub struct FeatureFlags { + modal_terminal: Option, +} + +impl FeatureFlags { + pub fn keymap_files(&self) -> Vec<&'static str> { + let mut res = vec![]; + if self.modal_terminal() { + res.push("keymaps/experiments/modal_terminal.json") + } + res + } + + pub fn modal_terminal(&self) -> bool { + self.modal_terminal.unwrap_or_default() + } +} + #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct EditorSettings { pub tab_size: Option, @@ -139,6 +159,7 @@ pub enum WorkingDirectory { #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct SettingsFileContent { + pub experiments: Option, #[serde(default)] pub projects_online_by_default: Option, #[serde(default)] @@ -189,6 +210,7 @@ impl Settings { .unwrap(); Self { + experiments: FeatureFlags::default(), buffer_font_family: font_cache .load_family(&[defaults.buffer_font_family.as_ref().unwrap()]) .unwrap(), @@ -247,6 +269,7 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); + merge(&mut self.experiments, data.experiments); // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { @@ -308,6 +331,7 @@ impl Settings { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { + experiments: FeatureFlags::default(), buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., default_buffer_font_size: 14., diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 25e952d591..134e5af7cc 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -46,7 +46,10 @@ use crate::mappings::{ ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { - cx.add_action(deploy_modal); + let settings = cx.global::(); + if settings.experiments.modal_terminal() { + cx.add_action(deploy_modal); + } terminal_view::init(cx); connected_view::init(cx); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index aa2f0b7ada..0bec005998 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -95,6 +95,11 @@ fn main() { .spawn(languages::init(languages.clone(), cx.background().clone())); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); + let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); + + watch_settings_file(default_settings, settings_file, themes.clone(), cx); + watch_keymap_file(keymap_file, cx); + context_menu::init(cx); project::Project::init(&client); client::Channel::init(&client); @@ -114,10 +119,6 @@ fn main() { terminal::init(cx); let db = cx.background().block(db); - let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); - - watch_settings_file(default_settings, settings_file, themes.clone(), cx); - watch_keymap_file(keymap_file, cx); cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); From 9752650a49cbac93ad458a09f3e2ca4c3959b431 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 14:51:06 -0700 Subject: [PATCH 72/74] Added note about settings --- crates/zed/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 0bec005998..141355742a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -97,6 +97,7 @@ fn main() { let (settings_file, keymap_file) = cx.background().block(config_files).unwrap(); + //Setup settings global before binding actions watch_settings_file(default_settings, settings_file, themes.clone(), cx); watch_keymap_file(keymap_file, cx); From 093ab96f84a037226cb7abc1799193929147add1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 15:38:07 -0700 Subject: [PATCH 73/74] Added experimental keybinding for the modal terminal --- assets/keymaps/experiments/modal_terminal.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/keymaps/experiments/modal_terminal.json b/assets/keymaps/experiments/modal_terminal.json index 427f3e1a70..4d33fc8ffe 100644 --- a/assets/keymaps/experiments/modal_terminal.json +++ b/assets/keymaps/experiments/modal_terminal.json @@ -1,4 +1,10 @@ [ + { + "context": "Workspace", + "bindings": { + "shift-escape": "terminal::DeployModal" + } + }, { "context": "ModalTerminal", "bindings": { From 4f5ab744298a3229f1abd42601b43efc8cbc7fa9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 22 Aug 2022 16:03:49 -0700 Subject: [PATCH 74/74] Added instructions for how to add feature flags --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 2cda4fb3a7..82b0e75d35 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,24 @@ script/zed_with_local_servers --release If you trigger `cmd-alt-i`, Zed will copy a JSON representation of the current window contents to the clipboard. You can paste this in a tool like [DJSON](https://chrome.google.com/webstore/detail/djson-json-viewer-formatt/chaeijjekipecdajnijdldjjipaegdjc?hl=en) to navigate the state of on-screen elements in a structured way. +### Experimental Features + +A feature flag can be added to Zed by: + +* Adding a setting to the crates/settings/src/settings.rs FeatureFlags struct. Use a boolean for a simple on/off, or use a struct to experiment with different configuration options. +* If the feature needs keybindings, add a file to the `assets/keymaps/experiments/` folder, then update the `FeatureFlags::keymap_files()` method to check for your feature's flag and add it's keybindings's path to the method's list. + +The Settings global should be initialized with the user's feature flags by the time the feature's `init(cx)` equivalent is called. + +To promote an experimental feature to a full feature: + +* Take the features settings (if any) and add them under a new variable in the Settings struct. Don't forget to add a `merge()` call in `set_user_settings()`! +* Take the feature's keybindings and add them to the default.json (or equivalent) file +* Remove the file from the `FeatureFlags::keymap_files()` method +* Remove the conditional in the feature's `init(cx)` equivalent. + +That's it 😸 + ### Wasm Plugins Zed has a Wasm-based plugin runtime which it currently uses to embed plugins. To compile Zed, you'll need to have the `wasm32-wasi` toolchain installed on your system. To install this toolchain, run: