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,346 @@
use crate::editor::Event as EditorEvent;
use crate::modal::{Modal, ModalViewState};
use crate::{
appearance::Appearance,
editor::{EditorView, PropagateAndNoOpNavigationKeys, SingleLineEditorOptions, TextOptions},
};
use regex::Regex;
use warp_editor::editor::NavigationKey;
use warpui::elements::{CrossAxisAlignment, Expanded, MainAxisSize};
use warpui::{
elements::{ChildView, Container, Empty, Flex, MouseStateHandle, ParentElement, Text},
ui_components::{
button::ButtonVariant,
components::{Coords, UiComponent, UiComponentStyles},
},
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
};
const LABEL_FONT_SIZE: f32 = 12.;
pub struct AddRegexModal {
name_editor: ViewHandle<EditorView>,
pattern_editor: ViewHandle<EditorView>,
cancel_button_mouse_state: MouseStateHandle,
submit_button_mouse_state: MouseStateHandle,
}
#[derive(Debug)]
pub enum AddRegexModalAction {
Cancel,
Submit,
}
pub enum AddRegexModalEvent {
Close,
Submit { name: String, pattern: String },
}
impl AddRegexModal {
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
let font_family = Appearance::as_ref(ctx).ui_font_family();
let name_editor = ctx.add_typed_action_view(|ctx| {
let options = SingleLineEditorOptions {
text: TextOptions {
font_family_override: Some(font_family),
..Default::default()
},
propagate_and_no_op_vertical_navigation_keys:
PropagateAndNoOpNavigationKeys::Always,
..Default::default()
};
let mut editor = EditorView::single_line(options, ctx);
editor.set_placeholder_text("e.g. \"Google API Key\"", ctx);
editor
});
let pattern_editor = ctx.add_typed_action_view(|ctx| {
let options = SingleLineEditorOptions {
text: TextOptions {
font_family_override: Some(font_family),
..Default::default()
},
propagate_and_no_op_vertical_navigation_keys:
PropagateAndNoOpNavigationKeys::Always,
..Default::default()
};
let mut editor = EditorView::single_line(options, ctx);
editor.set_placeholder_text("\\bAIza[0-9A-Za-z-_]{35}\\b", ctx);
editor
});
// Subscribe to editor events for tab navigation and re-rendering
ctx.subscribe_to_view(&name_editor, |me, _, event, ctx| {
me.handle_name_editor_event(event, ctx);
});
ctx.subscribe_to_view(&pattern_editor, |me, _, event, ctx| {
me.handle_pattern_editor_event(event, ctx);
});
Self {
name_editor,
pattern_editor,
cancel_button_mouse_state: Default::default(),
submit_button_mouse_state: Default::default(),
}
}
fn submit(&mut self, ctx: &mut ViewContext<Self>) {
let name = self.name_editor.as_ref(ctx).buffer_text(ctx);
let pattern = self.pattern_editor.as_ref(ctx).buffer_text(ctx);
let is_valid_regex = Regex::new(&pattern).is_ok();
if !pattern.trim().is_empty() && is_valid_regex {
ctx.emit(AddRegexModalEvent::Submit { name, pattern });
}
}
fn cancel(&mut self, ctx: &mut ViewContext<Self>) {
ctx.emit(AddRegexModalEvent::Close);
}
pub fn on_close(&mut self, ctx: &mut ViewContext<Self>) {
self.name_editor.update(ctx, |editor, ctx| {
editor.clear_buffer_and_reset_undo_stack(ctx);
});
self.pattern_editor.update(ctx, |editor, ctx| {
editor.clear_buffer_and_reset_undo_stack(ctx);
});
}
pub fn on_open(&mut self, ctx: &mut ViewContext<Self>) {
ctx.focus(&self.name_editor);
}
fn handle_name_editor_event(&mut self, event: &EditorEvent, ctx: &mut ViewContext<Self>) {
match event {
EditorEvent::Navigate(NavigationKey::Tab) => {
ctx.focus(&self.pattern_editor);
}
EditorEvent::Navigate(NavigationKey::ShiftTab) => {
// Wrap around to pattern editor (last field)
ctx.focus(&self.pattern_editor);
}
EditorEvent::Enter => {
// Submit if pattern is not empty and valid regex (same logic as submit button)
let pattern = self.pattern_editor.as_ref(ctx).buffer_text(ctx);
let is_valid_regex = Regex::new(&pattern).is_ok();
if !pattern.trim().is_empty() && is_valid_regex {
self.submit(ctx);
}
}
EditorEvent::Escape => {
// Close modal like clicking Cancel or X button
self.cancel(ctx);
}
EditorEvent::Edited(_) => {
// Re-render to update validation when name field changes
ctx.notify();
}
_ => {}
}
}
fn handle_pattern_editor_event(&mut self, event: &EditorEvent, ctx: &mut ViewContext<Self>) {
match event {
EditorEvent::Navigate(NavigationKey::Tab) => {
ctx.focus(&self.name_editor);
}
EditorEvent::Navigate(NavigationKey::ShiftTab) => {
ctx.focus(&self.name_editor);
}
EditorEvent::Enter => {
// Submit if pattern is not empty and valid regex (same logic as submit button)
let pattern = self.pattern_editor.as_ref(ctx).buffer_text(ctx);
let is_valid_regex = Regex::new(&pattern).is_ok();
if !pattern.trim().is_empty() && is_valid_regex {
self.submit(ctx);
}
}
EditorEvent::Escape => {
// Close modal like clicking Cancel or X button
self.cancel(ctx);
}
EditorEvent::Edited(_) => {
// Re-render to update button state when pattern field changes
ctx.notify();
}
_ => {}
}
}
}
impl Entity for AddRegexModal {
type Event = AddRegexModalEvent;
}
impl View for AddRegexModal {
fn ui_name() -> &'static str {
"AddRegexModal"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
// Check if regex field has at least 1 character
let pattern_text = self.pattern_editor.as_ref(app).buffer_text(app);
let is_valid_regex = Regex::new(&pattern_text).is_ok();
let is_submit_enabled = !pattern_text.trim().is_empty() && is_valid_regex;
let name_label = Text::new(
"Name (optional)",
appearance.ui_font_family(),
LABEL_FONT_SIZE,
)
.with_color(theme.active_ui_text_color().into())
.finish();
let regex_label = Text::new(
"Regex pattern",
appearance.ui_font_family(),
LABEL_FONT_SIZE,
)
.with_color(theme.active_ui_text_color().into())
.finish();
let button_style = UiComponentStyles {
font_size: Some(14.),
padding: Some(Coords::uniform(8.).left(12.).right(12.)),
..Default::default()
};
let mut add_button = appearance
.ui_builder()
.button(
ButtonVariant::Accent,
self.submit_button_mouse_state.clone(),
)
.with_text_label("Add regex".to_string())
.with_style(button_style);
if !is_submit_enabled {
add_button = add_button.disabled();
}
let buttons_row = Flex::row()
.with_main_axis_size(MainAxisSize::Max)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(
Expanded::new(
1.,
Container::new(if !is_valid_regex && !pattern_text.trim().is_empty() {
Text::new(
"Invalid regex",
appearance.ui_font_family(),
LABEL_FONT_SIZE,
)
.with_color(
appearance
.theme()
.sub_text_color(appearance.theme().background())
.into(),
)
.finish()
} else {
Empty::new().finish()
})
.with_margin_bottom(8.)
.finish(),
)
.finish(),
)
.with_child(
appearance
.ui_builder()
.button(
ButtonVariant::Secondary,
self.cancel_button_mouse_state.clone(),
)
.with_text_label("Cancel".to_string())
.with_style(button_style)
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(AddRegexModalAction::Cancel);
})
.finish(),
)
.with_child(
Container::new(
add_button
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(AddRegexModalAction::Submit);
})
.finish(),
)
.with_margin_left(12.)
.finish(),
)
.finish();
Flex::column()
.with_child(Container::new(name_label).with_margin_bottom(4.).finish())
.with_child(
Container::new(ChildView::new(&self.name_editor).finish())
.with_margin_bottom(16.)
.finish(),
)
.with_child(Container::new(regex_label).with_margin_bottom(4.).finish())
.with_child(
Container::new(ChildView::new(&self.pattern_editor).finish())
.with_margin_bottom(24.)
.finish(),
)
.with_child(buttons_row)
.finish()
}
}
impl TypedActionView for AddRegexModal {
type Action = AddRegexModalAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
AddRegexModalAction::Cancel => self.cancel(ctx),
AddRegexModalAction::Submit => self.submit(ctx),
}
}
}
pub struct AddRegexModalViewState {
state: ModalViewState<Modal<AddRegexModal>>,
}
impl AddRegexModalViewState {
pub fn new(state: ModalViewState<Modal<AddRegexModal>>) -> Self {
Self { state }
}
pub fn is_open(&self) -> bool {
self.state.is_open()
}
pub fn render(&self) -> Box<dyn Element> {
self.state.render()
}
pub fn open<T: View>(&mut self, ctx: &mut ViewContext<T>) {
self.state.open();
self.state.view.update(ctx, |modal, ctx| {
modal.body().update(ctx, |body, ctx| {
body.on_open(ctx);
});
});
}
pub fn close<T: View>(&mut self, ctx: &mut ViewContext<T>) {
self.state.close();
self.state.view.update(ctx, |modal, ctx| {
modal.body().update(ctx, |body, ctx| {
body.on_close(ctx);
});
});
}
}
+3
View File
@@ -0,0 +1,3 @@
mod add_regex_modal;
pub use add_regex_modal::{AddRegexModal, AddRegexModalEvent, AddRegexModalViewState};