Skip to content

Commit ae396d7

Browse files
Manage glimpse file prompt behavior (#20)
* Add support for skipping config prompts in specific repositories * fmt * clippy fix * fmt --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent d116df3 commit ae396d7

File tree

8 files changed

+102
-57
lines changed

8 files changed

+102
-57
lines changed

build.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fn main() {
4141
for lang in languages.values() {
4242
for ext in &lang.extensions {
4343
let ext = ext.trim_start_matches('.');
44-
code.push_str(&format!(" set.insert(\"{}\");\n", ext));
44+
code.push_str(&format!(" set.insert(\"{ext}\");\n"));
4545
}
4646
}
4747

@@ -54,7 +54,7 @@ fn main() {
5454

5555
for lang in languages.values() {
5656
for filename in &lang.filenames {
57-
code.push_str(&format!(" set.insert(\"{}\");\n", filename));
57+
code.push_str(&format!(" set.insert(\"{filename}\");\n"));
5858
}
5959
}
6060

@@ -67,7 +67,7 @@ fn main() {
6767

6868
for lang in languages.values() {
6969
for interpreter in &lang.interpreters {
70-
code.push_str(&format!(" set.insert(\"{}\");\n", interpreter));
70+
code.push_str(&format!(" set.insert(\"{interpreter}\");\n"));
7171
}
7272
}
7373

src/analyzer.rs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
100100
for pattern in includes {
101101
// Include patterns are positive patterns (no ! prefix)
102102
if let Err(e) = override_builder.add(pattern) {
103-
eprintln!("Warning: Invalid include pattern '{}': {}", pattern, e);
103+
eprintln!("Warning: Invalid include pattern '{pattern}': {e}");
104104
}
105105
}
106106
}
@@ -113,16 +113,13 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
113113
// Add a '!' prefix if it doesn't already have one
114114
// This makes it a negative pattern (exclude)
115115
let exclude_pattern = if !pattern.starts_with('!') {
116-
format!("!{}", pattern)
116+
format!("!{pattern}")
117117
} else {
118118
pattern.clone()
119119
};
120120

121121
if let Err(e) = override_builder.add(&exclude_pattern) {
122-
eprintln!(
123-
"Warning: Invalid exclude pattern '{}': {}",
124-
pattern, e
125-
);
122+
eprintln!("Warning: Invalid exclude pattern '{pattern}': {e}");
126123
}
127124
}
128125
Exclude::File(file_path) => {
@@ -149,8 +146,7 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
149146
let pattern = format!("!{}", file_path.display());
150147
if let Err(e) = override_builder.add(&pattern) {
151148
eprintln!(
152-
"Warning: Could not add file exclude pattern '{}': {}",
153-
pattern, e
149+
"Warning: Could not add file exclude pattern '{pattern}': {e}"
154150
);
155151
}
156152
}
@@ -195,7 +191,7 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
195191
for pattern in includes {
196192
// Include patterns are positive patterns (no ! prefix)
197193
if let Err(e) = override_builder.add(pattern) {
198-
eprintln!("Warning: Invalid include pattern '{}': {}", pattern, e);
194+
eprintln!("Warning: Invalid include pattern '{pattern}': {e}");
199195
}
200196
}
201197
}
@@ -208,14 +204,13 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
208204
// Add a '!' prefix if it doesn't already have one
209205
// This makes it a negative pattern (exclude)
210206
let exclude_pattern = if !pattern.starts_with('!') {
211-
format!("!{}", pattern)
207+
format!("!{pattern}")
212208
} else {
213209
pattern.clone()
214210
};
215211
if let Err(e) = override_builder.add(&exclude_pattern) {
216212
eprintln!(
217-
"Warning: Invalid exclude pattern '{}': {}",
218-
pattern, e
213+
"Warning: Invalid exclude pattern '{pattern}': {e}"
219214
);
220215
}
221216
}
@@ -336,7 +331,7 @@ mod tests {
336331
fs::create_dir_all(parent)?;
337332
}
338333
let mut file = File::create(&full_path)?;
339-
writeln!(file, "{}", content)?;
334+
writeln!(file, "{content}")?;
340335
created_files.push(full_path);
341336
}
342337

