Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions crates/gpui_term/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,17 @@ impl InteractiveElement for TerminalElement {
impl StatefulInteractiveElement for TerminalElement {}

impl TerminalElement {
fn owns_selection_drag(terminal_view: &Entity<TerminalView>, cx: &App) -> bool {
cx.try_global::<crate::TerminalSelectionOwner>()
.is_some_and(|owner| owner.0 == Some(terminal_view.entity_id()))
}

fn clear_selection_drag_owner(terminal_view: &Entity<TerminalView>, cx: &mut App) {
if Self::owns_selection_drag(terminal_view, cx) {
cx.set_global(crate::TerminalSelectionOwner(None));
}
}

pub fn new(
terminal: Entity<Terminal>,
terminal_view: Entity<TerminalView>,
Expand Down Expand Up @@ -693,6 +704,9 @@ impl TerminalElement {
terminal_view.update(cx, |view: &mut TerminalView, _| {
view.set_mouse_left_down_in_terminal(true);
});
cx.set_global(crate::TerminalSelectionOwner(Some(
terminal_view.entity_id(),
)));

let scroll_top = terminal_view.read(cx).scroll_top();
terminal.update(cx, |terminal, cx| {
Expand Down Expand Up @@ -759,6 +773,7 @@ impl TerminalElement {
);
cx.notify();
});
Self::clear_selection_drag_owner(terminal_view, cx);

true
}
Expand All @@ -774,6 +789,10 @@ impl TerminalElement {
window: &mut Window,
cx: &mut App,
) {
if !Self::owns_selection_drag(terminal_view, cx) {
return;
}

// If the drag started in this terminal view, keep updating the selection even if the
// cursor leaves the terminal hitbox (or focus changes).
if !e.dragging()
Expand Down Expand Up @@ -943,6 +962,7 @@ impl TerminalElement {
});

if was_scrollbar_dragging {
Self::clear_selection_drag_owner(&terminal_view, cx);
terminal_view.update(cx, |_, view_cx| view_cx.notify());
return;
}
Expand All @@ -951,6 +971,7 @@ impl TerminalElement {
terminal.mouse_up(e, cx);
cx.notify();
});
Self::clear_selection_drag_owner(&terminal_view, cx);
}
});
}
Expand Down Expand Up @@ -983,6 +1004,7 @@ impl TerminalElement {
});

if was_scrollbar_dragging {
Self::clear_selection_drag_owner(&terminal_view, cx);
terminal_view.update(cx, |_, view_cx| view_cx.notify());
return;
}
Expand All @@ -991,6 +1013,7 @@ impl TerminalElement {
terminal.mouse_up(e, cx);
cx.notify();
});
Self::clear_selection_drag_owner(&terminal_view, cx);
}
});
}
Expand Down
5 changes: 5 additions & 0 deletions crates/gpui_term/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ use bitflags::bitflags;

rust_i18n::i18n!("../../locales");

#[derive(Clone, Copy, Default)]
pub(crate) struct TerminalSelectionOwner(pub(crate) Option<gpui::EntityId>);

impl gpui::Global for TerminalSelectionOwner {}

mod backends;
mod builder;
pub mod cast;
Expand Down
12 changes: 12 additions & 0 deletions crates/gpui_term/src/view/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ impl TerminalView {
.on_action(cx.listener(TerminalView::stop_cast_recording))
.on_action(cx.listener(TerminalView::toggle_cast_recording))
.on_key_down(cx.listener(Self::key_down))
.on_mouse_move(cx.listener(|this, _, window, cx| {
// Hovering a visible terminal should make it the active input target. The
// terminal view only receives this event when it is the topmost hit element, so
// covered terminals cannot steal focus from the panel above them.
if cx
.try_global::<crate::TerminalSelectionOwner>()
.is_some_and(|owner| owner.0.is_some())
{
return;
}
window.focus(&this.focus_handle, cx);
}))
}

