code-moniker 0.4.0

Standalone CLI / linter for the code-moniker symbol graph: file and directory probes, project-wide architecture rules.
Documentation
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
}