use crate::rank::RankedEntry;
use crate::render::{TreeCache, TreeContextCache, to_tree};
use crate::tokens::TokenCounter;
use std::collections::HashSet;
pub fn binary_search_budget(
ranked_tags: &[RankedEntry],
max_map_tokens: usize,
chat_rel_fnames: &HashSet<String>,
max_line_length: usize,
token_counter: &TokenCounter,
tree_cache: &mut TreeCache,
tree_context_cache: &mut TreeContextCache,
) -> Option<String> {
let n = ranked_tags.len();
if n == 0 {
return None;
}
let mut lower_bound = 0usize;
let mut upper_bound = n;
let mut best_tree: Option<String> = None;
let mut best_tree_tokens = 0usize;
let mut middle = (max_map_tokens / 25).min(n);
while lower_bound <= upper_bound {
let current_tree = to_tree(
&ranked_tags[..middle],
chat_rel_fnames,
max_line_length,
tree_cache,
tree_context_cache,
);
let num_tokens = token_counter.count(¤t_tree);
let pct_err = if max_map_tokens > 0 {
(num_tokens as f64 - max_map_tokens as f64).abs() / max_map_tokens as f64
} else {
1.0
};
let is_under_budget = num_tokens <= max_map_tokens;
let is_better = num_tokens > best_tree_tokens;
let is_close_enough = pct_err < 0.15;
if (is_under_budget && is_better) || is_close_enough {
best_tree = Some(current_tree);
best_tree_tokens = num_tokens;
if is_close_enough {
break;
}
}
if num_tokens < max_map_tokens {
lower_bound = middle + 1;
} else {
if middle == 0 {
break;
}
upper_bound = middle - 1;
}
middle = (lower_bound + upper_bound) / 2;
if middle == 0 && lower_bound > upper_bound {
break;
}
}
best_tree
}
pub fn compute_effective_max(
max_map_tokens: usize,
chat_fnames_empty: bool,
max_context_window: Option<usize>,
map_mul_no_files: usize,
) -> usize {
if !chat_fnames_empty {
return max_map_tokens;
}
match max_context_window {
Some(window) if window > 4096 => {
let expanded = max_map_tokens.saturating_mul(map_mul_no_files);
let capped = window.saturating_sub(4096);
expanded.min(capped)
}
_ => max_map_tokens,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_bare(rel: &str) -> RankedEntry {
RankedEntry::Bare {
rel_fname: rel.to_string(),
score: 0.0,
}
}
#[test]
fn binary_search_empty() {
let counter = TokenCounter::new();
let mut tc = TreeCache::new();
let mut tcc = TreeContextCache::new();
let result =
binary_search_budget(&[], 1000, &HashSet::new(), 100, &counter, &mut tc, &mut tcc);
assert!(result.is_none());
}
#[test]
fn binary_search_small_budget() {
let entries: Vec<RankedEntry> = (0..100)
.map(|i| make_bare(&format!("file{}.rs", i)))
.collect();
let counter = TokenCounter::new();
let mut tc = TreeCache::new();
let mut tcc = TreeContextCache::new();
let result = binary_search_budget(
&entries,
50, &HashSet::new(),
100,
&counter,
&mut tc,
&mut tcc,
);
if let Some(tree) = result {
let tokens = counter.count(&tree);
assert!(tokens <= 50 || (tokens as f64 / 50.0) < 1.15);
}
}
#[test]
fn effective_max_with_chat() {
let result = compute_effective_max(1024, false, Some(100000), 8);
assert_eq!(result, 1024);
}
#[test]
fn effective_max_no_chat() {
let result = compute_effective_max(1024, true, Some(100000), 8);
assert_eq!(result, 1024 * 8); }
#[test]
fn effective_max_capped_by_window() {
let result = compute_effective_max(10000, true, Some(10000), 8);
assert_eq!(result, 10000 - 4096); }
#[test]
fn effective_max_no_window() {
let result = compute_effective_max(1024, true, None, 8);
assert_eq!(result, 1024);
}
}