fn terminal_view_root_mouse_handlers(
Expand Down
29 changes: 29 additions & 0 deletions crates/menubar/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
use gpui::App;

#[derive(Clone, Copy, Debug)]
pub struct MenuBarSettings {
pub auto_collapse: bool,
}

impl Default for MenuBarSettings {
fn default() -> Self {
Self {
auto_collapse: true,
}
}
}

impl gpui::Global for MenuBarSettings {}

pub fn set_auto_collapse(auto_collapse: bool, cx: &mut App) {
if cx.has_global::<MenuBarSettings>() {
cx.global_mut::<MenuBarSettings>().auto_collapse = auto_collapse;
} else {
cx.set_global(MenuBarSettings { auto_collapse });
}
cx.refresh_windows();
}

pub fn auto_collapse(cx: &App) -> bool {
cx.try_global::<MenuBarSettings>()
.map_or(true, |settings| settings.auto_collapse)
}

rust_i18n::i18n!("../../locales");

mod menu_bar;
Expand Down
20 changes: 17 additions & 3 deletions crates/menubar/src/menu_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use gpui_component::{
};
use rust_i18n::t;

use crate::state::MenuBarState;
use crate::{auto_collapse, state::MenuBarState};

const CONTEXT: &str = "FoldableAppMenuBar";

Expand Down Expand Up @@ -237,7 +237,15 @@ impl FoldableAppMenuBar {
window.prevent_default();
cx.stop_propagation();

self.state.on_fold_click();
if auto_collapse(cx) {
self.state.on_fold_click();
} else if self.state.selected_ix == Some(0) {
// With a permanently expanded menubar, clicking the first menu toggles only its
// popup; the menubar itself must remain visible.
self.state.selected_ix = None;
} else {
self.state.selected_ix = Some(0);
}
self.set_selected_index(self.state.selected_ix, window, cx);
}

Expand All @@ -246,7 +254,7 @@ impl FoldableAppMenuBar {
return;
}
// Don't expand/open from hover when folded.
if !self.state.expanded {
if !auto_collapse(cx) || !self.state.expanded {
return;
}
// Switch from other top-level menus back to the fold/app menu when the menubar is active.
Expand Down Expand Up @@ -314,6 +322,12 @@ impl Render for FoldableAppMenuBar {

self.sync_menus_from_app(window, cx);

if !auto_collapse(cx) {
self.state.expanded = true;
} else if self.state.selected_ix.is_none() {
self.state.expanded = false;
}

let fold_name: SharedString = self
.fold_menu
.as_ref()
Expand Down
4 changes: 4 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ Settings:
Appearance:
Theme: "Theme"
Language: "Language"
Menu: "Menu"
Terminal:
Terminal: "Terminal"
Behavior: "Behavior"
Expand Down Expand Up @@ -447,6 +448,9 @@ Settings:
language:
Title: "Language"
Description: "Choose the application UI language."
menu_auto_collapse:
Title: "Auto-Collapse Menu"
Description: "Whether to automatically collapse the application menu."
terminal:
default_backend:
Title: "Default backend"
Expand Down
4 changes: 4 additions & 0 deletions locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ Settings:
Appearance:
Theme: "主题"
Language: "语言"
Menu: "菜单"
Terminal:
Terminal: "终端"
Behavior: "行为"
Expand Down Expand Up @@ -447,6 +448,9 @@ Settings:
language:
Title: "语言"
Description: "选择应用界面语言。"
menu_auto_collapse:
Title: "自动折叠菜单"
Description: "是否自动折叠菜单。"
terminal:
default_backend:
Title: "默认后端"
Expand Down
69 changes: 50 additions & 19 deletions termua/src/panel/assistant_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use gpui_common::TermuaIcon;
use gpui_component::{
ActiveTheme as _, Disableable as _, Icon, IconName, Sizable as _,
button::{Button, ButtonVariants as _},
dialog::{DialogAction, DialogClose, DialogFooter},
h_flex,
input::{Textarea, TextareaState},
menu::{DropdownMenu as _, PopupMenu, PopupMenuItem},
Expand Down Expand Up @@ -1038,19 +1039,27 @@ impl AssistantPanelView {
cx: &mut Context<gpui_component::Root>,
) {
root.open_dialog(
move |dialog, _window, _app| {
move |dialog, _window, app| {
let cancel_button = Button::new("termua-assistant-run-command-cancel")
.label(t!("Assistant.Dialog.RunInTerminalCancel").to_string())
.debug_selector(|| "termua-assistant-run-command-cancel".to_string());
let run_button = Button::new("termua-assistant-run-command-run")
.label(t!("Assistant.Dialog.RunInTerminalOk").to_string())
.primary()
.debug_selector(|| "termua-assistant-run-command-run".to_string());

dialog
.title(t!("Assistant.Dialog.RunInTerminalTitle").to_string())
.w(px(720.))
.child(Self::run_command_dialog_body(
target_label.clone(),
&command,
app,
))
.button_props(
gpui_component::dialog::DialogButtonProps::default()
.ok_text(t!("Assistant.Dialog.RunInTerminalOk").to_string())
.cancel_text(t!("Assistant.Dialog.RunInTerminalCancel").to_string())
.show_cancel(true),
.footer(
DialogFooter::new()
.child(DialogClose::new().child(cancel_button))
.child(DialogAction::new().child(run_button)),
)
.on_ok({
let this = this.clone();
Expand All @@ -1073,15 +1082,25 @@ impl AssistantPanelView {
);
}

fn run_command_dialog_body(target_label: String, command: &str) -> AnyElement {
fn run_command_dialog_body(target_label: String, command: &str, app: &App) -> AnyElement {
let command_md = format!("```sh\n{command}\n```");
let field_label_color = app.theme().muted_foreground;
let card_border = app.theme().border.opacity(0.8);
let card_background = app.theme().border.opacity(0.12);

v_flex()
.gap_2()
.pt_2()
.gap_4()
.child(
h_flex()
.gap_2()
.items_start()
.child(div().child(t!("Assistant.Label.Target").to_string()))
.gap_3()
.items_center()
.child(
div()
.text_xs()
.text_color(field_label_color)
.child(t!("Assistant.Label.Target").to_string()),
)
.child(
div().min_w_0().child(
TextView::markdown("termua-assistant-run-target", target_label)
Expand All @@ -1090,15 +1109,27 @@ impl AssistantPanelView {
),
)
.child(
h_flex()
.gap_2()
.items_start()
.child(div().child(t!("Assistant.Label.Command").to_string()))
v_flex()
.gap_1()
.child(
div().min_w_0().child(
TextView::markdown("termua-assistant-run-command", command_md)
.selectable(true),
),
div()
.text_xs()
.text_color(field_label_color)
.child(t!("Assistant.Label.Command").to_string()),
)
.child(
div()
.w_full()
.min_w_0()
.p_3()
.rounded_lg()
.border_1()
.border_color(card_border)
.bg(card_background)
.child(
TextView::markdown("termua-assistant-run-command", command_md)
.selectable(true),
),
),
)
.into_any_element()
Expand Down
18 changes: 17 additions & 1 deletion termua/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ where
language
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct AppearanceSettings {
pub theme: ThemeMode,
Expand All @@ -151,6 +151,20 @@ pub struct AppearanceSettings {
pub light_theme: Option<String>,
/// Name of the selected dark theme config (from ThemeRegistry). None = registry default.
pub dark_theme: Option<String>,
/// Whether the in-window application menu collapses to the menu icon.
pub menu_auto_collapse: bool,
}

impl Default for AppearanceSettings {
fn default() -> Self {
Self {
theme: ThemeMode::default(),
language: Language::default(),
light_theme: None,
dark_theme: None,
menu_auto_collapse: true,
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -740,6 +754,7 @@ impl SettingsFile {

set_language(self.appearance.language, cx);
set_theme_mode(self.appearance.theme, window, cx);
menubar::set_auto_collapse(self.appearance.menu_auto_collapse, cx);
}

pub fn apply_assistant_settings(&self, cx: &mut App) {
Expand Down Expand Up @@ -1100,6 +1115,7 @@ mod tests {
let settings = SettingsFile::load_from_str_lenient("{}").unwrap();
assert_eq!(settings.terminal.copy_on_select, true);
assert_eq!(settings.terminal.option_as_meta, false);
assert!(settings.appearance.menu_auto_collapse);
assert_eq!(settings.terminal.ligatures_enabled(), true);
}

Expand Down
Loading
Loading