From f6006f5c44dbeb09b79fd1ef11abd22863a9e71a Mon Sep 17 00:00:00 2001 From: 141CJ <141cj+prod@proton.me> Date: Mon, 21 Sep 2026 16:48:02 -0400 Subject: [PATCH 1/6] perf!: only count lines of code if current directory is a git repository --- src/render.rs | 7 ++- src/telemetry.rs | 146 +++++++++++++++++++++++++---------------------- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/src/render.rs b/src/render.rs index f63374c..0b641f4 100644 --- a/src/render.rs +++ b/src/render.rs @@ -826,7 +826,12 @@ pub fn render_full( // grid math: left cell 38 (16 prefix + 22 value) + 2 gutter + // right cell 35 (16 prefix + 19 value) = 75. every variable value is // truncated into its slot, so long counts/names can never overflow. - let loc_val = pad_to_visible(&format!("{} lines", telem.total_lines), 22); + let loc_val = if git.is_repo { + pad_to_visible(&format!("{} lines", telem.total_lines), 22) + } else { + pad_to_visible("line count unavailable", 22) + }; + let loc_cell = format!( " {}\u{f121}{} {} {}{}{}", p.gauge_fill, diff --git a/src/telemetry.rs b/src/telemetry.rs index 8ff2a3a..c04667e 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use std::fs; use std::io::{BufRead, BufReader}; -use std::path::Path; +use std::path::{Path, PathBuf}; use ignore::WalkBuilder; +use crate::git; + #[derive(Debug, Clone, PartialEq, Default)] pub struct ProjectTelemetry { pub total_lines: usize, @@ -68,88 +70,94 @@ const MAX_COUNT_BYTES: u64 = 1024 * 1024; // walk workspace respecting .gitignore using ignore crate, counting lines of code pub fn count_lines_of_code(root: &Path) -> (usize, Option<(String, f32)>) { - let mut lang_counts: HashMap<&'static str, usize> = HashMap::new(); - let mut total_lines = 0; - - let walker = WalkBuilder::new(root) - .standard_filters(true) - .hidden(true) - .build(); + let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + let git_state = git::detect_git_state(&cwd).unwrap_or_default(); - for entry in walker.filter_map(Result::ok) { - if !entry.file_type().is_some_and(|ft| ft.is_file()) { - continue; - } + if git_state.is_repo { + let mut lang_counts: HashMap<&'static str, usize> = HashMap::new(); + let mut total_lines = 0; - let path = entry.path(); - let ext = path - .extension() - .and_then(|s| s.to_str()) - .unwrap_or("") - .to_lowercase(); + let walker = WalkBuilder::new(root) + .standard_filters(true) + .hidden(true) + .build(); - // unclassified extensions (binaries, media, archives) are skipped - // without opening: counting their bytes as code would be dishonest, - // and reading them is what made big directories slow. - let lang = extension_to_lang(&ext); - if lang == "other" { - continue; - } + for entry in walker.filter_map(Result::ok) { + if !entry.file_type().is_some_and(|ft| ft.is_file()) { + continue; + } - // cheap size gate before paying for a full read - if entry - .metadata() - .map(|m| m.len() > MAX_COUNT_BYTES) - .unwrap_or(true) - { - continue; - } + let path = entry.path(); + let ext = path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("") + .to_lowercase(); - let lang = extension_to_lang(&ext); + // unclassified extensions (binaries, media, archives) are skipped + // without opening: counting their bytes as code would be dishonest, + // and reading them is what made big directories slow. + let lang = extension_to_lang(&ext); + if lang == "other" { + continue; + } - if let Ok(file) = fs::File::open(path) { - let reader = BufReader::new(file); - let lines = reader.lines().count(); - if lines > 0 { - total_lines += lines; - *lang_counts.entry(lang).or_insert(0) += lines; + // cheap size gate before paying for a full read + if entry + .metadata() + .map(|m| m.len() > MAX_COUNT_BYTES) + .unwrap_or(true) + { + continue; } - } - } - if total_lines == 0 { - return (0, None); - } + let lang = extension_to_lang(&ext); - // top primary code language (ignoring config and markdown if code exists) - let mut top_code_lang: Option<(&'static str, usize)> = None; + if let Ok(file) = fs::File::open(path) { + let reader = BufReader::new(file); + let lines = reader.lines().count(); + if lines > 0 { + total_lines += lines; + *lang_counts.entry(lang).or_insert(0) += lines; + } + } - for (&lang, &count) in &lang_counts { - if lang != "markdown" - && lang != "config" - && lang != "other" - && top_code_lang.is_none_or(|(_, max)| count > max) - { - top_code_lang = Some((lang, count)); + if total_lines == 0 { + return (0, None); + } } - } - let top_lang = match top_code_lang { - Some((lang, count)) => { - let pct = (count as f32 / total_lines as f32) * 100.0; - Some((lang.to_string(), pct)) - } - _ => { - // fall back to overall top language - let overall = lang_counts.iter().max_by_key(|&(_, &count)| count); - overall.map(|(&lang, &count)| { - let pct = (count as f32 / total_lines as f32) * 100.0; - (lang.to_string(), pct) - }) + // top primary code language (ignoring config and markdown if code exists) + let mut top_code_lang: Option<(&'static str, usize)> = None; + + for (&lang, &count) in &lang_counts { + if lang != "markdown" + && lang != "config" + && lang != "other" + && top_code_lang.is_none_or(|(_, max)| count > max) + { + top_code_lang = Some((lang, count)); + } } - }; - (total_lines, top_lang) + let top_lang = match top_code_lang { + Some((lang, count)) => { + let pct = (count as f32 / total_lines as f32) * 100.0; + Some((lang.to_string(), pct)) + } + _ => { + // fall back to overall top language + let overall = lang_counts.iter().max_by_key(|&(_, &count)| count); + overall.map(|(&lang, &count)| { + let pct = (count as f32 / total_lines as f32) * 100.0; + (lang.to_string(), pct) + }) + } + }; + (total_lines, top_lang) + } else { + (0, None) + } } // detect real coverage report if present in standard locations From c41ce74c5d8e0c8bc8cb26eefe3d5dcc6060cfce Mon Sep 17 00:00:00 2001 From: 141CJ <141cj+prod@proton.me> Date: Mon, 21 Sep 2026 16:58:03 -0400 Subject: [PATCH 2/6] feat: add --force-count-lines argument for line counting in non-git directories --- src/main.rs | 13 +++++++++++-- src/render.rs | 3 ++- src/telemetry.rs | 8 ++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd59777..d005f95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,9 @@ struct Cli { #[arg(long)] full: bool, + #[arg(long, short)] + force_count_lines: bool, + #[command(subcommand)] command: Option, } @@ -45,8 +48,14 @@ fn main() -> Result<()> { if cli.full { let sys_telem = system::collect_system_telemetry(); - let proj_telem = telemetry::collect_project_telemetry(&cwd); - let output = render::render_full(&git_state, &sys_telem, &proj_telem, &palette); + let proj_telem = telemetry::collect_project_telemetry(cli.force_count_lines, &cwd); + let output = render::render_full( + cli.force_count_lines, + &git_state, + &sys_telem, + &proj_telem, + &palette, + ); print!("{}", output); } else { let top_lang = telemetry::detect_project_language(&cwd); diff --git a/src/render.rs b/src/render.rs index 0b641f4..6cf1638 100644 --- a/src/render.rs +++ b/src/render.rs @@ -567,6 +567,7 @@ fn sys_cell( // Full Mode (--full, 79 total width) pub fn render_full( + force_count_lines: bool, git: &GitState, sys: &SystemTelemetry, telem: &ProjectTelemetry, @@ -826,7 +827,7 @@ pub fn render_full( // grid math: left cell 38 (16 prefix + 22 value) + 2 gutter + // right cell 35 (16 prefix + 19 value) = 75. every variable value is // truncated into its slot, so long counts/names can never overflow. - let loc_val = if git.is_repo { + let loc_val = if git.is_repo || force_count_lines { pad_to_visible(&format!("{} lines", telem.total_lines), 22) } else { pad_to_visible("line count unavailable", 22) diff --git a/src/telemetry.rs b/src/telemetry.rs index c04667e..7fdc565 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -69,11 +69,11 @@ fn extension_to_lang(ext: &str) -> &'static str { const MAX_COUNT_BYTES: u64 = 1024 * 1024; // walk workspace respecting .gitignore using ignore crate, counting lines of code -pub fn count_lines_of_code(root: &Path) -> (usize, Option<(String, f32)>) { +pub fn count_lines_of_code(force_count_lines: bool, root: &Path) -> (usize, Option<(String, f32)>) { let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let git_state = git::detect_git_state(&cwd).unwrap_or_default(); - if git_state.is_repo { + if git_state.is_repo || force_count_lines { let mut lang_counts: HashMap<&'static str, usize> = HashMap::new(); let mut total_lines = 0; @@ -327,8 +327,8 @@ pub fn detect_project_language(root: &Path) -> Option { None } -pub fn collect_project_telemetry(root: &Path) -> ProjectTelemetry { - let (total_lines, top_language) = count_lines_of_code(root); +pub fn collect_project_telemetry(force_count_lines: bool, root: &Path) -> ProjectTelemetry { + let (total_lines, top_language) = count_lines_of_code(force_count_lines, root); let coverage_info = detect_coverage(root); ProjectTelemetry { From f854b5c86ee8805975cdb302b78c0f084539ec91 Mon Sep 17 00:00:00 2001 From: 141CJ <141cj+prod@proton.me> Date: Mon, 21 Sep 2026 17:01:36 -0400 Subject: [PATCH 3/6] fix: pass missing argument to render_full in test_all_mascot_reactions_render --- tests/integration_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3b3b8d6..f76c756 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -315,7 +315,7 @@ fn test_all_mascot_reactions_render() { } // full mode drives the same face from the same state - let full = render_full(git, &sys, &telem, &palette); + let full = render_full(true, git, &sys, &telem, &palette); assert!( full.contains(word), "full mode missing chip word '{}'", From 65ff494742364de8e7612177abad91de0a7179ed Mon Sep 17 00:00:00 2001 From: 141CJ <141cj+prod@proton.me> Date: Mon, 21 Sep 2026 17:08:24 -0400 Subject: [PATCH 4/6] docs: add info message for --force-count-lines argument --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index d005f95..8074f71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ struct Cli { #[arg(long)] full: bool, + /// counts lines if in non-git directory #[arg(long, short)] force_count_lines: bool, From c6d33284a54d201a373a8b22f52c9e24ad56f7bc Mon Sep 17 00:00:00 2001 From: 141CJ <141cj+prod@proton.me> Date: Mon, 21 Sep 2026 17:25:42 -0400 Subject: [PATCH 5/6] fix: prevent early return if line count is 0 --- src/telemetry.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/telemetry.rs b/src/telemetry.rs index 7fdc565..cf5fee6 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -70,8 +70,7 @@ const MAX_COUNT_BYTES: u64 = 1024 * 1024; // walk workspace respecting .gitignore using ignore crate, counting lines of code pub fn count_lines_of_code(force_count_lines: bool, root: &Path) -> (usize, Option<(String, f32)>) { - let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - let git_state = git::detect_git_state(&cwd).unwrap_or_default(); + let git_state = git::detect_git_state(root).unwrap_or_default(); if git_state.is_repo || force_count_lines { let mut lang_counts: HashMap<&'static str, usize> = HashMap::new(); @@ -111,8 +110,6 @@ pub fn count_lines_of_code(force_count_lines: bool, root: &Path) -> (usize, Opti continue; } - let lang = extension_to_lang(&ext); - if let Ok(file) = fs::File::open(path) { let reader = BufReader::new(file); let lines = reader.lines().count(); @@ -121,10 +118,10 @@ pub fn count_lines_of_code(force_count_lines: bool, root: &Path) -> (usize, Opti *lang_counts.entry(lang).or_insert(0) += lines; } } + } - if total_lines == 0 { - return (0, None); - } + if total_lines == 0 { + return (0, None); } // top primary code language (ignoring config and markdown if code exists) From c322b1a891a304374c11a0be47a6b2279a24f80a Mon Sep 17 00:00:00 2001 From: 141CJ <141cj+prod@proton.me> Date: Mon, 21 Sep 2026 17:53:13 -0400 Subject: [PATCH 6/6] fix: remove unused import --- src/telemetry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/telemetry.rs b/src/telemetry.rs index cf5fee6..e30ce35 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fs; use std::io::{BufRead, BufReader}; -use std::path::{Path, PathBuf}; +use std::path::Path; use ignore::WalkBuilder;