1+ use ignore:: WalkBuilder ;
12use std:: collections:: HashMap ;
2- use std:: path:: Path ;
3+ use std:: fs;
4+ use std:: path:: { Path , PathBuf } ;
35
46use lsp_server:: { Connection , Message , Notification , Request , Response } ;
57use lsp_types:: {
68 Diagnostic , DiagnosticSeverity , DidChangeTextDocumentParams , DidCloseTextDocumentParams ,
79 DidOpenTextDocumentParams , DidSaveTextDocumentParams , DocumentFormattingParams ,
8- DocumentSymbolParams , DocumentSymbolResponse , InitializedParams , Location , Position ,
9- PublishDiagnosticsParams , Range , SymbolInformation , SymbolKind , TextEdit , Uri ,
10- WorkspaceSymbolParams ,
10+ DocumentSymbolParams , DocumentSymbolResponse , InitializeParams , InitializedParams , Location ,
11+ Position , PublishDiagnosticsParams , Range , SymbolInformation , SymbolKind , TextEdit , Uri ,
12+ WorkspaceFolder , WorkspaceSymbolParams ,
1113} ;
1214use serde_json:: { from_value, to_value, Value } ;
1315use technique:: formatting:: Identity ;
@@ -22,12 +24,16 @@ use crate::problem::{calculate_column_number, calculate_line_number};
2224pub struct TechniqueLanguageServer {
2325 /// Map from URI to document content
2426 documents : HashMap < Uri , String > ,
27+ /// Workspace folders provided during initialization
28+ folders : Option < Vec < WorkspaceFolder > > ,
2529}
2630
2731impl TechniqueLanguageServer {
28- pub fn new ( ) -> Self {
32+ pub fn new ( params : InitializeParams ) -> Self {
33+ let folders = params. workspace_folders ;
2934 Self {
3035 documents : HashMap :: new ( ) ,
36+ folders,
3137 }
3238 }
3339
@@ -400,10 +406,16 @@ impl TechniqueLanguageServer {
400406 . to_lowercase ( ) ;
401407 debug ! ( "Workspace symbol request: query={:?}" , query) ;
402408
403- let mut all_symbols = Vec :: new ( ) ;
409+ let mut result = Vec :: new ( ) ;
410+ let mut searched = std:: collections:: HashSet :: new ( ) ;
404411
405- // Search through all open documents
412+ // First, search through documents we know are open (they have the
413+ // most up to date, possibly modified, content)
406414 for ( uri, content) in & self . documents {
415+ searched. insert ( PathBuf :: from (
416+ uri. path ( )
417+ . as_str ( ) ,
418+ ) ) ;
407419 let path = Path :: new (
408420 uri. path ( )
409421 . as_str ( ) ,
@@ -421,13 +433,46 @@ impl TechniqueLanguageServer {
421433 . to_lowercase ( )
422434 . contains ( & query)
423435 {
424- all_symbols. push ( symbol) ;
436+ result. push ( symbol) ;
437+ }
438+ }
439+ }
440+ }
441+
442+ // Then search all Technique files in the workspace that aren't
443+ // already open
444+ let paths = self . find_technique_files ( ) ;
445+ for path in paths {
446+ // Skip files we've already processed
447+ if searched. contains ( & path) {
448+ continue ;
449+ }
450+
451+ // Read and parse the file
452+ if let Ok ( content) = fs:: read_to_string ( & path) {
453+ if let Ok ( document) = parsing:: parse_with_recovery ( & path, & content) {
454+ // Create a URI for the file
455+ let uri: Uri = format ! ( "file://{}" , path. display( ) )
456+ . parse ( )
457+ . unwrap ( ) ;
458+ let symbols = self . extract_symbols_from_document ( & uri, & content, & document) ;
459+
460+ // Filter symbols by query
461+ for symbol in symbols {
462+ if query. is_empty ( )
463+ || symbol
464+ . name
465+ . to_lowercase ( )
466+ . contains ( & query)
467+ {
468+ result. push ( symbol) ;
469+ }
425470 }
426471 }
427472 }
428473 }
429474
430- Ok ( Some ( all_symbols ) )
475+ Ok ( Some ( result ) )
431476 }
432477
433478 fn extract_symbols_from_document (
@@ -528,6 +573,49 @@ impl TechniqueLanguageServer {
528573 Ok ( ( ) )
529574 }
530575
576+ /// Find all Technique files in the workspace so they can be scanned for
577+ /// procedure names and other symbols.
578+ fn find_technique_files ( & self ) -> Vec < PathBuf > {
579+ let mut paths = Vec :: new ( ) ;
580+
581+ if let Some ( ref folders) = self . folders {
582+ for folder in folders {
583+ let raw = folder
584+ . uri
585+ . as_str ( ) ;
586+
587+ // Strip file:// from path if present
588+ let filename = if raw. starts_with ( "file://" ) {
589+ & raw [ 7 ..]
590+ } else {
591+ raw
592+ } ;
593+
594+ let path = Path :: new ( filename) ;
595+ if path. exists ( ) {
596+ // Use the ignore crate's WalkBuilder to respect
597+ // files excluded by .gitignore
598+ let walker = WalkBuilder :: new ( & path) . build ( ) ;
599+
600+ for entry in walker {
601+ if let Ok ( entry) = entry {
602+ let path = entry. path ( ) ;
603+ if path. is_file ( ) {
604+ if let Some ( ext) = path. extension ( ) {
605+ if ext == "tq" {
606+ paths. push ( path. to_path_buf ( ) ) ;
607+ }
608+ }
609+ }
610+ }
611+ }
612+ }
613+ }
614+ }
615+
616+ paths
617+ }
618+
531619 fn convert_parsing_errors (
532620 & self ,
533621 _uri : & Uri ,
@@ -694,14 +782,14 @@ impl TechniqueLanguageServer {
694782
695783/// Calculate the byte offset of a substring within a parent string using
696784/// pointer arithmetic.
697- ///
785+ ///
698786/// Returns None if the substring is not actually part of the parent string,
699787/// checking first to see if the substring pointer is actually within the
700788/// bounds of the parent string.
701789fn calculate_slice_offset ( parent : & str , substring : & str ) -> Option < usize > {
702790 let parent_ptr = parent. as_ptr ( ) as usize ;
703791 let substring_ptr = substring. as_ptr ( ) as usize ;
704-
792+
705793 if substring_ptr >= parent_ptr && substring_ptr < parent_ptr + parent. len ( ) {
706794 Some ( substring_ptr - parent_ptr)
707795 } else {
0 commit comments