@@ -393,7 +388,7 @@ mod tests {
393388
// For patterns that should exclude, we need to add a "!" prefix
394389
// to make them negative patterns (exclusions)
395390
let exclude_pattern = if !pattern.starts_with('!') {
396-
format!("!{}", pattern)
391+
format!("!{pattern}")
397392
} else {
398393
pattern.clone()
399394
};
@@ -419,8 +414,7 @@ mod tests {
419414

420415
assert_eq!(
421416
is_ignored, should_exclude,
422-
"Failed for exclude: {:?}",
423-
exclude
417+
"Failed for exclude: {exclude:?}"
424418
);
425419
}
426420

@@ -587,12 +581,12 @@ mod tests {
587581

588582
// Test with depth limit of 1
589583
cli.max_depth = Some(1);
590-
let _ = process_directory(&cli)?;
584+
process_directory(&cli)?;
591585
// Verify only top-level files were processed
592586

593587
// Test with depth limit of 2
594588
cli.max_depth = Some(2);
595-
let _ = process_directory(&cli)?;
589+
process_directory(&cli)?;
596590
// Verify files up to depth 2 were processed
597591

598592
Ok(())
@@ -605,12 +599,12 @@ mod tests {
605599

606600
// Test without hidden files
607601
cli.hidden = false;
608-
let _ = process_directory(&cli)?;
602+
process_directory(&cli)?;
609603
// Verify hidden files were not processed
610604

611605
// Test with hidden files
612606
cli.hidden = true;
613-
let _ = process_directory(&cli)?;
607+
process_directory(&cli)?;
614608
// Verify hidden files were processed
615609

616610
Ok(())
@@ -622,7 +616,7 @@ mod tests {
622616
let rust_file = files.iter().find(|f| f.ends_with("main.rs")).unwrap();
623617

624618
let cli = create_test_cli(rust_file);
625-
let _ = process_directory(&cli)?;
619+
process_directory(&cli)?;
626620
// Verify single file was processed correctly
627621

628622
Ok(())

src/config.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ pub struct Config {
8282

8383
#[serde(default)]
8484
pub traverse_links: bool,
85+
86+
/// List of canonical project directories for which the user has already declined to
87+
/// save a local `.glimpse` configuration file. When a directory is present in this
88+
/// list Glimpse will not prompt the user again.
89+
#[serde(default)]
90+
pub skipped_prompt_repos: Vec<String>,
8591
}
8692

8793
impl Default for Config {
@@ -95,6 +101,7 @@ impl Default for Config {
95101
default_tokenizer_model: default_tokenizer_model(),
96102
default_link_depth: default_link_depth(),
97103
traverse_links: false,
104+
skipped_prompt_repos: Vec::new(),
98105
}
99106
}
100107
}
@@ -202,3 +209,16 @@ pub fn load_repo_config(path: &Path) -> anyhow::Result<RepoConfig> {
202209
Ok(RepoConfig::default())
203210
}
204211
}
212+
213+
/// Persist the provided global configuration to disk, overriding any existing config file.
214+
pub fn save_config(config: &Config) -> anyhow::Result<()> {
215+
let config_path = get_config_path()?;
216+
217+
if let Some(parent) = config_path.parent() {
218+
std::fs::create_dir_all(parent)?;
219+
}
220+
221+
let config_str = toml::to_string_pretty(config)?;
222+
std::fs::write(config_path, config_str)?;
223+
Ok(())
224+
}

src/git_processor.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,13 @@ mod tests {
7979
];
8080

8181
for url in valid_urls {
82-
assert!(
83-
GitProcessor::is_git_url(url),
84-
"URL should be valid: {}",
85-
url
86-
);
82+
assert!(GitProcessor::is_git_url(url), "URL should be valid: {url}");
8783
}
8884

8985
for url in invalid_urls {
9086
assert!(
9187
!GitProcessor::is_git_url(url),
92-
"URL should be invalid: {}",
93-
url
88+
"URL should be invalid: {url}"
9489
);
9590
}
9691
}
@@ -136,11 +131,11 @@ mod tests {
136131
let parsed_url = Url::parse(url).unwrap();
137132
let repo_name = parsed_url
138133
.path_segments()
139-
.and_then(|segments| segments.last())
134+
.and_then(|mut segments| segments.next_back())
140135
.map(|name| name.trim_end_matches(".git"))
141136
.unwrap_or("repo");
142137

143-
assert_eq!(repo_name, expected_name, "Failed for URL: {}", url);
138+
assert_eq!(repo_name, expected_name, "Failed for URL: {url}");
144139
}
145140
}
146141

@@ -155,7 +150,7 @@ mod tests {
155150
// Check for some common files that should be present
156151
assert!(path.join("Cargo.toml").exists(), "Cargo.toml should exist");
157152
}
158-
Err(e) => println!("Skipping clone test due to error: {}", e),
153+
Err(e) => println!("Skipping clone test due to error: {e}"),
159154
}
160155
}
161156
}

src/main.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ mod url_processor;
1010

1111
use crate::analyzer::process_directory;
1212
use crate::cli::Cli;
13-
use crate::config::{get_config_path, load_config, load_repo_config, save_repo_config, RepoConfig};
13+
use crate::config::{
14+
get_config_path, load_config, load_repo_config, save_config, save_repo_config, RepoConfig,
15+
};
1416
use crate::git_processor::GitProcessor;
1517
use crate::url_processor::UrlProcessor;
1618
use std::fs;
@@ -33,7 +35,7 @@ fn has_custom_options(args: &Cli) -> bool {
3335
}
3436

