Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3029de667e |
@@ -261,17 +261,13 @@ impl ProjectScanner {
|
||||
}
|
||||
|
||||
/// Cached snapshot, rescanning (off-thread) when the TTL has lapsed.
|
||||
/// Also the background timer's entry point: a tick inside the TTL is a
|
||||
/// no-op (#75).
|
||||
pub async fn repos(&self) -> std::sync::Arc<Vec<DiscoveredRepo>> {
|
||||
self.repos_inner(false).await
|
||||
}
|
||||
|
||||
async fn repos_inner(&self, force: bool) -> std::sync::Arc<Vec<DiscoveredRepo>> {
|
||||
let mut cache = self.cache.lock().await;
|
||||
if !force {
|
||||
if let Some((at, repos)) = cache.as_ref() {
|
||||
if at.elapsed() < self.ttl {
|
||||
return repos.clone();
|
||||
}
|
||||
if let Some((at, repos)) = cache.as_ref() {
|
||||
if at.elapsed() < self.ttl {
|
||||
return repos.clone();
|
||||
}
|
||||
}
|
||||
let roots = self.roots.clone();
|
||||
@@ -287,11 +283,11 @@ impl ProjectScanner {
|
||||
repos
|
||||
}
|
||||
|
||||
/// Rescan now, ignoring the TTL. Called by the background timer — going
|
||||
/// through `repos()` would hit the cache and make most ticks no-ops,
|
||||
/// leaving requests to pay for the rescan instead (#54 review).
|
||||
/// One background-refresh tick: rescan only if the cached snapshot has
|
||||
/// passed its TTL (#75) — the timer used to bypass it and re-walk the
|
||||
/// roots every [`REFRESH_INTERVAL`].
|
||||
async fn refresh(&self) {
|
||||
self.repos_inner(true).await;
|
||||
self.repos().await;
|
||||
}
|
||||
|
||||
/// Spawn the slow background refresh task. Drop the returned handle to
|
||||
@@ -544,6 +540,32 @@ mod tests {
|
||||
assert_eq!(fresh[0].name, "late");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn background_refresh_honours_the_ttl() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut scanner = ProjectScanner::new(
|
||||
vec![tmp.path().to_path_buf()],
|
||||
3,
|
||||
PathBuf::from("/nonexistent"),
|
||||
);
|
||||
|
||||
let empty = scanner.repos().await;
|
||||
assert!(empty.is_empty());
|
||||
|
||||
make_repo(tmp.path(), "late");
|
||||
// A background tick inside the TTL must not re-walk the filesystem:
|
||||
// the new repo stays invisible until the cache expires.
|
||||
scanner.refresh().await;
|
||||
assert!(scanner.repos().await.is_empty());
|
||||
|
||||
// Once the TTL lapses, the next tick picks it up.
|
||||
scanner.ttl = Duration::ZERO;
|
||||
scanner.refresh().await;
|
||||
let fresh = scanner.repos().await;
|
||||
assert_eq!(fresh.len(), 1);
|
||||
assert_eq!(fresh[0].name, "late");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_scanner_always_serves_empty() {
|
||||
let scanner = ProjectScanner::disabled();
|
||||
|
||||
@@ -238,26 +238,6 @@ mod tests {
|
||||
assert!(s.contains("ctrl-glorb"), "diagnostic: {s}");
|
||||
}
|
||||
|
||||
/// Regression for #70: the previous canonical form of a real Shift+S
|
||||
/// keypress was `"shift-S"`, which the default `"S"` binding never
|
||||
/// matched. Drive the actual crossterm event, not the binding string,
|
||||
/// so the dead-key bug can't come back unnoticed.
|
||||
#[test]
|
||||
fn real_shift_s_keypress_resolves_to_fast_forward_spawn() {
|
||||
use crossterm::event::{Event as CtEvent, KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
use crate::input::{canonicalise, Region};
|
||||
|
||||
let cfg = TuiConfig::load(&TuiSection::default()).unwrap();
|
||||
let ev = CtEvent::Key(KeyEvent::new(KeyCode::Char('S'), KeyModifiers::SHIFT));
|
||||
let key = canonicalise(&ev, |_, _| Region::Unknown).expect("key event canonicalises");
|
||||
|
||||
assert_eq!(
|
||||
cfg.keymap.resolve(Mode::Normal, &key.canonical()),
|
||||
Some(Action::FastForwardSpawn),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_chord_timeout_is_applied() {
|
||||
let mut section = TuiSection::default();
|
||||
|
||||
@@ -117,9 +117,7 @@ impl InputKey {
|
||||
if *alt {
|
||||
out.push_str("alt-");
|
||||
}
|
||||
// Uppercase already implies shift; crossterm sets the flag
|
||||
// on every such char, so skip the redundant prefix (#70).
|
||||
if *shift && !c.is_uppercase() {
|
||||
if *shift {
|
||||
out.push_str("shift-");
|
||||
}
|
||||
out.push(*c);
|
||||
@@ -582,28 +580,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn canonical_round_trips_simple_chars() {
|
||||
for s in ["a", "ctrl-a", "ctrl-w", "alt-x"] {
|
||||
for s in ["a", "ctrl-a", "ctrl-w", "alt-x", "shift-A"] {
|
||||
let k = InputKey::parse(s).unwrap();
|
||||
assert_eq!(k.canonical(), s, "round-trip {s:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// crossterm 0.28 attaches `KeyModifiers::SHIFT` to any uppercase char,
|
||||
/// so a real Shift+S delivers `Char { c: 'S', shift: true }`. Its
|
||||
/// canonical form must be the bare `"S"` a keymap binds, not `"shift-S"` —
|
||||
/// the uppercase letter already says "shifted"; a redundant prefix would
|
||||
/// make the default `S` binding unreachable from the keyboard (#70).
|
||||
#[test]
|
||||
fn shift_flag_is_redundant_for_an_already_uppercase_char() {
|
||||
let k = InputKey::Char {
|
||||
c: 'S',
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: true,
|
||||
};
|
||||
assert_eq!(k.canonical(), "S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonical_round_trips_specials() {
|
||||
for s in [
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
pnpmDeps = (pkgs.fetchPnpmDeps.override { pnpm = pkgs.pnpm_10; }) {
|
||||
inherit (finalAttrs) pname version src;
|
||||
fetcherVersion = 3;
|
||||
hash = "sha256-D9JdgI92vzAY8en2zGEEPd4hwOLVi3sr1joT4b17Be4=";
|
||||
hash = "sha256-Lhi41AtCK69SM4+7zih19+Njf7o2hyXrAV0W6VO9YxU=";
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
|
||||
Reference in New Issue
Block a user