use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::time::Instant;
use crate::args::LiveRefresh;
use crate::session::SessionOptions;
use crate::ui::events::UiMode;
use crate::ui::perf;
use crate::ui::shell::ShellEvent;
use crate::ui::store::navigation::NavigationState;
use crate::ui::store::navigation_tree::{build_change_navigator, build_navigator};
use crate::ui::workspace_read::{
LocalWorkspaceRegistry, UsageFocus, new_local_workspace, workspace_check_context,
};
use code_moniker_workspace::live::{WorkspaceLiveRefreshPlan, WorkspaceWatchRoot};
use code_moniker_workspace::source::LocalResourceCache;
mod action;
mod change_mode;
mod effect;
mod header_search;
mod navigation;
mod note_editor;
mod panel_focus;
mod runtime;
mod state;
mod store;
mod usage_lens;
mod workspace_refresh;
mod workspace_session;
pub(in crate::ui) use action::{AppAction, ShellAction};
pub(in crate::ui) use effect::Effect;
pub(in crate::ui) use header_search::{
HeaderKindFilter, HeaderSearchState, display_filter, header_search_label, kind_filter_summary,
lang_filter_summary,
};
pub(in crate::ui) use navigation::{
apply_navigation, close_selected_nav, filter_label, has_clearable_scope, is_filtered,
open_selected_nav, primary_selected, refresh_results, scope_label, select_def,
select_first_change, selected, selected_change_detail, selected_nav_row, set_view,
sync_contextual_view, toggle_selected_nav,
};
pub(in crate::ui) use note_editor::{
close_note_editor, cycle_note_editor_kind, cycle_note_editor_status, delete_note_from_editor,
edit_note_editor, move_note_editor_field, open_note_editor, save_note_from_editor,
show_notes_lens, sort_notes_for_lens,
};
pub(in crate::ui) use panel_focus::{
FocusCycle, close_panel_tree_node, copy_panel_snapshot, cycle_focus_region,
ensure_active_panel_selection, move_panel_selection, move_panel_to_edge, open_panel_tree_node,
toggle_panel_tree_node,
};
pub(in crate::ui) use runtime::{
dispatch_and_apply, handle_key, queue_startup_load, queue_task, queue_usage_lens_refresh,
set_event_sender, take_watch_roots_update, update,
};
pub(in crate::ui) use state::{
ActiveFilter, ChangePanelMode, CheckState, FocusRegion, NoteEditorField, NoteEditorState,
PanelNavigationState, PanelPolicy, TaskCompletion, View, VisualizationMode,
};
pub(in crate::ui) use store::AppStore;
pub(in crate::ui) use workspace_refresh::{
apply_file_catalog_store, apply_reloaded_store, handle_store_event, handle_store_event_sync,
refresh_workspace_on_demand,
};
use workspace_session::WorkspaceSession;
pub(in crate::ui) struct AppConfig {
pub(in crate::ui) scheme: String,
pub(in crate::ui) rules: PathBuf,
pub(in crate::ui) profile: Option<String>,
pub(in crate::ui) debug: bool,
pub(in crate::ui) live_refresh: LiveRefresh,
}
pub(in crate::ui) struct App {
pub(in crate::ui) app_store: AppStore,
pub(in crate::ui) workspace: WorkspaceSession,
pub(in crate::ui) config: AppConfig,
pub(in crate::ui) runtime: AppRuntime,
}
pub(in crate::ui) struct AppRuntime {
event_tx: Option<Sender<ShellEvent>>,
startup_load_pending: bool,
watch_roots_update: Option<Vec<WorkspaceWatchRoot>>,
usage_lens_generation: u64,
pending_live_plan: Option<WorkspaceLiveRefreshPlan>,
}
pub(in crate::ui) fn boot_app(
opts: SessionOptions,
scheme: String,
rules: PathBuf,
profile: Option<String>,
debug: bool,
live_refresh: LiveRefresh,
) -> App {
let (store, cache) = new_local_workspace(&opts);
let mut app = new_app(
store,
cache,
opts,
AppConfig {
scheme,
rules,
profile,
debug,
live_refresh,
},
);
app.runtime.startup_load_pending = true;
set_status(&mut app, "loading index...");
app
}
pub(in crate::ui) fn status(app: &App) -> &str {
app.app_store.status()
}
pub(in crate::ui) fn set_status(app: &mut App, status: impl Into<String>) {
dispatch_shell(app, ShellAction::SetStatus(status.into()));
}
pub(in crate::ui) fn append_status(app: &mut App, status: impl AsRef<str>) {
dispatch_shell(app, ShellAction::AppendStatus(status.as_ref().to_string()));
}
pub(in crate::ui) fn check_state(app: &App) -> &CheckState {
app.app_store.check_state()
}
pub(in crate::ui) fn set_check_state(app: &mut App, state: CheckState) {
dispatch_shell(app, ShellAction::SetCheckState(state));
}
pub(in crate::ui) fn dispatch_shell(app: &mut App, action: ShellAction) {
let refresh_search_options = matches!(
action,
ShellAction::SetHeaderSearchFilters { .. } | ShellAction::ClearFilter { .. }
);
dispatch_and_apply(app, &AppAction::Shell(action));
if refresh_search_options {
app.refresh_header_search_options();
}
}
pub(in crate::ui) fn view(app: &App) -> View {
app.app_store.shell().view
}
pub(in crate::ui) fn view_mode(app: &App) -> VisualizationMode {
app.app_store.shell().view_mode
}
pub(in crate::ui) fn panel_policy(app: &App) -> PanelPolicy {
app.app_store.shell().panel_policy
}
pub(in crate::ui) fn change_panel(app: &App) -> ChangePanelMode {
app.app_store.shell().change_panel
}
pub(in crate::ui) fn mode(app: &App) -> UiMode {
app.app_store.shell().mode
}
pub(in crate::ui) fn focus_region(app: &App) -> FocusRegion {
app.app_store.shell().focus_region
}
pub(in crate::ui) fn usage_lens(app: &App) -> Option<&UsageFocus> {
app.app_store.shell().usage_lens.as_ref()
}
pub(in crate::ui) fn views_show_all(app: &App) -> bool {
app.app_store.shell().views_show_all
}
pub(in crate::ui) fn note_editor(app: &App) -> Option<&NoteEditorState> {
app.app_store.shell().note_editor.as_ref()
}
pub(in crate::ui) fn main_split_percent(app: &App) -> u16 {
app.app_store.shell().main_split_percent
}
pub(in crate::ui) fn active_filter(app: &App) -> &ActiveFilter {
&app.app_store.shell().active_filter
}
pub(in crate::ui) fn header_search(app: &App) -> &HeaderSearchState {
&app.app_store.shell().header_search
}
pub(in crate::ui) fn navigation(app: &App) -> &NavigationState {
app.app_store.navigation()
}
pub(in crate::ui) fn store(app: &App) -> &LocalWorkspaceRegistry {
app.workspace.store()
}
pub(in crate::ui) fn notes(app: &App) -> code_moniker_workspace::notes::NotesDocument {
app.workspace.notes()
}
pub(in crate::ui) fn notes_error(app: &App) -> Option<&str> {
app.app_store.shell().notes_error.as_deref()
}
pub(in crate::ui) fn reload_notes(app: &mut App) -> anyhow::Result<()> {
match app.workspace.reload_notes() {
Ok(()) => {
app.app_store
.dispatch(&AppAction::Shell(ShellAction::SetNotesError(None)));
Ok(())
}
Err(error) => {
let message = format!("{error:#}");
app.app_store
.dispatch(&AppAction::Shell(ShellAction::SetNotesError(Some(
message.clone(),
))));
Err(anyhow::anyhow!(message))
}
}
}
pub(in crate::ui) fn mutate_notes<F, T>(app: &App, mutate: F) -> anyhow::Result<T>
where
F: FnOnce(&mut code_moniker_workspace::notes::NotesDocument) -> anyhow::Result<T>,
{
app.workspace
.index
.mutate_notes(&app.workspace.options.paths, mutate)
}
pub(in crate::ui) fn store_options(app: &App) -> SessionOptions {
app.workspace.options().clone()
}
pub(in crate::ui) fn store_root_label(app: &App) -> String {
crate::session::root_label_for_options(app.workspace.options())
}
pub(in crate::ui) fn store_watch_roots(app: &App) -> Vec<WorkspaceWatchRoot> {
store(app).watch_roots()
}
pub(in crate::ui) fn store_check_context(
app: &App,
) -> anyhow::Result<crate::ui::workspace_read::WorkspaceCheckContext> {
workspace_check_context(store(app), app.workspace.cache())
}
pub(in crate::ui) fn replace_store(
app: &mut App,
store: LocalWorkspaceRegistry,
cache: LocalResourceCache,
options: SessionOptions,
) {
app.workspace.replace(store, cache, options);
let _ = reload_notes(app);
}
pub(in crate::ui) fn app_rules_path(app: &App) -> &std::path::Path {
&app.config.rules
}
pub(in crate::ui) fn app_profile_name(app: &App) -> Option<&str> {
app.config.profile.as_deref()
}
pub(in crate::ui) fn debug(app: &App) -> bool {
app.config.debug
}
pub(in crate::ui) fn live_refresh_on_demand(app: &App) -> bool {
app.config.live_refresh.is_on_demand()
}
pub(in crate::ui) fn pending_live_plan_summary(app: &App) -> Option<String> {
app.runtime
.pending_live_plan
.as_ref()
.map(code_moniker_workspace::registry::WorkspaceStaleness::plan_summary)
}
pub(in crate::ui) fn new_app(
store: LocalWorkspaceRegistry,
cache: LocalResourceCache,
options: SessionOptions,
config: AppConfig,
) -> App {
let started = Instant::now();
let navigator = build_navigator(&store, &options.paths);
perf::record("app.new.build_navigator", started.elapsed(), "");
let started = Instant::now();
let change_navigator = build_change_navigator(&store);
perf::record("app.new.build_change_navigator", started.elapsed(), "");
let started = Instant::now();
let mut app_store = AppStore::new();
app_store.set_navigation(NavigationState::new(navigator, change_navigator));
let mut app = App {
app_store,
workspace: WorkspaceSession::new(store, cache, options),
config,
runtime: AppRuntime {
event_tx: None,
startup_load_pending: false,
watch_roots_update: None,
usage_lens_generation: 0,
pending_live_plan: None,
},
};
let _ = reload_notes(&mut app);
app.refresh_header_search_options();
set_status(
&mut app,
"Enter opens, Esc closes, s search, n note, 8 notes, d changes, u usages, y copies panel, c checks, q quits",
);
refresh_results(&mut app, false);
perf::record("app.new.finish", started.elapsed(), status(&app));
app
}