use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Instant;
use anyhow::Context;
use anyhow::Error;
use tui_pane::FocusedPane;
use tui_pane::Keymap as FrameworkKeymap;
use tui_pane::LoadedSettings;
use tui_pane::SettingsStore;
use tui_pane::ThemeRuntime;
use tui_pane::ToastSettings;
use super::App;
use super::async_tasks::Startup;
use super::scan_state::ScanState;
use crate::channel;
use crate::channel::Receiver;
use crate::channel::Sender;
use crate::config;
use crate::config::CargoPortConfig;
use crate::http::HttpClient;
use crate::lint;
use crate::lint::RuntimeHandle;
use crate::project::AbsolutePath;
use crate::project::RootItem;
use crate::project::WorkspaceMetadataStore;
use crate::scan;
use crate::scan::BackgroundMsg;
use crate::themes;
use crate::tui::background::Background;
use crate::tui::background::BackgroundChannels;
use crate::tui::integration;
use crate::tui::integration::AppPaneId;
use crate::tui::keymap;
use crate::tui::overlays::Overlays;
use crate::tui::panes::Panes;
use crate::tui::project_list::ProjectList;
use crate::tui::settings::StartupSettings;
use crate::tui::state::Ci;
use crate::tui::state::Config;
use crate::tui::state::GitStatusTracker;
use crate::tui::state::Inflight;
use crate::tui::state::Keymap;
use crate::tui::state::Lint;
use crate::tui::state::Net;
use crate::tui::state::Scan;
use crate::tui::state::SyncTracker;
use crate::tui::terminal::CiFetchMsg;
use crate::tui::terminal::CleanMsg;
use crate::tui::terminal::ExampleMsg;
use crate::tui::theme_roles;
use crate::watcher;
use crate::watcher::WatcherMsg;
pub(super) struct Inputs {
background_tx: Sender<BackgroundMsg>,
background_rx: Receiver<BackgroundMsg>,
cargo_port_config: CargoPortConfig,
http_client: HttpClient,
scan_started_at: Instant,
metadata_store: Arc<Mutex<WorkspaceMetadataStore>>,
raw_projects: Vec<RootItem>,
settings_store: SettingsStore,
toast_settings: ToastSettings,
}
pub(super) struct Channeled {
inputs: Inputs,
example_tx: Sender<ExampleMsg>,
example_rx: Receiver<ExampleMsg>,
ci_fetch_tx: Sender<CiFetchMsg>,
ci_fetch_rx: Receiver<CiFetchMsg>,
clean_tx: Sender<CleanMsg>,
clean_rx: Receiver<CleanMsg>,
}
pub(super) struct Started {
channeled: Channeled,
config_path: Option<AbsolutePath>,
lint_warning: Option<String>,
lint_runtime: Option<RuntimeHandle>,
watch_tx: Sender<WatcherMsg>,
projects: ProjectList,
}
pub(super) struct AppBuilder<S> {
state: S,
}
impl AppBuilder<Inputs> {
pub(super) fn new(
projects: &[RootItem],
background_tx: Sender<BackgroundMsg>,
background_rx: Receiver<BackgroundMsg>,
startup_settings: StartupSettings,
http_client: HttpClient,
scan_started_at: Instant,
metadata_store: Arc<Mutex<WorkspaceMetadataStore>>,
) -> Self {
Self {
state: Inputs {
background_tx,
background_rx,
cargo_port_config: startup_settings.config,
http_client,
scan_started_at,
metadata_store,
raw_projects: projects.to_vec(),
settings_store: startup_settings.store,
toast_settings: startup_settings.toast_settings,
},
}
}
pub(super) fn open_channels(self) -> AppBuilder<Channeled> {
let (example_tx, example_rx) = channel::unbounded();
let (ci_fetch_tx, ci_fetch_rx) = channel::unbounded();
let (clean_tx, clean_rx) = channel::unbounded();
AppBuilder {
state: Channeled {
inputs: self.state,
example_tx,
example_rx,
ci_fetch_tx,
ci_fetch_rx,
clean_tx,
clean_rx,
},
}
}
}
impl AppBuilder<Channeled> {
pub(super) fn run_startup(self) -> AppBuilder<Started> {
let inputs = &self.state.inputs;
config::set_active_config(&inputs.cargo_port_config);
let mut registry =
tui_pane::ThemeRegistry::from_dir_with_builtins(themes::themes_dir().as_deref());
theme_roles::apply_role_defaults_to_registry(&mut registry);
let resolved = registry.resolve_active(
&inputs.cargo_port_config.appearance.mode,
&inputs.cargo_port_config.appearance.light_theme,
&inputs.cargo_port_config.appearance.dark_theme,
None,
);
let mut initial_theme = (*resolved.theme).clone();
theme_roles::apply_role_defaults_to_theme(&mut initial_theme, None, resolved.appearance);
tui_pane::install_theme_state(tui_pane::ThemeState::with_registry(registry, initial_theme));
tui_pane::set_focused_pane_tint(inputs.cargo_port_config.appearance.focused_pane_tint);
let config_path = config::config_path();
let lint_spawn = lint::spawn(&inputs.cargo_port_config, inputs.background_tx.clone());
let watch_roots = scan::resolve_include_dirs(&inputs.cargo_port_config.tui.include_dirs);
let watch_tx = watcher::spawn_watcher(
&watch_roots,
inputs.background_tx.clone(),
inputs.cargo_port_config.tui.ci_run_count,
inputs.cargo_port_config.tui.include_non_rust,
inputs.http_client.clone(),
lint_spawn.handle.clone(),
Arc::clone(&inputs.metadata_store),
);
let built = scan::build_tree(
&inputs.raw_projects,
&inputs.cargo_port_config.tui.inline_dirs,
);
let projects = ProjectList::new(built);
AppBuilder {
state: Started {
channeled: self.state,
config_path,
lint_warning: lint_spawn.warning,
lint_runtime: lint_spawn.handle,
watch_tx,
projects,
},
}
}
}
impl AppBuilder<Started> {
pub(super) fn build(self) -> Result<App, Error> {
let started = self.state;
let channeled = started.channeled;
let inputs = channeled.inputs;
let panes = Panes::new(&inputs.cargo_port_config.cpu);
let mut projects = started.projects;
projects.init_runtime_state(inputs.cargo_port_config.lint.enabled);
let background = Background::new(BackgroundChannels {
background: (inputs.background_tx, inputs.background_rx),
ci_fetch: (channeled.ci_fetch_tx, channeled.ci_fetch_rx),
clean: (channeled.clean_tx, channeled.clean_rx),
example: (channeled.example_tx, channeled.example_rx),
watch_tx: started.watch_tx,
});
let lint = Lint::new(started.lint_runtime);
let inflight = Inflight::new();
let config_path_buf = started
.config_path
.as_ref()
.map(|p| p.as_path().to_path_buf());
let config = Config::new(config_path_buf, inputs.cargo_port_config);
let keymap_path_buf = keymap::keymap_path()
.as_ref()
.map(|p| p.as_path().to_path_buf());
let keymap = Keymap::new(keymap_path_buf.clone(), keymap::ResolvedKeymap::defaults());
let themes = ThemeRuntime::new(themes::themes_dir());
let scan = Scan::new(
ScanState::new(inputs.scan_started_at),
inputs.metadata_store,
);
let mut overlays = Overlays::new();
if let Some(warning) = started.lint_warning {
overlays.set_status_flash(warning, Instant::now());
}
let mut framework = tui_pane::Framework::new_with_settings(
FocusedPane::App(AppPaneId::ProjectList),
LoadedSettings {
store: inputs.settings_store,
toast_settings: inputs.toast_settings,
},
);
let framework_builder = FrameworkKeymap::<App>::builder().vim_mode(
integration::vim_mode_from_config(config.current().tui.navigation_keys),
);
let framework_builder = if let Some(path) = keymap_path_buf {
let display_path = path.display().to_string();
keymap::migrate_removed_action_keys_on_disk(&path)
.with_context(|| format!("migrating removed keymap actions in {display_path}"))?;
framework_builder
.load_toml(path)
.with_context(|| format!("loading keymap from {display_path}"))?
} else {
framework_builder
};
let framework_keymap =
integration::build_framework_keymap(framework_builder, &mut framework)
.with_context(|| "building framework keymap")?;
let mut app = App {
net: Net::new(inputs.http_client),
panes,
project_list: projects,
background,
inflight,
lint,
ci: Ci::new(),
config,
keymap,
themes,
sync_tracker: SyncTracker::default(),
git_status_tracker: GitStatusTracker::default(),
scan,
startup: Startup::new(),
visited_panes: std::iter::once(AppPaneId::ProjectList).collect(),
overlays,
confirm: None,
animation_started: Instant::now(),
mouse_pos: None,
framework,
framework_keymap: Rc::new(framework_keymap),
pending_nav_chord: Vec::new(),
};
app.finish_new();
Ok(app)
}
}
impl App {
fn finish_new(&mut self) {
self.panes.install_cpu_placeholder();
self.load_initial_keymap();
if let Some(warning) = self
.overlays
.status_flash()
.map(|(warning, _)| warning.clone())
{
self.show_timed_toast("Lint runtime", warning);
}
self.force_settings_if_unconfigured();
self.prune_inactive_project_state();
self.register_existing_projects();
let lint_registered = self.register_lint_for_root_items();
tracing::trace!(
target: tui_pane::PERF_LOG_TARGET,
count = lint_registered,
"startup_lint_runtime_registered_initial_projects"
);
if !self.project_list.is_empty() {
self.finish_watcher_registration_batch();
}
self.refresh_lint_runs_from_disk();
self.net
.set_force_github_rate_limit(self.config.current().debug.force_github_rate_limit);
self.net.spawn_rate_limit_prime();
self.warn_if_github_unauthenticated();
}
}