refactor(pi-natives): restructured config handling with env_uint! macro and scope validation
- Extracted environment variable parsing into centralized `env_uint!` macro for consistent configuration handling across modules. - Refactored recursion logic to validate scope narrowing before recursing, introducing `recursion_narrows_scope()` and `MIN_RECURSE_SAVINGS` constant. - Consolidated static configuration initialization from LazyLock to declarative `env_uint!` macro in render and cache modules. - Improved control flow statement classification in defaults module by replacing positional candidates with named function-context candidates.
This commit is contained in:
@@ -4,28 +4,21 @@
|
||||
//! [`RawChunkCandidate`] values. They are also used by the default (shared)
|
||||
//! classification in [`super::defaults`].
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::types::ChunkNode;
|
||||
use crate::env_uint;
|
||||
|
||||
// ── Configuration (environment overrides) ────────────────────────────────
|
||||
//
|
||||
// - `PI_CHUNK_LEAF_THRESHOLD` – default `15`
|
||||
// - `PI_CHUNK_MAX_LINES` – default `25`
|
||||
|
||||
fn env_usize(name: &str, default: usize) -> usize {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(default)
|
||||
env_uint! {
|
||||
// Configured leaf threshold.
|
||||
pub static LEAF_THRESHOLD: usize = "PI_CHUNK_LEAF_THRESHOLD" or 15 => [1, usize::MAX];
|
||||
// Configured max chunk lines.
|
||||
pub static MAX_CHUNK_LINES: usize = "PI_CHUNK_MAX_LINES" or 25 => [1, usize::MAX];
|
||||
// Configured min recurse savings.
|
||||
pub static MIN_RECURSE_SAVINGS: usize = "PI_CHUNK_MIN_SAVINGS" or 5 => [1, usize::MAX];
|
||||
}
|
||||
|
||||
pub static LEAF_THRESHOLD: LazyLock<usize> =
|
||||
LazyLock::new(|| env_usize("PI_CHUNK_LEAF_THRESHOLD", 15));
|
||||
pub static MAX_CHUNK_LINES: LazyLock<usize> = LazyLock::new(|| env_usize("PI_CHUNK_MAX_LINES", 25));
|
||||
|
||||
// ── Internal types ───────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -48,6 +48,26 @@ pub fn classify_root_default<'tree>(node: Node<'tree>, source: &str) -> RawChunk
|
||||
group_candidate(node, "stmts", source)
|
||||
},
|
||||
|
||||
// ── Control flow (top-level scripts) ──
|
||||
"if_statement"
|
||||
| "unless"
|
||||
| "guard_statement"
|
||||
| "switch_statement"
|
||||
| "switch_expression"
|
||||
| "case_statement"
|
||||
| "expression_switch_statement"
|
||||
| "type_switch_statement"
|
||||
| "select_statement"
|
||||
| "try_statement"
|
||||
| "try_block"
|
||||
| "for_statement"
|
||||
| "for_in_statement"
|
||||
| "for_of_statement"
|
||||
| "foreach_statement"
|
||||
| "while_statement"
|
||||
| "do_statement"
|
||||
| "with_statement" => classify_function_default(node, source),
|
||||
|
||||
// ── Containers / namespaces / modules ──
|
||||
"class_declaration"
|
||||
| "class_definition"
|
||||
@@ -241,8 +261,11 @@ pub fn classify_function_default<'tree>(
|
||||
node: Node<'tree>,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
match node.kind() {
|
||||
"if_statement" | "unless" | "guard_statement" => positional_candidate(node, "if", source),
|
||||
"if_statement" | "unless" | "guard_statement" => {
|
||||
make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source)
|
||||
},
|
||||
"switch_statement"
|
||||
| "switch_expression"
|
||||
| "case_statement"
|
||||
@@ -251,29 +274,61 @@ pub fn classify_function_default<'tree>(
|
||||
| "type_switch_statement"
|
||||
| "select_statement"
|
||||
| "receive_statement"
|
||||
| "yul_switch_statement" => positional_candidate(node, "switch", source),
|
||||
| "yul_switch_statement" => make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"try_statement" | "try_block" | "catch_clause" | "finally_clause" | "assembly_statement" => {
|
||||
positional_candidate(node, "try", source)
|
||||
make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
)
|
||||
},
|
||||
"for_statement" | "for_in_statement" | "for_of_statement" => {
|
||||
if looks_like_python_statement(node, source) {
|
||||
positional_candidate(node, "loop", source)
|
||||
let name = if looks_like_python_statement(node, source) {
|
||||
"loop".to_string()
|
||||
} else {
|
||||
positional_candidate(node, "for", source)
|
||||
}
|
||||
sanitize_node_kind(node.kind())
|
||||
};
|
||||
make_candidate(node, name, NameStyle::Named, None, fn_recurse(), false, source)
|
||||
},
|
||||
"while_statement" => {
|
||||
if looks_like_python_statement(node, source) {
|
||||
positional_candidate(node, "loop", source)
|
||||
let name = if looks_like_python_statement(node, source) {
|
||||
"loop"
|
||||
} else {
|
||||
positional_candidate(node, "while", source)
|
||||
}
|
||||
"while"
|
||||
};
|
||||
make_candidate(node, name.to_string(), NameStyle::Named, None, fn_recurse(), false, source)
|
||||
},
|
||||
"do_statement" | "with_statement" | "do_block" | "subshell" | "async_block"
|
||||
| "unsafe_block" | "const_block" | "block_expression" => {
|
||||
positional_candidate(node, "block", source)
|
||||
},
|
||||
"foreach_statement" => positional_candidate(node, "for", source),
|
||||
| "unsafe_block" | "const_block" | "block_expression" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"foreach_statement" => make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"defer_statement" | "go_statement" | "send_statement" => {
|
||||
group_candidate(node, "stmts", source)
|
||||
},
|
||||
|
||||
@@ -299,7 +299,10 @@ fn build_chunk(
|
||||
let should_recurse = !candidate.error
|
||||
&& recurse.is_some()
|
||||
&& !should_collapse
|
||||
&& (candidate.force_recurse || line_count > *LEAF_THRESHOLD || recurse_parse_errors > 0);
|
||||
&& (candidate.force_recurse
|
||||
|| recurse_parse_errors > 0
|
||||
|| (line_count > *LEAF_THRESHOLD
|
||||
&& recursion_narrows_scope(line_count, &child_candidates)));
|
||||
let children = if should_recurse {
|
||||
child_candidates
|
||||
.into_iter()
|
||||
@@ -520,6 +523,22 @@ fn assign_unique_names(mut candidates: Vec<RawChunkCandidate<'_>>) -> Vec<RawChu
|
||||
|
||||
// ── Collapse heuristics ──────────────────────────────────────────────────
|
||||
|
||||
/// Returns `true` when splitting a parent into children actually provides
|
||||
/// meaningful scope narrowing. Recursion is only worthwhile if addressing
|
||||
/// the largest child saves at least `PI_CHUNK_MIN_SAVINGS` lines compared
|
||||
/// to addressing the parent directly.
|
||||
fn recursion_narrows_scope(parent_lines: usize, children: &[RawChunkCandidate<'_>]) -> bool {
|
||||
if children.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let max_child_lines = children
|
||||
.iter()
|
||||
.map(|c| line_span(c.range_start_line, c.range_end_line))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
parent_lines.saturating_sub(max_child_lines) >= *MIN_RECURSE_SAVINGS
|
||||
}
|
||||
|
||||
fn should_collapse_trivial_children(
|
||||
parent: &RawChunkCandidate<'_>,
|
||||
children: &[RawChunkCandidate<'_>],
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::chunk::types::{ChunkNode, ChunkTree, RenderChunkTreeParams, VisibleLineRange};
|
||||
use crate::{
|
||||
chunk::types::{ChunkNode, ChunkTree, RenderChunkTreeParams, VisibleLineRange},
|
||||
env_uint,
|
||||
};
|
||||
|
||||
type ChunkLookup<'a> = HashMap<&'a str, &'a ChunkNode>;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub static DEFAULT_FULL_DISPLAY_THRESHOLD: LazyLock<usize> =
|
||||
LazyLock::new(|| read_env_usize("PI_CHUNK_FULL_DISPLAY_THRESHOLD", 80));
|
||||
pub static DEFAULT_PREVIEW_HEAD_LINES: LazyLock<usize> =
|
||||
LazyLock::new(|| read_env_usize("PI_CHUNK_PREVIEW_HEAD_LINES", 20));
|
||||
pub static DEFAULT_PREVIEW_TAIL_LINES: LazyLock<usize> =
|
||||
LazyLock::new(|| read_env_usize("PI_CHUNK_PREVIEW_TAIL_LINES", 8));
|
||||
env_uint! {
|
||||
// Configured full display threshold.
|
||||
static FULL_DISPLAY_THRESHOLD: usize = "PI_CHUNK_FULL_DISPLAY_THRESHOLD" or 80 => [1, usize::MAX];
|
||||
// Configured preview head lines.
|
||||
static PREVIEW_HEAD_LINES: usize = "PI_CHUNK_PREVIEW_HEAD_LINES" or 20 => [1, usize::MAX];
|
||||
// Configured preview tail lines.
|
||||
static PREVIEW_TAIL_LINES: usize = "PI_CHUNK_PREVIEW_TAIL_LINES" or 8 => [1, usize::MAX];
|
||||
}
|
||||
|
||||
pub fn line_to_containing_chunk_path(tree: &ChunkTree, line: u32) -> Option<String> {
|
||||
line_to_containing_chunk(tree, line).map(|chunk| chunk.path.clone())
|
||||
@@ -23,9 +26,9 @@ pub fn render_chunk_tree(params: &RenderChunkTreeParams) -> String {
|
||||
return String::new();
|
||||
};
|
||||
let source_lines: Vec<&str> = params.source.split('\n').collect();
|
||||
let full_display_threshold = *DEFAULT_FULL_DISPLAY_THRESHOLD;
|
||||
let preview_head_lines = *DEFAULT_PREVIEW_HEAD_LINES;
|
||||
let preview_tail_lines = *DEFAULT_PREVIEW_TAIL_LINES;
|
||||
let full_display_threshold = *FULL_DISPLAY_THRESHOLD;
|
||||
let preview_head_lines = *PREVIEW_HEAD_LINES;
|
||||
let preview_tail_lines = *PREVIEW_TAIL_LINES;
|
||||
let tab_replacement = params.tab_replacement.as_deref().unwrap_or(" ");
|
||||
let num_width = compute_num_width(
|
||||
¶ms.tree,
|
||||
@@ -117,14 +120,6 @@ pub fn render_chunk_tree(params: &RenderChunkTreeParams) -> String {
|
||||
ctx.out
|
||||
}
|
||||
|
||||
fn read_env_usize(name: &str, default_value: usize) -> usize {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(default_value)
|
||||
}
|
||||
|
||||
fn build_lookup(tree: &ChunkTree) -> ChunkLookup<'_> {
|
||||
tree
|
||||
.chunks
|
||||
|
||||
@@ -22,7 +22,7 @@ use ignore::WalkBuilder;
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::napi;
|
||||
|
||||
use crate::task;
|
||||
use crate::{env_uint, task};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Public types (re-exported by glob for backward compatibility)
|
||||
@@ -57,36 +57,25 @@ pub struct GlobMatch {
|
||||
// Cache policy
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const DEFAULT_CACHE_TTL_MS: u64 = 1_000;
|
||||
const DEFAULT_EMPTY_RECHECK_MS: u64 = 200;
|
||||
const DEFAULT_MAX_CACHE_ENTRIES: usize = 16;
|
||||
|
||||
fn env_u64(name: &str, default: u64) -> u64 {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(default)
|
||||
env_uint! {
|
||||
// Configured cache TTL in milliseconds.
|
||||
static CACHE_TTL_MS: u64 = "FS_SCAN_CACHE_TTL_MS" or 1_000 => [0, u64::MAX];
|
||||
// Configured empty-result recheck threshold in milliseconds.
|
||||
static EMPTY_RECHECK_MS: u64 = "FS_SCAN_EMPTY_RECHECK_MS" or 200 => [0, u64::MAX];
|
||||
// Configured maximum number of cache entries.
|
||||
static MAX_CACHE_ENTRIES: usize = "FS_SCAN_CACHE_MAX_ENTRIES" or 16 => [0, usize::MAX];
|
||||
}
|
||||
|
||||
fn env_usize(name: &str, default: usize) -> usize {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
/// Configured cache TTL in milliseconds.
|
||||
pub fn cache_ttl_ms() -> u64 {
|
||||
env_u64("FS_SCAN_CACHE_TTL_MS", DEFAULT_CACHE_TTL_MS)
|
||||
*CACHE_TTL_MS
|
||||
}
|
||||
|
||||
/// Configured empty-result recheck threshold in milliseconds.
|
||||
pub fn empty_recheck_ms() -> u64 {
|
||||
env_u64("FS_SCAN_EMPTY_RECHECK_MS", DEFAULT_EMPTY_RECHECK_MS)
|
||||
*EMPTY_RECHECK_MS
|
||||
}
|
||||
|
||||
fn max_cache_entries() -> usize {
|
||||
env_usize("FS_SCAN_CACHE_MAX_ENTRIES", DEFAULT_MAX_CACHE_ENTRIES)
|
||||
pub fn max_cache_entries() -> usize {
|
||||
*MAX_CACHE_ENTRIES
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -117,8 +106,7 @@ pub struct ScanResult {
|
||||
}
|
||||
|
||||
fn evict_oldest() {
|
||||
let max = max_cache_entries();
|
||||
if FS_CACHE.len() > max
|
||||
if FS_CACHE.len() > *MAX_CACHE_ENTRIES
|
||||
&& let Some(oldest_key) = FS_CACHE
|
||||
.iter()
|
||||
.min_by_key(|entry| entry.value().created_at)
|
||||
@@ -295,7 +283,7 @@ pub fn get_or_scan(
|
||||
use_gitignore: bool,
|
||||
ct: &task::CancelToken,
|
||||
) -> Result<ScanResult> {
|
||||
let ttl = cache_ttl_ms();
|
||||
let ttl = *CACHE_TTL_MS;
|
||||
if ttl == 0 {
|
||||
// Caching disabled – always scan fresh.
|
||||
let entries = collect_entries(root, include_hidden, use_gitignore, ct)?;
|
||||
|
||||
@@ -1,4 +1,35 @@
|
||||
/// Saturating cast from `u64` to `u32`, clamping at [`u32::MAX`].
|
||||
pub fn clamp_u32(value: u64) -> u32 {
|
||||
value.min(u32::MAX as u64) as u32
|
||||
#[macro_export]
|
||||
macro_rules! env_uint {
|
||||
// With clamp range: ... => [$min, $max];
|
||||
($( $vis:vis static $name:ident : $type:ty = $env:literal or $default:expr => [$min:expr, $max:expr];)*) => {
|
||||
$(
|
||||
$vis static $name: std::sync::LazyLock<$type> = std::sync::LazyLock::new(|| {
|
||||
std::env::var($env)
|
||||
.ok()
|
||||
.and_then(|v| std::str::FromStr::from_str(&v).ok())
|
||||
.unwrap_or($default)
|
||||
.clamp($min, $max)
|
||||
});
|
||||
)*
|
||||
};
|
||||
// Without clamp range: ...; (no => [])
|
||||
($( $vis:vis static $name:ident : $type:ty = $env:literal or $default:expr;)*) => {
|
||||
$(
|
||||
$vis static $name: std::sync::LazyLock<$type> = std::sync::LazyLock::new(|| {
|
||||
std::env::var($env)
|
||||
.ok()
|
||||
.and_then(|v| std::str::FromStr::from_str(&v).ok())
|
||||
.unwrap_or($default)
|
||||
});
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
/// Saturating cast from `u64` to `u32`, clamping at [`u32::MAX`].
|
||||
pub const fn clamp_u32(value: u64) -> u32 {
|
||||
if value > u32::MAX as u64 {
|
||||
u32::MAX
|
||||
} else {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user