3537
fn main() -> anyhow::Result<()> {
36-
let config = load_config()?;
38+
let mut config = load_config()?;
3739
let mut args = Cli::parse_with_config(&config)?;
3840

3941
if args.config_path {
@@ -59,20 +61,55 @@ fn main() -> anyhow::Result<()> {
5961
let repo_config = create_repo_config_from_args(&args);
6062
save_repo_config(&glimpse_file, &repo_config)?;
6163
println!("Configuration saved to {}", glimpse_file.display());
64+
65+
// If the user explicitly saved a config, remove this directory from the skipped list
66+
if let Ok(canonical_root) = std::fs::canonicalize(&root_dir) {
67+
let root_str = canonical_root.to_string_lossy().to_string();
68+
if let Some(pos) = config
69+
.skipped_prompt_repos
70+
.iter()
71+
.position(|p| p == &root_str)
72+
{
73+
config.skipped_prompt_repos.remove(pos);
74+
save_config(&config)?;
75+
}
76+
}
6277
} else if glimpse_file.exists() {
6378
println!("Loading configuration from {}", glimpse_file.display());
6479
let repo_config = load_repo_config(&glimpse_file)?;
6580
apply_repo_config(&mut args, &repo_config);
6681
} else if has_custom_options(&args) {
67-
print!("Would you like to save these options as defaults for this directory? (y/n): ");
68-
io::stdout().flush()?;
69-
let mut response = String::new();
70-
io::stdin().read_line(&mut response)?;
71-
72-
if response.trim().to_lowercase() == "y" {
73-
let repo_config = create_repo_config_from_args(&args);
74-
save_repo_config(&glimpse_file, &repo_config)?;
75-
println!("Configuration saved to {}", glimpse_file.display());
82+
// Determine canonical root directory path for consistent tracking
83+
let canonical_root = std::fs::canonicalize(&root_dir).unwrap_or(root_dir.clone());
84+
let root_str = canonical_root.to_string_lossy().to_string();
85+
86+
if !config.skipped_prompt_repos.contains(&root_str) {
87+
print!(
88+
"Would you like to save these options as defaults for this directory? (y/n): "
89+
);
90+
io::stdout().flush()?;
91+
let mut response = String::new();
92+
io::stdin().read_line(&mut response)?;
93+
94+
if response.trim().to_lowercase() == "y" {
95+
let repo_config = create_repo_config_from_args(&args);
96+
save_repo_config(&glimpse_file, &repo_config)?;
97+
println!("Configuration saved to {}", glimpse_file.display());
98+
99+
// In case it was previously skipped, remove from skipped list
100+
if let Some(pos) = config
101+
.skipped_prompt_repos
102+
.iter()
103+
.position(|p| p == &root_str)
104+
{
105+
config.skipped_prompt_repos.remove(pos);
106+
save_config(&config)?;
107+
}
108+
} else {
109+
// Record that user declined for this project
110+
config.skipped_prompt_repos.push(root_str);
111+
save_config(&config)?;
112+
}
76113
}
77114
}
78115
}
@@ -131,7 +168,7 @@ fn main() -> anyhow::Result<()> {
131168
fs::write(output_file, content)?;
132169
println!("Output written to: {}", output_file.display());
133170
} else if args.print {
134-
println!("{}", content);
171+
println!("{content}");
135172
} else {
136173
// Default behavior for URLs if no -f or --print: copy to clipboard
137174
match arboard::Clipboard::new()

src/output.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ fn try_copy_with_osc52(content: &str) -> Result<(), Box<dyn std::error::Error>>
143143
pub fn handle_output(content: String, args: &Cli) -> Result<()> {
144144
// Print to stdout if no other output method is specified
145145
if args.print {
146-
println!("{}", content);
146+
println!("{content}");
147147
}
148148

149149
// Copy to clipboard if requested
@@ -153,7 +153,7 @@ pub fn handle_output(content: String, args: &Cli) -> Result<()> {
153153
Err(_) => {
154154
match try_copy_with_osc52(&content) {
155155
Ok(_) => println!("Context prepared! (using terminal clipboard) Paste into your LLM of choice + Profit."),
156-
Err(e) => eprintln!("Warning: Failed to copy to clipboard: {}. Output will continue with other specified formats.", e)
156+
Err(e) => eprintln!("Warning: Failed to copy to clipboard: {e}. Output will continue with other specified formats.")
157157
}
158158
},
159159
}

src/source_detection.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ mod tests {
121121
assert_eq!(
122122
extract_interpreter(input).as_deref(),
123123
expected,
124-
"Failed for input: {}",
125-
input
124+
"Failed for input: {input}"
126125
);
127126
}
128127
}
@@ -151,7 +150,7 @@ mod tests {
151150
for (name, content, expected) in test_cases {
152151
let path = dir.path().join(name);
153152
let mut file = File::create(&path).unwrap();
154-
writeln!(file, "{}", content).unwrap();
153+
writeln!(file, "{content}").unwrap();
155154

156155
assert_eq!(
157156
is_source_file(&path),

0 commit comments

Comments
 (0)