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
+71
View File
@@ -0,0 +1,71 @@
use warp_multi_agent_api as api;
pub fn create_message(id: &str, task_id: &str) -> api::Message {
api::Message {
id: id.to_string(),
task_id: task_id.to_string(),
server_message_data: String::new(),
citations: vec![],
message: Some(api::message::Message::AgentOutput(
api::message::AgentOutput {
text: format!("Message content for {id}"),
},
)),
request_id: String::new(),
timestamp: None,
}
}
pub fn create_subagent_tool_call_message(
id: &str,
task_id: &str,
subtask_id: &str,
metadata: Option<api::message::tool_call::subagent::Metadata>,
) -> api::Message {
api::Message {
id: id.to_string(),
task_id: task_id.to_string(),
server_message_data: String::new(),
citations: vec![],
message: Some(api::message::Message::ToolCall(api::message::ToolCall {
tool_call_id: format!("{id}_tool_call"),
tool: Some(api::message::tool_call::Tool::Subagent(
api::message::tool_call::Subagent {
task_id: subtask_id.to_string(),
payload: String::new(),
metadata,
},
)),
})),
request_id: String::new(),
timestamp: None,
}
}
pub fn create_api_task(id: &str, messages: Vec<api::Message>) -> api::Task {
api::Task {
id: id.to_string(),
messages,
dependencies: None,
description: String::new(),
summary: String::new(),
server_data: String::new(),
}
}
pub fn create_api_subtask(
id: &str,
parent_task_id: &str,
messages: Vec<api::Message>,
) -> api::Task {
api::Task {
id: id.to_string(),
messages,
dependencies: Some(api::task::Dependencies {
parent_task_id: parent_task_id.to_string(),
}),
description: String::new(),
summary: String::new(),
server_data: String::new(),
}
}
+112
View File
@@ -0,0 +1,112 @@
use itertools::Itertools;
use unicode_width::UnicodeWidthChar;
use crate::terminal::event_listener::ChannelEventListener;
use crate::terminal::model::ansi::{self, Handler};
use crate::terminal::model::blockgrid::BlockGrid;
use crate::terminal::model::cell::Flags;
use crate::terminal::model::grid::grid_handler::PerformResetGridChecks;
use crate::terminal::model::grid::Dimensions as _;
use crate::terminal::model::index::{VisiblePoint, VisibleRow};
use crate::terminal::model::ObfuscateSecrets;
use crate::terminal::SizeInfo;
const MAX_SCROLL_LIMIT: usize = 1000;
/// Constructs a blockgrid from its contents as a string.
///
/// A `\n` will break line and `\r\n` will break line without wrapping.
///
/// This function will set `max_cursor` in the grid based on the position of
/// the last character. Some features rely on the max cursor appearing at the
/// end of a grid on a newline (e.g. block filtering), so when writing tests
/// you may need to end the string with `\r\n`, even if there's no content
/// after it.
///
/// # Example
/// The line `mock_blockgrid("hello\n:)\r\nearth!")` will create a blockgrid with the following cells:
/// ```
/// // [h][e][l][l][o][ ] <- WRAPLINE flag set
/// // [:][)][ ][ ][ ][ ]
/// // [e][a][r][t][h][!]
/// ```
///
pub fn mock_blockgrid(content: &str) -> BlockGrid {
let rows: Vec<&str> = content.split('\n').collect();
let num_cols = rows
.iter()
.map(|row| {
let sum: usize = row
.chars()
.filter(|c| *c != '\r')
// All characters in our mock blockgrid should have a minimum character width of one
// because any character that is rendered in the blockgrid will occupy at least one
// grapheme. This is true for the null-character '\0' as well.
.map(|c| usize::max(c.width().unwrap_or(1), 1))
.sum();
sum
})
.collect_vec();
let max_num_cols = num_cols.iter().cloned().max().unwrap_or(0);
// Create terminal with the appropriate dimensions.
let size = SizeInfo::new_without_font_metrics(rows.len(), max_num_cols);
let mut blockgrid = BlockGrid::new(
size,
MAX_SCROLL_LIMIT,
ChannelEventListener::new_for_test(),
ObfuscateSecrets::No,
PerformResetGridChecks::default(),
);
blockgrid.start();
// Fill blockgrid with content.
for (row, text) in rows.iter().enumerate() {
if !text.ends_with('\r') && row + 1 != rows.len() {
blockgrid.grid_storage_mut()[row][max_num_cols - 1]
.flags_mut()
.insert(Flags::WRAPLINE);
}
let mut index = 0;
for c in text.chars().take_while(|c| *c != '\r') {
blockgrid.grid_storage_mut()[row][index].c = c;
// All characters in our mock blockgrid should have a minimum character width of one
// because any character that is rendered in the blockgrid will occupy at least one
// grapheme. This is true for the null-character '\0' as well.
let width = usize::max(c.width().unwrap_or(1), 1);
if width == 2 {
blockgrid.grid_storage_mut()[row][index]
.flags_mut()
.insert(Flags::WIDE_CHAR);
blockgrid.grid_storage_mut()[row][index + 1]
.flags_mut()
.insert(Flags::WIDE_CHAR_SPACER);
}
index += width;
}
}
if !rows.is_empty() {
let total_cols = blockgrid.grid_handler().columns();
blockgrid.grid_handler_mut().update_cursor(|cursor| {
cursor.point = VisiblePoint {
row: VisibleRow(rows.len() - 1),
col: num_cols[rows.len() - 1].saturating_sub(1),
};
// If we are at the end of the line, we need to wrap the input on the next
// usage of the cursor for writing!
if num_cols[rows.len() - 1].saturating_sub(1) == total_cols - 1 {
cursor.input_needs_wrap = true;
}
});
blockgrid.grid_storage_mut().update_max_cursor();
}
blockgrid.on_finish_byte_processing(&ansi::ProcessorInput::new(&[]));
blockgrid
}
+31
View File
@@ -0,0 +1,31 @@
pub mod ai_agent_tasks;
pub mod blockgrid;
pub mod settings;
pub mod terminal;
mod virtual_fs;
pub use blockgrid::mock_blockgrid;
pub use terminal::add_window_with_terminal;
pub use virtual_fs::{Stub, VirtualFS};
macro_rules! assert_eventually {
($cond:expr, $($arg:tt)+) => {
$crate::test_util::assert_eventually!(20 => $cond, $($arg)+);
};
// Run the condition up to ticks times, yielding to the executor in between. If it does
// not become true, this panics with the provided format string + args.
($ticks:literal => $cond:expr, $($arg:tt)+) => {{
let mut pass = false;
for _ in 0..$ticks {
if $cond {
pass = true;
break;
}
warpui::r#async::Timer::after(std::time::Duration::from_millis(5)).await;
}
if !pass {
panic!("{}", format_args!($($arg)+));
}
}};
}
pub(crate) use assert_eventually;
+110
View File
@@ -0,0 +1,110 @@
#[cfg(test)]
use warpui::App;
#[cfg(test)]
pub fn initialize_settings_for_tests(app: &mut App) {
use warp_core::execution_mode::ExecutionMode;
initialize_settings_for_tests_with_mode(app, ExecutionMode::App, false);
}
#[cfg(test)]
pub fn initialize_settings_for_tests_with_mode(
app: &mut App,
mode: warp_core::execution_mode::ExecutionMode,
is_sandboxed: bool,
) {
use crate::{
ai::cloud_agent_settings::CloudAgentSettings,
drive::settings::WarpDriveSettings,
search::command_search::settings::CommandSearchSettings,
settings::{
app_icon::AppIconSettings, init_and_register_user_preferences,
manager::SettingsManager, AISettings, AccessibilitySettings, AliasExpansionSettings,
AppEditorSettings, BlockVisibilitySettings, ChangelogSettings,
CloudPreferencesSettings, CodeSettings, DebugSettings, EmacsBindingsSettings,
FontSettings, GPUSettings, InputModeSettings, InputSettings, NativePreferenceSettings,
PaneSettings, SameLinePromptBlockSettings, ScrollSettings, SelectionSettings,
SshSettings, ThemeSettings, VimBannerSettings,
},
terminal::{
general_settings::GeneralSettings, keys_settings::KeysSettings,
ligature_settings::LigatureSettings, safe_mode_settings::SafeModeSettings,
session_settings::SessionSettings, settings::TerminalSettings,
shared_session::settings::SharedSessionSettings, warpify::settings::WarpifySettings,
BlockListSettings,
},
undo_close::UndoCloseSettings,
user_config::WarpConfig,
window_settings::WindowSettings,
workspace::tab_settings::TabSettings,
};
use warp_core::{execution_mode::AppExecutionMode, semantic_selection::SemanticSelection};
app.add_singleton_model(|ctx| AppExecutionMode::new(mode, is_sandboxed, ctx));
app.update(init_and_register_user_preferences);
app.add_singleton_model(|_ctx| SettingsManager::default());
app.add_singleton_model(WarpConfig::mock);
AccessibilitySettings::register(app);
app.update(AISettings::register_and_subscribe_to_events);
AliasExpansionSettings::register(app);
CloudAgentSettings::register(app);
AppEditorSettings::register(app);
BlockVisibilitySettings::register(app);
BlockListSettings::register(app);
ChangelogSettings::register(app);
CloudPreferencesSettings::register(app);
CommandSearchSettings::register(app);
DebugSettings::register(app);
AppIconSettings::register(app);
EmacsBindingsSettings::register(app);
#[cfg(feature = "local_fs")]
{
crate::util::file::external_editor::EditorSettings::register(app);
}
FontSettings::register(app);
GeneralSettings::register(app);
GPUSettings::register(app);
InputModeSettings::register(app);
InputSettings::register(app);
KeysSettings::register(app);
LigatureSettings::register(app);
#[cfg(target_os = "linux")]
{
use crate::settings::LinuxAppConfiguration;
LinuxAppConfiguration::register(app);
}
NativePreferenceSettings::register(app);
SafeModeSettings::register(app);
SameLinePromptBlockSettings::register(app);
ScrollSettings::register(app);
SelectionSettings::register(app);
app.update(|ctx| {
WarpifySettings::register(ctx);
});
SessionSettings::register(app);
SshSettings::register(app);
TabSettings::register(app);
TerminalSettings::register(app);
PaneSettings::register(app);
ThemeSettings::register(app);
UndoCloseSettings::register(app);
VimBannerSettings::register(app);
WarpDriveSettings::register(app);
WindowSettings::register(app);
SharedSessionSettings::register(app);
CodeSettings::register(app);
SemanticSelection::register(app);
app.update(|ctx| {
// Register a no-op secure storage provider for testing.
warpui_extras::secure_storage::register_noop("test", ctx);
// Add settings models that are backed by secure storage, not user preferences.
ctx.add_singleton_model(ai::api_keys::ApiKeyManager::new);
});
}
+168
View File
@@ -0,0 +1,168 @@
use ai::index::full_source_code_embedding::manager::CodebaseIndexManager;
use repo_metadata::repositories::DetectedRepositories;
#[cfg(feature = "local_fs")]
use repo_metadata::RepoMetadataModel;
use warp_core::ui::appearance::Appearance;
use crate::ai::active_agent_views_model::ActiveAgentViewsModel;
use crate::ai::agent_conversations_model::AgentConversationsModel;
use crate::ai::ambient_agents::github_auth_notifier::GitHubAuthNotifier;
use crate::ai::document::ai_document_model::AIDocumentModel;
use crate::ai::mcp::{
gallery::MCPGalleryManager, templatable_manager::TemplatableMCPServerManager,
};
use crate::ai::persisted_workspace::PersistedWorkspace;
use crate::ai::skills::SkillManager;
use crate::code_review::git_status_update::GitStatusUpdateModel;
use crate::terminal::cli_agent_sessions::CLIAgentSessionsModel;
use crate::warp_managed_paths_watcher::WarpManagedPathsWatcher;
use warpui::SingletonEntity;
use warpui::{platform::WindowStyle, App, ViewHandle, WindowId};
use watcher::HomeDirectoryWatcher;
use super::settings::initialize_settings_for_tests;
use crate::ai::blocklist::BlocklistAIPermissions;
use crate::ai::blocklist::SerializedBlockListItem;
use crate::ai::execution_profiles::profiles::AIExecutionProfilesModel;
use crate::ai::llms::LLMPreferences;
use crate::ai::outline::RepoOutlines;
use crate::ai::restored_conversations::RestoredAgentConversations;
use crate::auth::auth_manager::AuthManager;
use crate::auth::AuthStateProvider;
use crate::changelog_model::ChangelogModel;
use crate::pricing::PricingInfoModel;
use crate::server::telemetry::context_provider::AppTelemetryContextProvider;
use crate::suggestions::ignored_suggestions_model::IgnoredSuggestionsModel;
use crate::terminal::shared_session::permissions_manager::SessionPermissionsManager;
use crate::terminal::view::inline_banner::ByoLlmAuthBannerSessionState;
use crate::undo_close::UndoCloseStack;
use crate::workspace::{OneTimeModalModel, WorkspaceRegistry};
use crate::AgentNotificationsModel;
use crate::{
ai::{blocklist::BlocklistAIHistoryModel, AIRequestUsageModel},
cloud_object::model::persistence::CloudModel,
context_chips::prompt::Prompt,
experiments,
network::NetworkStatus,
search::files::model::FileSearchModel,
server::{
cloud_objects::{listener::Listener, update_manager::UpdateManager},
server_api::ServerApiProvider,
sync_queue::SyncQueue,
},
settings::PrivacySettings,
settings_view::keybindings::KeybindingChangedNotifier,
system::SystemInfo,
system::SystemStats,
terminal::{
alt_screen_reporting::AltScreenReporting, keys::TerminalKeybindings,
resizable_data::ResizableData, History, TerminalView,
},
workflows::local_workflows::LocalWorkflows,
workspace::{sync_inputs::SyncedInputState, ActiveSession},
workspaces::{
team_tester::TeamTesterStatus, update_manager::TeamUpdateManager,
user_workspaces::UserWorkspaces,
},
};
use repo_metadata::watcher::DirectoryWatcher;
/// Initializes all of the necessary models to use a terminal view.
pub fn initialize_app_for_terminal_view(app: &mut App) {
initialize_settings_for_tests(app);
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
app.add_singleton_model(|ctx| ChangelogModel::new(ServerApiProvider::as_ref(ctx).get()));
app.add_singleton_model(|_| NetworkStatus::new());
app.add_singleton_model(|_| SystemStats::new());
app.add_singleton_model(|_| Prompt::mock());
app.add_singleton_model(SyncQueue::mock);
app.add_singleton_model(CloudModel::mock);
app.add_singleton_model(UserWorkspaces::default_mock);
app.add_singleton_model(TeamTesterStatus::mock);
app.add_singleton_model(TeamUpdateManager::mock);
app.add_singleton_model(UpdateManager::mock);
app.add_singleton_model(MCPGalleryManager::new);
app.add_singleton_model(Listener::mock);
app.add_singleton_model(|_| Appearance::mock());
app.add_singleton_model(PrivacySettings::mock);
app.add_singleton_model(|_ctx| SyncedInputState::mock());
app.add_singleton_model(|_| ResizableData::default());
app.add_singleton_model(LocalWorkflows::new);
app.add_singleton_model(|_| History::default());
app.add_singleton_model(|_| BlocklistAIHistoryModel::new_for_test());
app.add_singleton_model(|_| CLIAgentSessionsModel::new());
app.add_singleton_model(|_| ActiveAgentViewsModel::new());
app.add_singleton_model(BlocklistAIPermissions::new);
app.add_singleton_model(AgentNotificationsModel::new);
app.add_singleton_model(UndoCloseStack::new);
app.add_singleton_model(|ctx| {
AIRequestUsageModel::new_for_test(ServerApiProvider::as_ref(ctx).get_ai_client(), ctx)
});
app.add_singleton_model(|_| KeybindingChangedNotifier::new());
app.add_singleton_model(TerminalKeybindings::new);
app.add_singleton_model(|_| ActiveSession::default());
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
app.add_singleton_model(AppTelemetryContextProvider::new_context_provider);
app.add_singleton_model(AuthManager::new_for_test);
app.add_singleton_model(LLMPreferences::new);
app.add_singleton_model(SessionPermissionsManager::new);
app.add_singleton_model(DirectoryWatcher::new);
app.add_singleton_model(|_| DetectedRepositories::default());
#[cfg(feature = "local_fs")]
app.add_singleton_model(RepoMetadataModel::new);
app.add_singleton_model(FileSearchModel::new);
app.add_singleton_model(|_| GitStatusUpdateModel::new());
app.add_singleton_model(RepoOutlines::new_for_test);
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
app.add_singleton_model(SkillManager::new);
app.add_singleton_model(|ctx| {
CodebaseIndexManager::new_for_test(ServerApiProvider::as_ref(ctx).get(), ctx)
});
app.add_singleton_model(|_| TemplatableMCPServerManager::default());
app.add_singleton_model(|ctx| {
AIExecutionProfilesModel::new(&crate::LaunchMode::new_for_unit_test(), ctx)
});
#[cfg(feature = "voice_input")]
app.add_singleton_model(voice_input::VoiceInput::new);
#[cfg(not(target_family = "wasm"))]
app.add_singleton_model(SystemInfo::new);
app.add_singleton_model(|_| RestoredAgentConversations::new(vec![]));
app.add_singleton_model(OneTimeModalModel::new);
app.add_singleton_model(|_| WorkspaceRegistry::new());
app.add_singleton_model(|_| IgnoredSuggestionsModel::new(vec![]));
app.add_singleton_model(|_| PricingInfoModel::new());
app.add_singleton_model(AIDocumentModel::new);
app.add_singleton_model(ByoLlmAuthBannerSessionState::new);
app.add_singleton_model(|_| GitHubAuthNotifier::new());
app.add_singleton_model(AgentConversationsModel::new);
app.add_singleton_model(PersistedWorkspace::new_for_test);
app.update(experiments::init);
AltScreenReporting::register(app);
}
/// Creates a window in `app` with a [`TerminalView`] as the root view.
/// Returns the handle to that terminal view.
pub fn add_window_with_terminal(
app: &mut App,
restored_blocks: Option<&[SerializedBlockListItem]>,
) -> ViewHandle<TerminalView> {
add_window_with_id_and_terminal(app, restored_blocks).1
}
/// Creates a window in `app` with a [`TerminalView`] as the root view.
/// Returns the WindowID and the handle to that terminal view.
pub fn add_window_with_id_and_terminal(
app: &mut App,
restored_blocks: Option<&[SerializedBlockListItem]>,
) -> (WindowId, ViewHandle<TerminalView>) {
let tips_model = app.add_model(|_| Default::default());
app.add_window(WindowStyle::NotStealFocus, |ctx| {
TerminalView::new_for_test(tips_model, restored_blocks, ctx)
})
}
+58
View File
@@ -0,0 +1,58 @@
use std::path::PathBuf;
pub use virtual_fs::{Dirs, Stub, VirtualFS};
pub trait WarpDirs {
#[allow(dead_code)]
fn git_repository_fixture(&self) -> PathBuf {
Warp::fixtures().join("git_repository")
}
}
impl WarpDirs for Dirs {}
pub struct Warp;
impl Warp {
#[allow(dead_code)]
pub fn executable() -> PathBuf {
let mut path = {
let mut build = "debug";
if !cfg!(debug_assertions) {
build = "release";
}
std::env::var("CARGO_TARGET_DIR")
.ok()
.map(|directory| PathBuf::from(directory).join(build))
.unwrap_or_else(|| Self::root().join(format!("target/{}", &build)))
};
path.push("warp");
path
}
#[allow(dead_code)]
pub fn fixtures() -> PathBuf {
Self::root().join("tests/fixtures")
}
pub fn root() -> PathBuf {
let manifest_dir = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
PathBuf::from(manifest_dir)
} else {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
};
if manifest_dir.join("Cargo.lock").exists() {
manifest_dir
} else {
manifest_dir
.parent()
.expect("Could not find the debug binaries directory")
.parent()
.expect("Could not find the debug binaries directory")
.to_path_buf()
}
}
}