fix(core): cap incremental scan in get_line() to prevent O(N) blocking
Add SCAN_AHEAD_LIMIT (10000 lines) to get_line() in Sampling state. Without this, jumping to end-of-file (G) during progressive loading would scan the entire file byte-by-byte on the main thread, blocking the UI and consuming excessive memory. Lines beyond the scanned region + limit now return None, which the TUI renders as empty. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -389,6 +389,11 @@ pub fn spawn_visual_height_rebuild(
|
||||
/// Maximum bytes to scan during initial open for the Sampling state.
|
||||
const INITIAL_SCAN_BYTES: usize = 64 * 1024;
|
||||
|
||||
/// Maximum number of additional lines to scan beyond what's already cached
|
||||
/// in a single `get_line()` call. Prevents O(N) blocking when the user
|
||||
/// jumps far ahead (e.g. `G` to end-of-file) during the Loading state.
|
||||
const SCAN_AHEAD_LIMIT: usize = 10_000;
|
||||
|
||||
pub struct ProgressiveFileReader {
|
||||
path: PathBuf,
|
||||
pub state: ReaderState,
|
||||
@@ -512,32 +517,23 @@ impl ProgressiveFileReader {
|
||||
let mut newlines = scanned_newlines.borrow_mut();
|
||||
let mut up_to = scanned_up_to.borrow_mut();
|
||||
|
||||
// We need `idx + 1` newlines to have `idx` lines available.
|
||||
// Line i starts after the i-th newline (or at byte 0 for line 0)
|
||||
// and ends at the (i+1)-th newline (or end of file).
|
||||
// So to return line `idx`, we need at least `idx + 1` newline positions
|
||||
// (the idx-th newline marks end of line idx-1/start of line idx,
|
||||
// and the (idx+1)-th newline marks end of line idx).
|
||||
// Actually: line 0 starts at byte 0, ends at newline[0].
|
||||
// line 1 starts at newline[0]+1, ends at newline[1].
|
||||
// line i starts at newline[i-1]+1, ends at newline[i].
|
||||
// So to serve line idx, we need newline positions up to index idx.
|
||||
let scan_limit = newlines.len() + SCAN_AHEAD_LIMIT;
|
||||
|
||||
// Extend scan if needed
|
||||
while newlines.len() <= idx && *up_to < mmap_data.len() {
|
||||
// Extend scan if needed, but stop at scan_limit to avoid O(N) blocking
|
||||
while newlines.len() <= idx
|
||||
&& newlines.len() < scan_limit
|
||||
&& *up_to < mmap_data.len()
|
||||
{
|
||||
let remaining = &mmap_data[*up_to..];
|
||||
if let Some(rel_pos) = memchr::memchr(b'\n', remaining) {
|
||||
newlines.push(*up_to + rel_pos);
|
||||
*up_to += rel_pos + 1;
|
||||
} else {
|
||||
// No more newlines; rest of file is the last line
|
||||
*up_to = mmap_data.len();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if line idx is beyond what's available
|
||||
// If idx == newlines.len(), it's the last line (after last newline, no trailing \n)
|
||||
if idx > newlines.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user