Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion crates/code2prompt-core/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ pub struct Code2PromptConfig {
/// (parallelized during file I/O). This flag only controls whether the breakdown
/// is shown to users in the final output.
pub token_map_enabled: bool,

/// If true, starts with all files deselected.
pub deselected: bool,
}

impl Code2PromptConfig {
Expand Down Expand Up @@ -148,6 +151,9 @@ pub struct TomlConfig {

/// Token map
pub token_map_enabled: bool,

/// Initial selection state
pub deselected: bool,
}

impl TomlConfig {
Expand Down Expand Up @@ -208,7 +214,8 @@ impl TomlConfig {

builder
.user_variables(self.user_variables.clone())
.token_map_enabled(self.token_map_enabled);
.token_map_enabled(self.token_map_enabled)
.deselected(self.deselected);

builder.build().unwrap_or_default()
}
Expand Down Expand Up @@ -249,6 +256,7 @@ pub fn export_config_to_toml(config: &Code2PromptConfig) -> Result<String, toml:
},
user_variables: config.user_variables.clone(),
token_map_enabled: config.token_map_enabled,
deselected: config.deselected,
};

toml_config.to_string()
Expand Down
46 changes: 39 additions & 7 deletions crates/code2prompt-core/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,23 @@ pub struct SelectionEngine {

/// Cache for performance
cache: HashMap<PathBuf, bool>,

/// Default behavior when no patterns or user actions match
deselected_by_default: bool,
}

