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
+315
View File
@@ -0,0 +1,315 @@
use std::collections::HashSet;
use settings::Setting as _;
use crate::{
report_if_error, terminal::general_settings::GeneralSettings,
util::bindings::trigger_to_keystroke,
};
use chrono::{DateTime, FixedOffset};
mod main_page;
pub mod utils;
pub use main_page::{ResourceCenterMainEvent, ResourceCenterMainView};
mod keybindings_page;
pub use keybindings_page::KeybindingsView;
mod section_views;
pub use section_views::{ChangelogSectionView, ContentSectionView, FeatureSectionView};
pub mod sections;
mod view;
use serde::{Deserialize, Serialize};
pub use view::{ResourceCenterAction, ResourceCenterEvent, ResourceCenterPage, ResourceCenterView};
use warpui::{keymap::Keystroke, AppContext, Entity, SingletonEntity};
use self::section_views::feature_section::FeatureSection;
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
std::cmp::Eq,
Serialize,
Deserialize,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(
description = "A welcome tip shown to new users.",
rename_all = "snake_case"
)]
pub enum Tip {
#[schemars(description = "A non-interactive informational hint.")]
Hint(TipHint),
#[schemars(description = "An interactive tip that triggers an action when clicked.")]
Action(TipAction),
}
// Tips that aren't clickable to dispatch an action
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
std::cmp::Eq,
Serialize,
Deserialize,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(description = "A non-interactive tip hint.", rename_all = "snake_case")]
pub enum TipHint {
CreateBlock,
BlockSelect,
BlockAction,
}
// Tips that are clickable and dispatch an action
#[derive(
Clone,
Copy,
Debug,
Hash,
PartialEq,
std::cmp::Eq,
Serialize,
Deserialize,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(description = "An interactive tip action.", rename_all = "snake_case")]
pub enum TipAction {
CommandPalette,
SplitPane,
ThemePicker,
HistorySearch,
CommandSearch,
AiCommandSearch,
SaveNewLaunchConfig,
WarpAI,
// This toggles Warp Drive rather than opening it. This enum can't directly be
// renamed because we serialize it into the welcome tips.
OpenWarpDrive,
Changelog,
// Note that this item has been deprecated from the UI and is not in any section.
// We are leaving it in this enum to ensure that we don't re-use `Workflows` as a
// value. Since old clients will have this value in their user defaults, we want
// to prevent future usage of this enum value.
Workflows,
}
impl TipAction {
pub fn editable_binding_name(&self) -> &'static str {
match self {
TipAction::CommandPalette => "workspace:toggle_command_palette",
TipAction::SplitPane => "pane_group:add_right",
TipAction::HistorySearch => "input:search_command_history",
TipAction::CommandSearch => "workspace:show_command_search",
TipAction::AiCommandSearch => "input:toggle_natural_language_command_search",
TipAction::ThemePicker => "workspace:show_theme_chooser",
TipAction::SaveNewLaunchConfig => "workspace:open_launch_config_save_modal",
TipAction::WarpAI => "workspace:toggle_ai_assistant",
TipAction::OpenWarpDrive => "workspace:toggle_left_panel",
// Slash commands are also registered as editable bindings, so callers can look them up here
// the same way they do regular app actions.
TipAction::Changelog => "/changelog",
TipAction::Workflows => "input:toggle_workflows",
}
}
pub fn keyboard_shortcut(&self, ctx: &mut AppContext) -> Option<Keystroke> {
ctx.editable_bindings()
.find(|binding| binding.name == self.editable_binding_name())
.and_then(|binding| trigger_to_keystroke(binding.trigger))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
// Section item that dispatches an action within the app
pub struct FeatureItem {
pub title: &'static str,
pub description: &'static str,
pub feature: Tip,
pub editable_binding_name: Option<&'static str>,
pub shortcut: Option<Keystroke>,
}
impl FeatureItem {
pub fn new(
title: &'static str,
description: &'static str,
feature: Tip,
ctx: &mut AppContext,
) -> Self {
let editable_binding_name;
let shortcut;
match feature {
Tip::Hint(_) => {
editable_binding_name = None;
shortcut = None;
}
Tip::Action(tip) => {
editable_binding_name = Some(tip.editable_binding_name());
shortcut = tip.keyboard_shortcut(ctx);
}
}
Self {
title,
description,
feature,
editable_binding_name,
shortcut,
}
}
}
#[derive(Clone, Debug)]
// Section item that links to an external URL
pub struct ContentItem {
pub title: &'static str,
pub description: &'static str,
pub url: &'static str,
pub button_label: &'static str,
}
pub enum Section {
Feature(FeatureSectionData),
Content(ContentSectionData),
Changelog(),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FeatureSectionData {
pub section_name: FeatureSection,
pub items: Vec<FeatureItem>,
}
#[derive(Clone)]
pub struct ContentSectionData {
pub section_name: FeatureSection,
pub items: Vec<ContentItem>,
}
#[derive(Clone)]
pub struct ChangelogSectionData {
pub section_name: FeatureSection,
pub date: DateTime<FixedOffset>,
pub new_features_markdown: String,
pub improvements_markdown: String,
pub coming_soon_markdown: String,
}
#[derive(Default)]
pub struct TipsCompleted {
pub features_used: HashSet<Tip>,
pub skipped_or_completed: bool,
pub gamified_tips_count: Option<usize>,
}
impl Entity for TipsCompleted {
type Event = ();
}
impl FeatureSectionData {
pub fn is_section_completed(&self, tips_completed: &TipsCompleted) -> bool {
self.items
.iter()
.all(|item| tips_completed.features_used.contains(&item.feature))
}
pub fn tips_completed_count(&self, tips_completed: &TipsCompleted) -> usize {
self.items
.iter()
.filter(|item| tips_completed.features_used.contains(&item.feature))
.count()
}
}
/// Marks the welcome tip as used, writes their current state to a cloud synced preference.
pub fn mark_feature_used_and_write_to_user_defaults(
feature: Tip,
tips_completed: &mut TipsCompleted,
ctx: &mut AppContext,
) {
if tips_completed.mark_feature_used(feature) {
GeneralSettings::handle(ctx).update(ctx, |general_settings, ctx| {
report_if_error!(general_settings
.welcome_tips_features_used
.set_value(tips_completed.features_used.clone(), ctx));
if tips_completed.skipped_or_completed {
report_if_error!(general_settings
.welcome_tips_skipped_or_completed
.set_value(true, ctx));
}
});
}
}
/// Updates the model to reflect welcome tips are skipped, writes to user defaults, and sends telemetry.
pub fn skip_tips_and_write_to_user_defaults(
tips_completed: &mut TipsCompleted,
ctx: &mut AppContext,
) {
tips_completed.skipped_or_completed = true;
GeneralSettings::handle(ctx).update(ctx, |general_settings, ctx| {
report_if_error!(general_settings
.welcome_tips_skipped_or_completed
.set_value(true, ctx));
});
}
/// Updates the model to reflect welcome tips are skipped, writes to user defaults, and sends telemetry.
pub fn complete_tips_and_write_to_user_defaults(
tips_completed: &mut TipsCompleted,
ctx: &mut AppContext,
) {
tips_completed.skipped_or_completed = true;
GeneralSettings::handle(ctx).update(ctx, |general_settings, ctx| {
report_if_error!(general_settings
.welcome_tips_skipped_or_completed
.set_value(true, ctx));
});
}
impl TipsCompleted {
pub fn new(features_used: HashSet<Tip>, skipped_or_completed: bool) -> Self {
Self {
features_used,
skipped_or_completed,
gamified_tips_count: None,
}
}
/// Returns true if the feature previously wasn't used.
pub fn mark_feature_used(&mut self, feature: Tip) -> bool {
let is_new_value = self.features_used.insert(feature);
// Check if all gamified tips are completed
if let Some(total_tips) = self.gamified_tips_count {
if is_new_value && self.features_used.len() == total_tips {
self.skipped_or_completed = true;
}
}
is_new_value
}
pub fn serialized_tips(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(&self.features_used)
}
pub fn completed_count(&self) -> usize {
self.features_used.len()
}
pub fn set_gamified_tips_count(&mut self, total: usize) {
self.gamified_tips_count = Some(total)
}
}