From 1a2a5383668218c5ac6c7d771b43f8ff1101b191 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Aug 2024 01:01:46 +0300 Subject: [PATCH] Improve Linux terminal keymap and context menu (#16845) Follow-up https://github.com/zed-industries/zed/pull/16085 that fixes the search deploy to be actually a part of the terminal-related bindings. Part of https://github.com/zed-industries/zed/issues/16839 Also * fixes few other bindings to use `shift` and avoid conflicts with the existing key bindings. * adds terminal inline assist to the context menu and makes both the menu and the button to dynamically adjust to `assist.enabled` settings change It is still unclear to me, why certain labels for certain bindings are wrong (it's still showing `ctrl-w` for closing the terminal tab, and `shift-insert` instead of `ctrl-shift-v` for Paste, while Insert is near and has a `ctrl-shift-c` binding shown) but at least the keys work now. Release notes: - Improved Linux terminal keymap and context menu --- Cargo.lock | 1 + assets/keymaps/default-linux.json | 9 ++-- crates/assistant/src/assistant.rs | 9 +--- crates/assistant/src/assistant_panel.rs | 28 +++---------- crates/assistant/src/inline_assistant.rs | 20 +++++++-- crates/assistant/src/prompt_library.rs | 5 +-- .../quick_action_bar/src/quick_action_bar.rs | 3 +- crates/terminal_view/Cargo.toml | 1 + crates/terminal_view/src/terminal_panel.rs | 41 ++++++++++++++----- crates/terminal_view/src/terminal_view.rs | 13 ++++++ crates/zed_actions/src/lib.rs | 7 ++++ 11 files changed, 87 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 966633f3dd..e3ff217f97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10993,6 +10993,7 @@ dependencies = [ "ui", "util", "workspace", + "zed_actions", ] [[package]] diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index bbb8450598..7ba1b33af6 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -474,8 +474,7 @@ { "context": "!Terminal", "bindings": { - "ctrl-shift-c": "collab_panel::ToggleFocus", - "ctrl-shift-f": "buffer_search::Deploy" + "ctrl-shift-c": "collab_panel::ToggleFocus" } }, { @@ -614,11 +613,15 @@ "ctrl-alt-space": "terminal::ShowCharacterPalette", "ctrl-shift-c": "terminal::Copy", "ctrl-insert": "terminal::Copy", - // "ctrl-a": "editor::SelectAll", // conflicts with readline "ctrl-shift-v": "terminal::Paste", "shift-insert": "terminal::Paste", "ctrl-enter": "assistant::InlineAssist", + // Overrides for conflicting keybindings "ctrl-w": ["terminal::SendKeystroke", "ctrl-w"], + "ctrl-shift-a": "editor::SelectAll", + "ctrl-shift-f": "buffer_search::Deploy", + "ctrl-shift-l": "terminal::Clear", + "ctrl-shift-w": "pane::CloseActiveItem", "ctrl-e": ["terminal::SendKeystroke", "ctrl-e"], "up": ["terminal::SendKeystroke", "up"], "pageup": ["terminal::SendKeystroke", "pageup"], diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index 2e7242baf6..c6fa9b2f97 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -26,7 +26,7 @@ pub use context_store::*; use feature_flags::FeatureFlagAppExt; use fs::Fs; use gpui::Context as _; -use gpui::{actions, impl_actions, AppContext, Global, SharedString, UpdateGlobal}; +use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; use indexed_docs::IndexedDocsRegistry; pub(crate) use inline_assistant::*; use language_model::{ @@ -69,13 +69,6 @@ actions!( const DEFAULT_CONTEXT_LINES: usize = 50; -#[derive(Clone, Default, Deserialize, PartialEq)] -pub struct InlineAssist { - prompt: Option, -} - -impl_actions!(assistant, [InlineAssist]); - #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct MessageId(clock::Lamport); diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 898a8f88d0..faec9f8ef7 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -12,10 +12,10 @@ use crate::{ slash_command_picker, terminal_inline_assistant::TerminalInlineAssistant, Assist, CacheStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore, - CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistId, - InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand, - PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split, - ToggleFocus, ToggleModelSelector, WorkflowStepResolution, WorkflowStepView, + CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId, InlineAssistant, + InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand, PendingSlashCommandStatus, + QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split, ToggleFocus, + ToggleModelSelector, WorkflowStepResolution, WorkflowStepView, }; use crate::{ContextStoreEvent, ModelPickerDelegate}; use anyhow::{anyhow, Result}; @@ -82,6 +82,7 @@ use workspace::{ ToolbarItemView, Workspace, }; use workspace::{searchable::SearchableItemHandle, NewFile}; +use zed_actions::InlineAssist; pub fn init(cx: &mut AppContext) { workspace::FollowableViewRegistry::register::(cx); @@ -107,29 +108,12 @@ pub fn init(cx: &mut AppContext) { cx.observe_new_views( |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext| { let settings = AssistantSettings::get_global(cx); - if !settings.enabled { - return; - } - - terminal_panel.register_tab_bar_button(cx.new_view(|_| InlineAssistTabBarButton), cx); + terminal_panel.asssistant_enabled(settings.enabled, cx); }, ) .detach(); } -struct InlineAssistTabBarButton; - -impl Render for InlineAssistTabBarButton { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("terminal_inline_assistant", IconName::ZedAssistant) - .icon_size(IconSize::Small) - .on_click(cx.listener(|_, _, cx| { - cx.dispatch_action(InlineAssist::default().boxed_clone()); - })) - .tooltip(move |cx| Tooltip::for_action("Inline Assist", &InlineAssist::default(), cx)) - } -} - pub enum AssistantPanelEvent { ContextEdited, } diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 94f9a7fcf3..dfe26cf6fd 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1,6 +1,7 @@ use crate::{ - humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, - CharOperation, LineDiff, LineOperation, ModelSelector, StreamingDiff, + assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder, + AssistantPanel, AssistantPanelEvent, CharOperation, LineDiff, LineOperation, ModelSelector, + StreamingDiff, }; use anyhow::{anyhow, Context as _, Result}; use client::{telemetry::Telemetry, ErrorExt}; @@ -35,7 +36,7 @@ use language_model::{ use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use rope::Rope; -use settings::Settings; +use settings::{Settings, SettingsStore}; use smol::future::FutureExt; use std::{ cmp, @@ -47,6 +48,7 @@ use std::{ task::{self, Poll}, time::{Duration, Instant}, }; +use terminal_view::terminal_panel::TerminalPanel; use theme::ThemeSettings; use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip}; use util::{RangeExt, ResultExt}; @@ -131,6 +133,18 @@ impl InlineAssistant { Self::update_global(cx, |this, cx| this.handle_workspace_event(event, cx)); }) .detach(); + + let workspace = workspace.clone(); + cx.observe_global::(move |cx| { + let Some(terminal_panel) = workspace.read(cx).panel::(cx) else { + return; + }; + let enabled = AssistantSettings::get_global(cx).enabled; + terminal_panel.update(cx, |terminal_panel, cx| { + terminal_panel.asssistant_enabled(enabled, cx) + }); + }) + .detach(); } fn handle_workspace_event(&mut self, event: &workspace::Event, cx: &mut WindowContext) { diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs index c8bafdf7fa..fddafec1c4 100644 --- a/crates/assistant/src/prompt_library.rs +++ b/crates/assistant/src/prompt_library.rs @@ -1,6 +1,4 @@ -use crate::{ - slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssist, InlineAssistant, -}; +use crate::{slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssistant}; use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use collections::{HashMap, HashSet}; @@ -44,6 +42,7 @@ use ui::{ use util::{ResultExt, TryFutureExt}; use uuid::Uuid; use workspace::Workspace; +use zed_actions::InlineAssist; actions!( prompt_library, diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index 4d031eb60c..0d530d6821 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -1,5 +1,5 @@ use assistant::assistant_settings::AssistantSettings; -use assistant::{AssistantPanel, InlineAssist}; +use assistant::AssistantPanel; use editor::actions::{ AddSelectionAbove, AddSelectionBelow, DuplicateLineDown, GoToDiagnostic, GoToHunk, GoToPrevDiagnostic, GoToPrevHunk, MoveLineDown, MoveLineUp, SelectAll, SelectLargerSyntaxNode, @@ -20,6 +20,7 @@ use ui::{ use workspace::{ item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; +use zed_actions::InlineAssist; mod repl_menu; mod toggle_markdown_preview; diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 3067354aa0..09b0b0d2d5 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -36,6 +36,7 @@ theme.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] client = { workspace = true, features = ["test-support"] } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 988dfa346f..14ad7181fc 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -33,6 +33,7 @@ use workspace::{ }; use anyhow::Result; +use zed_actions::InlineAssist; const TERMINAL_PANEL_KEY: &str = "TerminalPanel"; @@ -68,7 +69,8 @@ pub struct TerminalPanel { _subscriptions: Vec, deferred_tasks: HashMap>, enabled: bool, - additional_tab_bar_buttons: Vec, + assistant_enabled: bool, + assistant_tab_bar_button: Option, } impl TerminalPanel { @@ -154,23 +156,25 @@ impl TerminalPanel { deferred_tasks: HashMap::default(), _subscriptions: subscriptions, enabled, - additional_tab_bar_buttons: Vec::new(), + assistant_enabled: false, + assistant_tab_bar_button: None, }; this.apply_tab_bar_buttons(cx); this } - pub fn register_tab_bar_button( - &mut self, - button: impl Into, - cx: &mut ViewContext, - ) { - self.additional_tab_bar_buttons.push(button.into()); + pub fn asssistant_enabled(&mut self, enabled: bool, cx: &mut ViewContext) { + self.assistant_enabled = enabled; + if enabled { + self.assistant_tab_bar_button = Some(cx.new_view(|_| InlineAssistTabBarButton).into()); + } else { + self.assistant_tab_bar_button = None; + } self.apply_tab_bar_buttons(cx); } fn apply_tab_bar_buttons(&self, cx: &mut ViewContext) { - let additional_buttons = self.additional_tab_bar_buttons.clone(); + let assistant_tab_bar_button = self.assistant_tab_bar_button.clone(); self.pane.update(cx, |pane, cx| { pane.set_render_tab_bar_buttons(cx, move |pane, cx| { if !pane.has_focus(cx) && !pane.context_menu_focused(cx) { @@ -179,7 +183,7 @@ impl TerminalPanel { let focus_handle = pane.focus_handle(cx); let right_children = h_flex() .gap_2() - .children(additional_buttons.clone()) + .children(assistant_tab_bar_button.clone()) .child( PopoverMenu::new("terminal-tab-bar-popover-menu") .trigger( @@ -686,6 +690,10 @@ impl TerminalPanel { fn has_no_terminals(&self, cx: &WindowContext) -> bool { self.pane.read(cx).items_len() == 0 && self.pending_terminals_to_add == 0 } + + pub fn assistant_enabled(&self) -> bool { + self.assistant_enabled + } } async fn wait_for_terminals_tasks( @@ -851,6 +859,19 @@ impl Panel for TerminalPanel { } } +struct InlineAssistTabBarButton; + +impl Render for InlineAssistTabBarButton { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + IconButton::new("terminal_inline_assistant", IconName::ZedAssistant) + .icon_size(IconSize::Small) + .on_click(cx.listener(|_, _, cx| { + cx.dispatch_action(InlineAssist::default().boxed_clone()); + })) + .tooltip(move |cx| Tooltip::for_action("Inline Assist", &InlineAssist::default(), cx)) + } +} + #[derive(Serialize, Deserialize)] struct SerializedTerminalPanel { items: Vec, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 81b97211f2..2d3530eb75 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -25,6 +25,7 @@ use terminal::{ TerminalSize, }; use terminal_element::{is_blank, TerminalElement}; +use terminal_panel::TerminalPanel; use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip}; use util::{paths::PathWithPosition, ResultExt}; use workspace::{ @@ -40,6 +41,7 @@ use anyhow::Context; use serde::Deserialize; use settings::{Settings, SettingsStore}; use smol::Timer; +use zed_actions::InlineAssist; use std::{ cmp, @@ -210,6 +212,13 @@ impl TerminalView { position: gpui::Point, cx: &mut ViewContext, ) { + let assistant_enabled = self + .workspace + .upgrade() + .and_then(|workspace| workspace.read(cx).panel::(cx)) + .map_or(false, |terminal_panel| { + terminal_panel.read(cx).assistant_enabled() + }); let context_menu = ContextMenu::build(cx, |menu, _| { menu.context(self.focus_handle.clone()) .action("New Terminal", Box::new(NewTerminal)) @@ -218,6 +227,10 @@ impl TerminalView { .action("Paste", Box::new(Paste)) .action("Select All", Box::new(SelectAll)) .action("Clear", Box::new(Clear)) + .when(assistant_enabled, |menu| { + menu.separator() + .action("Inline Assist", Box::new(InlineAssist::default())) + }) .separator() .action("Close", Box::new(CloseActiveItem { save_intent: None })) }); diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index ec7847c848..4bd4796091 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -40,3 +40,10 @@ actions!( ResetUiFontSize ] ); + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct InlineAssist { + pub prompt: Option, +} + +impl_actions!(assistant, [InlineAssist]);