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,202 @@
use pathfinder_color::ColorU;
use pathfinder_geometry::vector::Vector2F;
use warp_core::ui::{appearance::Appearance, Icon};
use warpui::{
elements::{
Align, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DropShadow, Flex,
MainAxisAlignment, ParentElement as _, Radius, Shrinkable,
},
fonts::Weight,
ui_components::components::{BorderStyle, Coords, UiComponent as _, UiComponentStyles},
AppContext, Element, Entity, FocusContext, SingletonEntity as _, TypedActionView, View,
ViewContext, ViewHandle,
};
use crate::editor::{EditorOptions, EditorView, Event as EditorEvent, TextOptions};
const PROMPT_INPUT_HEIGHT: f32 = 56.;
const ICON_MARGIN_LEFT: f32 = 12.;
const ICON_MARGIN_RIGHT: f32 = 6.;
pub struct GlowingEditor {
editor: ViewHandle<EditorView>,
/// A closure that returns if the current editor content are valid and can be submitted.
validator: Box<dyn Fn(&str) -> bool>,
/// Whether or not the last submission attempt failed validation.
has_error: bool,
}
impl GlowingEditor {
pub fn new(placeholder: impl Into<String>, ctx: &mut ViewContext<Self>) -> Self {
let appearance = Appearance::as_ref(ctx);
let font_family = appearance.ui_font_family();
let font_size = appearance.ui_font_size() + 2.;
let editor = ctx.add_typed_action_view(|ctx| {
let options = EditorOptions {
soft_wrap: true,
text: TextOptions {
font_size_override: Some(font_size),
font_family_override: Some(font_family),
..Default::default()
},
..Default::default()
};
let mut editor = EditorView::new(options, ctx);
editor.set_placeholder_text(placeholder, ctx);
editor
});
ctx.subscribe_to_view(&editor, move |me, _, event, ctx| {
me.handle_editor_event(event, ctx);
});
Self {
editor,
validator: Box::new(|_| true),
has_error: false,
}
}
/// Validates the input contents using the provided `validator` whenever a submit action is
/// attempted.
#[expect(dead_code, reason = "Nothing needs validation currently.")]
pub fn with_validator<F: Fn(&str) -> bool + 'static>(mut self, validator: F) -> Self {
self.validator = Box::new(validator);
self
}
pub fn clear_buffer(&mut self, ctx: &mut ViewContext<Self>) {
self.editor.update(ctx, |editor, ctx| {
editor.clear_buffer(ctx);
});
}
fn handle_editor_event(&mut self, event: &EditorEvent, ctx: &mut ViewContext<Self>) {
match event {
EditorEvent::Enter => {
let prompt = self
.editor
.read(ctx, |editor, ctx| editor.buffer_text(ctx).trim().to_owned());
if prompt.is_empty() {
return;
}
if !(self.validator)(&prompt) {
self.has_error = true;
ctx.notify();
} else {
self.has_error = false;
self.clear_buffer(ctx);
ctx.emit(GlowingEditorEvent::Submit(prompt));
}
}
EditorEvent::Escape => ctx.emit(GlowingEditorEvent::Cancel),
// Clear error state when user types (since this is submit-only validation)
EditorEvent::Edited(_) => {
if self.has_error {
self.has_error = false;
ctx.notify();
}
}
_ => (),
}
}
}
pub enum GlowingEditorEvent {
Submit(String),
Cancel,
}
impl Entity for GlowingEditor {
type Event = GlowingEditorEvent;
}
impl TypedActionView for GlowingEditor {
type Action = ();
}
impl View for GlowingEditor {
fn ui_name() -> &'static str {
"GlowingEditor"
}
fn on_focus(&mut self, focus_ctx: &FocusContext, ctx: &mut ViewContext<Self>) {
if focus_ctx.is_self_focused() {
ctx.focus(&self.editor);
}
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
let input_box = Shrinkable::new(
1.,
Align::new(
appearance
.ui_builder()
.text_input(self.editor.clone())
.with_style(UiComponentStyles {
font_weight: Some(Weight::Semibold),
border_style: Some(BorderStyle::None),
border_width: Some(0.),
background: Some(ColorU::transparent_black().into()),
padding: Some(Coords::uniform(10.).left(0.)),
..Default::default()
})
.build()
.finish(),
)
.finish(),
)
.finish();
let font_size = appearance.ui_font_size() + 2.;
let agent_icon = Container::new(
ConstrainedBox::new(
Icon::AgentMode
.to_warpui_icon(theme.sub_text_color(theme.background()))
.finish(),
)
.with_height(font_size)
.with_width(font_size)
.finish(),
)
.with_margin_left(ICON_MARGIN_LEFT)
.with_margin_right(ICON_MARGIN_RIGHT)
.finish();
let editor_content = ConstrainedBox::new(
Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_main_axis_alignment(MainAxisAlignment::Start)
.with_children([agent_icon, input_box])
.finish(),
)
.with_min_height(PROMPT_INPUT_HEIGHT);
let border_fill = if self.has_error {
theme.ui_error_color()
} else {
theme.outline().into_solid()
};
let shadow_color = if self.has_error {
ColorU::new(255, 0, 0, 100) // Red shadow with higher opacity for error
} else {
ColorU::new(255, 143, 253, 15) // Default purple shadow
};
Container::new(editor_content.finish())
.with_border(border_fill)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
.with_drop_shadow(
DropShadow::new_with_standard_offset_and_spread(shadow_color)
.with_offset(Vector2F::zero()),
)
.with_background(theme.background().into_solid())
.finish()
}
}