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
+91
View File
@@ -0,0 +1,91 @@
use crate::ai::agent::AIAgentTodo;
use super::AIAgentTodoId;
pub(crate) mod popup;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AIAgentTodoList {
completed_items: Vec<AIAgentTodo>,
pending_items: Vec<AIAgentTodo>,
}
impl AIAgentTodoList {
pub fn with_pending_items(mut self, pending_items: Vec<AIAgentTodo>) -> Self {
self.pending_items = pending_items;
self
}
pub fn with_completed_items(mut self, completed_items: Vec<AIAgentTodo>) -> Self {
self.completed_items = completed_items;
self
}
pub fn update_pending_items(&mut self, pending_items: Vec<AIAgentTodo>) {
self.pending_items = pending_items;
}
pub fn clear_pending_items(&mut self) {
self.pending_items.clear();
}
pub fn len(&self) -> usize {
self.pending_items.len() + self.completed_items.len()
}
pub fn is_finished(&self) -> bool {
self.pending_items.is_empty() && !self.completed_items.is_empty()
}
pub fn is_empty(&self) -> bool {
self.pending_items.is_empty() && self.completed_items.is_empty()
}
pub fn in_progress_item(&self) -> Option<&AIAgentTodo> {
self.pending_items.first()
}
pub fn pending_items(&self) -> &[AIAgentTodo] {
&self.pending_items
}
pub fn completed_items(&self) -> &[AIAgentTodo] {
&self.completed_items
}
pub fn is_pending(&self, todo_id: &AIAgentTodoId) -> bool {
self.pending_items.iter().any(|item| &item.id == todo_id)
}
pub fn is_completed(&self, todo_id: &AIAgentTodoId) -> bool {
self.completed_items.iter().any(|item| &item.id == todo_id)
}
pub fn get_item(&self, todo_id: &AIAgentTodoId) -> Option<&AIAgentTodo> {
self.items().find(|item| &item.id == todo_id)
}
pub fn get_item_index(&self, todo_id: &AIAgentTodoId) -> Option<usize> {
self.items().position(|item| &item.id == todo_id)
}
fn items(&self) -> impl Iterator<Item = &AIAgentTodo> {
self.completed_items.iter().chain(self.pending_items.iter())
}
pub fn update_pending_todos(&mut self, todos: Vec<AIAgentTodo>) {
self.pending_items = todos;
}
pub fn mark_todos_complete(&mut self, completed_todo_ids: Vec<String>) {
for completed_todo_id in completed_todo_ids.into_iter() {
if let Some(item) = self
.pending_items
.iter()
.position(|item| item.id == completed_todo_id.clone().into())
.map(|i| self.pending_items.remove(i))
{
self.completed_items.push(item);
}
}
}
}
+319
View File
@@ -0,0 +1,319 @@
use crate::ai::blocklist::{BlocklistAIContextEvent, BlocklistAIContextModel};
use pathfinder_color::ColorU;
use warp_core::ui::appearance::Appearance;
use warp_core::ui::theme::Fill;
use warpui::elements::{
ClippedScrollStateHandle, ClippedScrollable, Dismiss, Empty, Expanded, ParentElement,
SavePosition, ScrollTarget, ScrollToPositionMode, ScrollbarWidth, Shrinkable,
};
use warpui::fonts::FamilyId;
use warpui::ModelHandle;
use warpui::SingletonEntity;
use warpui::{
elements::{
Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DropShadow, Flex,
MainAxisSize, Radius, Text,
},
fonts::{Properties, Weight},
keymap::FixedBinding,
AppContext, Element, Entity, EntityId, TypedActionView, View, ViewContext,
};
use crate::ai::agent::icons::{in_progress_icon, pending_icon, succeeded_icon};
use crate::ai::agent::todos::AIAgentTodoList;
use crate::ai::blocklist::{BlocklistAIHistoryEvent, BlocklistAIHistoryModel};
use crate::ui_components::blended_colors;
pub struct AgentTodosPopupView {
terminal_view_id: EntityId,
ai_context_model: ModelHandle<BlocklistAIContextModel>,
scroll_state: ClippedScrollStateHandle,
}
const IN_PROGRESS_POSITION_ID: &str = "AgentTodosPopup-in-progress";
#[derive(Debug, Clone, Copy)]
pub enum AgentTodosPopupAction {
ClosePopup,
}
pub enum AgentTodosPopupEvent {
Close,
}
struct Styles {
ui_font_family: FamilyId,
background: Fill,
main_text_color: ColorU,
sub_text_color: ColorU,
detail_font_size: f32,
}
pub fn init(app: &mut AppContext) {
use warpui::keymap::macros::*;
app.register_fixed_bindings([FixedBinding::new(
"escape",
AgentTodosPopupAction::ClosePopup,
id!(AgentTodosPopupView::ui_name()),
)]);
}
impl AgentTodosPopupView {
pub fn new(
terminal_view_id: EntityId,
ai_context_model: ModelHandle<BlocklistAIContextModel>,
ctx: &mut ViewContext<Self>,
) -> Self {
let blocklist_history_model = BlocklistAIHistoryModel::handle(ctx);
ctx.subscribe_to_model(&blocklist_history_model, move |me, _, event, ctx| {
me.handle_blocklist_history_event(event, ctx);
});
ctx.subscribe_to_model(&ai_context_model, move |_, _, event, ctx| {
if let BlocklistAIContextEvent::PendingQueryStateUpdated = event {
ctx.notify();
}
});
Self {
terminal_view_id,
ai_context_model,
scroll_state: Default::default(),
}
}
fn handle_blocklist_history_event(
&mut self,
event: &BlocklistAIHistoryEvent,
ctx: &mut ViewContext<Self>,
) {
if let BlocklistAIHistoryEvent::UpdatedTodoList { terminal_view_id } = event {
if *terminal_view_id == self.terminal_view_id {
ctx.notify();
}
}
}
/// Scroll to the in-progress item, if not currently visible.
pub fn scroll_to_in_progress_item(&self) {
self.scroll_state.scroll_to_position(ScrollTarget {
position_id: IN_PROGRESS_POSITION_ID.to_string(),
mode: ScrollToPositionMode::FullyIntoView,
});
}
fn close(&mut self, ctx: &mut ViewContext<Self>) {
ctx.emit(AgentTodosPopupEvent::Close);
}
fn styles(&self, appearance: &Appearance) -> Styles {
let theme = appearance.theme();
let background = theme.surface_1();
let main_text_color = blended_colors::text_main(theme, background);
let sub_text_color = blended_colors::text_sub(theme, background);
let detail_font_size = appearance.ui_font_size();
let ui_font_family = appearance.ui_font_family();
Styles {
ui_font_family,
background,
main_text_color,
sub_text_color,
detail_font_size,
}
}
fn render_header(
&self,
app: &warpui::AppContext,
todo_list: &AIAgentTodoList,
) -> Box<dyn warpui::Element> {
let appearance = Appearance::as_ref(app);
let styles = self.styles(appearance);
let theme = appearance.theme();
let completed_count = todo_list.completed_items().len();
let total_count = todo_list.pending_items().len() + completed_count;
let mut header_row = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center);
let mut header = Text::new(
"Tasks".to_string(),
appearance.header_font_family(),
styles.detail_font_size + 2.,
)
.with_color(styles.main_text_color)
.with_style(Properties::default().weight(Weight::Semibold));
header.add_text_with_highlights(
format!(" {completed_count}/{total_count}"),
theme.sub_text_color(theme.surface_1()).into(),
Properties::default().weight(Weight::Semibold),
);
header_row.add_child(header.finish());
header_row.finish()
}
}
impl View for AgentTodosPopupView {
fn ui_name() -> &'static str {
"AgentTodosPopup"
}
fn render(&self, app: &warpui::AppContext) -> Box<dyn warpui::Element> {
let Some(todo_list) = self
.ai_context_model
.as_ref(app)
.selected_conversation_todolist(app)
else {
// We don't have an empty state.
// Assume the popup will only be shown if there are todos.
return Empty::new().finish();
};
let appearance = Appearance::as_ref(app);
let styles = self.styles(appearance);
let theme = appearance.theme();
let background = styles.background;
let main_text_color = styles.main_text_color;
let sub_text_color = styles.sub_text_color;
let detail_font_size = styles.detail_font_size;
let ui_font_family = styles.ui_font_family;
let mut list_col = Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_spacing(12.);
let items_with_icons = todo_list
.completed_items()
.iter()
.map(|item| (item, succeeded_icon(appearance)))
.chain(
todo_list
.pending_items()
.iter()
.enumerate()
.map(|(i, item)| {
(
item,
if i == 0 {
in_progress_icon(appearance)
} else {
pending_icon(appearance)
},
)
}),
);
for (item, status_icon) in items_with_icons {
let mut row = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_main_axis_size(MainAxisSize::Max);
// Status icon
row.add_child(
Container::new(
ConstrainedBox::new(status_icon.finish())
.with_width(16.)
.with_height(16.)
.finish(),
)
.with_margin_right(8.)
.finish(),
);
let is_in_progress = todo_list
.in_progress_item()
.map(|t| t.id.clone())
.as_ref()
.map(|id| &item.id == id)
.unwrap_or(false);
// Title and status
let mut text_col =
Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch);
let text_color = if is_in_progress {
main_text_color
} else {
sub_text_color
};
text_col.add_child(
Text::new(item.title.clone(), ui_font_family, detail_font_size)
.with_color(text_color)
.finish(),
);
row.add_child(Expanded::new(1.0, text_col.finish()).finish());
let row = if is_in_progress {
SavePosition::new(row.finish(), IN_PROGRESS_POSITION_ID).finish()
} else {
row.finish()
};
list_col.add_child(row);
}
let header = Container::new(self.render_header(app, todo_list))
.with_padding_top(16.)
.with_horizontal_padding(16.)
.with_padding_bottom(8.)
.finish();
let scrollable_body = ClippedScrollable::vertical(
self.scroll_state.clone(),
Container::new(list_col.finish())
.with_horizontal_padding(16.)
.with_padding_bottom(16.)
.finish(),
ScrollbarWidth::Auto,
theme.nonactive_ui_detail().into(),
theme.active_ui_detail().into(),
warpui::elements::Fill::None,
)
.with_overlayed_scrollbar()
.finish();
let panel_col = Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_child(header)
.with_child(Shrinkable::new(1.0, scrollable_body).finish());
Dismiss::new(
ConstrainedBox::new(
Container::new(panel_col.finish())
.with_background(background)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
.with_border(Border::all(1.).with_border_fill(theme.outline()))
.with_drop_shadow(DropShadow::default())
.finish(),
)
.with_width(300.)
.with_max_height(420.)
.finish(),
)
.prevent_interaction_with_other_elements()
.on_dismiss(|ctx, _app| {
ctx.dispatch_typed_action(AgentTodosPopupAction::ClosePopup);
})
.finish()
}
}
impl Entity for AgentTodosPopupView {
type Event = AgentTodosPopupEvent;
}
impl TypedActionView for AgentTodosPopupView {
type Action = AgentTodosPopupAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
AgentTodosPopupAction::ClosePopup => {
self.close(ctx);
}
}
}
}