first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+12 -15
View File
@@ -1,21 +1,18 @@
use galaxy_util::path::user_friendly_path;
use galaxyui::{
elements::{
Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisSize,
MouseStateHandle, ParentElement, Radius, Text,
},
platform::Cursor,
ui_components::{
button::{ButtonTooltipPosition, ButtonVariant},
components::{UiComponent, UiComponentStyles},
},
AppContext, Element, SingletonEntity,
use galaxyui::elements::{
Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisSize,
MouseStateHandle, ParentElement, Radius, Text,
};
use warpui::platform::Cursor;
use warpui::ui_components::button::{ButtonTooltipPosition, ButtonVariant};
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
use warpui::{AppContext, Element, SingletonEntity};
use crate::{
appearance::Appearance, settings::ai::DefaultSessionMode, tab_configs::TabConfig,
terminal::available_shells::AvailableShell, workspace::WorkspaceAction,
};
use crate::appearance::Appearance;
use crate::settings::ai::DefaultSessionMode;
use crate::tab_configs::TabConfig;
use crate::terminal::available_shells::AvailableShell;
use crate::workspace::WorkspaceAction;
pub(crate) const SIDECAR_WIDTH: f32 = 260.;
const SIDECAR_PADDING: f32 = 12.;
+20 -20
View File
@@ -1,16 +1,15 @@
use std::path::PathBuf;
use galaxyui::{
elements::ChildView, ui_components::components::UiComponentStyles, AppContext, Element, Entity,
TypedActionView, View, ViewContext, ViewHandle,
};
use galaxyui::elements::ChildView;
use galaxyui::ui_components::components::UiComponentStyles;
use galaxyui::{AppContext, Element, Entity, TypedActionView, View, ViewContext, ViewHandle};
use crate::{
code_review::diff_state::DiffStateModel,
tab_configs::PickerStyle,
util::git::detect_current_branch,
view_components::{DropdownItem, FilterableDropdown},
use crate::tab_configs::PickerStyle;
use crate::util::git::{
detect_current_branch, get_all_branches, get_all_branches_with_known_main,
sort_branches_main_first, BranchEntry,
};
use crate::view_components::{DropdownItem, FilterableDropdown};
const DEFAULT_DROPDOWN_WIDTH: f32 = 380.;
/// Placeholder text shown in the dropdown top bar while branches are loading.
@@ -137,10 +136,9 @@ impl BranchPicker {
async move {
let branches = match known_main {
Some(ref main) => {
DiffStateModel::get_all_branches_with_known_main(&cwd, main, None, false)
.await
get_all_branches_with_known_main(&cwd, main, None, false).await
}
None => DiffStateModel::get_all_branches(&cwd, None, false).await,
None => get_all_branches(&cwd, None, false).await,
};
// git for-each-ref only lists refs backed by actual commits,
@@ -153,7 +151,10 @@ impl BranchPicker {
if let Ok(current) = detect_current_branch(&cwd).await {
let trimmed = current.trim().to_string();
if !trimmed.is_empty() {
return Ok(vec![(trimmed, true)]);
return Ok(vec![BranchEntry {
name: trimmed,
is_main: true,
}]);
}
}
branches
@@ -185,20 +186,19 @@ impl BranchPicker {
if me.cached_main_branch.is_none() {
me.cached_main_branch = branches
.iter()
.find(|(_, is_main)| *is_main)
.map(|(name, _)| name.clone());
.find(|entry| entry.is_main)
.map(|entry| entry.name.clone());
}
// Main branches first, then the rest in recency order.
let mut items: Vec<DropdownItem<String>> =
DiffStateModel::sort_branches_main_first(&branches)
.map(|(name, _)| DropdownItem::new(name.clone(), name.clone()))
.collect();
let mut items: Vec<DropdownItem<String>> = sort_branches_main_first(&branches)
.map(|entry| DropdownItem::new(entry.name.clone(), entry.name.clone()))
.collect();
// Add the default as the first item if it isn't already in the list
// (e.g. the user typed a branch name that doesn't exist locally yet).
if let Some(ref default) = me.default_value {
if !branches.iter().any(|(name, _)| name == default) {
if !branches.iter().any(|entry| entry.name == *default) {
items.insert(0, DropdownItem::new(default.clone(), default.clone()));
}
}
+1 -2
View File
@@ -10,8 +10,6 @@ pub mod session_config_rendering;
pub mod tab_config;
pub mod telemetry;
use galaxy_core::ui::theme::Fill;
pub use new_worktree_modal::{NewWorktreeModal, NewWorktreeModalEvent};
pub use params_modal::{TabConfigParamsModal, TabConfigParamsModalEvent};
#[cfg(feature = "local_fs")]
@@ -19,6 +17,7 @@ pub(crate) use tab_config::build_worktree_config_toml;
pub use tab_config::{
render_tab_config, TabConfig, TabConfigError, TabConfigParam, TabConfigParamType,
};
use galaxy_core::ui::theme::Fill;
/// Optional visual overrides for BranchPicker / RepoPicker dropdowns.
pub struct PickerStyle {
+17 -25
View File
@@ -1,21 +1,17 @@
use std::path::PathBuf;
use galaxyui::{
elements::{
Border, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Element,
Fill as ElementFill, Flex, MainAxisAlignment, MainAxisSize, MouseStateHandle, Padding,
ParentElement, Radius, Shrinkable, Text,
},
fonts::{Properties, Weight},
keymap::FixedBinding,
platform::Cursor,
ui_components::{
button::ButtonVariant,
checkbox::Checkbox,
components::{Coords, UiComponent, UiComponentStyles},
},
AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
use galaxyui::elements::{
Border, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Element,
Fill as ElementFill, Flex, MainAxisAlignment, MainAxisSize, MouseStateHandle, Padding,
ParentElement, Radius, Shrinkable, Text,
};
use warpui::fonts::{Properties, Weight};
use warpui::keymap::FixedBinding;
use warpui::platform::Cursor;
use warpui::ui_components::button::ButtonVariant;
use warpui::ui_components::checkbox::Checkbox;
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use warpui::{AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle};
/// Registers keybindings for the new-worktree modal (ESC to close).
pub fn init(app: &mut AppContext) {
@@ -29,16 +25,12 @@ pub fn init(app: &mut AppContext) {
use galaxy_core::ui::theme::color::internal_colors;
use crate::{
ai::persisted_workspace::PersistedWorkspace,
appearance::Appearance,
editor::{EditorView, Event as EditorEvent, SingleLineEditorOptions},
modal::ModalAction,
tab_configs::{
branch_picker::BranchPicker,
repo_picker::{RepoPicker, RepoPickerEvent},
},
};
use crate::ai::persisted_workspace::PersistedWorkspace;
use crate::appearance::Appearance;
use crate::editor::{EditorView, Event as EditorEvent, SingleLineEditorOptions};
use crate::modal::ModalAction;
use crate::tab_configs::branch_picker::BranchPicker;
use crate::tab_configs::repo_picker::{RepoPicker, RepoPickerEvent};
/// Gap between sections in the modal body (repo picker, branch picker, checkbox).
const SECTION_GAP: f32 = 16.;
+34 -76
View File
@@ -1,38 +1,36 @@
use std::{collections::HashMap, path::PathBuf};
use std::collections::HashMap;
use std::path::PathBuf;
use galaxy_core::ui::theme::color::internal_colors;
use galaxy_core::ui::Icon;
use galaxy_editor::editor::NavigationKey;
use galaxyui::elements::{
Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container,
CornerRadius, CrossAxisAlignment, Fill, Flex, Hoverable, MainAxisAlignment, MainAxisSize,
MouseStateHandle, Padding, ParentElement, Radius, SavePosition, ScrollTarget,
ScrollToPositionMode, ScrollbarWidth, Shrinkable, Text,
};
use galaxyui::fonts::{Properties, Weight};
use galaxyui::keymap::macros::*;
use galaxyui::keymap::{FixedBinding, Keystroke};
use galaxyui::platform::Cursor;
use galaxyui::ui_components::components::UiComponent;
use galaxyui::{
elements::{
Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container,
CornerRadius, CrossAxisAlignment, Fill, Flex, Hoverable, MainAxisAlignment, MainAxisSize,
MouseStateHandle, Padding, ParentElement, Radius, SavePosition, ScrollTarget,
ScrollToPositionMode, ScrollbarWidth, Shrinkable, Text,
},
fonts::{Properties, Weight},
keymap::{macros::*, FixedBinding, Keystroke},
platform::Cursor,
ui_components::components::UiComponent,
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
ViewHandle,
};
use crate::{
appearance::Appearance,
editor::{
EditorView, Event as EditorEvent, PropagateAndNoOpNavigationKeys, SingleLineEditorOptions,
TextOptions,
},
modal::ModalAction,
tab_configs::{
branch_picker::BranchPicker,
repo_picker::{RepoPicker, RepoPickerEvent},
PickerStyle, TabConfig, TabConfigParam, TabConfigParamType,
},
view_components::action_button::{
ActionButton, DisabledTheme, KeystrokeSource, NakedTheme, PrimaryTheme,
},
use crate::appearance::Appearance;
use crate::editor::{
EditorView, Event as EditorEvent, PropagateAndNoOpNavigationKeys, SingleLineEditorOptions,
TextOptions,
};
use crate::modal::ModalAction;
use crate::tab_configs::branch_picker::BranchPicker;
use crate::tab_configs::repo_picker::{RepoPicker, RepoPickerEvent};
use crate::tab_configs::{PickerStyle, TabConfig, TabConfigParam, TabConfigParamType};
use crate::view_components::action_button::{
ActionButton, DisabledTheme, KeystrokeSource, NakedTheme, PrimaryTheme,
};
pub fn init(app: &mut AppContext) {
@@ -42,20 +40,16 @@ pub fn init(app: &mut AppContext) {
TabConfigParamsModalAction::Escape,
id!("TabConfigParamsModal"),
),
// Enter and Space only fire when no EditorView descendant is focused.
// When a text field or picker filter editor has focus, the editor
// consumes these keys and the modal handles them via event
// subscriptions instead (see handle_editor_event).
// Enter only fires when no EditorView descendant is focused. When a
// text field or picker filter editor has focus, the editor consumes
// it and the modal handles submit via event subscriptions instead
// (see handle_editor_event). Space is owned by FilterableDropdown
// itself when a picker is focused, so the modal no longer binds it.
FixedBinding::new(
"enter",
TabConfigParamsModalAction::Submit,
id!("TabConfigParamsModal") & !id!("EditorView"),
),
FixedBinding::new(
"space",
TabConfigParamsModalAction::ToggleDropdown,
id!("TabConfigParamsModal") & !id!("EditorView"),
),
]);
}
@@ -149,7 +143,6 @@ pub enum TabConfigParamsModalAction {
Cancel,
Submit,
Escape,
ToggleDropdown,
}
impl TabConfigParamsModal {
@@ -324,10 +317,9 @@ impl TabConfigParamsModal {
self.pending_config = Some(config);
// When the only fields are dropdowns, focus the modal itself so
// Enter (submit) and Space (toggle dropdown) fixed bindings fire.
// When there are text fields, focus the first one so the user can
// start typing immediately.
// When the only fields are dropdowns, focus the modal itself so the
// Enter (submit) fixed binding fires. When there are text fields,
// focus the first one so the user can start typing immediately.
if self.has_text_fields() {
self.focus_field(0, ctx);
} else {
@@ -405,35 +397,6 @@ impl TabConfigParamsModal {
.any(|(_, _, field)| matches!(field, ParamField::Text(_)))
}
fn dropdown_count(&self) -> usize {
self.param_fields
.iter()
.filter(|(_, _, field)| !matches!(field, ParamField::Text(_)))
.count()
}
fn toggle_single_dropdown(&mut self, ctx: &mut ViewContext<Self>) {
let mut opened = false;
for (_, _, field) in &self.param_fields {
match field {
ParamField::Branch { picker, .. } => {
opened = picker.update(ctx, |p, ctx| p.toggle_dropdown(ctx));
break;
}
ParamField::Repo { picker, .. } => {
opened = picker.update(ctx, |p, ctx| p.toggle_dropdown(ctx));
break;
}
ParamField::Text(_) => {}
}
}
// When the dropdown just closed, reclaim focus so Enter/Space
// fixed bindings continue to work.
if !opened && !self.has_text_fields() {
ctx.focus_self();
}
}
fn focus_field(&self, index: usize, ctx: &mut ViewContext<Self>) {
if let Some((_, _, field)) = self.param_fields.get(index) {
match field {
@@ -505,8 +468,8 @@ impl View for TabConfigParamsModal {
fn on_focus(&mut self, focus_ctx: &FocusContext, ctx: &mut ViewContext<Self>) {
// When focus arrives directly at this view (not at a child), keep
// self-focus so the Enter/Space fixed bindings fire. This happens
// on initial open (via focus_self in on_open) and when the Modal
// self-focus so the Enter fixed binding fires. This happens on
// initial open (via focus_self in on_open) and when the Modal
// wrapper re-focuses the body after a child (like a dropdown)
// releases focus.
if focus_ctx.is_self_focused() && !self.has_text_fields() {
@@ -729,11 +692,6 @@ impl TypedActionView for TabConfigParamsModal {
ctx.emit(TabConfigParamsModalEvent::Close);
}
TabConfigParamsModalAction::Submit => self.try_submit(ctx),
TabConfigParamsModalAction::ToggleDropdown => {
if self.dropdown_count() <= 1 {
self.toggle_single_dropdown(ctx);
}
}
}
}
}
@@ -1,23 +1,21 @@
use std::path::PathBuf;
use pathfinder_geometry::vector::vec2f;
use galaxy_core::ui::theme::Fill;
use galaxyui::elements::{
Align, ChildAnchor, ChildView, Container, OffsetPositioning, ParentAnchor, ParentOffsetBounds,
Stack,
};
use galaxyui::keymap::{FixedBinding, Keystroke};
use galaxyui::ui_components::components::{UiComponent, UiComponentStyles};
use galaxyui::{
elements::{
Align, ChildAnchor, ChildView, Container, OffsetPositioning, ParentAnchor,
ParentOffsetBounds, Stack,
},
keymap::{FixedBinding, Keystroke},
ui_components::components::{UiComponent, UiComponentStyles},
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
};
use pathfinder_geometry::vector::vec2f;
use crate::{
appearance::Appearance,
ui_components::dialog::{dialog_styles, Dialog},
view_components::action_button::{
ActionButton, DangerPrimaryTheme, KeystrokeSource, NakedTheme,
},
use crate::appearance::Appearance;
use crate::ui_components::dialog::{dialog_styles, Dialog};
use crate::view_components::action_button::{
ActionButton, DangerPrimaryTheme, KeystrokeSource, NakedTheme,
};
pub(crate) fn init(app: &mut AppContext) {
+45 -18
View File
@@ -1,18 +1,18 @@
use std::path::PathBuf;
use galaxy_util::path::user_friendly_path;
use galaxyui::elements::{Border, ChildView, Container, Hoverable, MouseStateHandle, Text};
use galaxyui::platform::Cursor;
use galaxyui::text_layout::ClipConfig;
use galaxyui::ui_components::components::UiComponentStyles;
use galaxyui::{
elements::{Border, ChildView, Container, Hoverable, MouseStateHandle, Text},
platform::Cursor,
ui_components::components::UiComponentStyles,
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
};
use crate::{
ai::persisted_workspace::{PersistedWorkspace, PersistedWorkspaceEvent},
appearance::Appearance,
tab_configs::PickerStyle,
view_components::{DropdownItem, FilterableDropdown},
};
use crate::ai::persisted_workspace::{PersistedWorkspace, PersistedWorkspaceEvent};
use crate::appearance::Appearance;
use crate::tab_configs::PickerStyle;
use crate::view_components::{DropdownItem, FilterableDropdown};
const DEFAULT_DROPDOWN_WIDTH: f32 = 380.;
@@ -147,22 +147,46 @@ impl RepoPicker {
// workspaces() already returns entries sorted by most-recently-touched.
// "+ Add new repo..." is a sticky footer (not a list item) so it is
// not included here.
//
// Each item's `display_text` is the full user-friendly form
// (`~`-prefixed). The dropdown clips it at render width via
// `ClipConfig::start()`, so distinct paths with shared trailing
// segments stay readable without character-count approximation.
// The action carries the *raw* absolute path so consumers reading
// `RepoPickerEvent::Selected` keep getting a real filesystem path.
let home = dirs::home_dir().map(|p| p.display().to_string());
let items: Vec<DropdownItem<RepoPickerAction>> = PersistedWorkspace::as_ref(ctx)
.workspaces()
.filter(|ws| ws.path.exists())
.map(|ws| {
let path_str = ws.path.to_string_lossy().into_owned();
DropdownItem::new(path_str.clone(), RepoPickerAction::Select(path_str))
let display = user_friendly_path(&path_str, home.as_deref()).into_owned();
DropdownItem::new(display, RepoPickerAction::Select(path_str.clone()))
.with_clip_config(ClipConfig::start())
.with_tooltip(path_str)
})
.collect();
let path_to_select = select_path
let raw_to_select = select_path
.or(self.selected.as_deref())
.map(|s| s.to_owned());
// Mirror the raw path into `self.selected` so `selected_value()`
// returns a real filesystem path even before the user explicitly
// picks something. Load-bearing for `new_worktree_modal::on_open`,
// which reads `repo_picker.selected_value()` at modal-open time when
// its own `selected_repo` is still `None`.
if let Some(ref raw) = raw_to_select {
self.selected = Some(raw.clone());
}
self.dropdown.update(ctx, |dropdown, ctx| {
dropdown.set_items(items, ctx);
if let Some(ref path) = path_to_select {
dropdown.set_selected_by_name(path.as_str(), ctx);
// Match by the action (which carries the raw absolute path) so two
// repos that left-clip to identical-looking labels can't be
// confused at preselection time.
if let Some(ref raw) = raw_to_select {
dropdown.set_selected_by_action(RepoPickerAction::Select(raw.clone()), ctx);
}
});
@@ -176,11 +200,14 @@ impl RepoPicker {
self.dropdown.as_ref(ctx).is_expanded()
}
/// Returns the currently shown selected repo path.
pub fn selected_value(&self, app: &AppContext) -> Option<String> {
self.selected
.clone()
.or_else(|| self.dropdown.as_ref(app).selected_item_label())
/// Returns the currently shown selected repo path (raw absolute path).
///
/// `refresh_items` eagerly mirrors any pre-selected raw path into
/// `self.selected`, so we never need to fall back to the dropdown's
/// `selected_item_label` — that would return the `~`-abbreviated display
/// string, not a usable filesystem path.
pub fn selected_value(&self, _app: &AppContext) -> Option<String> {
self.selected.clone()
}
}
+6 -8
View File
@@ -1,20 +1,18 @@
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
#[cfg(feature = "local_fs")]
use anyhow::Result;
use crate::app_state::{BranchSnapshot, LeafContents, LeafSnapshot, PaneNodeSnapshot};
use crate::launch_configs::launch_config::SplitDirection;
use crate::terminal::cli_agent::CLIAgent;
use crate::themes::theme::AnsiColorIdentifier;
use crate::ui_components::icons::Icon;
use super::tab_config::{
generated_worktree_path_string, TabConfig, TabConfigPaneNode, TabConfigPaneType,
TabConfigParam, TabConfigParamType, AUTOGENERATED_BRANCH_NAME_PARAM,
};
use crate::app_state::{BranchSnapshot, LeafContents, LeafSnapshot, PaneNodeSnapshot};
use crate::launch_configs::launch_config::SplitDirection;
use crate::terminal::cli_agent::CLIAgent;
use crate::themes::theme::AnsiColorIdentifier;
use crate::ui_components::icons::Icon;
/// The type of session the user wants to start.
///
+5 -7
View File
@@ -7,24 +7,22 @@ use galaxyui::elements::{
};
use galaxyui::fonts::Weight;
use galaxyui::keymap::macros::id;
use galaxyui::keymap::FixedBinding;
use galaxyui::keymap::Keystroke;
use galaxyui::keymap::{FixedBinding, Keystroke};
use galaxyui::platform::file_picker::FilePickerConfiguration;
use galaxyui::FocusContext;
use galaxyui::{
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
ViewHandle,
};
use pathfinder_geometry::vector::vec2f;
use super::session_config::{is_git_repo, SessionConfigSelection, SessionType};
use super::session_config_rendering;
use crate::appearance::Appearance;
use crate::ui_components::blended_colors;
use crate::view_components::action_button::{
ActionButton, ButtonSize, KeystrokeSource, NakedTheme, PrimaryTheme,
};
use super::session_config::{is_git_repo, SessionConfigSelection, SessionType};
use super::session_config_rendering;
pub fn init(app: &mut galaxyui::AppContext) {
app.register_fixed_bindings([FixedBinding::new(
"enter",
@@ -1,6 +1,9 @@
use std::path::Path;
use std::sync::Arc;
use pathfinder_color::ColorU;
use pathfinder_geometry::vector::vec2f;
use galaxy_core::ui::theme::{Fill, WarpTheme};
use galaxyui::elements::{
Border, ChildAnchor, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Expanded,
Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, OffsetPositioning,
@@ -10,13 +13,7 @@ use galaxyui::fonts::{Properties, Weight};
use galaxyui::geometry::vector::Vector2F;
use galaxyui::platform::Cursor;
use galaxyui::ui_components::components::UiComponent;
use galaxyui::Element;
use galaxyui::EventContext;
use pathfinder_color::ColorU;
use pathfinder_geometry::vector::vec2f;
use galaxy_core::ui::theme::Fill;
use galaxy_core::ui::theme::GalaxyTheme;
use galaxyui::{Element, EventContext};
use crate::appearance::Appearance;
use crate::tab_configs::session_config::SessionType;
+1 -2
View File
@@ -1,8 +1,7 @@
use std::path::Path;
use crate::terminal::cli_agent::CLIAgent;
use super::*;
use crate::terminal::cli_agent::CLIAgent;
fn generated_worktree_path_string(repo: &str, worktree_name: &str) -> String {
super::super::tab_config::generated_worktree_path(Path::new(repo), worktree_name)
+1 -2
View File
@@ -1,9 +1,8 @@
use std::collections::HashMap;
use std::path::Path;
use crate::launch_configs::launch_config::{PaneTemplateType, SplitDirection};
use super::*;
use crate::launch_configs::launch_config::{PaneTemplateType, SplitDirection};
const WORKTREE_TOML: &str = r#"
name = "New Worktree"