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,271 @@
use crate::ai::agent::icons::yellow_stop_icon;
use crate::ai::blocklist::block::toggleable_items::{ToggleableItemBuilder, ToggleableItemsView};
use crate::ai::blocklist::inline_action::inline_action_header::INLINE_ACTION_HORIZONTAL_PADDING;
use crate::ai::blocklist::inline_action::inline_action_icons::icon_size;
use crate::appearance::Appearance;
use crate::ui_components::blended_colors;
use lsp::supported_servers::LSPServerType;
use std::path::PathBuf;
use warpui::{
elements::{
Border, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Expanded,
Flex, MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement, Radius, Shrinkable,
},
keymap::Keystroke,
ui_components::{
button::ButtonVariant,
components::{Coords, UiComponent, UiComponentStyles},
keyboard_shortcut::KeyboardShortcut,
text::Span,
},
AppContext, Element, SingletonEntity, TypedActionView, ViewContext, ViewHandle,
};
use super::{InitProjectBlockAction, InitStepBlock};
#[derive(Debug, Clone)]
pub struct LSPServerInfo {
pub server_type: LSPServerType,
pub is_installed: bool,
}
/// Creates a ToggleableItemsView configured for LSP server selection.
pub fn create_lsp_server_selector(
server_info: Vec<LSPServerInfo>,
repo_path: PathBuf,
ctx: &mut ViewContext<InitStepBlock>,
) -> ViewHandle<ToggleableItemsView<LSPServerInfo>> {
let builder = ToggleableItemBuilder::<LSPServerInfo>::new(
|info, app| {
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
Span::new(
info.server_type.binary_name(),
UiComponentStyles {
font_family_id: Some(appearance.ui_font_family()),
font_color: Some(theme.main_text_color(theme.background()).into_solid()),
font_size: Some(appearance.monospace_font_size()),
..Default::default()
},
)
},
// Default all as selected
|_| true,
);
let view = ToggleableItemsView::new(server_info, builder);
let view_handle = ctx.add_typed_action_view(|_| view);
// Set up event handling for checkbox selection changes
let repo_path_for_submit = repo_path.clone();
ctx.subscribe_to_view(
&view_handle,
move |parent_me, lsp_view_handle, event, parent_ctx| {
use crate::ai::blocklist::block::toggleable_items::ToggleableItemsEvent;
match event {
ToggleableItemsEvent::SelectionChanged => {
// Notify to trigger re-render (button state may have changed)
parent_ctx.notify();
}
ToggleableItemsEvent::SubmitRequested => {
// Get selected items
let selected_items: Vec<LSPServerInfo> = {
let view = lsp_view_handle.as_ref(parent_ctx);
view.get_selected_items().cloned().collect()
};
if !selected_items.is_empty() {
parent_me.handle_action(
&InitProjectBlockAction::SetupLanguageServers {
server_info: selected_items,
repo_path: repo_path_for_submit.clone(),
},
parent_ctx,
);
}
}
}
},
);
view_handle
}
/// Renders the complete LSP server selector block with header and checkboxes.
pub fn render_lsp_selector_block(
action_view: &ViewHandle<ToggleableItemsView<LSPServerInfo>>,
repo_path: &std::path::Path,
skip_mouse_state: &MouseStateHandle,
enable_mouse_state: &MouseStateHandle,
appearance: &Appearance,
ctx: &AppContext,
) -> Box<dyn Element> {
let mut step_content = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch);
// Header
let header_background = appearance.theme().surface_2();
let mut header_row = Flex::row()
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
.with_main_axis_size(MainAxisSize::Max)
.with_cross_axis_alignment(CrossAxisAlignment::Center);
// Left side: icon + text
let mut left_content = Flex::row()
.with_main_axis_alignment(MainAxisAlignment::Start)
.with_cross_axis_alignment(CrossAxisAlignment::Center);
left_content.add_child(
Container::new(
ConstrainedBox::new(yellow_stop_icon(appearance).finish())
.with_width(icon_size(ctx))
.with_height(icon_size(ctx))
.finish(),
)
.with_margin_right(8.)
.finish(),
);
let title_element = Span::new(
"Would you like to enable available language support for this codebase? This will give you smarter code navigation and inline error checking.",
UiComponentStyles {
font_family_id: Some(appearance.ui_font_family()),
font_color: Some(blended_colors::text_main(appearance.theme(), header_background)),
font_size: Some(appearance.monospace_font_size()),
..Default::default()
},
)
.with_soft_wrap()
.build()
.finish();
left_content.add_child(
Expanded::new(
1.,
Container::new(title_element).with_margin_right(8.).finish(),
)
.finish(),
);
header_row.add_child(Shrinkable::new(1., left_content.finish()).finish());
// Right side: buttons with proper action dispatch
let view = action_view.as_ref(ctx);
let selected_items: Vec<LSPServerInfo> = view.get_selected_items().cloned().collect();
let mut buttons_row = Flex::row()
.with_spacing(4.)
.with_cross_axis_alignment(CrossAxisAlignment::Center);
let skip_button = appearance
.ui_builder()
.button(ButtonVariant::Text, skip_mouse_state.clone())
.with_text_label("Skip for now".to_string())
.build()
.on_click(|ctx, _, _| {
ctx.dispatch_typed_action(InitProjectBlockAction::SkipLanguageServers);
})
.finish();
buttons_row.add_child(Container::new(skip_button).with_margin_right(4.).finish());
// Enable button with Enter shortcut
let any_selected = !selected_items.is_empty();
let any_needs_download = selected_items.iter().any(|info| !info.is_installed);
let enable_label = if any_needs_download {
"Install and enable"
} else {
"Enable language support"
};
// Create keyboard shortcut for Enter
let submit_keystroke = Keystroke::parse("enter").expect("can parse enter");
let submit_shortcut = KeyboardShortcut::new(
&submit_keystroke,
UiComponentStyles {
font_family_id: Some(appearance.ui_font_family()),
font_size: Some(appearance.monospace_font_size() - 2.),
font_color: Some(blended_colors::text_sub(
appearance.theme(),
appearance.theme().accent(),
)),
margin: Some(Coords::default().left(6.)),
..Default::default()
},
)
.text_only()
.build()
.finish();
let enable_button_row = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(
Span::new(
enable_label,
UiComponentStyles {
font_family_id: Some(appearance.ui_font_family()),
font_size: Some(appearance.monospace_font_size()),
..Default::default()
},
)
.build()
.finish(),
)
.with_child(submit_shortcut)
.finish();
let mut enable_button = appearance
.ui_builder()
.button(ButtonVariant::Accent, enable_mouse_state.clone())
.with_custom_label(enable_button_row);
if !any_selected {
enable_button = enable_button.disabled();
}
// Capture selected items and repo_path for the click handler
let selected_items_clone = selected_items.clone();
let repo_path_clone = repo_path.to_path_buf();
let enable_button_element = enable_button
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(InitProjectBlockAction::SetupLanguageServers {
server_info: selected_items_clone.clone(),
repo_path: repo_path_clone.clone(),
});
})
.finish();
buttons_row.add_child(
Container::new(enable_button_element)
.with_margin_right(4.)
.finish(),
);
header_row.add_child(buttons_row.finish());
let header_container = Container::new(header_row.finish())
.with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING)
.with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING)
.with_vertical_padding(12.)
.with_background(header_background)
.with_corner_radius(CornerRadius::with_top(Radius::Pixels(8.)))
.finish();
step_content.add_child(header_container);
let checkboxes_container = Container::new(ChildView::new(action_view).finish())
.with_uniform_padding(16.)
.finish();
step_content.add_child(checkboxes_container);
Container::new(step_content.finish())
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
.with_border(Border::all(1.).with_border_fill(appearance.theme().surface_2()))
.with_background(appearance.theme().surface_1())
.finish()
}
File diff suppressed because it is too large Load Diff
+598
View File
@@ -0,0 +1,598 @@
use std::path::{Path, PathBuf};
use ai::index::full_source_code_embedding::manager::CodebaseIndexManager;
use ai::project_context::model::ProjectContextModel;
use enum_iterator::Sequence;
use lsp::supported_servers::LSPServerType;
#[cfg(not(target_family = "wasm"))]
use repo_metadata::repositories::DetectedRepositories;
use warpui::{Entity, ModelContext, SingletonEntity as _};
use crate::{
ai::persisted_workspace::PersistedWorkspace,
settings::CodeSettings,
terminal::view::init_project::{
lsp_server_selector::LSPServerInfo, CodebaseIndexingResult, CreateEnvironmentResult,
InitActionResult, LanguageServersResult, ProjectScopedRulesResult, FILES_TO_CHECK,
LINKABLE_FILES,
},
workspaces::user_workspaces::UserWorkspaces,
};
const INIT_STEP_COUNT: usize = enum_iterator::cardinality::<InitStepKind>();
/// Steps in fixed order - index determines display order.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Sequence)]
#[repr(usize)]
pub enum InitStepKind {
Welcome = 0,
CodebaseContext = 1,
LanguageServers = 2,
ProjectScopedRules = 3,
CreateEnvironment = 4,
}
/// Data needed for views to render ready steps, used to create [`InitStepBlock`]s
#[derive(Clone, Debug)]
pub enum InitStepData {
CodebaseContext {
pwd_path: PathBuf,
},
LanguageServers {
servers: Vec<LSPServerInfo>,
repo_path: PathBuf,
},
ProjectScopedRules {
linkable_files: Vec<PathBuf>,
},
CreateEnvironment,
}
/// Status of a step in the /init flow
pub enum InitStepStatus {
/// Async computation in progress (determining if step is needed)
Pending,
/// Ready for user interaction (contains data for view to render)
Ready(InitStepData),
/// User initiated action, e.g. AI generating WARP.md
Running,
/// Done (accepted, skipped, or auto-completed)
Completed(InitActionResult),
}
impl InitStepStatus {
pub fn is_pending(&self) -> bool {
matches!(self, InitStepStatus::Pending)
}
pub fn is_completed(&self) -> bool {
matches!(self, InitStepStatus::Completed(_))
}
pub fn is_running(&self) -> bool {
matches!(self, InitStepStatus::Running)
}
}
pub struct InitStep {
pub kind: InitStepKind,
pub status: InitStepStatus,
}
impl InitStep {
fn new_pending(kind: InitStepKind) -> Self {
Self {
kind,
status: InitStepStatus::Pending,
}
}
fn new_ready(kind: InitStepKind, data: InitStepData) -> Self {
Self {
kind,
status: InitStepStatus::Ready(data),
}
}
fn new_completed(kind: InitStepKind, result: InitActionResult) -> Self {
Self {
kind,
status: InitStepStatus::Completed(result),
}
}
}
pub struct InitProjectModel {
/// Fixed-size array of steps. None = step disabled/not applicable.
/// Index corresponds to InitStepKind discriminant.
steps: [Option<InitStep>; INIT_STEP_COUNT],
/// Index of current step shown to user
current_step_index: usize,
/// Whether the /init was cancelled
is_cancelled: bool,
/// If project already has all setup done before /init started
is_already_setup: bool,
/// Root path for this /init session
#[cfg(feature = "local_fs")]
root_path: PathBuf,
path_env_var: Option<String>,
}
impl InitProjectModel {
/// Create new InitProjectModel
pub fn new(
pwd_path: PathBuf,
path_env_var: Option<String>,
ctx: &mut ModelContext<Self>,
) -> Self {
let is_already_setup = !Self::should_have_available_steps(&pwd_path, ctx);
Self {
steps: [None, None, None, None, None],
current_step_index: 0,
is_cancelled: false,
is_already_setup,
#[cfg(feature = "local_fs")]
root_path: pwd_path,
path_env_var,
}
}
/// Start the /init flow: compute steps, emit welcome step, progress to next
pub fn start(&mut self, ctx: &mut ModelContext<Self>) {
#[cfg(feature = "local_fs")]
let pwd_path = self.root_path.clone();
#[cfg(not(feature = "local_fs"))]
let pwd_path = PathBuf::new();
// Welcome step is always Completed immediately (no async needed)
self.set_step(
InitStepKind::Welcome,
Some(InitStep::new_completed(
InitStepKind::Welcome,
InitActionResult::Welcome,
)),
);
// Start async computations for subsequent steps
self.compute_codebase_context_step(&pwd_path, ctx);
if self.path_env_var.is_some() {
self.compute_language_servers_step(&pwd_path, ctx);
}
self.compute_project_scoped_rules_step(&pwd_path, ctx);
// CreateEnvironment step is always Ready (no async computation)
self.set_step(
InitStepKind::CreateEnvironment,
Some(InitStep::new_ready(
InitStepKind::CreateEnvironment,
InitStepData::CreateEnvironment,
)),
);
// Emit welcome step immediately, then progress to next
ctx.emit(InitProjectModelEvent::InsertStep(InitStepKind::Welcome));
self.maybe_emit_next_step(ctx);
}
/// Check if there are any steps that need user action
pub fn should_have_available_steps(path: &Path, ctx: &warpui::AppContext) -> bool {
// Note that we consider auto-indexing setting to true to satisfy the codebase context step.
// This avoids the potential race condition with the banner showing just when we start auto-indexing.
let has_pending_codebase_context = UserWorkspaces::as_ref(ctx)
.is_codebase_context_enabled(ctx)
&& CodebaseIndexManager::as_ref(ctx)
.get_codebase_index_status_for_path(path, ctx)
.is_none()
&& !*CodeSettings::as_ref(ctx).auto_indexing_enabled;
let has_pending_project_scoped_rules = ProjectContextModel::as_ref(ctx)
.find_applicable_rules(path)
.is_none();
has_pending_codebase_context || has_pending_project_scoped_rules
}
pub fn get_step(&self, kind: InitStepKind) -> Option<&InitStep> {
self.steps
.get(kind as usize)
.and_then(|maybe_step| maybe_step.as_ref())
}
fn get_step_mut(&mut self, kind: InitStepKind) -> Option<&mut InitStep> {
self.steps
.get_mut(kind as usize)
.and_then(|maybe_step| maybe_step.as_mut())
}
fn set_step(&mut self, kind: InitStepKind, step: Option<InitStep>) {
if let Some(slot) = self.steps.get_mut(kind as usize) {
*slot = step;
}
}
pub fn is_cancelled(&self) -> bool {
self.is_cancelled
}
pub fn is_already_setup(&self) -> bool {
self.is_already_setup
}
pub fn is_completed(&self) -> bool {
if self.is_cancelled {
return true;
}
// All non-None steps must be Completed
self.steps.iter().all(|step| {
step.as_ref()
.map(|s| s.status.is_completed())
.unwrap_or(true)
})
}
/// Returns true if /init is still active (not completed and not cancelled)
pub fn is_active(&self) -> bool {
!self.is_completed() && !self.is_cancelled
}
#[cfg(feature = "local_fs")]
pub fn root_path(&self) -> &Path {
&self.root_path
}
pub fn path_env_var(&self) -> Option<&String> {
self.path_env_var.as_ref()
}
/// Mark a step as completed with the given result
pub fn mark_step_completed(
&mut self,
kind: InitStepKind,
result: InitActionResult,
ctx: &mut ModelContext<Self>,
) {
if let Some(step) = self.get_step_mut(kind) {
step.status = InitStepStatus::Completed(result);
ctx.emit(InitProjectModelEvent::StepCompleted(kind));
self.maybe_emit_next_step(ctx);
}
}
/// Mark a step as Running
pub fn mark_step_running(&mut self, kind: InitStepKind, ctx: &mut ModelContext<Self>) {
if let Some(step) = self.get_step_mut(kind) {
step.status = InitStepStatus::Running;
ctx.notify();
}
}
/// Disable the regenerate button for project scoped rules (after user clicks it)
pub fn disable_regenerate_button(&mut self) {
if let Some(step) = self.get_step_mut(InitStepKind::ProjectScopedRules) {
match &mut step.status {
InitStepStatus::Completed(InitActionResult::ProjectScopedRules(
ProjectScopedRulesResult::GenerateNew {
button_disabled, ..
},
))
| InitStepStatus::Completed(InitActionResult::ProjectScopedRules(
ProjectScopedRulesResult::AlreadyExists { button_disabled },
)) => {
*button_disabled = true;
}
_ => {}
}
}
}
/// Cancel the /init flow. This will notify subscribed blocks to rerender.
pub fn cancel(&mut self, ctx: &mut ModelContext<Self>) {
self.is_cancelled = true;
// Mark Ready or Running steps as cancelled/skipped
for step in self.steps.iter_mut().flatten() {
if matches!(
step.status,
InitStepStatus::Ready(_) | InitStepStatus::Running
) {
let skipped_result = match step.kind {
InitStepKind::Welcome => continue,
InitStepKind::CodebaseContext => {
InitActionResult::CodebaseContext(CodebaseIndexingResult::Skipped)
}
InitStepKind::LanguageServers => {
InitActionResult::LanguageServers(LanguageServersResult::Skipped)
}
InitStepKind::ProjectScopedRules => {
InitActionResult::ProjectScopedRules(ProjectScopedRulesResult::Skipped)
}
InitStepKind::CreateEnvironment => {
InitActionResult::CreateEnvironment(CreateEnvironmentResult::Skipped)
}
};
step.status = InitStepStatus::Completed(skipped_result);
}
}
ctx.emit(InitProjectModelEvent::Cancelled);
}
/// Check if next step should be emitted, emit if ready.
/// Only emits if current step is Completed (or None).
fn maybe_emit_next_step(&mut self, ctx: &mut ModelContext<Self>) {
if self.is_cancelled {
return;
}
// Check current step is Completed or None before advancing
let current_step_completed = self.steps[self.current_step_index]
.as_ref()
.map(|s| s.status.is_completed())
.unwrap_or(true);
if !current_step_completed {
return;
}
let next_index = self.current_step_index + 1;
if next_index >= INIT_STEP_COUNT {
ctx.emit(InitProjectModelEvent::InitCompleted);
return;
}
match &self.steps[next_index] {
None => {
// Step disabled/not applicable, skip to next
self.current_step_index = next_index;
self.maybe_emit_next_step(ctx);
}
Some(step) if !step.status.is_pending() => {
// Ready to show (or auto-completed)
self.current_step_index = next_index;
ctx.emit(InitProjectModelEvent::InsertStep(step.kind));
// If auto-completed, immediately try next step
if step.status.is_completed() {
self.maybe_emit_next_step(ctx);
}
}
Some(_) => {
// Still Pending, wait for async to finish
}
}
}
fn compute_codebase_context_step(&mut self, pwd_path: &Path, ctx: &mut ModelContext<Self>) {
if !UserWorkspaces::as_ref(ctx).is_codebase_context_enabled(ctx) {
// Feature disabled, leave as None
return;
}
let codebase_index_manager = CodebaseIndexManager::handle(ctx);
let is_indexed = codebase_index_manager
.as_ref(ctx)
.get_codebase_index_status_for_path(pwd_path, ctx)
.is_some();
if is_indexed {
// Already indexed, mark as completed
self.set_step(
InitStepKind::CodebaseContext,
Some(InitStep::new_completed(
InitStepKind::CodebaseContext,
InitActionResult::CodebaseContext(CodebaseIndexingResult::Accepted),
)),
);
} else {
// Ready for user interaction
self.set_step(
InitStepKind::CodebaseContext,
Some(InitStep::new_ready(
InitStepKind::CodebaseContext,
InitStepData::CodebaseContext {
pwd_path: pwd_path.to_path_buf(),
},
)),
);
}
}
fn compute_language_servers_step(&mut self, pwd_path: &Path, ctx: &mut ModelContext<Self>) {
// Start as Pending
self.set_step(
InitStepKind::LanguageServers,
Some(InitStep::new_pending(InitStepKind::LanguageServers)),
);
let pwd_path = pwd_path.to_path_buf();
#[cfg(not(target_family = "wasm"))]
let repo_root = DetectedRepositories::as_ref(ctx)
.get_root_for_path(&pwd_path)
.unwrap_or_else(|| pwd_path.clone());
#[cfg(target_family = "wasm")]
let repo_root = pwd_path.clone();
let repo_root_for_callback = repo_root.clone();
let executor = lsp::CommandBuilder::new(self.path_env_var.clone());
let http_client =
crate::server::server_api::ServerApiProvider::as_ref(ctx).get_http_client();
ctx.spawn(
async move {
let mut relevant_servers = Vec::new();
for server_type in LSPServerType::all() {
let candidate = server_type.candidate(http_client.clone());
let should_suggest = candidate
.should_suggest_for_repo(&repo_root, &executor)
.await;
if should_suggest {
let is_installed = candidate.is_installed(&executor).await;
relevant_servers.push(LSPServerInfo {
server_type,
is_installed,
});
}
}
relevant_servers
},
move |me, relevant_servers, ctx| {
let repo_root = repo_root_for_callback;
if relevant_servers.is_empty() {
// No relevant servers, mark step as None (skip it)
me.set_step(InitStepKind::LanguageServers, None);
me.maybe_emit_next_step(ctx);
return;
}
// Check if already enabled and filter
let enabled_server_types: Vec<LSPServerType> = {
let enabled_lsp_servers =
PersistedWorkspace::as_ref(ctx).enabled_lsp_servers(&pwd_path);
enabled_lsp_servers
.map(|servers| servers.collect())
.unwrap_or_default()
};
let filtered_servers: Vec<LSPServerInfo> = relevant_servers
.into_iter()
.filter(|info| !enabled_server_types.contains(&info.server_type))
.collect();
if filtered_servers.is_empty() {
// All relevant servers already enabled, auto-complete
me.set_step(
InitStepKind::LanguageServers,
Some(InitStep::new_completed(
InitStepKind::LanguageServers,
InitActionResult::LanguageServers(LanguageServersResult::Accepted {
enabled_servers: enabled_server_types,
servers_to_install: Vec::new(),
}),
)),
);
} else {
// Ready for user interaction
me.set_step(
InitStepKind::LanguageServers,
Some(InitStep::new_ready(
InitStepKind::LanguageServers,
InitStepData::LanguageServers {
servers: filtered_servers,
repo_path: repo_root,
},
)),
);
}
me.maybe_emit_next_step(ctx);
},
);
}
fn compute_project_scoped_rules_step(&mut self, pwd_path: &Path, ctx: &mut ModelContext<Self>) {
// Start as Pending
self.set_step(
InitStepKind::ProjectScopedRules,
Some(InitStep::new_pending(InitStepKind::ProjectScopedRules)),
);
let path_to_check: Vec<PathBuf> = FILES_TO_CHECK
.iter()
.chain(LINKABLE_FILES.iter())
.map(|name| pwd_path.join(name))
.collect();
ctx.spawn(
async move {
let mut exists = vec![];
for path in path_to_check {
if path.exists() {
exists.push(path);
}
}
exists
},
move |me, existing_files, ctx| {
let has_agents_md = existing_files.iter().any(|p| {
p.file_name()
.map(|n| {
let name = n.to_string_lossy().to_lowercase();
name == "agents.md" || name == "warp.md"
})
.unwrap_or(false)
});
if has_agents_md {
// Already has AGENTS.md or WARP.md, mark as completed
me.set_step(
InitStepKind::ProjectScopedRules,
Some(InitStep::new_completed(
InitStepKind::ProjectScopedRules,
InitActionResult::ProjectScopedRules(
ProjectScopedRulesResult::AlreadyExists {
button_disabled: false,
},
),
)),
);
} else {
// Collect linkable files
let linkable_files: Vec<PathBuf> = existing_files
.into_iter()
.filter(|p| {
p.file_name()
.map(|n| {
let name = n.to_string_lossy();
LINKABLE_FILES.iter().any(|lf| name.ends_with(lf))
})
.unwrap_or(false)
})
.collect();
me.set_step(
InitStepKind::ProjectScopedRules,
Some(InitStep::new_ready(
InitStepKind::ProjectScopedRules,
InitStepData::ProjectScopedRules { linkable_files },
)),
);
}
me.maybe_emit_next_step(ctx);
},
);
}
}
/// Events emitted by InitProjectModel
#[derive(Debug, Clone)]
pub enum InitProjectModelEvent {
/// Insert a new step block into the terminal view
InsertStep(InitStepKind),
/// A step was completed
StepCompleted(InitStepKind),
/// The /init flow was cancelled
Cancelled,
/// All steps completed
InitCompleted,
/// Trigger AGENTS.md generation slash command
GenerateProjectRules,
/// Trigger AGENTS.md regeneration
RegenerateProjectRules,
/// View codebase context status
ViewCodebaseContextStatus,
/// Language server installed and enabled
LanguageServerInstalledAndEnabled,
/// Trigger create environment slash command
CreateEnvironment,
/// Cloud environment was created
EnvironmentCreated,
}
impl Entity for InitProjectModel {
type Event = InitProjectModelEvent;
}