From a77256eb2ce6ef9fc81ab3789ba22c004b8153d0 Mon Sep 17 00:00:00 2001 From: wzc520pyfm <1528857653@qq.com> Date: Tue, 27 Oct 2026 16:51:25 +0000 Subject: [PATCH 1/3] Customize how long git branch names appear in the prompt chip Long branch names were always truncated from the start, which hides ticket prefixes. Add a truncation-direction setting and an optional display regex, with Features settings and a command palette toggle. Co-authored-by: wzc520pyfm <1528857653@qq.com> --- app/src/context_chips/display_chip.rs | 66 ++++- app/src/settings_view/features_page.rs | 299 ++++++++++++++++++++++- app/src/settings_view/mod.rs | 1 + app/src/terminal/settings.rs | 21 ++ app/src/util/git_branch_display.rs | 104 ++++++++ app/src/util/git_branch_display_tests.rs | 151 ++++++++++++ app/src/util/mod.rs | 1 + app/src/workspace/view.rs | 8 +- 8 files changed, 634 insertions(+), 17 deletions(-) create mode 100644 app/src/util/git_branch_display.rs create mode 100644 app/src/util/git_branch_display_tests.rs diff --git a/app/src/context_chips/display_chip.rs b/app/src/context_chips/display_chip.rs index e03f4287e77..f5eeb9894d5 100644 --- a/app/src/context_chips/display_chip.rs +++ b/app/src/context_chips/display_chip.rs @@ -51,10 +51,12 @@ use crate::terminal::cli_agent_sessions::CLIAgentSessionsModel; use crate::terminal::input::{MenuPositioning, MenuPositioningProvider}; use crate::terminal::model::session::SessionType; use crate::terminal::model_events::ModelEventDispatcher; +use crate::terminal::settings::{TerminalSettings, TerminalSettingsChangedEvent}; use crate::terminal::view::ambient_agent::AmbientAgentViewModel; use crate::ui_components::blended_colors; use crate::ui_components::icons::Icon; use crate::util::bindings::keybinding_name_to_display_string; +use crate::util::git_branch_display::format_git_branch_name; use crate::util::truncation::truncate_from_beginning; use crate::view_components::action_button::{ActionButtonTheme, NakedTheme}; use crate::view_components::{FeaturePopup, NewFeaturePopupEvent, NewFeaturePopupLabel}; @@ -1129,6 +1131,21 @@ impl DisplayChip { } }); + if matches!( + chip_result.kind, + ContextChipKind::ShellGitBranch | ContextChipKind::GitBranchStatus + ) { + ctx.subscribe_to_model(&TerminalSettings::handle(ctx), |_, _, event, ctx| { + if matches!( + event, + TerminalSettingsChangedEvent::GitBranchNameTruncationSetting { .. } + | TerminalSettingsChangedEvent::GitBranchNamePatternSetting { .. } + ) { + ctx.notify(); + } + }); + } + // Subscribe to ambient agent model changes to re-render when the state changes if let Some(ref ambient_agent_model) = config.ambient_agent_view_model { ctx.subscribe_to_model(ambient_agent_model, |_, _, _, ctx| { @@ -1388,11 +1405,18 @@ impl DisplayChip { let is_interactive = !self.is_shared_session_viewer && !self.is_cli_agent_session_active(app); let is_in_agent_view = self.is_in_agent_view; - let chip_text = self.text.clone(); + let full_branch = self.text.clone(); + let displayed_branch = git_branch_chip_label(&full_branch, app); + let tooltip = if full_branch == displayed_branch { + "Change git branch".to_string() + } else { + format!("Change git branch ({full_branch})") + }; let hover = Hoverable::new(self.mouse_state.clone(), move |state| { let hovered = state.is_hovered() && is_interactive; let mut config = - UdiChipConfig::new_with_icon(Icon::GitBranch, font_color, chip_text.clone()) + UdiChipConfig::new_with_icon(Icon::GitBranch, font_color, displayed_branch.clone()) + .with_truncate_text(false) .with_hovered(hovered); if is_in_agent_view { config = config.for_agent_view(); @@ -1403,7 +1427,7 @@ impl DisplayChip { if state.is_hovered() && is_interactive && !menu_open { let tool_tip = appearance .ui_builder() - .tool_tip("Change git branch".to_string()) + .tool_tip(tooltip.clone()) .build() .finish(); stack.add_positioned_overlay_child(tool_tip, udi_tooltip_positioning()); @@ -1513,20 +1537,22 @@ impl DisplayChip { let font_size = udi_font_size(appearance); let is_interactive = !self.is_shared_session_viewer && !self.is_cli_agent_session_active(app); - let fallback_branch = self.text.clone(); let tracking_status = tracking_status .clone() .or_else(|| GitBranchTrackingStatus::from_display_text(&self.text)); - let tooltip_text = tracking_status + let raw_branch = tracking_status .as_ref() - .map(GitBranchTrackingStatus::tooltip_text); + .map(|status| status.branch.clone()) + .unwrap_or_else(|| self.text.clone()); + let displayed_branch = git_branch_chip_label(&raw_branch, app); + let tooltip_text = match tracking_status.as_ref() { + Some(status) if displayed_branch == raw_branch => Some(status.tooltip_text()), + Some(status) => Some(format!("{} ({raw_branch})", status.tooltip_text())), + None if displayed_branch != raw_branch => Some(raw_branch.clone()), + None => None, + }; let hover = Hoverable::new(self.mouse_state.clone(), move |state| { - let branch = tracking_status - .as_ref() - .map(|status| status.branch.clone()) - .unwrap_or_else(|| fallback_branch.clone()); - let mut content = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center); add_git_branch_status_child( &mut content, @@ -1537,7 +1563,13 @@ impl DisplayChip { ); add_git_branch_status_child( &mut content, - git_branch_status_text(branch, font_color, font_family, font_size, appearance), + git_branch_status_text( + displayed_branch.clone(), + font_color, + font_family, + font_size, + appearance, + ), GIT_BRANCH_STATUS_MAIN_GAP, ); @@ -2352,6 +2384,16 @@ pub(crate) fn chip_container( .with_horizontal_padding(spacing::UDI_CHIP_HORIZONTAL_PADDING) } +fn git_branch_chip_label(branch: &str, app: &AppContext) -> String { + let settings = TerminalSettings::as_ref(app); + format_git_branch_name( + branch, + *settings.git_branch_name_truncation, + settings.git_branch_name_pattern.as_str(), + UDI_CHIP_MAX_NUM_CHARACTERS, + ) +} + pub(crate) fn render_udi_chip(config: UdiChipConfig, appearance: &Appearance) -> Box { let font_size = udi_font_size(appearance); let icon_size = font_size; diff --git a/app/src/settings_view/features_page.rs b/app/src/settings_view/features_page.rs index 1ca19482b78..3e7a5c53fb4 100644 --- a/app/src/settings_view/features_page.rs +++ b/app/src/settings_view/features_page.rs @@ -45,8 +45,8 @@ use super::settings_page::{ render_dropdown_item_label, render_local_only_icon, }; use super::{ - DisplayCount, SettingsAction, SettingsSection, ToggleSettingActionPair, features, flags, - render_beta_chip, + DisplayCount, SettingActionPairContexts, SettingActionPairDescriptions, SettingsAction, + SettingsSection, ToggleSettingActionPair, features, flags, render_beta_chip, }; use crate::appearance::Appearance; use crate::default_terminal::DefaultTerminal; @@ -99,8 +99,10 @@ use crate::terminal::session_settings::{ SessionSettingsChangedEvent, ShouldConfirmCloseSession, }; use crate::terminal::settings::{ - AsyncFindEnabled, MaximumGridSize, Osc52ClipboardAccess, Osc52ClipboardAccessSetting, - ShowTerminalZeroStateBlock, TerminalSettings, TerminalSettingsChangedEvent, UseAudibleBell, + AsyncFindEnabled, GitBranchNamePatternSetting, GitBranchNameTruncation, + GitBranchNameTruncationSetting, MaximumGridSize, Osc52ClipboardAccess, + Osc52ClipboardAccessSetting, ShowTerminalZeroStateBlock, TerminalSettings, + TerminalSettingsChangedEvent, UseAudibleBell, }; use crate::terminal::{BlockListSettings, PreserveInputFocusOnBlockSelection, SnackbarEnabled}; use crate::undo_close::UndoCloseSettings; @@ -108,6 +110,7 @@ use crate::user_config::{WarpConfig, WarpConfigUpdateEvent}; use crate::util::bindings::{ keybinding_name_to_display_string, reset_keybinding_to_default, set_custom_keybinding, }; +use crate::util::git_branch_display::git_branch_name_pattern_is_valid; use crate::view_components::{Dropdown, DropdownItem, FilterableDropdown}; use crate::workspace::WorkspaceAction; use crate::workspace::tab_settings::{NewTabPlacement, TabSettings, TabSettingsChangedEvent}; @@ -296,6 +299,20 @@ pub fn init_actions_from_parent_view( .use_audible_bell .is_supported_on_current_platform(), ), + ToggleSettingActionPair::custom( + SettingActionPairDescriptions::new( + "Show the start of long git branch names", + "Show the end of long git branch names", + ), + builder(SettingsAction::FeaturesPageToggle( + FeaturesPageAction::ToggleGitBranchNameTruncation, + )), + SettingActionPairContexts::new( + context.clone() & !id!(flags::GIT_BRANCH_NAME_SHOW_START), + context.clone() & id!(flags::GIT_BRANCH_NAME_SHOW_START), + ), + None, + ), ToggleSettingActionPair::new( "autosuggestions", builder(SettingsAction::FeaturesPageToggle( @@ -815,6 +832,9 @@ pub enum FeaturesPageAction { SetPreferredGraphicsBackend(Option), SetNewTabPlacement(NewTabPlacement), SetOsc52ClipboardAccess(Osc52ClipboardAccess), + SetGitBranchNameTruncation(GitBranchNameTruncation), + ToggleGitBranchNameTruncation, + SetGitBranchNamePattern, SetDefaultSessionMode(DefaultSessionMode), SetDefaultTabConfig(String), SearchForKeybinding(String), @@ -859,6 +879,7 @@ const NOTIFICATIONS_DOCS_URL: &str = "https://docs.warp.dev/terminal/more-featur const QUAKE_DROPDOWN_WIDTH: f32 = 130.; const MAX_BLOCK_SIZE_INPUT_BOX_WIDTH: f32 = 80.; +const GIT_BRANCH_PATTERN_INPUT_WIDTH: f32 = 280.; const MIN_MAX_GRID_SIZE: usize = 100; @@ -1242,6 +1263,30 @@ impl FeaturesPageAction { action: "SetOsc52ClipboardAccess".to_string(), value: format!("{access:?}"), }, + Self::SetGitBranchNameTruncation(truncation) => TelemetryEvent::FeaturesPageAction { + action: "SetGitBranchNameTruncation".to_string(), + value: format!("{truncation:?}"), + }, + Self::ToggleGitBranchNameTruncation => TelemetryEvent::FeaturesPageAction { + action: "ToggleGitBranchNameTruncation".to_string(), + value: format!( + "{:?}", + *TerminalSettings::as_ref(ctx).git_branch_name_truncation + ), + }, + Self::SetGitBranchNamePattern => { + let is_empty = TerminalSettings::as_ref(ctx) + .git_branch_name_pattern + .is_empty(); + TelemetryEvent::FeaturesPageAction { + action: "SetGitBranchNamePattern".to_string(), + value: if is_empty { + "empty".to_string() + } else { + "custom".to_string() + }, + } + } Self::SetDefaultSessionMode(mode) => TelemetryEvent::FeaturesPageAction { action: "SetDefaultSessionMode".to_string(), value: format!("{mode:?}"), @@ -1448,6 +1493,9 @@ pub struct FeaturesPageView { graphics_backend_dropdown: ViewHandle>, new_tab_placement_dropdown: ViewHandle>, osc52_clipboard_access_dropdown: ViewHandle>, + git_branch_name_truncation_dropdown: ViewHandle>, + git_branch_name_pattern_editor: ViewHandle, + git_branch_pattern_valid: bool, default_session_mode_dropdown: ViewHandle>, tab_behavior: Tracked, completions_keystroke: Tracked, @@ -2025,6 +2073,26 @@ impl TypedActionView for FeaturesPageView { ); }); } + SetGitBranchNameTruncation(truncation) => { + TerminalSettings::handle(ctx).update(ctx, |terminal_settings, ctx| { + report_if_error!( + terminal_settings + .git_branch_name_truncation + .set_value(*truncation, ctx) + ); + }); + } + ToggleGitBranchNameTruncation => { + TerminalSettings::handle(ctx).update(ctx, |terminal_settings, ctx| { + let next = terminal_settings.git_branch_name_truncation.toggled(); + report_if_error!( + terminal_settings + .git_branch_name_truncation + .set_value(next, ctx) + ); + }); + } + SetGitBranchNamePattern => self.save_git_branch_name_pattern(ctx), SetDefaultSessionMode(mode) => self.set_default_session_mode(mode, ctx), SetDefaultTabConfig(path) => { AISettings::handle(ctx).update(ctx, |ai_settings, ctx| { @@ -2386,6 +2454,31 @@ impl FeaturesPageView { ctx, ); } + if matches!( + event, + TerminalSettingsChangedEvent::GitBranchNameTruncationSetting { .. } + ) { + Self::update_git_branch_name_truncation_dropdown( + me.git_branch_name_truncation_dropdown.clone(), + ctx, + ); + } + if matches!( + event, + TerminalSettingsChangedEvent::GitBranchNamePatternSetting { .. } + ) { + let saved = terminal_settings + .as_ref(ctx) + .git_branch_name_pattern + .clone(); + me.git_branch_pattern_valid = git_branch_name_pattern_is_valid(&saved); + me.git_branch_name_pattern_editor + .update(ctx, |editor, ctx| { + if editor.buffer_text(ctx) != saved { + editor.set_buffer_text(&saved, ctx); + } + }); + } ctx.notify() }, ); @@ -2469,6 +2562,12 @@ impl FeaturesPageView { let osc52_clipboard_access_dropdown = ctx.add_typed_action_view(Dropdown::new); Self::update_osc52_clipboard_access_dropdown(osc52_clipboard_access_dropdown.clone(), ctx); + let git_branch_name_truncation_dropdown = ctx.add_typed_action_view(Dropdown::new); + Self::update_git_branch_name_truncation_dropdown( + git_branch_name_truncation_dropdown.clone(), + ctx, + ); + ctx.subscribe_to_model(&TabSettings::handle(ctx), |me, _, event, ctx| { if matches!(event, TabSettingsChangedEvent::NewTabPlacement { .. }) { Self::update_new_tab_placement_dropdown(me.new_tab_placement_dropdown.clone(), ctx); @@ -2632,6 +2731,20 @@ impl FeaturesPageView { me.handle_block_size_editor_event(event, ctx); }); + let git_branch_name_pattern_editor = ctx.add_typed_action_view(|ctx| { + EditorView::single_line(width_and_height_editor_options.clone(), ctx) + }); + git_branch_name_pattern_editor.update(ctx, |editor, ctx| { + let pattern = TerminalSettings::as_ref(ctx) + .git_branch_name_pattern + .clone(); + editor.set_placeholder_text("^([A-Z]+-[0-9]+)", ctx); + editor.set_buffer_text(&pattern, ctx); + }); + ctx.subscribe_to_view(&git_branch_name_pattern_editor, |me, _, event, ctx| { + me.handle_git_branch_name_pattern_editor_event(event, ctx) + }); + let mouse_scroll_input_editor = ctx.add_typed_action_view(|ctx| { EditorView::single_line(width_and_height_editor_options.clone(), ctx) }); @@ -2753,6 +2866,13 @@ impl FeaturesPageView { graphics_backend_dropdown, new_tab_placement_dropdown, osc52_clipboard_access_dropdown, + git_branch_name_truncation_dropdown, + git_branch_name_pattern_editor, + git_branch_pattern_valid: git_branch_name_pattern_is_valid( + TerminalSettings::as_ref(ctx) + .git_branch_name_pattern + .as_str(), + ), default_session_mode_dropdown, tab_behavior: Default::default(), @@ -3054,6 +3174,8 @@ impl FeaturesPageView { terminal_widgets.push(Box::new(SmartSelectWidget::default())); terminal_widgets.push(Box::new(CopyOnSelectWidget::default())); terminal_widgets.push(Box::new(Osc52ClipboardAccessWidget::default())); + terminal_widgets.push(Box::new(GitBranchNameTruncationWidget::default())); + terminal_widgets.push(Box::new(GitBranchNamePatternWidget::default())); terminal_widgets.push(Box::new(NewTabPlacementWidget::default())); let mut system_widgets: Vec>> = vec![]; @@ -3713,6 +3835,77 @@ impl FeaturesPageView { }); } + fn update_git_branch_name_truncation_dropdown( + dropdown: ViewHandle>, + ctx: &mut ViewContext, + ) { + dropdown.update(ctx, |dropdown, ctx| { + let values = vec![GitBranchNameTruncation::End, GitBranchNameTruncation::Start]; + let current_value = *TerminalSettings::as_ref(ctx).git_branch_name_truncation; + let selected_index = values + .iter() + .position(|val| *val == current_value) + .unwrap_or(0); + + dropdown.set_items( + values + .into_iter() + .map(|val| { + DropdownItem::new( + val.as_dropdown_label(), + FeaturesPageAction::SetGitBranchNameTruncation(val), + ) + }) + .collect(), + ctx, + ); + dropdown.set_selected_by_index(selected_index, ctx); + }); + } + + fn handle_git_branch_name_pattern_editor_event( + &mut self, + event: &EditorEvent, + ctx: &mut ViewContext, + ) { + match event { + EditorEvent::Edited(_) => { + let pattern = self + .git_branch_name_pattern_editor + .as_ref(ctx) + .buffer_text(ctx); + self.git_branch_pattern_valid = git_branch_name_pattern_is_valid(&pattern); + ctx.notify(); + } + EditorEvent::Enter | EditorEvent::Blurred => { + ctx.dispatch_typed_action(&FeaturesPageAction::SetGitBranchNamePattern); + } + EditorEvent::Escape => ctx.emit(FeaturesSettingsPageEvent::FocusModal), + _ => {} + } + } + + fn save_git_branch_name_pattern(&mut self, ctx: &mut ViewContext) { + let pattern = self + .git_branch_name_pattern_editor + .as_ref(ctx) + .buffer_text(ctx); + let pattern = pattern.trim().to_string(); + if !git_branch_name_pattern_is_valid(&pattern) { + self.git_branch_pattern_valid = false; + ctx.notify(); + return; + } + self.git_branch_pattern_valid = true; + TerminalSettings::handle(ctx).update(ctx, |terminal_settings, ctx| { + report_if_error!( + terminal_settings + .git_branch_name_pattern + .set_value(pattern, ctx) + ); + }); + } + fn update_default_session_mode_dropdown( dropdown: ViewHandle>, ctx: &mut ViewContext, @@ -7407,6 +7600,104 @@ impl SettingsWidget for Osc52ClipboardAccessWidget { } } +#[derive(Default)] +struct GitBranchNameTruncationWidget {} + +impl SettingsWidget for GitBranchNameTruncationWidget { + type View = FeaturesPageView; + + fn search_terms(&self) -> &str { + "git branch name truncation prompt chip ticket prefix start end" + } + + fn render( + &self, + view: &Self::View, + appearance: &Appearance, + app: &AppContext, + ) -> Box { + render_dropdown_item( + appearance, + "Long git branch names", + Some("Choose which part of a long git branch name stays visible in the prompt."), + None, + LocalOnlyIconState::for_setting( + GitBranchNameTruncationSetting::storage_key(), + GitBranchNameTruncationSetting::sync_to_cloud(), + &mut view + .button_mouse_states + .local_only_icon_tooltip_states + .borrow_mut(), + app, + ), + None, + &view.git_branch_name_truncation_dropdown, + ) + } +} + +#[derive(Default)] +struct GitBranchNamePatternWidget {} + +impl SettingsWidget for GitBranchNamePatternWidget { + type View = FeaturesPageView; + + fn search_terms(&self) -> &str { + "git branch display pattern regex ticket id prompt chip" + } + + fn render( + &self, + view: &Self::View, + appearance: &Appearance, + app: &AppContext, + ) -> Box { + let border_color: Option = if view.git_branch_pattern_valid { + None + } else { + Some(themes::theme::Fill::error().into()) + }; + let input_field = appearance + .ui_builder() + .text_input(view.git_branch_name_pattern_editor.clone()) + .with_style(UiComponentStyles { + width: Some(GIT_BRANCH_PATTERN_INPUT_WIDTH), + padding: Some(Coords { + top: 4., + bottom: 4., + left: 6., + right: 6., + }), + background: Some(appearance.theme().surface_2().into()), + border_color, + ..Default::default() + }) + .build() + .finish(); + + render_body_item::( + "Git branch display pattern".into(), + None, + LocalOnlyIconState::for_setting( + GitBranchNamePatternSetting::storage_key(), + GitBranchNamePatternSetting::sync_to_cloud(), + &mut view + .button_mouse_states + .local_only_icon_tooltip_states + .borrow_mut(), + app, + ), + ToggleState::Enabled, + appearance, + input_field, + Some( + "Optional regular expression. The prompt shows the first capture group, or the whole match when there is no group. For example, ^([A-Z]+-[0-9]+) shows ABC-123 from ABC-123-some-long-feature." + .to_string(), + ), + ) + } +} + #[derive(Default)] struct NewTabPlacementWidget {} diff --git a/app/src/settings_view/mod.rs b/app/src/settings_view/mod.rs index 18632a97c66..d749a8b18b7 100644 --- a/app/src/settings_view/mod.rs +++ b/app/src/settings_view/mod.rs @@ -570,6 +570,7 @@ pub mod flags { pub const SYNC_ALL_TABS_FLAG: &str = "Sync_All_Tabs_Enabled"; pub const SYNC_ALL_PANES_IN_CURRENT_TAB: &str = "Sync_All_Panes_In_Current_Tab"; pub const USE_AUDIBLE_BELL_CONTEXT_FLAG: &str = "Use_Audible_Terminal_Bell"; + pub const GIT_BRANCH_NAME_SHOW_START: &str = "Git_Branch_Name_Show_Start"; pub const SHOW_INPUT_HINT_TEXT_CONTEXT_FLAG: &str = "Show_Input_Hint_text"; pub const SHOW_AGENT_TIPS_FLAG: &str = "Show_Agent_Tips"; pub const SHOW_OZ_UPDATES_IN_ZERO_STATE_FLAG: &str = "Show_Oz_Updates_In_Zero_State"; diff --git a/app/src/terminal/settings.rs b/app/src/terminal/settings.rs index 81c398e2802..07dc28da957 100644 --- a/app/src/terminal/settings.rs +++ b/app/src/terminal/settings.rs @@ -6,6 +6,7 @@ use warpui::units::Pixels; use warpui::{AppContext, SingletonEntity}; use crate::settings::{AISettings, InputSettings, TerminalSpacing}; +pub use crate::util::git_branch_display::GitBranchNameTruncation; #[derive( Clone, @@ -206,6 +207,26 @@ define_settings_group!(TerminalSettings, settings: [ toml_path: "experimental.async_find_enabled", description: "Use an improved implementation of find to keep the UI responsive while searching for matches on large outputs.", }, + git_branch_name_truncation: GitBranchNameTruncationSetting { + type: GitBranchNameTruncation, + default: GitBranchNameTruncation::default(), + supported_platforms: SupportedPlatforms::ALL, + sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), + surface: settings::SettingSurfaces::GUI, + private: false, + toml_path: "terminal.prompt.git_branch_name_truncation", + description: "Which end of a long git branch name stays visible in the prompt chip. `end` keeps the end of the name. `start` keeps the beginning, such as a ticket id.", + }, + git_branch_name_pattern: GitBranchNamePatternSetting { + type: String, + default: String::new(), + supported_platforms: SupportedPlatforms::ALL, + sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), + surface: settings::SettingSurfaces::GUI, + private: false, + toml_path: "terminal.prompt.git_branch_name_pattern", + description: "Optional regular expression applied to the git branch name before the prompt chip shows it. When the pattern matches, the first capture group is displayed, or the whole match when there is no capture group. Leave empty to show the full branch name. Example: ^([A-Z]+-[0-9]+) shows ABC-123 from ABC-123-some-long-feature.", + }, ]); impl TerminalSettings { diff --git a/app/src/util/git_branch_display.rs b/app/src/util/git_branch_display.rs new file mode 100644 index 00000000000..3bf1c72f02f --- /dev/null +++ b/app/src/util/git_branch_display.rs @@ -0,0 +1,104 @@ +use regex::Regex; +use serde::{Deserialize, Serialize}; + +use super::truncation::{truncate_from_beginning, truncate_from_end}; + +/// Maximum length of a user-supplied branch display pattern. +const MAX_GIT_BRANCH_NAME_PATTERN_LEN: usize = 256; + +/// Which end of a long git branch name the prompt chip keeps. +#[derive( + Clone, + Copy, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + schemars::JsonSchema, + settings_value::SettingsValue, +)] +#[schemars( + description = "Which end of a long git branch name the prompt chip keeps.", + rename_all = "snake_case" +)] +pub enum GitBranchNameTruncation { + /// Keep the end of the name. The start is replaced with an ellipsis. + #[default] + #[schemars(description = "Keep the end of a long branch name.")] + End, + /// Keep the start of the name, such as a ticket id. + #[schemars(description = "Keep the start of a long branch name.")] + Start, +} + +impl GitBranchNameTruncation { + pub fn as_dropdown_label(self) -> &'static str { + match self { + Self::End => "End of name", + Self::Start => "Start of name", + } + } + + pub fn toggled(self) -> Self { + match self { + Self::End => Self::Start, + Self::Start => Self::End, + } + } +} + +/// Whether `pattern` can be saved as a git branch display pattern. +/// +/// An empty pattern is valid and means the full branch name is shown. +pub fn git_branch_name_pattern_is_valid(pattern: &str) -> bool { + let pattern = pattern.trim(); + if pattern.len() > MAX_GIT_BRANCH_NAME_PATTERN_LEN { + return false; + } + pattern.is_empty() || Regex::new(pattern).is_ok() +} + +/// Formats a git branch name for the prompt chip. +/// +/// A non-empty `pattern` replaces the name with the first regex capture group, +/// or the whole match when the pattern has no groups. An empty, invalid, or +/// non-matching pattern leaves the branch name unchanged. The result is then +/// shortened to `max_chars`. +pub fn format_git_branch_name( + branch: &str, + truncation: GitBranchNameTruncation, + pattern: &str, + max_chars: usize, +) -> String { + let displayed = matched_branch_text(branch, pattern).unwrap_or_else(|| branch.to_string()); + match truncation { + GitBranchNameTruncation::End => truncate_from_beginning(&displayed, max_chars), + GitBranchNameTruncation::Start => truncate_from_end(&displayed, max_chars), + } +} + +/// The branch text selected by `pattern`, when the pattern matches. +fn matched_branch_text(branch: &str, pattern: &str) -> Option { + let pattern = pattern.trim(); + if pattern.is_empty() || pattern.len() > MAX_GIT_BRANCH_NAME_PATTERN_LEN { + return None; + } + let re = Regex::new(pattern).ok()?; + let caps = re.captures(branch)?; + let text = if re.captures_len() > 1 { + caps.get(1)?.as_str() + } else { + caps.get(0)?.as_str() + }; + if text.is_empty() { + None + } else { + Some(text.to_string()) + } +} + +#[cfg(test)] +#[path = "git_branch_display_tests.rs"] +mod tests; diff --git a/app/src/util/git_branch_display_tests.rs b/app/src/util/git_branch_display_tests.rs new file mode 100644 index 00000000000..ab7c61cba7a --- /dev/null +++ b/app/src/util/git_branch_display_tests.rs @@ -0,0 +1,151 @@ +use settings_value::SettingsValue; + +use super::{GitBranchNameTruncation, format_git_branch_name, git_branch_name_pattern_is_valid}; +use crate::util::truncation::truncate_from_beginning; + +const LONG_BRANCH: &str = "ABC-123-some-long-feature-that-needs-to-be-implemented"; + +#[test] +fn end_truncation_matches_existing_beginning_truncation() { + let displayed = format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::End, "", 40); + assert_eq!(displayed, truncate_from_beginning(LONG_BRANCH, 40)); + assert!(!displayed.contains("ABC-123")); + assert!(displayed.starts_with('…')); +} + +#[test] +fn start_truncation_keeps_the_ticket_prefix() { + let displayed = format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::Start, "", 40); + assert!(displayed.starts_with("ABC-123")); + assert!(displayed.ends_with('…')); + assert_eq!(displayed.chars().count(), 40); +} + +#[test] +fn short_names_are_not_truncated() { + let branch = "main"; + assert_eq!( + format_git_branch_name(branch, GitBranchNameTruncation::End, "", 40), + branch + ); + assert_eq!( + format_git_branch_name(branch, GitBranchNameTruncation::Start, "", 40), + branch + ); +} + +#[test] +fn capture_group_shows_only_the_ticket_id() { + let displayed = format_git_branch_name( + LONG_BRANCH, + GitBranchNameTruncation::End, + r"^([A-Z]+-\d+)", + 40, + ); + assert_eq!(displayed, "ABC-123"); +} + +#[test] +fn pattern_without_groups_shows_the_whole_match() { + let displayed = + format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::Start, r"ABC-\d+", 40); + assert_eq!(displayed, "ABC-123"); +} + +#[test] +fn unmatched_pattern_falls_back_to_the_full_name() { + let displayed = format_git_branch_name( + LONG_BRANCH, + GitBranchNameTruncation::Start, + r"^TICKET-(\d+)", + 40, + ); + assert_eq!( + displayed, + format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::Start, "", 40) + ); +} + +#[test] +fn invalid_pattern_falls_back_to_the_full_name() { + let displayed = + format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::End, r"(unclosed", 40); + assert_eq!( + displayed, + format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::End, "", 40) + ); +} + +#[test] +fn whitespace_around_a_pattern_is_ignored() { + let displayed = format_git_branch_name( + LONG_BRANCH, + GitBranchNameTruncation::End, + " ^([A-Z]+-\\d+) ", + 40, + ); + assert_eq!(displayed, "ABC-123"); +} + +#[test] +fn unmatched_capture_group_keeps_the_full_name() { + let displayed = format_git_branch_name( + "feature-without-ticket", + GitBranchNameTruncation::End, + r"^([A-Z]+-\d+)?", + 40, + ); + assert_eq!(displayed, "feature-without-ticket"); +} + +#[test] +fn unicode_start_truncation_keeps_char_boundaries() { + let branch = "票-1-很长的分支名字需要被截断才能放进提示"; + let displayed = format_git_branch_name(branch, GitBranchNameTruncation::Start, "", 8); + assert!(displayed.starts_with("票-1")); + assert!(displayed.ends_with('…')); + assert_eq!(displayed.chars().count(), 8); + assert!(branch.starts_with(displayed.trim_end_matches('…'))); +} + +#[test] +fn pattern_validity_accepts_empty_and_rejects_broken_regex() { + assert!(git_branch_name_pattern_is_valid("")); + assert!(git_branch_name_pattern_is_valid(" ")); + assert!(git_branch_name_pattern_is_valid(r"^([A-Z]+-\d+)")); + assert!(!git_branch_name_pattern_is_valid(r"(unclosed")); + assert!(!git_branch_name_pattern_is_valid(&"a".repeat(257))); +} + +#[test] +fn truncation_serializes_as_snake_case() { + assert_eq!( + GitBranchNameTruncation::default(), + GitBranchNameTruncation::End + ); + assert_eq!( + GitBranchNameTruncation::End.to_file_value(), + serde_json::json!("end") + ); + assert_eq!( + GitBranchNameTruncation::Start.to_file_value(), + serde_json::json!("start") + ); + assert_eq!( + GitBranchNameTruncation::from_file_value(&serde_json::json!("start")).unwrap(), + GitBranchNameTruncation::Start + ); + assert!(GitBranchNameTruncation::from_file_value(&serde_json::json!("middle")).is_none()); +} + +#[test] +fn toggled_switches_between_ends() { + assert_eq!( + GitBranchNameTruncation::End.toggled(), + GitBranchNameTruncation::Start + ); + assert_eq!( + GitBranchNameTruncation::Start.toggled(), + GitBranchNameTruncation::End + ); +} diff --git a/app/src/util/mod.rs b/app/src/util/mod.rs index 2ed14db4899..fa039bf7162 100644 --- a/app/src/util/mod.rs +++ b/app/src/util/mod.rs @@ -5,6 +5,7 @@ pub mod extensions; #[cfg(feature = "local_fs")] pub mod file; pub mod git; +pub(crate) mod git_branch_display; pub mod image; pub(crate) mod link_detection; pub mod links; diff --git a/app/src/workspace/view.rs b/app/src/workspace/view.rs index c25484fab76..5eb128d0433 100644 --- a/app/src/workspace/view.rs +++ b/app/src/workspace/view.rs @@ -418,7 +418,7 @@ use crate::terminal::session_settings::{ NewSessionSource, NotificationsMode, NotificationsSettings, SessionSettings, SessionSettingsChangedEvent, WorkingDirectoryMode, }; -use crate::terminal::settings::{SpacingMode, TerminalSettings}; +use crate::terminal::settings::{GitBranchNameTruncation, SpacingMode, TerminalSettings}; #[cfg(feature = "integration_tests")] use crate::terminal::shared_session::SharedSessionSource; use crate::terminal::shared_session::{IsSharedSessionCreator, SharedSessionActionSource}; @@ -23601,6 +23601,12 @@ impl Workspace { if *terminal_settings.use_audible_bell { context.set.insert(flags::USE_AUDIBLE_BELL_CONTEXT_FLAG); } + if matches!( + *terminal_settings.git_branch_name_truncation, + GitBranchNameTruncation::Start + ) { + context.set.insert(flags::GIT_BRANCH_NAME_SHOW_START); + } if *terminal_settings.show_terminal_zero_state_block.value() { context .set From c141063df2471cb23972935b74377be268cc784b Mon Sep 17 00:00:00 2001 From: wzc520pyfm <1528857653@qq.com> Date: Wed, 28 Oct 2026 02:31:19 +0000 Subject: [PATCH 2/3] Save the git branch display pattern when the settings field commits The features page is mid-update while the editor blur or enter event is delivered, so a synchronous typed-action dispatch never reached the handler and the pattern was discarded. Co-authored-by: wzc520pyfm <1528857653@qq.com> --- app/src/settings_view/features_page.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/settings_view/features_page.rs b/app/src/settings_view/features_page.rs index 3e7a5c53fb4..59ae0848993 100644 --- a/app/src/settings_view/features_page.rs +++ b/app/src/settings_view/features_page.rs @@ -3878,7 +3878,9 @@ impl FeaturesPageView { ctx.notify(); } EditorEvent::Enter | EditorEvent::Blurred => { - ctx.dispatch_typed_action(&FeaturesPageAction::SetGitBranchNamePattern); + // The features page is mid-update while this editor event is + // delivered, so a synchronous dispatch never reaches its handler. + ctx.dispatch_typed_action_deferred(FeaturesPageAction::SetGitBranchNamePattern); } EditorEvent::Escape => ctx.emit(FeaturesSettingsPageEvent::FocusModal), _ => {} From 5127c82ffdec2013227bf85cdce6df0b4e1efa70 Mon Sep 17 00:00:00 2001 From: wzc520pyfm <1528857653@qq.com> Date: Wed, 28 Oct 2026 03:16:27 +0000 Subject: [PATCH 3/3] Keep an ellipsis when shortening long git branch names Drop the display regex. The prompt still hides the start of a long name by default, and a setting can hide the end instead. A separate pair of character counts hides the middle while keeping both edges. Co-authored-by: wzc520pyfm <1528857653@qq.com> --- app/src/context_chips/display_chip.rs | 6 +- app/src/settings_view/features_page.rs | 335 ++++++++++++++++------- app/src/terminal/settings.rs | 27 +- app/src/util/git_branch_display.rs | 125 +++++---- app/src/util/git_branch_display_tests.rs | 166 +++++------ app/src/workspace/view.rs | 2 +- 6 files changed, 415 insertions(+), 246 deletions(-) diff --git a/app/src/context_chips/display_chip.rs b/app/src/context_chips/display_chip.rs index f5eeb9894d5..7ea03998572 100644 --- a/app/src/context_chips/display_chip.rs +++ b/app/src/context_chips/display_chip.rs @@ -1139,7 +1139,8 @@ impl DisplayChip { if matches!( event, TerminalSettingsChangedEvent::GitBranchNameTruncationSetting { .. } - | TerminalSettingsChangedEvent::GitBranchNamePatternSetting { .. } + | TerminalSettingsChangedEvent::GitBranchNamePrefixChars { .. } + | TerminalSettingsChangedEvent::GitBranchNameSuffixChars { .. } ) { ctx.notify(); } @@ -2389,7 +2390,8 @@ fn git_branch_chip_label(branch: &str, app: &AppContext) -> String { format_git_branch_name( branch, *settings.git_branch_name_truncation, - settings.git_branch_name_pattern.as_str(), + *settings.git_branch_name_prefix_chars, + *settings.git_branch_name_suffix_chars, UDI_CHIP_MAX_NUM_CHARACTERS, ) } diff --git a/app/src/settings_view/features_page.rs b/app/src/settings_view/features_page.rs index 59ae0848993..55473de365c 100644 --- a/app/src/settings_view/features_page.rs +++ b/app/src/settings_view/features_page.rs @@ -99,7 +99,7 @@ use crate::terminal::session_settings::{ SessionSettingsChangedEvent, ShouldConfirmCloseSession, }; use crate::terminal::settings::{ - AsyncFindEnabled, GitBranchNamePatternSetting, GitBranchNameTruncation, + AsyncFindEnabled, GitBranchNamePrefixChars, GitBranchNameTruncation, GitBranchNameTruncationSetting, MaximumGridSize, Osc52ClipboardAccess, Osc52ClipboardAccessSetting, ShowTerminalZeroStateBlock, TerminalSettings, TerminalSettingsChangedEvent, UseAudibleBell, @@ -110,7 +110,7 @@ use crate::user_config::{WarpConfig, WarpConfigUpdateEvent}; use crate::util::bindings::{ keybinding_name_to_display_string, reset_keybinding_to_default, set_custom_keybinding, }; -use crate::util::git_branch_display::git_branch_name_pattern_is_valid; +use crate::util::git_branch_display::GIT_BRANCH_NAME_EDGE_CHARS_LIMIT; use crate::view_components::{Dropdown, DropdownItem, FilterableDropdown}; use crate::workspace::WorkspaceAction; use crate::workspace::tab_settings::{NewTabPlacement, TabSettings, TabSettingsChangedEvent}; @@ -834,7 +834,6 @@ pub enum FeaturesPageAction { SetOsc52ClipboardAccess(Osc52ClipboardAccess), SetGitBranchNameTruncation(GitBranchNameTruncation), ToggleGitBranchNameTruncation, - SetGitBranchNamePattern, SetDefaultSessionMode(DefaultSessionMode), SetDefaultTabConfig(String), SearchForKeybinding(String), @@ -879,7 +878,7 @@ const NOTIFICATIONS_DOCS_URL: &str = "https://docs.warp.dev/terminal/more-featur const QUAKE_DROPDOWN_WIDTH: f32 = 130.; const MAX_BLOCK_SIZE_INPUT_BOX_WIDTH: f32 = 80.; -const GIT_BRANCH_PATTERN_INPUT_WIDTH: f32 = 280.; +const GIT_BRANCH_EDGE_CHARS_INPUT_WIDTH: f32 = 48.; const MIN_MAX_GRID_SIZE: usize = 100; @@ -1274,19 +1273,6 @@ impl FeaturesPageAction { *TerminalSettings::as_ref(ctx).git_branch_name_truncation ), }, - Self::SetGitBranchNamePattern => { - let is_empty = TerminalSettings::as_ref(ctx) - .git_branch_name_pattern - .is_empty(); - TelemetryEvent::FeaturesPageAction { - action: "SetGitBranchNamePattern".to_string(), - value: if is_empty { - "empty".to_string() - } else { - "custom".to_string() - }, - } - } Self::SetDefaultSessionMode(mode) => TelemetryEvent::FeaturesPageAction { action: "SetDefaultSessionMode".to_string(), value: format!("{mode:?}"), @@ -1494,8 +1480,10 @@ pub struct FeaturesPageView { new_tab_placement_dropdown: ViewHandle>, osc52_clipboard_access_dropdown: ViewHandle>, git_branch_name_truncation_dropdown: ViewHandle>, - git_branch_name_pattern_editor: ViewHandle, - git_branch_pattern_valid: bool, + git_branch_name_prefix_chars_editor: ViewHandle, + git_branch_name_suffix_chars_editor: ViewHandle, + git_branch_prefix_chars_valid: bool, + git_branch_suffix_chars_valid: bool, default_session_mode_dropdown: ViewHandle>, tab_behavior: Tracked, completions_keystroke: Tracked, @@ -2092,7 +2080,6 @@ impl TypedActionView for FeaturesPageView { ); }); } - SetGitBranchNamePattern => self.save_git_branch_name_pattern(ctx), SetDefaultSessionMode(mode) => self.set_default_session_mode(mode, ctx), SetDefaultTabConfig(path) => { AISettings::handle(ctx).update(ctx, |ai_settings, ctx| { @@ -2465,19 +2452,27 @@ impl FeaturesPageView { } if matches!( event, - TerminalSettingsChangedEvent::GitBranchNamePatternSetting { .. } + TerminalSettingsChangedEvent::GitBranchNamePrefixChars { .. } ) { - let saved = terminal_settings - .as_ref(ctx) - .git_branch_name_pattern - .clone(); - me.git_branch_pattern_valid = git_branch_name_pattern_is_valid(&saved); - me.git_branch_name_pattern_editor - .update(ctx, |editor, ctx| { - if editor.buffer_text(ctx) != saved { - editor.set_buffer_text(&saved, ctx); - } - }); + let saved = *terminal_settings.as_ref(ctx).git_branch_name_prefix_chars; + me.git_branch_prefix_chars_valid = true; + me.sync_git_branch_edge_chars_editor( + me.git_branch_name_prefix_chars_editor.clone(), + saved, + ctx, + ); + } + if matches!( + event, + TerminalSettingsChangedEvent::GitBranchNameSuffixChars { .. } + ) { + let saved = *terminal_settings.as_ref(ctx).git_branch_name_suffix_chars; + me.git_branch_suffix_chars_valid = true; + me.sync_git_branch_edge_chars_editor( + me.git_branch_name_suffix_chars_editor.clone(), + saved, + ctx, + ); } ctx.notify() }, @@ -2731,18 +2726,36 @@ impl FeaturesPageView { me.handle_block_size_editor_event(event, ctx); }); - let git_branch_name_pattern_editor = ctx.add_typed_action_view(|ctx| { + let git_branch_name_prefix_chars_editor = ctx.add_typed_action_view(|ctx| { EditorView::single_line(width_and_height_editor_options.clone(), ctx) }); - git_branch_name_pattern_editor.update(ctx, |editor, ctx| { - let pattern = TerminalSettings::as_ref(ctx) - .git_branch_name_pattern - .clone(); - editor.set_placeholder_text("^([A-Z]+-[0-9]+)", ctx); - editor.set_buffer_text(&pattern, ctx); + git_branch_name_prefix_chars_editor.update(ctx, |editor, ctx| { + editor.set_buffer_text( + &format!( + "{}", + *TerminalSettings::as_ref(ctx).git_branch_name_prefix_chars + ), + ctx, + ); }); - ctx.subscribe_to_view(&git_branch_name_pattern_editor, |me, _, event, ctx| { - me.handle_git_branch_name_pattern_editor_event(event, ctx) + ctx.subscribe_to_view(&git_branch_name_prefix_chars_editor, |me, _, event, ctx| { + me.handle_git_branch_edge_chars_editor_event(event, true, ctx); + }); + + let git_branch_name_suffix_chars_editor = ctx.add_typed_action_view(|ctx| { + EditorView::single_line(width_and_height_editor_options.clone(), ctx) + }); + git_branch_name_suffix_chars_editor.update(ctx, |editor, ctx| { + editor.set_buffer_text( + &format!( + "{}", + *TerminalSettings::as_ref(ctx).git_branch_name_suffix_chars + ), + ctx, + ); + }); + ctx.subscribe_to_view(&git_branch_name_suffix_chars_editor, |me, _, event, ctx| { + me.handle_git_branch_edge_chars_editor_event(event, false, ctx); }); let mouse_scroll_input_editor = ctx.add_typed_action_view(|ctx| { @@ -2867,12 +2880,10 @@ impl FeaturesPageView { new_tab_placement_dropdown, osc52_clipboard_access_dropdown, git_branch_name_truncation_dropdown, - git_branch_name_pattern_editor, - git_branch_pattern_valid: git_branch_name_pattern_is_valid( - TerminalSettings::as_ref(ctx) - .git_branch_name_pattern - .as_str(), - ), + git_branch_name_prefix_chars_editor, + git_branch_name_suffix_chars_editor, + git_branch_prefix_chars_valid: true, + git_branch_suffix_chars_valid: true, default_session_mode_dropdown, tab_behavior: Default::default(), @@ -3175,7 +3186,7 @@ impl FeaturesPageView { terminal_widgets.push(Box::new(CopyOnSelectWidget::default())); terminal_widgets.push(Box::new(Osc52ClipboardAccessWidget::default())); terminal_widgets.push(Box::new(GitBranchNameTruncationWidget::default())); - terminal_widgets.push(Box::new(GitBranchNamePatternWidget::default())); + terminal_widgets.push(Box::new(GitBranchNameMiddleCharsWidget::default())); terminal_widgets.push(Box::new(NewTabPlacementWidget::default())); let mut system_widgets: Vec>> = vec![]; @@ -3840,7 +3851,11 @@ impl FeaturesPageView { ctx: &mut ViewContext, ) { dropdown.update(ctx, |dropdown, ctx| { - let values = vec![GitBranchNameTruncation::End, GitBranchNameTruncation::Start]; + let values = vec![ + GitBranchNameTruncation::OmitStart, + GitBranchNameTruncation::OmitEnd, + GitBranchNameTruncation::OmitMiddle, + ]; let current_value = *TerminalSettings::as_ref(ctx).git_branch_name_truncation; let selected_index = values .iter() @@ -3863,49 +3878,112 @@ impl FeaturesPageView { }); } - fn handle_git_branch_name_pattern_editor_event( + fn sync_git_branch_edge_chars_editor( + &self, + editor: ViewHandle, + saved: usize, + ctx: &mut ViewContext, + ) { + let saved = saved.to_string(); + editor.update(ctx, |editor, ctx| { + if editor.buffer_text(ctx) != saved { + editor.set_buffer_text(&saved, ctx); + } + }); + } + + fn handle_git_branch_edge_chars_editor_event( &mut self, event: &EditorEvent, + prefix: bool, ctx: &mut ViewContext, ) { match event { EditorEvent::Edited(_) => { - let pattern = self - .git_branch_name_pattern_editor + let editor = if prefix { + self.git_branch_name_prefix_chars_editor.clone() + } else { + self.git_branch_name_suffix_chars_editor.clone() + }; + let valid = editor .as_ref(ctx) - .buffer_text(ctx); - self.git_branch_pattern_valid = git_branch_name_pattern_is_valid(&pattern); + .buffer_text(ctx) + .parse::() + .is_ok_and(|input| input <= GIT_BRANCH_NAME_EDGE_CHARS_LIMIT); + if prefix { + self.git_branch_prefix_chars_valid = valid; + } else { + self.git_branch_suffix_chars_valid = valid; + } ctx.notify(); } EditorEvent::Enter | EditorEvent::Blurred => { - // The features page is mid-update while this editor event is - // delivered, so a synchronous dispatch never reaches its handler. - ctx.dispatch_typed_action_deferred(FeaturesPageAction::SetGitBranchNamePattern); + self.save_git_branch_edge_chars(prefix, ctx); } EditorEvent::Escape => ctx.emit(FeaturesSettingsPageEvent::FocusModal), _ => {} } } - fn save_git_branch_name_pattern(&mut self, ctx: &mut ViewContext) { - let pattern = self - .git_branch_name_pattern_editor - .as_ref(ctx) - .buffer_text(ctx); - let pattern = pattern.trim().to_string(); - if !git_branch_name_pattern_is_valid(&pattern) { - self.git_branch_pattern_valid = false; + fn save_git_branch_edge_chars(&mut self, prefix: bool, ctx: &mut ViewContext) { + let editor = if prefix { + self.git_branch_name_prefix_chars_editor.clone() + } else { + self.git_branch_name_suffix_chars_editor.clone() + }; + let buffer_text = editor.as_ref(ctx).buffer_text(ctx); + if let Ok(input) = buffer_text.parse::() { + let clamped = input.min(GIT_BRANCH_NAME_EDGE_CHARS_LIMIT); + if clamped != input { + editor.update(ctx, |editor, ctx| { + editor.set_buffer_text(&format!("{clamped}"), ctx); + }); + } + if prefix { + self.git_branch_prefix_chars_valid = true; + } else { + self.git_branch_suffix_chars_valid = true; + } + let current = if prefix { + *TerminalSettings::as_ref(ctx).git_branch_name_prefix_chars + } else { + *TerminalSettings::as_ref(ctx).git_branch_name_suffix_chars + }; + if current == clamped { + ctx.notify(); + return; + } + TerminalSettings::handle(ctx).update(ctx, |settings, ctx| { + if prefix { + report_if_error!( + settings + .git_branch_name_prefix_chars + .set_value(clamped, ctx) + ); + } else { + report_if_error!( + settings + .git_branch_name_suffix_chars + .set_value(clamped, ctx) + ); + } + }); + } else { + let saved = if prefix { + *TerminalSettings::as_ref(ctx).git_branch_name_prefix_chars + } else { + *TerminalSettings::as_ref(ctx).git_branch_name_suffix_chars + }; + editor.update(ctx, |editor, ctx| { + editor.set_buffer_text(&format!("{saved}"), ctx); + }); + if prefix { + self.git_branch_prefix_chars_valid = true; + } else { + self.git_branch_suffix_chars_valid = true; + } ctx.notify(); - return; } - self.git_branch_pattern_valid = true; - TerminalSettings::handle(ctx).update(ctx, |terminal_settings, ctx| { - report_if_error!( - terminal_settings - .git_branch_name_pattern - .set_value(pattern, ctx) - ); - }); } fn update_default_session_mode_dropdown( @@ -7609,7 +7687,7 @@ impl SettingsWidget for GitBranchNameTruncationWidget { type View = FeaturesPageView; fn search_terms(&self) -> &str { - "git branch name truncation prompt chip ticket prefix start end" + "git branch name truncation prompt chip ticket prefix omit start end middle ellipsis" } fn render( @@ -7621,7 +7699,9 @@ impl SettingsWidget for GitBranchNameTruncationWidget { render_dropdown_item( appearance, "Long git branch names", - Some("Choose which part of a long git branch name stays visible in the prompt."), + Some( + "Choose which part of a long git branch name the prompt replaces with an ellipsis.", + ), None, LocalOnlyIconState::for_setting( GitBranchNameTruncationSetting::storage_key(), @@ -7639,13 +7719,65 @@ impl SettingsWidget for GitBranchNameTruncationWidget { } #[derive(Default)] -struct GitBranchNamePatternWidget {} +struct GitBranchNameMiddleCharsWidget {} -impl SettingsWidget for GitBranchNamePatternWidget { +impl GitBranchNameMiddleCharsWidget { + fn edge_input( + label: &'static str, + editor: ViewHandle, + valid: bool, + appearance: &Appearance, + ) -> Box { + let theme = appearance.theme(); + let border_color: Option = if valid { + None + } else { + Some(themes::theme::Fill::error().into()) + }; + Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_child( + Text::new_inline( + label, + appearance.ui_font_family(), + appearance.ui_font_size() - 2., + ) + .with_color(theme.active_ui_text_color().into()) + .finish(), + ) + .with_child( + Container::new( + appearance + .ui_builder() + .text_input(editor) + .with_style(UiComponentStyles { + width: Some(GIT_BRANCH_EDGE_CHARS_INPUT_WIDTH), + padding: Some(Coords { + top: 4., + bottom: 4., + left: 6., + right: 6., + }), + background: Some(theme.surface_2().into()), + border_color, + ..Default::default() + }) + .build() + .finish(), + ) + .with_margin_left(6.) + .with_margin_right(12.) + .finish(), + ) + .finish() + } +} + +impl SettingsWidget for GitBranchNameMiddleCharsWidget { type View = FeaturesPageView; fn search_terms(&self) -> &str { - "git branch display pattern regex ticket id prompt chip" + "git branch name middle ellipsis characters prefix suffix start end prompt chip" } fn render( @@ -7654,35 +7786,28 @@ impl SettingsWidget for GitBranchNamePatternWidget { appearance: &Appearance, app: &AppContext, ) -> Box { - let border_color: Option = if view.git_branch_pattern_valid { - None - } else { - Some(themes::theme::Fill::error().into()) - }; - let input_field = appearance - .ui_builder() - .text_input(view.git_branch_name_pattern_editor.clone()) - .with_style(UiComponentStyles { - width: Some(GIT_BRANCH_PATTERN_INPUT_WIDTH), - padding: Some(Coords { - top: 4., - bottom: 4., - left: 6., - right: 6., - }), - background: Some(appearance.theme().surface_2().into()), - border_color, - ..Default::default() - }) - .build() + let inputs = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_child(Self::edge_input( + "Start", + view.git_branch_name_prefix_chars_editor.clone(), + view.git_branch_prefix_chars_valid, + appearance, + )) + .with_child(Self::edge_input( + "End", + view.git_branch_name_suffix_chars_editor.clone(), + view.git_branch_suffix_chars_valid, + appearance, + )) .finish(); render_body_item::( - "Git branch display pattern".into(), + "Characters kept at each end".into(), None, LocalOnlyIconState::for_setting( - GitBranchNamePatternSetting::storage_key(), - GitBranchNamePatternSetting::sync_to_cloud(), + GitBranchNamePrefixChars::storage_key(), + GitBranchNamePrefixChars::sync_to_cloud(), &mut view .button_mouse_states .local_only_icon_tooltip_states @@ -7691,9 +7816,9 @@ impl SettingsWidget for GitBranchNamePatternWidget { ), ToggleState::Enabled, appearance, - input_field, + inputs, Some( - "Optional regular expression. The prompt shows the first capture group, or the whole match when there is no group. For example, ^([A-Z]+-[0-9]+) shows ABC-123 from ABC-123-some-long-feature." + "Used only when Long git branch names is set to Omit the middle. The prompt keeps this many characters from the start and from the end, with an ellipsis between them." .to_string(), ), ) diff --git a/app/src/terminal/settings.rs b/app/src/terminal/settings.rs index 07dc28da957..b1cac34de17 100644 --- a/app/src/terminal/settings.rs +++ b/app/src/terminal/settings.rs @@ -6,7 +6,10 @@ use warpui::units::Pixels; use warpui::{AppContext, SingletonEntity}; use crate::settings::{AISettings, InputSettings, TerminalSpacing}; -pub use crate::util::git_branch_display::GitBranchNameTruncation; +pub use crate::util::git_branch_display::{ + DEFAULT_GIT_BRANCH_NAME_PREFIX_CHARS, DEFAULT_GIT_BRANCH_NAME_SUFFIX_CHARS, + GitBranchNameTruncation, +}; #[derive( Clone, @@ -215,17 +218,27 @@ define_settings_group!(TerminalSettings, settings: [ surface: settings::SettingSurfaces::GUI, private: false, toml_path: "terminal.prompt.git_branch_name_truncation", - description: "Which end of a long git branch name stays visible in the prompt chip. `end` keeps the end of the name. `start` keeps the beginning, such as a ticket id.", + description: "Which part of a long git branch name the prompt chip replaces with an ellipsis. `omit_start` hides the beginning and is the original behavior. `omit_end` hides the end so a ticket prefix stays visible. `omit_middle` hides the middle and keeps the configured number of characters from each end.", }, - git_branch_name_pattern: GitBranchNamePatternSetting { - type: String, - default: String::new(), + git_branch_name_prefix_chars: GitBranchNamePrefixChars { + type: usize, + default: DEFAULT_GIT_BRANCH_NAME_PREFIX_CHARS, + supported_platforms: SupportedPlatforms::ALL, + sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), + surface: settings::SettingSurfaces::GUI, + private: false, + toml_path: "terminal.prompt.git_branch_name_prefix_chars", + description: "When long git branch names omit the middle, how many characters to keep from the start. Ignored for the other truncation modes.", + }, + git_branch_name_suffix_chars: GitBranchNameSuffixChars { + type: usize, + default: DEFAULT_GIT_BRANCH_NAME_SUFFIX_CHARS, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), surface: settings::SettingSurfaces::GUI, private: false, - toml_path: "terminal.prompt.git_branch_name_pattern", - description: "Optional regular expression applied to the git branch name before the prompt chip shows it. When the pattern matches, the first capture group is displayed, or the whole match when there is no capture group. Leave empty to show the full branch name. Example: ^([A-Z]+-[0-9]+) shows ABC-123 from ABC-123-some-long-feature.", + toml_path: "terminal.prompt.git_branch_name_suffix_chars", + description: "When long git branch names omit the middle, how many characters to keep from the end. Ignored for the other truncation modes.", }, ]); diff --git a/app/src/util/git_branch_display.rs b/app/src/util/git_branch_display.rs index 3bf1c72f02f..9a88c7bfb92 100644 --- a/app/src/util/git_branch_display.rs +++ b/app/src/util/git_branch_display.rs @@ -1,12 +1,20 @@ -use regex::Regex; use serde::{Deserialize, Serialize}; use super::truncation::{truncate_from_beginning, truncate_from_end}; -/// Maximum length of a user-supplied branch display pattern. -const MAX_GIT_BRANCH_NAME_PATTERN_LEN: usize = 256; +/// Largest leading or trailing character count a user can save. +/// +/// The prompt chip itself is capped separately. Counts above that cap are reduced while formatting +/// so the ellipsis still fits. +pub const GIT_BRANCH_NAME_EDGE_CHARS_LIMIT: usize = 40; + +/// Characters kept from the start when the middle of a branch name is hidden. +pub const DEFAULT_GIT_BRANCH_NAME_PREFIX_CHARS: usize = 12; -/// Which end of a long git branch name the prompt chip keeps. +/// Characters kept from the end when the middle of a branch name is hidden. +pub const DEFAULT_GIT_BRANCH_NAME_SUFFIX_CHARS: usize = 12; + +/// Which part of a long git branch name the prompt chip replaces with an ellipsis. #[derive( Clone, Copy, @@ -20,83 +28,100 @@ const MAX_GIT_BRANCH_NAME_PATTERN_LEN: usize = 256; settings_value::SettingsValue, )] #[schemars( - description = "Which end of a long git branch name the prompt chip keeps.", + description = "Which part of a long git branch name the prompt chip replaces with an ellipsis.", rename_all = "snake_case" )] pub enum GitBranchNameTruncation { - /// Keep the end of the name. The start is replaced with an ellipsis. + /// Hide the start of the name. This is the original prompt behavior. #[default] - #[schemars(description = "Keep the end of a long branch name.")] - End, - /// Keep the start of the name, such as a ticket id. - #[schemars(description = "Keep the start of a long branch name.")] - Start, + #[schemars(description = "Hide the start of a long branch name.")] + OmitStart, + /// Hide the end of the name so a prefix, such as a ticket id, stays visible. + #[schemars(description = "Hide the end of a long branch name.")] + OmitEnd, + /// Hide the middle and keep a fixed number of characters from each end. + #[schemars(description = "Hide the middle of a long branch name.")] + OmitMiddle, } impl GitBranchNameTruncation { pub fn as_dropdown_label(self) -> &'static str { match self { - Self::End => "End of name", - Self::Start => "Start of name", + Self::OmitStart => "Omit the start", + Self::OmitEnd => "Omit the end", + Self::OmitMiddle => "Omit the middle", } } + /// Switches between hiding the start and hiding the end. + /// + /// Middle omission is not part of that pair. Toggling it shows the start of the name instead. pub fn toggled(self) -> Self { match self { - Self::End => Self::Start, - Self::Start => Self::End, + Self::OmitStart | Self::OmitMiddle => Self::OmitEnd, + Self::OmitEnd => Self::OmitStart, } } } -/// Whether `pattern` can be saved as a git branch display pattern. -/// -/// An empty pattern is valid and means the full branch name is shown. -pub fn git_branch_name_pattern_is_valid(pattern: &str) -> bool { - let pattern = pattern.trim(); - if pattern.len() > MAX_GIT_BRANCH_NAME_PATTERN_LEN { - return false; - } - pattern.is_empty() || Regex::new(pattern).is_ok() -} - /// Formats a git branch name for the prompt chip. /// -/// A non-empty `pattern` replaces the name with the first regex capture group, -/// or the whole match when the pattern has no groups. An empty, invalid, or -/// non-matching pattern leaves the branch name unchanged. The result is then -/// shortened to `max_chars`. +/// `prefix_chars` and `suffix_chars` apply only when `truncation` hides the middle. A shortened +/// name always keeps an ellipsis where text was removed. pub fn format_git_branch_name( branch: &str, truncation: GitBranchNameTruncation, - pattern: &str, + prefix_chars: usize, + suffix_chars: usize, max_chars: usize, ) -> String { - let displayed = matched_branch_text(branch, pattern).unwrap_or_else(|| branch.to_string()); match truncation { - GitBranchNameTruncation::End => truncate_from_beginning(&displayed, max_chars), - GitBranchNameTruncation::Start => truncate_from_end(&displayed, max_chars), + GitBranchNameTruncation::OmitStart => truncate_from_beginning(branch, max_chars), + GitBranchNameTruncation::OmitEnd => truncate_from_end(branch, max_chars), + GitBranchNameTruncation::OmitMiddle => { + truncate_middle(branch, prefix_chars, suffix_chars, max_chars) + } } } -/// The branch text selected by `pattern`, when the pattern matches. -fn matched_branch_text(branch: &str, pattern: &str) -> Option { - let pattern = pattern.trim(); - if pattern.is_empty() || pattern.len() > MAX_GIT_BRANCH_NAME_PATTERN_LEN { - return None; +/// Hides the middle of `text`. +/// +/// Keeps `prefix_chars` from the start and `suffix_chars` from the end. A name no longer than those +/// two edges is shown in full. When the edges plus the ellipsis would pass `max_chars`, the suffix +/// shrinks before the prefix so a ticket id at the start survives. +fn truncate_middle( + text: &str, + prefix_chars: usize, + suffix_chars: usize, + max_chars: usize, +) -> String { + if max_chars == 0 { + return String::new(); + } + let chars: Vec = text.chars().collect(); + let len = chars.len(); + if len <= max_chars && len <= prefix_chars.saturating_add(suffix_chars) { + return text.to_string(); + } + + let mut prefix = prefix_chars.min(len); + let mut suffix = suffix_chars.min(len); + let edge_budget = max_chars.saturating_sub(1); + if prefix + suffix > edge_budget { + let overflow = prefix + suffix - edge_budget; + let suffix_reduction = overflow.min(suffix); + suffix -= suffix_reduction; + prefix = prefix.saturating_sub(overflow - suffix_reduction); + } + if prefix + suffix >= len { + return text.to_string(); } - let re = Regex::new(pattern).ok()?; - let caps = re.captures(branch)?; - let text = if re.captures_len() > 1 { - caps.get(1)?.as_str() - } else { - caps.get(0)?.as_str() - }; - if text.is_empty() { - None - } else { - Some(text.to_string()) + if prefix == 0 && suffix == 0 { + return "…".to_string(); } + let start: String = chars[..prefix].iter().collect(); + let end: String = chars[len - suffix..].iter().collect(); + format!("{start}…{end}") } #[cfg(test)] diff --git a/app/src/util/git_branch_display_tests.rs b/app/src/util/git_branch_display_tests.rs index ab7c61cba7a..dc67478f022 100644 --- a/app/src/util/git_branch_display_tests.rs +++ b/app/src/util/git_branch_display_tests.rs @@ -1,107 +1,105 @@ use settings_value::SettingsValue; -use super::{GitBranchNameTruncation, format_git_branch_name, git_branch_name_pattern_is_valid}; -use crate::util::truncation::truncate_from_beginning; +use super::{ + DEFAULT_GIT_BRANCH_NAME_PREFIX_CHARS, DEFAULT_GIT_BRANCH_NAME_SUFFIX_CHARS, + GitBranchNameTruncation, format_git_branch_name, +}; +use crate::util::truncation::{truncate_from_beginning, truncate_from_end}; const LONG_BRANCH: &str = "ABC-123-some-long-feature-that-needs-to-be-implemented"; +fn format( + branch: &str, + truncation: GitBranchNameTruncation, + prefix_chars: usize, + suffix_chars: usize, + max_chars: usize, +) -> String { + format_git_branch_name(branch, truncation, prefix_chars, suffix_chars, max_chars) +} + #[test] -fn end_truncation_matches_existing_beginning_truncation() { - let displayed = format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::End, "", 40); +fn omit_start_matches_existing_beginning_truncation() { + let displayed = format(LONG_BRANCH, GitBranchNameTruncation::OmitStart, 12, 12, 40); assert_eq!(displayed, truncate_from_beginning(LONG_BRANCH, 40)); - assert!(!displayed.contains("ABC-123")); assert!(displayed.starts_with('…')); + assert!(!displayed.contains("ABC-123")); } #[test] -fn start_truncation_keeps_the_ticket_prefix() { - let displayed = format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::Start, "", 40); +fn omit_end_keeps_the_ticket_prefix_and_the_ellipsis() { + let displayed = format(LONG_BRANCH, GitBranchNameTruncation::OmitEnd, 3, 3, 40); + assert_eq!(displayed, truncate_from_end(LONG_BRANCH, 40)); assert!(displayed.starts_with("ABC-123")); assert!(displayed.ends_with('…')); assert_eq!(displayed.chars().count(), 40); } #[test] -fn short_names_are_not_truncated() { - let branch = "main"; - assert_eq!( - format_git_branch_name(branch, GitBranchNameTruncation::End, "", 40), - branch - ); - assert_eq!( - format_git_branch_name(branch, GitBranchNameTruncation::Start, "", 40), - branch - ); -} - -#[test] -fn capture_group_shows_only_the_ticket_id() { - let displayed = format_git_branch_name( - LONG_BRANCH, - GitBranchNameTruncation::End, - r"^([A-Z]+-\d+)", - 40, - ); - assert_eq!(displayed, "ABC-123"); +fn edge_counts_do_not_change_start_or_end_omission() { + let with_defaults = format(LONG_BRANCH, GitBranchNameTruncation::OmitStart, 12, 12, 40); + let with_other_counts = format(LONG_BRANCH, GitBranchNameTruncation::OmitStart, 1, 30, 40); + assert_eq!(with_defaults, with_other_counts); } #[test] -fn pattern_without_groups_shows_the_whole_match() { - let displayed = - format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::Start, r"ABC-\d+", 40); - assert_eq!(displayed, "ABC-123"); +fn short_names_keep_every_character() { + let branch = "main"; + for truncation in [ + GitBranchNameTruncation::OmitStart, + GitBranchNameTruncation::OmitEnd, + GitBranchNameTruncation::OmitMiddle, + ] { + assert_eq!(format(branch, truncation, 12, 12, 40), branch); + } } #[test] -fn unmatched_pattern_falls_back_to_the_full_name() { - let displayed = format_git_branch_name( +fn omit_middle_keeps_both_edges_and_the_ellipsis() { + let displayed = format( LONG_BRANCH, - GitBranchNameTruncation::Start, - r"^TICKET-(\d+)", + GitBranchNameTruncation::OmitMiddle, + DEFAULT_GIT_BRANCH_NAME_PREFIX_CHARS, + DEFAULT_GIT_BRANCH_NAME_SUFFIX_CHARS, 40, ); - assert_eq!( - displayed, - format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::Start, "", 40) - ); + assert_eq!(displayed, "ABC-123-some…-implemented"); + assert!(displayed.contains('…')); + assert_ne!(displayed, LONG_BRANCH); } #[test] -fn invalid_pattern_falls_back_to_the_full_name() { - let displayed = - format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::End, r"(unclosed", 40); +fn omit_middle_shows_the_name_when_the_edges_cover_it() { + let branch = "ABC-123-short"; assert_eq!( - displayed, - format_git_branch_name(LONG_BRANCH, GitBranchNameTruncation::End, "", 40) + format(branch, GitBranchNameTruncation::OmitMiddle, 8, 8, 40), + branch ); } #[test] -fn whitespace_around_a_pattern_is_ignored() { - let displayed = format_git_branch_name( - LONG_BRANCH, - GitBranchNameTruncation::End, - " ^([A-Z]+-\\d+) ", - 40, - ); - assert_eq!(displayed, "ABC-123"); +fn omit_middle_shrinks_the_suffix_before_the_prefix() { + let displayed = format(LONG_BRANCH, GitBranchNameTruncation::OmitMiddle, 30, 30, 40); + assert_eq!(displayed.chars().count(), 40); + let without_ellipsis: String = displayed.chars().filter(|c| *c != '…').collect(); + assert_eq!(without_ellipsis.chars().count(), 39); + assert!(displayed.starts_with(&LONG_BRANCH.chars().take(30).collect::())); + assert!(LONG_BRANCH.ends_with(&displayed.chars().skip(31).collect::())); + assert_eq!(displayed.chars().nth(30), Some('…')); } #[test] -fn unmatched_capture_group_keeps_the_full_name() { - let displayed = format_git_branch_name( - "feature-without-ticket", - GitBranchNameTruncation::End, - r"^([A-Z]+-\d+)?", - 40, +fn omit_middle_with_no_edge_characters_is_an_ellipsis() { + assert_eq!( + format(LONG_BRANCH, GitBranchNameTruncation::OmitMiddle, 0, 0, 40), + "…" ); - assert_eq!(displayed, "feature-without-ticket"); } #[test] -fn unicode_start_truncation_keeps_char_boundaries() { +fn unicode_omit_end_keeps_char_boundaries() { let branch = "票-1-很长的分支名字需要被截断才能放进提示"; - let displayed = format_git_branch_name(branch, GitBranchNameTruncation::Start, "", 8); + let displayed = format(branch, GitBranchNameTruncation::OmitEnd, 0, 0, 8); assert!(displayed.starts_with("票-1")); assert!(displayed.ends_with('…')); assert_eq!(displayed.chars().count(), 8); @@ -109,43 +107,49 @@ fn unicode_start_truncation_keeps_char_boundaries() { } #[test] -fn pattern_validity_accepts_empty_and_rejects_broken_regex() { - assert!(git_branch_name_pattern_is_valid("")); - assert!(git_branch_name_pattern_is_valid(" ")); - assert!(git_branch_name_pattern_is_valid(r"^([A-Z]+-\d+)")); - assert!(!git_branch_name_pattern_is_valid(r"(unclosed")); - assert!(!git_branch_name_pattern_is_valid(&"a".repeat(257))); +fn unicode_omit_middle_keeps_char_boundaries() { + let branch = "票-1-很长的分支名字需要被截断才能放进提示"; + let displayed = format(branch, GitBranchNameTruncation::OmitMiddle, 3, 2, 40); + assert_eq!(displayed, "票-1…提示"); } #[test] fn truncation_serializes_as_snake_case() { assert_eq!( GitBranchNameTruncation::default(), - GitBranchNameTruncation::End + GitBranchNameTruncation::OmitStart ); assert_eq!( - GitBranchNameTruncation::End.to_file_value(), - serde_json::json!("end") + GitBranchNameTruncation::OmitStart.to_file_value(), + serde_json::json!("omit_start") ); assert_eq!( - GitBranchNameTruncation::Start.to_file_value(), - serde_json::json!("start") + GitBranchNameTruncation::OmitEnd.to_file_value(), + serde_json::json!("omit_end") ); assert_eq!( - GitBranchNameTruncation::from_file_value(&serde_json::json!("start")).unwrap(), - GitBranchNameTruncation::Start + GitBranchNameTruncation::OmitMiddle.to_file_value(), + serde_json::json!("omit_middle") ); - assert!(GitBranchNameTruncation::from_file_value(&serde_json::json!("middle")).is_none()); + assert_eq!( + GitBranchNameTruncation::from_file_value(&serde_json::json!("omit_middle")).unwrap(), + GitBranchNameTruncation::OmitMiddle + ); + assert!(GitBranchNameTruncation::from_file_value(&serde_json::json!("end")).is_none()); } #[test] -fn toggled_switches_between_ends() { +fn toggled_switches_between_hiding_the_start_and_the_end() { + assert_eq!( + GitBranchNameTruncation::OmitStart.toggled(), + GitBranchNameTruncation::OmitEnd + ); assert_eq!( - GitBranchNameTruncation::End.toggled(), - GitBranchNameTruncation::Start + GitBranchNameTruncation::OmitEnd.toggled(), + GitBranchNameTruncation::OmitStart ); assert_eq!( - GitBranchNameTruncation::Start.toggled(), - GitBranchNameTruncation::End + GitBranchNameTruncation::OmitMiddle.toggled(), + GitBranchNameTruncation::OmitEnd ); } diff --git a/app/src/workspace/view.rs b/app/src/workspace/view.rs index 5eb128d0433..ec63cc9e858 100644 --- a/app/src/workspace/view.rs +++ b/app/src/workspace/view.rs @@ -23603,7 +23603,7 @@ impl Workspace { } if matches!( *terminal_settings.git_branch_name_truncation, - GitBranchNameTruncation::Start + GitBranchNameTruncation::OmitEnd ) { context.set.insert(flags::GIT_BRANCH_NAME_SHOW_START); }