impl SelectionEngine {
/// Create a new SelectionEngine with base patterns
pub fn new(include_patterns: Vec<String>, exclude_patterns: Vec<String>) -> Self {
pub fn new(
include_patterns: Vec<String>,
exclude_patterns: Vec<String>,
deselected_by_default: bool,
) -> Self {
Self {
filter_engine: FilterEngine::new(&include_patterns, &exclude_patterns),
user_actions: Vec::new(),
cache: HashMap::new(),
deselected_by_default,
}
}

Expand Down Expand Up @@ -74,9 +82,12 @@ impl SelectionEngine {
// If there are include patterns, use them
self.filter_engine.matches_patterns(path)
} else {
// No include patterns: default behavior is to include all files
// (unless excluded by exclude patterns)
!self.filter_engine.is_excluded(path)
// No include patterns: default behavior depends on deselected_by_default
if self.deselected_by_default {
false
} else {
!self.filter_engine.is_excluded(path)
}
}
}

Expand Down Expand Up @@ -241,6 +252,12 @@ impl SelectionEngine {
pub fn filter_engine(&self) -> &FilterEngine {
&self.filter_engine
}

/// Set whether the engine should default to deselected
pub fn set_deselected_by_default(&mut self, value: bool) {
self.deselected_by_default = value;
self.cache.clear();
}
}

impl std::fmt::Debug for SelectionEngine {
Expand All @@ -249,6 +266,7 @@ impl std::fmt::Debug for SelectionEngine {
.field("filter_engine", &self.filter_engine)
.field("user_actions", &self.user_actions)
.field("cache_size", &self.cache.len())
.field("deselected_by_default", &self.deselected_by_default)
.finish()
}
}
Expand All @@ -259,7 +277,7 @@ mod tests {

#[test]
fn test_specificity_calculation() {
let engine = SelectionEngine::new(vec![], vec![]);
let engine = SelectionEngine::new(vec![], vec![], false);

assert_eq!(engine.calculate_specificity(Path::new("file.rs")), 1);
assert_eq!(engine.calculate_specificity(Path::new("src/main.rs")), 2);
Expand All @@ -271,7 +289,7 @@ mod tests {

#[test]
fn test_precedence_rules() {
let mut engine = SelectionEngine::new(vec![], vec![]);
let mut engine = SelectionEngine::new(vec![], vec![], false);

// Add less specific action first
engine.exclude_file(PathBuf::from("src"));
Expand All @@ -286,7 +304,7 @@ mod tests {

#[test]
fn test_recent_wins_over_old() {
let mut engine = SelectionEngine::new(vec![], vec![]);
let mut engine = SelectionEngine::new(vec![], vec![], false);

// First action
engine.exclude_file(PathBuf::from("main.rs"));
Expand All @@ -296,4 +314,18 @@ mod tests {
engine.include_file(PathBuf::from("main.rs"));
assert!(engine.is_selected(Path::new("main.rs")));
}

#[test]
fn test_deselected_by_default() {
let mut engine = SelectionEngine::new(vec![], vec![], true);

// By default everything is deselected
assert!(!engine.is_selected(Path::new("main.rs")));
assert!(!engine.is_selected(Path::new("src/lib.rs")));

// User action should still work
engine.include_file(PathBuf::from("main.rs"));
assert!(engine.is_selected(Path::new("main.rs")));
assert!(!engine.is_selected(Path::new("src/lib.rs")));
}
Comment on lines +318 to +330
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test coverage for the new deselected_by_default feature only covers the case when there are no include patterns. Consider adding a test case to verify the interaction between deselected_by_default=true and include patterns to ensure that include patterns still work as expected when this flag is enabled. For example, when deselected_by_default=true and include_patterns=["*.rs"], files matching "*.rs" should still be selected.

Copilot uses AI. Check for mistakes.
}
10 changes: 10 additions & 0 deletions crates/code2prompt-core/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl Code2PromptSession {
let selection_engine = SelectionEngine::new(
config.include_patterns.clone(),
config.exclude_patterns.clone(),
config.deselected,
);

Self {
Expand All @@ -92,6 +93,7 @@ impl Code2PromptSession {
self.selection_engine = SelectionEngine::new(
self.config.include_patterns.clone(),
self.config.exclude_patterns.clone(),
self.config.deselected,
);
self
}
Expand All @@ -102,6 +104,7 @@ impl Code2PromptSession {
self.selection_engine = SelectionEngine::new(
self.config.include_patterns.clone(),
self.config.exclude_patterns.clone(),
self.config.deselected,
);
self
}
Expand Down Expand Up @@ -177,6 +180,13 @@ impl Code2PromptSession {
self.selection_engine.has_user_actions()
}

/// Set deselected by default and update selection engine
pub fn set_deselected(&mut self, value: bool) -> &mut Self {
self.config.deselected = value;
self.selection_engine.set_deselected_by_default(value);
self
}

/// Loads the codebase data (source tree and file list) into the session.
pub fn load_codebase(&mut self) -> Result<()> {
let (tree, files) = traverse_directory(&self.config, Some(&mut self.selection_engine))
Expand Down
4 changes: 4 additions & 0 deletions crates/code2prompt/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ pub struct Cli {
#[clap(long, value_name = "PERCENT")]
pub token_map_min_percent: Option<f64>,

/// Start with all files deselected
#[clap(long)]
pub deselected: bool,

#[arg(long, hide = true)]
pub clipboard_daemon: bool,
}
Expand Down
4 changes: 3 additions & 1 deletion crates/code2prompt/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ pub fn build_session(

let cfg_diff_enabled = cfg.map(|c| c.diff_enabled).unwrap_or(false);
let cfg_token_map_enabled = cfg.map(|c| c.token_map_enabled).unwrap_or(false);
let cfg_deselected = cfg.map(|c| c.deselected).unwrap_or(false);

configuration
.diff_enabled(args.diff || cfg_diff_enabled)
Expand All @@ -171,7 +172,8 @@ pub fn build_session(
.hidden(args.hidden)
.no_codeblock(args.no_codeblock)
.follow_symlinks(args.follow_symlinks)
.token_map_enabled(args.token_map || cfg_token_map_enabled || tui_mode);
.token_map_enabled(args.token_map || cfg_token_map_enabled || tui_mode)
.deselected(args.deselected || cfg_deselected);

// User variables from config (if available)
if let Some(c) = cfg {
Expand Down
5 changes: 5 additions & 0 deletions crates/code2prompt/src/model/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub enum SettingKey {
FollowSymlinks,
HiddenFiles,
NoIgnore,
Deselected,
}

impl SettingsState {
Expand Down Expand Up @@ -162,6 +163,10 @@ impl SettingsState {
session.config.no_ignore = !session.config.no_ignore;
"No Ignore"
}
(SettingKey::Deselected, SettingAction::Toggle | SettingAction::Cycle) => {
session.set_deselected(!session.config.deselected);
"Deselected by Default"
}
_ => "Unknown Setting",
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/code2prompt/src/view/formatters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ pub fn format_settings_groups(session: &Code2PromptSession) -> Vec<SettingsGroup
description: "Ignore .gitignore rules".to_string(),
setting_type: SettingType::Boolean(session.config.no_ignore),
},
SettingsItem {
key: SettingKey::Deselected,
name: "Deselected by Default".to_string(),
description: "Start with all files deselected".to_string(),
setting_type: SettingType::Boolean(session.config.deselected),
},
],
},
]
Expand Down
Loading