diff --git a/src/app/state/rest.rs b/src/app/state/rest.rs index e88ddaf..1c44318 100644 --- a/src/app/state/rest.rs +++ b/src/app/state/rest.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use tokio::sync::RwLock; -use super::misc::{DirCache, InputState, MiscState, ScrollState}; +use super::misc::{DirCache, InputState, MentionIndex, MiscState, ScrollState}; use super::runtime::{SessionRuntime, TurnEvent}; use super::types::{Origin, Toast, TranscriptCache}; use crate::app::lsp::LspManager; @@ -54,6 +54,7 @@ pub struct AppStateRest { pub memory_dir: PathBuf, pub worktrees_dir: PathBuf, pub dir_cache: Arc>, + pub mention_index: MentionIndex, pub edit_log: EditLog, pub session_runtime: Option, pub sessions: Vec, @@ -110,6 +111,7 @@ impl AppStateRest { turn_in_flight: Arc::new(Mutex::new(false)), abort_flag: Arc::new(std::sync::atomic::AtomicBool::new(false)), dir_cache: Arc::new(RwLock::new(dir_cache)), + mention_index: MentionIndex::new(), edit_log: EditLog::new(session_dir), session_runtime: Some(SessionRuntime::new(session_dir.to_path_buf())), workflow_engine: WorkflowEngine::new(), @@ -204,6 +206,44 @@ impl AppStateRest { }); } + // Fire-and-forget background file index build for `@file` mention + // autocomplete. + // + // Flow: spawn OS thread -> `ignore::Walk` each workspace root, + // collecting file paths (workspace-index-prefixed for roots beyond + // the first, matching `resolve_path`'s `[N]path` convention) -> stop + // once 50,000 entries are collected -> store the result in + // `mention_index`. + // + // Why a raw thread and not a background tokio task: there is no + // persistent async runtime driving the render loop, and this is + // blocking filesystem I/O -- a dedicated thread keeps startup + // non-blocking. Not joined, same rationale as the LSP provisioning + // thread above: a slow/huge repo must not delay the TUI appearing. + { + let mention_index = state.mention_index.clone(); + let roots = state.workspace_roots.clone(); + std::thread::spawn(move || { + const MAX_MENTION_ENTRIES: usize = 50_000; + let mut paths = Vec::new(); + 'roots: for (i, root) in roots.iter().enumerate() { + for entry in ignore::Walk::new(root).flatten() { + if !entry.path().is_file() { + continue; + } + let rel = entry.path().strip_prefix(root).unwrap_or(entry.path()); + let rel_str = rel.display().to_string(); + let formatted = if i == 0 { rel_str } else { format!("[{i}]{rel_str}") }; + paths.push(formatted); + if paths.len() >= MAX_MENTION_ENTRIES { + break 'roots; + } + } + } + mention_index.set(paths); + }); + } + state } @@ -278,6 +318,7 @@ impl AppStateRest { memory_dir: self.memory_dir.clone(), worktrees_dir: self.worktrees_dir.clone(), dir_cache: self.dir_cache.clone(), + mention_index: self.mention_index.clone(), origin, graduated_checks: Vec::new(), lsp_manager: self.lsp_manager.clone(), diff --git a/src/tool/mod.rs b/src/tool/mod.rs index 69b7d35..6b7c190 100644 --- a/src/tool/mod.rs +++ b/src/tool/mod.rs @@ -47,6 +47,7 @@ pub struct ToolCtx { pub memory_dir: PathBuf, pub worktrees_dir: PathBuf, pub dir_cache: std::sync::Arc>, + pub mention_index: super::app::state::misc::MentionIndex, pub origin: crate::app::state::types::Origin, pub graduated_checks: Vec, pub lsp_manager: Arc>, @@ -94,6 +95,7 @@ pub struct ToolCtxBuilder { pub memory_dir: PathBuf, pub worktrees_dir: PathBuf, pub dir_cache: std::sync::Arc>, + pub mention_index: super::app::state::misc::MentionIndex, pub origin: crate::app::state::types::Origin, pub graduated_checks: Vec, pub lsp_manager: Arc>, @@ -110,6 +112,7 @@ impl Default for ToolCtxBuilder { memory_dir: PathBuf::new(), worktrees_dir: PathBuf::new(), dir_cache: std::sync::Arc::new(tokio::sync::RwLock::new(super::app::state::misc::DirCache::new())), + mention_index: super::app::state::misc::MentionIndex::new(), origin: crate::app::state::types::Origin::Main, graduated_checks: Vec::new(), lsp_manager: Arc::new(Mutex::new(crate::app::lsp::LspManager::new())), @@ -138,6 +141,7 @@ impl ToolCtxBuilder { memory_dir: self.memory_dir, worktrees_dir: self.worktrees_dir, dir_cache: self.dir_cache, + mention_index: self.mention_index, origin: self.origin, graduated_checks: self.graduated_checks, lsp_manager: self.lsp_manager,