From 9b0930b4b8eb7aaf731fe3ece8e73e465d58413f Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Mon, 20 Apr 2026 23:15:12 -0500 Subject: [PATCH 1/4] Support collection line format --- src/cli/handlers.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 4 ++++ 2 files changed, 46 insertions(+) diff --git a/src/cli/handlers.rs b/src/cli/handlers.rs index 1d76ac5..b301818 100644 --- a/src/cli/handlers.rs +++ b/src/cli/handlers.rs @@ -2160,6 +2160,26 @@ fn handle_collection_list(cli: &Cli, mode: &OutputMode, args: &CollectionListArg if let Some(ref bookmark_id) = args.bookmark { let bm = find_bookmark(&db, bookmark_id)?; let collections = db.list_collections_for_bookmark(&bm.id)?; + + // Custom line format for bookmark's collections + if let Some(ref template) = args.line_format { + let mut stdout = io::stdout().lock(); + for c in &collections { + let short_id = output::short_id(&c.id); + let line = template + .replace("{ID}", short_id) + .replace("{id}", short_id) + .replace("{NAME}", &c.name) + .replace("{name}", &c.name) + .replace("{DESCRIPTION}", c.description.as_deref().unwrap_or("")) + .replace("{description}", c.description.as_deref().unwrap_or("")) + .replace("{CREATED}", &c.created_at) + .replace("{created}", &c.created_at); + writeln!(stdout, "{line}")?; + } + return Ok(()); + } + match mode { OutputMode::Json => write_json_success(&collections)?, OutputMode::Table => { @@ -2183,6 +2203,28 @@ fn handle_collection_list(cli: &Cli, mode: &OutputMode, args: &CollectionListArg } } else { let collections = db.list_collections()?; + + // Custom line format for collections with counts + if let Some(ref template) = args.line_format { + let mut stdout = io::stdout().lock(); + for (c, count) in &collections { + let short_id = output::short_id(&c.id); + let line = template + .replace("{ID}", short_id) + .replace("{id}", short_id) + .replace("{NAME}", &c.name) + .replace("{name}", &c.name) + .replace("{COUNT}", &count.to_string()) + .replace("{count}", &count.to_string()) + .replace("{DESCRIPTION}", c.description.as_deref().unwrap_or("")) + .replace("{description}", c.description.as_deref().unwrap_or("")) + .replace("{CREATED}", &c.created_at) + .replace("{created}", &c.created_at); + writeln!(stdout, "{line}")?; + } + return Ok(()); + } + match mode { OutputMode::Json => write_json_success(&collections)?, OutputMode::Table => { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 5b3e7b6..fa8ca7a 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -492,6 +492,10 @@ pub struct CollectionListArgs { /// List collections containing this bookmark #[arg(long)] pub bookmark: Option, + + /// Custom line format template (placeholders: {ID}, {NAME}, {COUNT}, {DESCRIPTION}, {CREATED}) + #[arg(long)] + pub line_format: Option, } #[derive(Debug, clap::Args)] From 89e16bfe0e8de5d0d54f5dda4445ad02d57c6281 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Mon, 20 Apr 2026 23:18:21 -0500 Subject: [PATCH 2/4] Update tv channel --- extras/television-integration/codemark-collections.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extras/television-integration/codemark-collections.toml b/extras/television-integration/codemark-collections.toml index fd19c30..2395c91 100644 --- a/extras/television-integration/codemark-collections.toml +++ b/extras/television-integration/codemark-collections.toml @@ -4,12 +4,12 @@ description = "Codemark collections picker - select a collection to view its boo requirements = ["codemark"] [source] -command = "cd \"$(dirname $(git rev-parse --path-format=absolute --git-common-dir))\" && codemark collection list --format tv --db .codemark/codemark.db" +command = "codemark collection list --line-format \"{NAME}\t{COUNT}\t{DESCRIPTION}\" --db .codemark/codemark.db" display = "{split:\t:0} ({split:\t:1} bookmarks) {split:\t:2}" output = "{split:\t:0}" [preview] -command = "cd \"$(dirname $(git rev-parse --path-format=absolute --git-common-dir))\" && codemark collection show {split:\t:0} --format table --db .codemark/codemark.db" +command = "codemark collection show {split:\t:0} --format table --db .codemark/codemark.db" offset = "0" [ui.preview_panel] @@ -21,5 +21,5 @@ enter = "actions:show" [actions.show] description = "Show bookmarks in this collection (opens television with codemark channel)" -command = "sh -c 'cd \"$(dirname $(git rev-parse --path-format=absolute --git-common-dir))\" && tv --source-command=\"codemark list --format tv --collection $1 --db .codemark/codemark.db\" codemark' placeholder {split:\t:0}" +command = "sh -c 'tv --source-command=\"codemark list --format tv --collection $1 --db .codemark/codemark.db\" codemark' placeholder {split:\t:0}" mode = "execute" From 59abd043a6d289b3704ace78df278c2ee9fedc66 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Mon, 20 Apr 2026 23:35:57 -0500 Subject: [PATCH 3/4] Lint --- src/config.rs | 11 +++++---- src/engine/resolution.rs | 8 +++--- src/git/context.rs | 2 +- src/query/generator.rs | 53 +++++++++++++++------------------------- tests/cli_integration.rs | 20 +++++++-------- 5 files changed, 40 insertions(+), 54 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9a16484..218cad2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -140,6 +140,7 @@ impl StorageConfig { impl HealthConfig { /// Get the auto-archive days threshold (default: 7). + #[allow(dead_code)] pub fn auto_archive_days(&self) -> u32 { self.auto_archive_after_days.unwrap_or(7) } @@ -421,7 +422,7 @@ mod tests { let config = Config::default(); assert_eq!(config.storage.max_resolutions(), 20); assert_eq!(config.health.auto_archive_days(), 7); - assert_eq!(config.semantic.is_enabled(), true); + assert!(config.semantic.is_enabled()); } #[test] @@ -607,7 +608,7 @@ gui = ["mygui", "code"] // Verify the config can be loaded let config = Config::load(&tmp); assert_eq!(config.storage.max_resolutions(), 20); - assert_eq!(config.semantic.is_enabled(), true); + assert!(config.semantic.is_enabled()); // Second call should not overwrite let created_again = Config::init_default(&tmp).unwrap(); @@ -717,7 +718,7 @@ auto_archive_after_days = 14 // Global values preserved assert_eq!(global.storage.max_resolutions(), 15); - assert_eq!(global.semantic.is_enabled(), true); + assert!(global.semantic.is_enabled()); // Local value applied assert_eq!(global.health.auto_archive_days(), 14); @@ -749,7 +750,7 @@ enabled = true // Local values (equal to defaults) should win over global assert_eq!(global.storage.max_resolutions(), 20); - assert_eq!(global.semantic.is_enabled(), true); + assert!(global.semantic.is_enabled()); } #[test] @@ -760,7 +761,7 @@ enabled = true .expect("Default config template should parse correctly"); assert_eq!(config.storage.max_resolutions(), 20); assert_eq!(config.health.auto_archive_days(), 7); - assert_eq!(config.semantic.is_enabled(), true); + assert!(config.semantic.is_enabled()); assert_eq!(config.open.default, Some("vim +{LINE_START} {FILE}".to_string())); assert!(config.open.extensions.contains_key("rs")); assert!(config.open.extensions.contains_key("swift")); diff --git a/src/engine/resolution.rs b/src/engine/resolution.rs index 2f0186c..7021854 100644 --- a/src/engine/resolution.rs +++ b/src/engine/resolution.rs @@ -235,13 +235,11 @@ mod tests { fn find_function_range(tree: &tree_sitter::Tree, source: &str, name: &str) -> (usize, usize) { fn search(node: tree_sitter::Node, source: &str, name: &str) -> Option<(usize, usize)> { - if node.kind() == "function_declaration" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if node.kind() == "function_declaration" + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { diff --git a/src/git/context.rs b/src/git/context.rs index 5171a91..a601632 100644 --- a/src/git/context.rs +++ b/src/git/context.rs @@ -391,7 +391,7 @@ mod tests { fn is_ancestor_true() { // HEAD~1 should be an ancestor of HEAD in a repo with commits let ctx = detect_context(Path::new(".")).unwrap(); - let head = ctx.head_commit.unwrap(); + let _head = ctx.head_commit.unwrap(); // Create a temp repo with known ancestry let tmp = std::env::temp_dir().join("codemark_test_ancestor_true"); diff --git a/src/query/generator.rs b/src/query/generator.rs index 9bcc5ad..228c5b1 100644 --- a/src/query/generator.rs +++ b/src/query/generator.rs @@ -526,14 +526,13 @@ mod tests { fn find_function_byte_range(tree: &Tree, source: &str, func_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { - if node.kind() == "function_declaration" { - if let Some(name_node) = node.child_by_field_name("name") { + if node.kind() == "function_declaration" + && let Some(name_node) = node.child_by_field_name("name") { let text = &source[name_node.byte_range()]; if text == name { return Some((node.start_byte(), node.end_byte())); } } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -651,13 +650,11 @@ mod tests { fn find_rust_function_byte_range(tree: &Tree, source: &str, func_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { - if node.kind() == "function_item" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if node.kind() == "function_item" + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -764,13 +761,11 @@ mod tests { fn find_ts_function_byte_range(tree: &Tree, source: &str, func_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { let kind = node.kind(); - if kind == "function_declaration" || kind == "method_definition" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if (kind == "function_declaration" || kind == "method_definition") + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -834,13 +829,11 @@ mod tests { fn find_py_function_byte_range(tree: &Tree, source: &str, func_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { let kind = node.kind(); - if kind == "function_definition" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if kind == "function_definition" + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -917,13 +910,11 @@ mod tests { fn find_go_function_range(tree: &Tree, source: &str, func_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { let kind = node.kind(); - if kind == "function_declaration" || kind == "method_declaration" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if (kind == "function_declaration" || kind == "method_declaration") + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { @@ -979,13 +970,11 @@ mod tests { fn find_java_method_range(tree: &Tree, source: &str, method_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { - if node.kind() == "method_declaration" || node.kind() == "constructor_declaration" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if (node.kind() == "method_declaration" || node.kind() == "constructor_declaration") + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { @@ -1042,13 +1031,11 @@ mod tests { fn find_csharp_method_range(tree: &Tree, source: &str, method_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { - if node.kind() == "method_declaration" { - if let Some(name_node) = node.child_by_field_name("name") { - if &source[name_node.byte_range()] == name { + if node.kind() == "method_declaration" + && let Some(name_node) = node.child_by_field_name("name") + && &source[name_node.byte_range()] == name { return Some((node.start_byte(), node.end_byte())); } - } - } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 3323793..eeb13aa 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -110,7 +110,7 @@ impl Codemark { let tree = obj.peel_to_tree().unwrap(); // Checkout the tree with force - repo.checkout_tree(&tree.as_object(), Some(git2::build::CheckoutBuilder::new().force())) + repo.checkout_tree(tree.as_object(), Some(git2::build::CheckoutBuilder::new().force())) .unwrap(); // Update HEAD to point to the commit (detached HEAD state) @@ -1416,7 +1416,7 @@ fn git_repo_heal_force_bypasses_ancestry_check() { // Create initial file and bookmark let commit_a = cm.commit("test.rs", "fn original() {}", "Commit A"); let json = cm.run_json(&["add", "--file", &cm.file_path("test.rs"), "--range", "1"]); - let id = json["data"]["id"].as_str().unwrap().to_string(); + let _id = json["data"]["id"].as_str().unwrap().to_string(); cm.run_json(&["heal"]); // Make more commits and heal again @@ -1512,7 +1512,7 @@ fn git_repo_move_method_then_heal_gets_new_resolution() { let cm = Codemark::with_git_repo(); // Initial file with function at line 1 - let commit_a = cm.commit("test.rs", "fn my_function() {}\nfn other() {}", "Commit A"); + let _commit_a = cm.commit("test.rs", "fn my_function() {}\nfn other() {}", "Commit A"); // Create bookmark targeting the function let json = cm.run_json(&[ @@ -1589,7 +1589,7 @@ fn git_repo_resolve_fails_when_function_deleted() { let cm = Codemark::with_git_repo(); // Initial file with function - let commit_a = cm.commit("test.rs", "fn my_function() {}\nfn other() {}", "Commit A"); + let _commit_a = cm.commit("test.rs", "fn my_function() {}\nfn other() {}", "Commit A"); // Create bookmark targeting the function let json = cm.run_json(&[ @@ -1609,7 +1609,7 @@ fn git_repo_resolve_fails_when_function_deleted() { assert_eq!(resolve_json["data"]["method"], "exact", "should find exact match initially"); // Delete the function (only keep other function) - let commit_b = cm.commit("test.rs", "fn other() {}", "Commit B"); + let _commit_b = cm.commit("test.rs", "fn other() {}", "Commit B"); // Resolve should now fail or fall back to a different method let resolve_json = cm.run_json(&["resolve", &id[..8]]); @@ -1657,7 +1657,7 @@ fn git_repo_bookmark_goes_stale_when_code_completely_changed() { let cm = Codemark::with_git_repo(); // Initial file with function - let commit_a = cm.commit("test.rs", "fn original_function() { return 42; }", "Commit A"); + let _commit_a = cm.commit("test.rs", "fn original_function() { return 42; }", "Commit A"); // Create bookmark targeting the function let json = cm.run_json(&[ @@ -1677,7 +1677,7 @@ fn git_repo_bookmark_goes_stale_when_code_completely_changed() { // Completely remove the function and make the file empty // This should cause resolution to fail entirely (stale, not drifted) - let commit_b = cm.commit("test.rs", "", "Commit B - empty file"); + let _commit_b = cm.commit("test.rs", "", "Commit B - empty file"); // Heal should mark the bookmark as stale (can't find the function at all) let heal_json = cm.run_json(&["heal"]); @@ -1710,7 +1710,7 @@ fn git_repo_resolve_fails_when_file_deleted() { let cm = Codemark::with_git_repo(); // Initial file with function - let commit_a = cm.commit("test.rs", "fn my_function() {}", "Commit A"); + let _commit_a = cm.commit("test.rs", "fn my_function() {}", "Commit A"); // Create bookmark targeting the function let json = cm.run_json(&[ @@ -1771,7 +1771,7 @@ fn git_repo_function_renamed_resolve_uses_fallback() { let cm = Codemark::with_git_repo(); // Initial file with function - let commit_a = cm.commit("test.rs", "fn my_function() {}", "Commit A"); + let _commit_a = cm.commit("test.rs", "fn my_function() {}", "Commit A"); // Create bookmark targeting the function let json = cm.run_json(&[ @@ -1786,7 +1786,7 @@ fn git_repo_function_renamed_resolve_uses_fallback() { let id = json["data"]["id"].as_str().unwrap().to_string(); // Rename the function - let commit_b = cm.commit("test.rs", "fn renamed_function() {}", "Commit B - function renamed"); + let _commit_b = cm.commit("test.rs", "fn renamed_function() {}", "Commit B - function renamed"); // Resolve should use fallback (relaxed/minimal) or fail let resolve_json = cm.run_json(&["resolve", &id[..8]]); From ebd1f623351880788b995a7ece53c5e9d34b58e4 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Mon, 20 Apr 2026 23:38:06 -0500 Subject: [PATCH 4/4] Fix formatting --- src/engine/resolution.rs | 7 +++--- src/query/generator.rs | 53 +++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/engine/resolution.rs b/src/engine/resolution.rs index 7021854..bf7ef89 100644 --- a/src/engine/resolution.rs +++ b/src/engine/resolution.rs @@ -237,9 +237,10 @@ mod tests { fn search(node: tree_sitter::Node, source: &str, name: &str) -> Option<(usize, usize)> { if node.kind() == "function_declaration" && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { diff --git a/src/query/generator.rs b/src/query/generator.rs index 228c5b1..62d56a0 100644 --- a/src/query/generator.rs +++ b/src/query/generator.rs @@ -527,12 +527,13 @@ mod tests { fn find_function_byte_range(tree: &Tree, source: &str, func_name: &str) -> (usize, usize) { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { if node.kind() == "function_declaration" - && let Some(name_node) = node.child_by_field_name("name") { - let text = &source[name_node.byte_range()]; - if text == name { - return Some((node.start_byte(), node.end_byte())); - } + && let Some(name_node) = node.child_by_field_name("name") + { + let text = &source[name_node.byte_range()]; + if text == name { + return Some((node.start_byte(), node.end_byte())); } + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -652,9 +653,10 @@ mod tests { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { if node.kind() == "function_item" && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -763,9 +765,10 @@ mod tests { let kind = node.kind(); if (kind == "function_declaration" || kind == "method_definition") && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -831,9 +834,10 @@ mod tests { let kind = node.kind(); if kind == "function_definition" && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(range) = search(child, source, name) { @@ -912,9 +916,10 @@ mod tests { let kind = node.kind(); if (kind == "function_declaration" || kind == "method_declaration") && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { @@ -972,9 +977,10 @@ mod tests { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { if (node.kind() == "method_declaration" || node.kind() == "constructor_declaration") && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) { @@ -1033,9 +1039,10 @@ mod tests { fn search(node: Node, source: &str, name: &str) -> Option<(usize, usize)> { if node.kind() == "method_declaration" && let Some(name_node) = node.child_by_field_name("name") - && &source[name_node.byte_range()] == name { - return Some((node.start_byte(), node.end_byte())); - } + && &source[name_node.byte_range()] == name + { + return Some((node.start_byte(), node.end_byte())); + } let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { if let Some(r) = search(child, source, name) {