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
+170 -143
View File
@@ -1,48 +1,58 @@
use galaxy_core::features::FeatureFlag;
use galaxy_core::report_error;
use galaxy_core::ui::theme::color::internal_colors;
use galaxyui::elements::new_scrollable::SingleAxisConfig;
use galaxyui::elements::{
ClippedScrollStateHandle, ConstrainedBox, Empty, Fill, FormattedTextElement, Highlight,
HighlightedHyperlink, Hoverable, MainAxisAlignment, MainAxisSize, NewScrollable, SavePosition,
SelectableArea, SizeConstraintCondition, SizeConstraintSwitch,
};
use galaxyui::fonts::Weight;
use galaxyui::platform::{Cursor, OperatingSystem};
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use parking_lot::{FairMutex, RwLock};
use pathfinder_color::ColorU;
use settings::Setting as _;
use std::cmp::Ordering;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use std::{cmp::Ordering, rc::Rc};
use lazy_static::lazy_static;
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
use parking_lot::{FairMutex, RwLock};
use pathfinder_color::ColorU;
use pathfinder_geometry::vector::vec2f;
use settings::Setting as _;
use galaxy_core::features::FeatureFlag;
use galaxy_core::report_error;
use galaxy_core::semantic_selection::SemanticSelection;
use galaxy_core::ui::appearance::Appearance;
use galaxy_editor::{
content::buffer::InitialBufferState, render::element::VerticalExpansionBehavior,
use galaxy_core::ui::theme::color::internal_colors;
use galaxy_editor::content::buffer::InitialBufferState;
use galaxy_editor::render::element::VerticalExpansionBehavior;
use galaxyui::clipboard::ClipboardContent;
use galaxyui::elements::new_scrollable::SingleAxisConfig;
use galaxyui::elements::{
resizable_state_handle, Border, ChildAnchor, ChildView, ClippedScrollStateHandle,
ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DragBarSide, DropShadow, Empty,
Expanded, Fill, Flex, FormattedTextElement, Highlight, HighlightedHyperlink, Hoverable,
MainAxisAlignment, MainAxisSize, MouseStateHandle, NewScrollable, OffsetPositioning,
ParentElement, PositionedElementAnchor, PositionedElementOffsetBounds, Radius, Resizable,
ResizableStateHandle, SavePosition, SelectableArea, SelectionHandle, Shrinkable,
SizeConstraintCondition, SizeConstraintSwitch, Stack, Text,
};
use galaxyui::r#async::Timer;
use galaxyui::fonts::{Properties, Style, Weight};
use galaxyui::keymap::{EditableBinding, Keystroke};
use galaxyui::platform::{Cursor, OperatingSystem};
use galaxyui::r#async::{SpawnedFutureHandle, Timer};
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use galaxyui::{
clipboard::ClipboardContent,
elements::{
Border, ChildAnchor, ChildView, Container, CornerRadius, CrossAxisAlignment, DropShadow,
Expanded, Flex, MouseStateHandle, OffsetPositioning, ParentElement,
PositionedElementAnchor, PositionedElementOffsetBounds, Radius, SelectionHandle,
Shrinkable, Stack, Text,
},
fonts::{Properties, Style},
keymap::{EditableBinding, Keystroke},
r#async::SpawnedFutureHandle,
AppContext, Element, Entity, EntityId, ModelHandle, SingletonEntity, TypedActionView, View,
ViewContext, ViewHandle,
};
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
use crate::ai::agent::{AIAgentPtyWriteMode, CancellationReason};
use super::cli_controller::{CLISubagentController, CLISubagentEvent, UserTakeOverReason};
use super::model::{AIBlockModel, AIBlockModelHelper, AIBlockModelImpl, AIBlockOutputStatus};
use super::view_impl::common::{
render_debug_footer, render_failed_output, render_informational_footer, render_text_sections,
DebugFooterProps, FailedOutputProps, TextSectionsProps,
};
use super::view_impl::output::are_all_text_sections_empty;
use super::{EmbeddedCodeEditorView, SecretRedactionState, TableSectionHandles};
use crate::ai::agent::conversation::AIConversationId;
use crate::ai::agent::icons::yellow_stop_icon;
use crate::ai::agent::task::TaskId;
use crate::ai::agent::{
AIAgentActionType, AIAgentInput, AIAgentOutput, AIAgentOutputMessageType, AIAgentPtyWriteMode,
AIAgentText, AIAgentTextSection, CancellationReason, ProgrammingLanguage, WebSearchStatus,
};
use crate::ai::blocklist::block::view_impl::common::{
render_query_text, UserQueryProps, BLOCKED_ACTION_MESSAGE_FOR_GREP_OR_FILE_GLOB,
BLOCKED_ACTION_MESSAGE_FOR_READING_FILES, BLOCKED_ACTION_MESSAGE_FOR_SEARCHING_CODEBASE,
@@ -51,72 +61,47 @@ use crate::ai::blocklist::block::view_impl::common::{
LOAD_OUTPUT_MESSAGE_FOR_READING_FILES, LOAD_OUTPUT_MESSAGE_FOR_SEARCH_CODEBASE,
LOAD_OUTPUT_MESSAGE_FOR_WEB_SEARCH,
};
use crate::ai::blocklist::block::TextLocation;
use crate::ai::blocklist::code_block::CodeSnippetButtonHandles;
use crate::ai::blocklist::inline_action::inline_action_icons::icon_size;
use crate::ai::blocklist::permissions::is_agent_mode_autonomy_allowed;
use crate::ai::blocklist::{
BlocklistAIActionModel, BlocklistAIHistoryEvent, BlocklistAIPermissions,
};
use crate::ai::control_code_parser::{parse_control_codes_from_bytes, ParsedControlCodeOutput};
use crate::code::editor::view::{CodeEditorEvent, CodeEditorRenderOptions};
use crate::menu::MenuItemFields;
use crate::send_telemetry_from_ctx;
use crate::ai::execution_profiles::profiles::{
AIExecutionProfilesModel, AIExecutionProfilesModelEvent,
};
use crate::code::editor::view::{CodeEditorEvent, CodeEditorRenderOptions, CodeEditorView};
use crate::code::editor_management::CodeSource;
use crate::editor::InteractionState;
use crate::menu::{Event as MenuEvent, Menu, MenuItemFields, MenuVariant};
use crate::server::telemetry::TelemetryEvent;
use crate::settings::AISettings;
use crate::settings_view::SettingsSection;
use crate::terminal::input::SET_INPUT_MODE_TERMINAL_ACTION_NAME;
use crate::terminal::model::block::BlockId;
use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode;
use crate::terminal::{ShellLaunchData, TerminalModel};
use crate::ui_components::blended_colors;
use crate::ui_components::icons::Icon;
use crate::util::link_detection::{detect_links, DetectedLinksState};
use crate::view_components::action_button::{
ButtonSize, KeystrokeSource, NakedTheme, PrimaryTheme,
};
use crate::view_components::compactible_action_button::{
render_compact_and_regular_button_rows, CompactibleActionButton, RenderCompactibleActionButton,
};
use crate::view_components::compactible_split_action_button::CompactibleSplitActionButton;
use crate::view_components::DismissibleToast;
use crate::workspace::WorkspaceAction;
use crate::ToastStack;
use crate::{
ai::{
agent::{
conversation::AIConversationId, task::TaskId, AIAgentActionType, AIAgentOutput,
AIAgentOutputMessageType, AIAgentText, AIAgentTextSection, ProgrammingLanguage,
WebSearchStatus,
},
blocklist::{
code_block::CodeSnippetButtonHandles, BlocklistAIActionModel, BlocklistAIHistoryEvent,
BlocklistAIPermissions,
},
execution_profiles::profiles::{AIExecutionProfilesModel, AIExecutionProfilesModelEvent},
},
code::{editor::view::CodeEditorView, editor_management::CodeSource},
editor::InteractionState,
menu::{Event as MenuEvent, Menu, MenuVariant},
settings_view::SettingsSection,
terminal::safe_mode_settings::get_secret_obfuscation_mode,
ui_components::{blended_colors, icons::Icon},
view_components::{
action_button::{ButtonSize, KeystrokeSource, NakedTheme, PrimaryTheme},
compactible_action_button::{
render_compact_and_regular_button_rows, CompactibleActionButton,
RenderCompactibleActionButton,
},
compactible_split_action_button::CompactibleSplitActionButton,
},
BlocklistAIHistoryModel,
};
use crate::ai::agent::AIAgentInput;
use crate::ai::blocklist::block::TextLocation;
use crate::util::link_detection::{detect_links, DetectedLinksState};
use crate::ai::agent::icons::yellow_stop_icon;
use crate::ai::blocklist::inline_action::inline_action_icons::icon_size;
use super::cli_controller::{CLISubagentController, CLISubagentEvent, UserTakeOverReason};
use super::model::AIBlockModelHelper;
use super::TableSectionHandles;
use super::{
model::{AIBlockModel, AIBlockModelImpl, AIBlockOutputStatus},
view_impl::{
common::{
render_debug_footer, render_failed_output, render_informational_footer,
render_text_sections, DebugFooterProps, FailedOutputProps, TextSectionsProps,
},
output::are_all_text_sections_empty,
},
EmbeddedCodeEditorView, SecretRedactionState,
};
use crate::{send_telemetry_from_ctx, BlocklistAIHistoryModel, ToastStack};
const MENU_WIDTH: f32 = 200.0;
const MAX_HEIGHT: f32 = 320.0;
const MIN_RESIZABLE_WIDTH: f32 = 360.0;
const MIN_RESIZABLE_HEIGHT: f32 = 40.0;
const MIN_REMAINING_WINDOW_WIDTH: f32 = 200.0;
const MIN_REMAINING_WINDOW_HEIGHT: f32 = 100.0;
const AVATAR_RIGHT_MARGIN: f32 = 8.;
const CONTENT_PADDING: f32 = 12.;
const ALLOW_ACTION_POSITION_ID: &str = "allow-action-position-id";
@@ -145,7 +130,8 @@ const HAS_PENDING_NON_TRANSFER_CONTROL_ACTION_CONTEXT_KEY: &str =
const BLOCKED_ACTION_MESSAGE_FOR_TRANSFER_CONTROL: &str = "Agent is asking you to take control.";
pub fn init(app: &mut AppContext) {
use galaxyui::keymap::{macros::*, FixedBinding};
use galaxyui::keymap::macros::*;
use galaxyui::keymap::FixedBinding;
app.register_fixed_bindings([
FixedBinding::new(
@@ -234,6 +220,8 @@ pub struct CLISubagentView {
is_input_dismissed: bool,
input_dismiss_timer_handle: Option<SpawnedFutureHandle>,
resizable_width: ResizableStateHandle,
resizable_height: ResizableStateHandle,
current_working_directory: Option<String>,
shell_launch_data: Option<ShellLaunchData>,
@@ -488,6 +476,8 @@ impl CLISubagentView {
always_allow_read_files_checked,
is_input_dismissed: false,
input_dismiss_timer_handle: None,
resizable_width: resizable_state_handle(MIN_RESIZABLE_WIDTH),
resizable_height: resizable_state_handle(MAX_HEIGHT),
current_working_directory,
shell_launch_data,
selected_text: Arc::new(RwLock::new(None)),
@@ -734,9 +724,8 @@ impl CLISubagentView {
.and_then(|language| language.to_extension())
{
// Since this is a code snippet, construct a fake path name for looking up the language.
let fake_path_string = format!("snippet.{extension}");
let fake_path = std::path::Path::new(&fake_path_string);
view.set_language_with_path(fake_path, ctx);
let fake_path = format!("/snippet.{extension}");
view.set_language_with_local_path(Path::new(&fake_path), ctx);
}
}
let starting_line_number = source.as_ref().and_then(|s| {
@@ -977,6 +966,11 @@ impl View for CLISubagentView {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
let semantic_selection = SemanticSelection::handle(app).as_ref(app);
let resizable_height = self
.resizable_height
.lock()
.map(|g| g.size())
.unwrap_or(MAX_HEIGHT);
let mut result = Flex::column()
.with_main_axis_size(MainAxisSize::Min)
@@ -1035,6 +1029,7 @@ impl View for CLISubagentView {
child: selectable_text.finish(),
background_color: internal_colors::accent_bg(theme).into(),
border: Some(Border::all(1.).with_border_fill(theme.accent())),
max_height: resizable_height,
},
app,
)
@@ -1148,6 +1143,7 @@ impl View for CLISubagentView {
border: Some(Border::all(1.).with_border_fill(
internal_colors::neutral_3(theme),
)),
max_height: resizable_height,
},
app,
)
@@ -1175,6 +1171,7 @@ impl View for CLISubagentView {
internal_colors::neutral_3(theme),
),
),
max_height: resizable_height,
},
app,
)
@@ -1190,57 +1187,64 @@ impl View for CLISubagentView {
let mut output_border = Border::all(1.).with_border_fill(internal_colors::neutral_3(theme));
if let AIBlockOutputStatus::Failed { error, .. } = &status {
output_border = Border::all(1.).with_border_color(theme.ui_error_color());
output_items.add_child(render_failed_output(
FailedOutputProps {
error,
is_ai_input_enabled: false,
invalid_api_key_button_handle: &self
.state_handles
.invalid_api_key_button_handle,
aws_bedrock_credentials_error_view: None,
icon_right_margin: AVATAR_RIGHT_MARGIN,
},
app,
));
// While an automatic resume is still in flight, keep the failed exchange
// quiet: don't switch to the error border, and skip the banner, the "won't
// count towards usage" notice, and the debug footer. The full failure UI is
// surfaced only once recovery has actually failed. Dogfood builds (Local/Dev)
// opt out so developers still see every transport failure aggressively.
if !error.should_suppress_during_recovery() {
output_border = Border::all(1.).with_border_color(theme.ui_error_color());
output_items.add_child(render_failed_output(
FailedOutputProps {
error,
is_ai_input_enabled: false,
invalid_api_key_button_handle: &self
.state_handles
.invalid_api_key_button_handle,
aws_bedrock_credentials_error_view: None,
icon_right_margin: AVATAR_RIGHT_MARGIN,
},
app,
));
if !self.model.is_restored() && !error.is_invalid_api_key() {
output_items.add_child(
Container::new(render_informational_footer(
app,
"This response won't count towards your usage. \"Take over\" to continue."
.to_string(),
))
.with_margin_top(8.)
.with_margin_left(icon_size(app) + AVATAR_RIGHT_MARGIN)
.finish(),
);
if !self.model.is_restored() && !error.is_invalid_api_key() {
output_items.add_child(
Container::new(render_informational_footer(
app,
"This response won't count towards your usage. \"Take over\" to continue."
.to_string(),
))
.with_margin_top(8.)
.with_margin_left(icon_size(app) + AVATAR_RIGHT_MARGIN)
.finish(),
);
output_items.add_child(
Container::new(render_debug_footer(
DebugFooterProps {
conversation: self.model.conversation(app),
model: self.model.as_ref(),
debug_copy_button_handle: self
.state_handles
.debug_copy_button_handle
.clone(),
submit_issue_button_handle: self
.state_handles
.submit_issue_button_handle
.clone(),
should_render_feedback_below: true,
},
|debug_id, ctx| {
ctx.dispatch_typed_action(CLISubagentAction::CopyDebugId(debug_id))
},
|ctx| ctx.dispatch_typed_action(CLISubagentAction::OpenFeedbackDocs),
app,
))
.with_margin_top(8.)
.with_margin_left(icon_size(app) + AVATAR_RIGHT_MARGIN)
.finish(),
);
output_items.add_child(
Container::new(render_debug_footer(
DebugFooterProps {
conversation: self.model.conversation(app),
model: self.model.as_ref(),
debug_copy_button_handle: self
.state_handles
.debug_copy_button_handle
.clone(),
submit_issue_button_handle: self
.state_handles
.submit_issue_button_handle
.clone(),
should_render_feedback_below: true,
},
|debug_id, ctx| {
ctx.dispatch_typed_action(CLISubagentAction::CopyDebugId(debug_id))
},
|ctx| ctx.dispatch_typed_action(CLISubagentAction::OpenFeedbackDocs),
app,
))
.with_margin_top(8.)
.with_margin_left(icon_size(app) + AVATAR_RIGHT_MARGIN)
.finish(),
);
}
}
}
@@ -1277,6 +1281,7 @@ impl View for CLISubagentView {
child: output.finish(),
background_color: internal_colors::neutral_2(appearance.theme()),
border: Some(output_border),
max_height: resizable_height,
},
app,
)
@@ -1296,6 +1301,7 @@ impl View for CLISubagentView {
input: input.clone(),
mode,
scroll_state: self.state_handles.input_scroll_state.clone(),
max_height: resizable_height,
},
app,
)),
@@ -1396,7 +1402,24 @@ impl View for CLISubagentView {
);
}
result.finish()
let content = result.finish();
let width_resizable = Resizable::new(self.resizable_width.clone(), content)
.with_dragbar_side(DragBarSide::Left)
.on_resize(|ctx, _| ctx.notify())
.with_bounds_callback(Box::new(|window_size| {
let max = (window_size.x() - MIN_REMAINING_WINDOW_WIDTH).max(MIN_RESIZABLE_WIDTH);
(MIN_RESIZABLE_WIDTH, max)
}))
.finish();
Resizable::new(self.resizable_height.clone(), width_resizable)
.with_dragbar_side(DragBarSide::Top)
.on_resize(|ctx, _| ctx.notify())
.with_bounds_callback(Box::new(|window_size| {
let max = (window_size.y() - MIN_REMAINING_WINDOW_HEIGHT).max(MIN_RESIZABLE_HEIGHT);
(MIN_RESIZABLE_HEIGHT, max)
}))
.finish()
}
fn keymap_context(&self, app: &AppContext) -> galaxyui::keymap::Context {
@@ -1708,6 +1731,7 @@ struct ScrollableContainerProps {
child: Box<dyn Element>,
background_color: ColorU,
border: Option<Border>,
max_height: f32,
}
fn render_scrollable_container(props: ScrollableContainerProps, _app: &AppContext) -> Container {
@@ -1716,6 +1740,7 @@ fn render_scrollable_container(props: ScrollableContainerProps, _app: &AppContex
child,
background_color,
border,
max_height,
} = props;
let scrollable = NewScrollable::vertical(
@@ -1731,7 +1756,7 @@ fn render_scrollable_container(props: ScrollableContainerProps, _app: &AppContex
.finish();
let clipped = ConstrainedBox::new(scrollable)
.with_max_height(MAX_HEIGHT)
.with_max_height(max_height)
.finish();
let mut container = Container::new(clipped)
@@ -1915,6 +1940,7 @@ struct WriteToPtyInputProps {
input: bytes::Bytes,
mode: AIAgentPtyWriteMode,
scroll_state: ClippedScrollStateHandle,
max_height: f32,
}
fn render_write_to_pty_input(props: WriteToPtyInputProps, app: &AppContext) -> Box<dyn Element> {
@@ -1922,6 +1948,7 @@ fn render_write_to_pty_input(props: WriteToPtyInputProps, app: &AppContext) -> B
input,
mode,
scroll_state,
max_height,
} = props;
let appearance = Appearance::as_ref(app);
@@ -1967,7 +1994,7 @@ fn render_write_to_pty_input(props: WriteToPtyInputProps, app: &AppContext) -> B
.finish();
let clipped = ConstrainedBox::new(scrollable)
.with_max_height(MAX_HEIGHT)
.with_max_height(max_height)
.finish();
Container::new(clipped)