Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use warpui::platform::FullscreenState;
|
||||
|
||||
use warpui::AppContext;
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::agent_conversations_model::AgentManagementFilters;
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::InputConfig;
|
||||
use crate::ai::blocklist::SerializedBlockListItem;
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::drive::OpenWarpDriveObjectSettings;
|
||||
use crate::root_view::quake_mode_window_id;
|
||||
use crate::server::ids::SyncId;
|
||||
use crate::settings_view::{environments_page::EnvironmentsPage, SettingsSection};
|
||||
use crate::tab::SelectedTabColor;
|
||||
use crate::terminal::ShellLaunchData;
|
||||
use crate::themes::theme::AnsiColorIdentifier;
|
||||
use crate::workspace::view::left_panel::ToolPanelView;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AppState {
|
||||
pub windows: Vec<WindowSnapshot>,
|
||||
pub active_window_index: Option<usize>,
|
||||
pub block_lists: Arc<HashMap<PaneUuid, Vec<SerializedBlockListItem>>>,
|
||||
pub running_mcp_servers: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PaneUuid(pub Vec<u8>);
|
||||
|
||||
/// Wrapper for persisting agent management filters to restore.
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PersistedAgentManagementFilters {
|
||||
pub filters: AgentManagementFilters,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WindowSnapshot {
|
||||
pub tabs: Vec<TabSnapshot>,
|
||||
pub active_tab_index: usize,
|
||||
pub bounds: Option<RectF>,
|
||||
pub fullscreen_state: FullscreenState,
|
||||
pub quake_mode: bool,
|
||||
pub universal_search_width: Option<f32>,
|
||||
pub warp_ai_width: Option<f32>,
|
||||
pub voltron_width: Option<f32>,
|
||||
pub warp_drive_index_width: Option<f32>,
|
||||
pub left_panel_open: bool,
|
||||
pub vertical_tabs_panel_open: bool,
|
||||
pub left_panel_width: Option<f32>,
|
||||
pub right_panel_width: Option<f32>,
|
||||
pub agent_management_filters: Option<PersistedAgentManagementFilters>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TabSnapshot {
|
||||
pub custom_title: Option<String>,
|
||||
pub root: PaneNodeSnapshot,
|
||||
pub default_directory_color: Option<AnsiColorIdentifier>,
|
||||
pub selected_color: SelectedTabColor,
|
||||
pub left_panel: Option<LeftPanelSnapshot>,
|
||||
pub right_panel: Option<RightPanelSnapshot>,
|
||||
}
|
||||
|
||||
impl TabSnapshot {
|
||||
pub(crate) fn color(&self) -> Option<AnsiColorIdentifier> {
|
||||
self.selected_color.resolve(self.default_directory_color)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(
|
||||
clippy::large_enum_variant,
|
||||
reason = "LeafSnapshot is significantly larger than BranchSnapshot due to nested snapshot types."
|
||||
)]
|
||||
pub enum PaneNodeSnapshot {
|
||||
Branch(BranchSnapshot),
|
||||
Leaf(LeafSnapshot),
|
||||
}
|
||||
|
||||
impl PaneNodeSnapshot {
|
||||
pub fn has_horizontal_split(&self) -> bool {
|
||||
match self {
|
||||
PaneNodeSnapshot::Leaf(_) => false,
|
||||
PaneNodeSnapshot::Branch(BranchSnapshot {
|
||||
direction,
|
||||
children,
|
||||
}) => {
|
||||
let self_has_split = *direction == SplitDirection::Horizontal && children.len() > 1;
|
||||
self_has_split
|
||||
|| children
|
||||
.iter()
|
||||
.any(|(_, child)| child.has_horizontal_split())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct BranchSnapshot {
|
||||
pub direction: SplitDirection,
|
||||
pub children: Vec<(PaneFlex, PaneNodeSnapshot)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LeafSnapshot {
|
||||
pub is_focused: bool,
|
||||
pub custom_vertical_tabs_title: Option<String>,
|
||||
pub contents: LeafContents,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum LeafContents {
|
||||
Terminal(TerminalPaneSnapshot),
|
||||
Notebook(NotebookPaneSnapshot),
|
||||
AIDocument(AIDocumentPaneSnapshot),
|
||||
Code(CodePaneSnapShot),
|
||||
EnvVarCollection(EnvVarCollectionPaneSnapshot),
|
||||
EnvironmentManagement(EnvironmentManagementPaneSnapshot),
|
||||
Workflow(WorkflowPaneSnapshot),
|
||||
Settings(SettingsPaneSnapshot),
|
||||
AIFact(AIFactPaneSnapshot),
|
||||
ExecutionProfileEditor,
|
||||
CodeReview(CodeReviewPaneSnapshot),
|
||||
AmbientAgent(AmbientAgentPaneSnapshot),
|
||||
/// The in-app network log pane. Not persisted across restarts because the
|
||||
/// backing log is an in-memory ring buffer that starts empty on launch.
|
||||
NetworkLog,
|
||||
/// An entrypoint pane type to launch other pane types from a search palette. The default view
|
||||
/// when creating a tab.
|
||||
Welcome {
|
||||
startup_directory: Option<PathBuf>,
|
||||
},
|
||||
/// A new first-time user experience which prioritizes choosing a coding repository.
|
||||
GetStarted,
|
||||
}
|
||||
|
||||
#[cfg(feature = "local_fs")]
|
||||
impl LeafContents {
|
||||
/// Whether this pane content should be written to (and later restored
|
||||
/// from) the SQLite app-state database.
|
||||
///
|
||||
/// Non-persisted pane types are skipped entirely during the pane tree
|
||||
/// traversal in `save_app_state`, so no `pane_nodes` row is inserted for
|
||||
/// them. This is important: inserting a `pane_nodes` row with
|
||||
/// `is_leaf = true` but no matching `pane_leaves` row leaves an orphan
|
||||
/// that `read_node` cannot resolve, which causes the surrounding tab's
|
||||
/// restoration to fail and the whole tab to disappear on restart.
|
||||
pub(crate) fn is_persisted(&self) -> bool {
|
||||
match self {
|
||||
// Network log: the backing log is an in-memory ring buffer that
|
||||
// starts empty on launch; persisting would also regress back to
|
||||
// an on-disk log via the app-state database.
|
||||
LeafContents::NetworkLog
|
||||
// Environment management panes are opened on-demand via workspace
|
||||
// actions and have no persistable state.
|
||||
| LeafContents::EnvironmentManagement(_) => false,
|
||||
LeafContents::Terminal(_)
|
||||
| LeafContents::Notebook(_)
|
||||
| LeafContents::AIDocument(_)
|
||||
| LeafContents::Code(_)
|
||||
| LeafContents::EnvVarCollection(_)
|
||||
| LeafContents::Workflow(_)
|
||||
| LeafContents::Settings(_)
|
||||
| LeafContents::AIFact(_)
|
||||
| LeafContents::ExecutionProfileEditor
|
||||
| LeafContents::CodeReview(_)
|
||||
| LeafContents::AmbientAgent(_)
|
||||
| LeafContents::Welcome { .. }
|
||||
| LeafContents::GetStarted => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Snapshot of an ambient agent pane.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AmbientAgentPaneSnapshot {
|
||||
pub uuid: Vec<u8>,
|
||||
// `task_id` is purposefully optional,
|
||||
// as you can have a valid state (i.e. an empty cloud mode pane) where it is None.
|
||||
pub task_id: Option<AmbientAgentTaskId>,
|
||||
}
|
||||
|
||||
/// Snapshot of the contents of a terminal pane.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TerminalPaneSnapshot {
|
||||
pub uuid: Vec<u8>,
|
||||
pub cwd: Option<String>,
|
||||
pub shell_launch_data: Option<ShellLaunchData>,
|
||||
pub is_active: bool,
|
||||
pub is_read_only: bool,
|
||||
pub input_config: Option<InputConfig>,
|
||||
pub llm_model_override: Option<String>,
|
||||
pub active_profile_id: Option<SyncId>,
|
||||
pub conversation_ids_to_restore: Vec<AIConversationId>,
|
||||
/// The active conversation ID if the agent view was open in fullscreen mode.
|
||||
/// When `Some`, the agent view should be restored to fullscreen for this conversation.
|
||||
pub active_conversation_id: Option<AIConversationId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum NotebookPaneSnapshot {
|
||||
CloudNotebook {
|
||||
/// The ID of the notebook that was open in this pane. There are 3 possibilities:
|
||||
/// 1. The pane contains a newly-created notebook that has not been edited yet. It might not
|
||||
/// have an ID yet (client or server), so this will be `None`.
|
||||
/// 2. The pane contains a notebook that hasn't been synced to the server yet, so this will
|
||||
/// contain a client ID that should exist in SQLite.
|
||||
/// 3. The pane contains a notebook that's known to the server, so this will contain the
|
||||
/// server ID.
|
||||
notebook_id: Option<SyncId>,
|
||||
// Settings for the notebook pane when it's opened (such as a folder to focus upon opening)
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
},
|
||||
LocalFileNotebook {
|
||||
/// The path to the local file that was open in this pane. This may be `None` if
|
||||
/// the pane contained an unreadable file.
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AIDocumentPaneSnapshot {
|
||||
Local {
|
||||
document_id: String,
|
||||
version: i32,
|
||||
content: Option<String>,
|
||||
title: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CodePaneTabSnapshot {
|
||||
pub path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CodePaneSnapShot {
|
||||
Local {
|
||||
tabs: Vec<CodePaneTabSnapshot>,
|
||||
active_tab_index: usize,
|
||||
/// The full `CodeSource` for this pane, serialized as JSON in the DB.
|
||||
source: Option<CodeSource>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum WorkflowPaneSnapshot {
|
||||
CloudWorkflow {
|
||||
workflow_id: Option<SyncId>,
|
||||
// Settings for the workflow pane when it's opened (such as a folder to focus upon opening)
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum EnvVarCollectionPaneSnapshot {
|
||||
// CloudEnvVarCollection snapshots operate under the same heuristics
|
||||
// as NotebookPaneSnapshot::CloudNotebook
|
||||
CloudEnvVarCollection {
|
||||
env_var_collection_id: Option<SyncId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EnvironmentManagementPaneSnapshot {
|
||||
pub mode: EnvironmentsPage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SettingsPaneSnapshot {
|
||||
Local {
|
||||
current_page: SettingsSection,
|
||||
search_query: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AIFactPaneSnapshot {
|
||||
Personal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CodeReviewPaneSnapshot {
|
||||
Local {
|
||||
terminal_uuid: Vec<u8>,
|
||||
repo_path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum LeftPanelDisplayedTab {
|
||||
FileTree,
|
||||
GlobalSearch,
|
||||
WarpDrive,
|
||||
ConversationListView,
|
||||
}
|
||||
|
||||
impl From<ToolPanelView> for LeftPanelDisplayedTab {
|
||||
fn from(view: ToolPanelView) -> Self {
|
||||
match view {
|
||||
ToolPanelView::ProjectExplorer => LeftPanelDisplayedTab::FileTree,
|
||||
ToolPanelView::GlobalSearch { .. } => LeftPanelDisplayedTab::GlobalSearch,
|
||||
ToolPanelView::WarpDrive => LeftPanelDisplayedTab::WarpDrive,
|
||||
ToolPanelView::ConversationListView => LeftPanelDisplayedTab::ConversationListView,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LeftPanelSnapshot {
|
||||
pub left_panel_displayed_tab: LeftPanelDisplayedTab,
|
||||
pub pane_group_id: String,
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RightPanelSnapshot {
|
||||
pub pane_group_id: String,
|
||||
pub width: usize,
|
||||
pub is_maximized: bool,
|
||||
}
|
||||
|
||||
/// Copied from pane group model, which should be private to pane group.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SplitDirection {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PaneFlex(pub f32);
|
||||
|
||||
pub fn get_app_state(app: &AppContext) -> AppState {
|
||||
let active_window_id = app.windows().active_window();
|
||||
let quake_mode_id = quake_mode_window_id();
|
||||
|
||||
let mut active_window_index = None;
|
||||
|
||||
let mut windows = vec![];
|
||||
|
||||
for (index, window_id) in app.window_ids().enumerate() {
|
||||
// Determine index of active window
|
||||
if let Some(active_window_id) = active_window_id {
|
||||
if active_window_id == window_id {
|
||||
active_window_index = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(first_workspace) = app
|
||||
.views_of_type::<Workspace>(window_id)
|
||||
.as_ref()
|
||||
.and_then(|workspaces| workspaces.first())
|
||||
{
|
||||
let ws = first_workspace.as_ref(app);
|
||||
if ws.is_drag_preview_workspace() {
|
||||
continue;
|
||||
}
|
||||
let snapshot = ws.snapshot(
|
||||
window_id,
|
||||
quake_mode_id.map(|id| id == window_id).unwrap_or(false),
|
||||
app,
|
||||
);
|
||||
if !snapshot.tabs.is_empty() {
|
||||
windows.push(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppState {
|
||||
windows,
|
||||
active_window_index,
|
||||
block_lists: Default::default(),
|
||||
running_mcp_servers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "app_state_tests.rs"]
|
||||
mod tests;
|
||||
Reference in New Issue
Block a user