Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,460 @@
use crate::{
ai::agent::icons::{yellow_running_icon, yellow_stop_icon},
view_components::compactible_action_button::{
CompactibleActionButton, RenderCompactibleActionButton, SMALL_SIZE_SWITCH_THRESHOLD,
},
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use settings::Setting as _;
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::Arc;
use warp_core::semantic_selection::SemanticSelection;
use warp_core::{features::FeatureFlag, ui::Icon};
use warpui::{
elements::{
get_rich_content_position_id, Border, Clipped, Container, CornerRadius, CrossAxisAlignment,
Flex, FormattedTextElement, MouseStateHandle, ParentElement, Radius, SavePosition,
SelectableArea, SelectionHandle,
},
keymap::{FixedBinding, Keystroke},
AppContext, Element, Entity, EntityId, FocusContext, SingletonEntity, TypedActionView, View,
ViewContext,
};
use crate::{
ai::blocklist::block::view_impl::{CONTENT_HORIZONTAL_PADDING, CONTENT_ITEM_VERTICAL_MARGIN},
ai::blocklist::inline_action::inline_action_header::INLINE_ACTION_HORIZONTAL_PADDING,
ai::blocklist::inline_action::inline_action_header::{
ExpandedConfig, HeaderConfig, InteractionMode,
},
ai::blocklist::inline_action::inline_action_icons::{self},
appearance::Appearance,
settings::InputModeSettings,
terminal::{
block_list_element::BlockListMenuSource, block_list_viewport::InputMode,
view::TerminalAction,
},
ui_components::blended_colors,
view_components::action_button::{ButtonSize, KeystrokeSource, NakedTheme, PrimaryTheme},
};
/// The vertical padding applied to the env var collection block's content body.
/// For horizontal padding, use [`INLINE_ACTION_HORIZONTAL_PADDING`] for consistency.
const ENV_VAR_COLLECTION_BODY_VERTICAL_PADDING: f32 = 16.;
const ENV_VAR_COLLECTION_CANCEL_LABEL: &str = "Cancel";
const ENV_VAR_COLLECTION_ACCEPT_LABEL: &str = "Run";
lazy_static! {
static ref CANCEL_ENV_VAR_COLLECTION_KEYSTROKE: Keystroke = Keystroke {
ctrl: true,
key: "c".to_owned(),
..Default::default()
};
static ref ACCEPT_ENV_VAR_COLLECTION_KEYSTROKE: Keystroke = Keystroke {
key: "enter".to_owned(),
..Default::default()
};
}
#[derive(Debug, Clone)]
pub enum EnvVarCollectionBlockEvent {
Cancelled,
RanCommand(String),
ToggledExpanded(String),
TextSelected,
}
#[derive(Debug, Clone)]
pub enum EnvVarCollectionBlockAction {
Cancel,
RunCommand,
/// Only applies to text selections made at the `EnvVarCollectionBlock` level. Child views of the
/// `EnvVarCollectionBlock` are responsible for managing their own text selection states.
SelectText,
ToggleExpanded,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum EnvVarCollectionState {
/// The env var command is loaded and waiting to be run by the user.
WaitingForUser,
/// The env var command is currently running, after being accepted by the user.
Running,
/// The env var command finished running and succeeded.
Succeeded,
/// The env var command finished running and failed.
Failed,
/// The env var command was cancelled at some point before completing
/// (i.e. before [`Self::Succeeded`] or [`Self::Failed`]).
Cancelled,
}
impl EnvVarCollectionState {
fn has_completed(&self) -> bool {
matches!(self, Self::Succeeded | Self::Failed | Self::Cancelled)
}
}
pub struct EnvVarCollectionBlock {
cancel_button: CompactibleActionButton,
accept_button: CompactibleActionButton,
command: String,
command_output: Option<String>,
state: EnvVarCollectionState,
block_id: String,
view_id: EntityId,
/// The output grid needs to be selectable to allow users to copy the command to their clipboard.
/// Only applies to text selections made at the `EnvVarCollectionBlock` level. Child views of the
/// `EnvVarCollectionBlock` are responsible for managing their own text selection states.
selection_handle: SelectionHandle,
selected_text: Arc<RwLock<Option<String>>>,
header_is_expanded: bool,
header_mouse_state: MouseStateHandle,
}
pub fn init(app: &mut AppContext) {
use warpui::keymap::macros::*;
app.register_fixed_bindings([
FixedBinding::new(
"ctrl-c",
EnvVarCollectionBlockAction::Cancel,
id!(EnvVarCollectionBlock::ui_name()),
),
FixedBinding::new(
"enter",
EnvVarCollectionBlockAction::RunCommand,
id!(EnvVarCollectionBlock::ui_name()),
),
FixedBinding::new(
"numpadenter",
EnvVarCollectionBlockAction::RunCommand,
id!(EnvVarCollectionBlock::ui_name()),
),
]);
}
impl EnvVarCollectionBlock {
pub fn new(
block_id: String,
_collection_title: String,
command: String,
ctx: &mut ViewContext<Self>,
) -> Self {
let cancel_button = CompactibleActionButton::new(
ENV_VAR_COLLECTION_CANCEL_LABEL.to_string(),
Some(KeystrokeSource::Fixed(
CANCEL_ENV_VAR_COLLECTION_KEYSTROKE.clone(),
)),
ButtonSize::InlineActionHeader,
EnvVarCollectionBlockAction::Cancel,
Icon::X,
Arc::new(NakedTheme),
ctx,
);
let accept_button = CompactibleActionButton::new(
ENV_VAR_COLLECTION_ACCEPT_LABEL.to_string(),
Some(KeystrokeSource::Fixed(
ACCEPT_ENV_VAR_COLLECTION_KEYSTROKE.clone(),
)),
ButtonSize::InlineActionHeader,
EnvVarCollectionBlockAction::RunCommand,
Icon::Check,
Arc::new(PrimaryTheme),
ctx,
);
Self {
cancel_button,
accept_button,
command,
command_output: None,
state: EnvVarCollectionState::WaitingForUser,
block_id,
view_id: ctx.view_id(),
selection_handle: Default::default(),
selected_text: Default::default(),
header_is_expanded: false,
header_mouse_state: Default::default(),
}
}
fn handle_toggle_expanded(&mut self, ctx: &mut ViewContext<Self>) {
self.header_is_expanded = !self.header_is_expanded;
ctx.emit(EnvVarCollectionBlockEvent::ToggledExpanded(
self.block_id.clone(),
));
ctx.notify();
}
pub fn focus(&self, ctx: &mut ViewContext<Self>) {
ctx.focus_self();
}
pub fn get_block_id(&self) -> &String {
&self.block_id
}
pub fn is_block_completed(&self) -> bool {
self.state.has_completed()
}
pub fn is_running(&self) -> bool {
self.state == EnvVarCollectionState::Running
}
pub fn on_succeeded(&mut self, ctx: &mut ViewContext<Self>) {
self.state = EnvVarCollectionState::Succeeded;
ctx.notify();
}
pub fn on_failed(&mut self, output: Option<String>, ctx: &mut ViewContext<Self>) {
self.command_output = output;
self.state = EnvVarCollectionState::Failed;
ctx.notify();
}
fn run_command(&mut self, ctx: &mut ViewContext<Self>) {
if !self.is_block_completed() {
self.state = EnvVarCollectionState::Running;
ctx.emit(EnvVarCollectionBlockEvent::RanCommand(self.command.clone()));
ctx.notify();
}
}
pub fn cancel(&mut self, ctx: &mut ViewContext<Self>) {
if !self.is_block_completed() {
self.state = EnvVarCollectionState::Cancelled;
ctx.emit(EnvVarCollectionBlockEvent::Cancelled);
ctx.notify();
}
}
/// Returns the currently selected text within the entire `EnvVarCollectionBlock` view sub-hierarchy.
/// There **shouldn't** be more than one instance of selected text at any given time across
/// any view within the same `EnvVarCollectionBlock` view sub-hierarchy.
pub fn selected_text(&self, _ctx: &AppContext) -> Option<String> {
self.selected_text.read().clone()
}
pub fn clear_selection(&mut self, ctx: &mut ViewContext<Self>) {
self.selection_handle.clear();
*self.selected_text.write() = None;
ctx.notify();
}
pub fn handle_ctrl_c(&mut self, ctx: &mut ViewContext<Self>) {
self.cancel(ctx);
}
fn render_header(&self, app: &AppContext) -> Box<dyn Element> {
const COMMAND_WAITING_FOR_USER_MESSAGE: &str =
"OK if I run this command and read the output?";
let title: Cow<'static, str> = if self.state == EnvVarCollectionState::WaitingForUser {
COMMAND_WAITING_FOR_USER_MESSAGE.into()
} else {
self.command.clone().into()
};
let appearance = Appearance::as_ref(app);
let icon = match self.state {
EnvVarCollectionState::WaitingForUser => Some(yellow_stop_icon(appearance)),
EnvVarCollectionState::Running => Some(yellow_running_icon(appearance)),
EnvVarCollectionState::Succeeded => {
Some(inline_action_icons::green_check_icon(appearance))
}
EnvVarCollectionState::Failed => Some(inline_action_icons::red_x_icon(appearance)),
EnvVarCollectionState::Cancelled => {
Some(inline_action_icons::cancelled_icon(appearance))
}
};
let interaction_mode = match self.state {
EnvVarCollectionState::WaitingForUser => {
let buttons: Vec<Rc<dyn RenderCompactibleActionButton>> = vec![
Rc::new(self.cancel_button.clone()),
Rc::new(self.accept_button.clone()),
];
Some(InteractionMode::ActionButtons {
action_buttons: buttons,
size_switch_threshold: SMALL_SIZE_SWITCH_THRESHOLD,
})
}
EnvVarCollectionState::Failed => {
let expansion_config =
ExpandedConfig::new(self.header_is_expanded, self.header_mouse_state.clone())
.with_toggle_callback(move |ctx| {
ctx.dispatch_typed_action(EnvVarCollectionBlockAction::ToggleExpanded);
});
Some(InteractionMode::ManuallyExpandable(expansion_config))
}
_ => None,
};
let mut config = HeaderConfig::new(title, app).with_selectable_text();
if let Some(icon) = icon {
config = config.with_icon(icon);
}
if let Some(mode) = interaction_mode {
config = config.with_interaction_mode(mode);
}
config.render(app)
}
}
impl Entity for EnvVarCollectionBlock {
type Event = EnvVarCollectionBlockEvent;
}
impl View for EnvVarCollectionBlock {
fn ui_name() -> &'static str {
"EnvVarCollectionBlock"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
// Build the stateless header based on current state
let header_element = self.render_header(app);
let mut content = Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_child(Clipped::new(header_element).finish());
let is_header_expanded =
self.state == EnvVarCollectionState::WaitingForUser || self.header_is_expanded;
let is_input_pinned_to_top =
*InputModeSettings::as_ref(app).input_mode.value() == InputMode::PinnedToTop;
// If we're expanding the env var collection block downward, we want the "Viewing command
// detail" row to look connected to the block rendered below, which affects styling. Note
// that `EnvVarCollectionState::Failed` is the only state that permits expansion toggling.
let should_expand_downward = is_header_expanded
&& !is_input_pinned_to_top
&& self.state == EnvVarCollectionState::Failed;
if is_header_expanded && self.state == EnvVarCollectionState::WaitingForUser {
let selectable_child = Container::new(
FormattedTextElement::from_str(
self.command.clone(),
appearance.monospace_font_family(),
appearance.monospace_font_size(),
)
.with_color(blended_colors::text_main(theme, theme.background()))
.with_line_height_ratio(1.3)
.set_selectable(true)
.finish(),
)
.with_horizontal_padding(INLINE_ACTION_HORIZONTAL_PADDING)
.with_vertical_padding(ENV_VAR_COLLECTION_BODY_VERTICAL_PADDING)
.with_background(theme.background())
.with_corner_radius(CornerRadius::with_bottom(Radius::Pixels(8.)))
.finish();
let semantic_selection = SemanticSelection::as_ref(app);
let selected_text = self.selected_text.clone();
let view_id = self.view_id;
let mut selectable = SelectableArea::new(
self.selection_handle.clone(),
move |selection_args, _, _| {
*selected_text.write() = selection_args.selection;
},
SavePosition::new(
selectable_child,
get_rich_content_position_id(&view_id).as_str(),
)
.finish(),
)
.with_word_boundaries_policy(semantic_selection.word_boundary_policy())
.with_smart_select_fn(semantic_selection.smart_select_fn())
.on_selection_updated(|ctx, _| {
ctx.dispatch_typed_action(EnvVarCollectionBlockAction::SelectText)
})
.on_selection_right_click(move |ctx, position| {
ctx.dispatch_typed_action(TerminalAction::BlockListContextMenu(
BlockListMenuSource::RichContentTextRightClick {
rich_content_view_id: view_id,
position_in_rich_content: position,
},
))
});
if FeatureFlag::RectSelection.is_enabled() {
selectable = selectable.should_support_rect_select();
}
content.add_child(selectable.finish());
}
let border_color = if self.state == EnvVarCollectionState::WaitingForUser {
theme.accent()
} else {
theme.surface_2()
};
let content = Container::new(content.finish())
// Since expanded details are rendered using a regular block, having a non-zero horizontal
// margin while toggled expanded will cause the body to look wider than the header.
.with_horizontal_margin(if should_expand_downward {
0.
} else {
CONTENT_HORIZONTAL_PADDING
})
// Since expanded details are rendered using a regular block, having a non-zero bottom
// margin while toggled expanded will cause the body to look disconnected from the header.
.with_margin_bottom(if should_expand_downward {
0.
} else {
CONTENT_ITEM_VERTICAL_MARGIN
})
// Rounded corners will make the header feel disconnected from its expanded details.
.with_corner_radius(if should_expand_downward {
CornerRadius::with_top(Radius::Pixels(9.))
} else {
CornerRadius::with_all(Radius::Pixels(9.))
})
.with_border(Border::all(1.).with_border_fill(border_color))
.finish();
Container::new(content)
.with_padding_top(CONTENT_ITEM_VERTICAL_MARGIN)
.with_background(theme.ai_blocks_overlay())
.with_border(Border::top(1.).with_border_fill(theme.outline()))
.finish()
}
fn on_focus(&mut self, focus_ctx: &FocusContext, ctx: &mut ViewContext<Self>) {
if focus_ctx.is_self_focused() {
ctx.focus_self();
ctx.notify();
}
}
}
impl TypedActionView for EnvVarCollectionBlock {
type Action = EnvVarCollectionBlockAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
EnvVarCollectionBlockAction::RunCommand => self.run_command(ctx),
EnvVarCollectionBlockAction::Cancel => self.cancel(ctx),
EnvVarCollectionBlockAction::SelectText => {
ctx.emit(EnvVarCollectionBlockEvent::TextSelected)
}
EnvVarCollectionBlockAction::ToggleExpanded => self.handle_toggle_expanded(ctx),
}
}
}