Skip to content

Commit 028844c

Browse files
committed
feat: Opens random file to kick off index
1 parent 55b2559 commit 028844c

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

crates/code-agent-sdk/src/sdk/workspace_manager.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ impl WorkspaceManager {
157157
tracing::warn!("Failed to start file watching: {}", e);
158158
}
159159

160+
// Auto-open representative files to enable workspace symbol search out-of-the-box
161+
if let Err(e) = self.auto_open_representative_files().await {
162+
tracing::warn!("Failed to auto-open representative files: {}", e);
163+
}
164+
160165
Ok(())
161166
}
162167

@@ -505,6 +510,148 @@ impl WorkspaceManager {
505510
tracing::info!("🔍 File watching started for languages: {:?}", detected_languages);
506511
Ok(())
507512
}
513+
514+
/// Automatically open representative files for each language to enable workspace symbol search
515+
async fn auto_open_representative_files(&mut self) -> Result<()> {
516+
let detected_languages = self.get_detected_languages()?;
517+
518+
for language in detected_languages {
519+
if let Some(file_path) = self.find_representative_file(&language).await? {
520+
tracing::debug!("Auto-opening representative file for {}: {:?}", language, file_path);
521+
522+
// Read file content
523+
if let Ok(content) = std::fs::read_to_string(&file_path) {
524+
// Check if file is already opened
525+
if self.is_file_opened(&file_path) {
526+
continue;
527+
}
528+
529+
// Determine language ID from file extension
530+
let language_id = if let Some(ext) = file_path.extension().and_then(|ext| ext.to_str()) {
531+
self.config_manager.get_language_for_extension(ext)
532+
.unwrap_or_else(|| "plaintext".to_string())
533+
} else {
534+
"plaintext".to_string()
535+
};
536+
537+
// Get LSP client for this file
538+
if let Ok(Some(client)) = self.get_client_for_file(&file_path).await {
539+
// Create LSP parameters for opening the file
540+
let uri = url::Url::from_file_path(&file_path)
541+
.map_err(|_| anyhow::anyhow!("Invalid file path: {:?}", file_path))?;
542+
543+
let params = lsp_types::DidOpenTextDocumentParams {
544+
text_document: lsp_types::TextDocumentItem {
545+
uri,
546+
language_id,
547+
version: 1,
548+
text: content,
549+
},
550+
};
551+
552+
// Open the file in the LSP client
553+
if let Err(e) = client.did_open(params).await {
554+
tracing::warn!("Failed to auto-open file {:?} for {}: {}", file_path, language, e);
555+
} else {
556+
tracing::debug!("Successfully auto-opened file for {} workspace indexing", language);
557+
558+
// Mark file as opened
559+
self.opened_files.insert(file_path.clone(), FileState {
560+
version: 1,
561+
is_open: true,
562+
});
563+
}
564+
}
565+
}
566+
}
567+
}
568+
569+
Ok(())
570+
}
571+
572+
/// Find a representative file for the given language to trigger workspace indexing
573+
async fn find_representative_file(&self, language: &str) -> Result<Option<PathBuf>> {
574+
let extensions = self.config_manager.get_extensions_for_language(language);
575+
576+
// Search for files with matching extensions in the workspace
577+
for extension in extensions {
578+
if let Some(file_path) = self.find_first_file_with_extension(&extension).await? {
579+
return Ok(Some(file_path));
580+
}
581+
}
582+
583+
Ok(None)
584+
}
585+
586+
/// Find the first file with the given extension in the workspace, respecting exclude patterns
587+
async fn find_first_file_with_extension(&self, extension: &str) -> Result<Option<PathBuf>> {
588+
use std::fs;
589+
590+
// Get all exclude patterns from config
591+
let exclude_patterns = self.config_manager.get_all_exclude_patterns();
592+
593+
fn should_exclude_path(path: &Path, exclude_patterns: &[String]) -> bool {
594+
let path_str = path.to_string_lossy();
595+
596+
// Check against exclude patterns (simple glob-like matching)
597+
for pattern in exclude_patterns {
598+
if pattern.starts_with("**/") && pattern.ends_with("/**") {
599+
// Pattern like "**/node_modules/**"
600+
let dir_name = &pattern[3..pattern.len()-3];
601+
if path_str.contains(&format!("/{}/", dir_name)) ||
602+
path_str.ends_with(&format!("/{}", dir_name)) {
603+
return true;
604+
}
605+
} else if pattern.starts_with("**/") {
606+
// Pattern like "**/dist"
607+
let suffix = &pattern[3..];
608+
if path_str.ends_with(suffix) {
609+
return true;
610+
}
611+
}
612+
}
613+
614+
// Also exclude hidden directories
615+
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
616+
if name.starts_with('.') {
617+
return true;
618+
}
619+
}
620+
621+
false
622+
}
623+
624+
fn find_file_recursive(dir: &Path, extension: &str, exclude_patterns: &[String]) -> Option<PathBuf> {
625+
if should_exclude_path(dir, exclude_patterns) {
626+
return None;
627+
}
628+
629+
if let Ok(entries) = fs::read_dir(dir) {
630+
for entry in entries.flatten() {
631+
let path = entry.path();
632+
633+
if should_exclude_path(&path, exclude_patterns) {
634+
continue;
635+
}
636+
637+
if path.is_file() {
638+
if let Some(file_ext) = path.extension().and_then(|e| e.to_str()) {
639+
if file_ext == extension {
640+
return Some(path);
641+
}
642+
}
643+
} else if path.is_dir() {
644+
if let Some(found) = find_file_recursive(&path, extension, exclude_patterns) {
645+
return Some(found);
646+
}
647+
}
648+
}
649+
}
650+
None
651+
}
652+
653+
Ok(find_file_recursive(&self.workspace_root, extension, &exclude_patterns))
654+
}
508655
}
509656

510657
#[cfg(test)]

0 commit comments

Comments
 (0)