@@ -40,6 +40,10 @@ pub struct LspEvalContext {
4040 open_files : Arc < RwLock < HashMap < PathBuf , String > > > ,
4141 netlist_subscriptions : Arc < RwLock < HashMap < PathBuf , HashMap < String , JsonValue > > > > ,
4242 suppress_netlist_updates : Arc < RwLock < HashSet < PathBuf > > > ,
43+ /// Per-file cache of the last successful eval output, populated during
44+ /// `parse_file_with_contents` so that `viewer/getState` and
45+ /// `on_save_diagnostics` can reuse it without a redundant full evaluation.
46+ last_eval_outputs : Arc < RwLock < HashMap < PathBuf , pcb_zen_core:: EvalOutput > > > ,
4347 custom_request_handler : Option < Arc < CustomRequestHandler > > ,
4448}
4549
@@ -162,6 +166,7 @@ impl Default for LspEvalContext {
162166 open_files,
163167 netlist_subscriptions : Arc :: new ( RwLock :: new ( HashMap :: new ( ) ) ) ,
164168 suppress_netlist_updates : Arc :: new ( RwLock :: new ( HashSet :: new ( ) ) ) ,
169+ last_eval_outputs : Arc :: new ( RwLock :: new ( HashMap :: new ( ) ) ) ,
165170 custom_request_handler : None ,
166171 }
167172 }
@@ -278,6 +283,21 @@ impl LspEvalContext {
278283 self . suppress_netlist_updates . write ( ) . unwrap ( ) . remove ( & key)
279284 }
280285
286+ fn set_last_eval_output ( & self , path : & Path , output : pcb_zen_core:: EvalOutput ) {
287+ let key = self . normalize_path ( path) ;
288+ self . last_eval_outputs . write ( ) . unwrap ( ) . insert ( key, output) ;
289+ }
290+
291+ fn get_last_eval_output ( & self , path : & Path ) -> Option < pcb_zen_core:: EvalOutput > {
292+ let key = self . normalize_path ( path) ;
293+ self . last_eval_outputs . read ( ) . unwrap ( ) . get ( & key) . cloned ( )
294+ }
295+
296+ fn clear_last_eval_output ( & self , path : & Path ) {
297+ let key = self . normalize_path ( path) ;
298+ self . last_eval_outputs . write ( ) . unwrap ( ) . remove ( & key) ;
299+ }
300+
281301 fn evaluate_with_inputs (
282302 & self ,
283303 path_buf : & Path ,
@@ -536,6 +556,7 @@ impl LspContext for LspEvalContext {
536556 if let Ok ( canon) = self . file_provider . canonicalize ( path) {
537557 self . inner . clear_file_contents ( & canon) ;
538558 }
559+ self . clear_last_eval_output ( path) ;
539560 self . maybe_invalidate_symbol_library ( path) ;
540561 self . maybe_invalidate_resolution_cache ( path) ;
541562 }
@@ -573,19 +594,9 @@ impl LspContext for LspEvalContext {
573594 _ => return vec ! [ ] ,
574595 } ;
575596
576- // Full eval to get schematic (same pattern as evaluate_with_inputs)
577- let config = self . config_for ( path) ;
578- let mut ctx = EvalContext :: from_session_and_config ( Default :: default ( ) , config)
579- . set_source_path ( path. to_path_buf ( ) ) ;
580-
581- // Use open-file contents if available so we simulate the buffer, not disk
582- let contents = self . get_load_contents ( uri) . ok ( ) . flatten ( ) ;
583- if let Some ( ref contents) = contents {
584- ctx = ctx. set_source_contents ( contents. clone ( ) ) ;
585- }
586-
587- let eval_result = ctx. eval ( ) ;
588- let Some ( eval_output) = eval_result. output else {
597+ // Reuse the eval result produced during save validation to avoid a
598+ // second full evaluation on the save path.
599+ let Some ( eval_output) = self . get_last_eval_output ( path) else {
589600 return vec ! [ ] ;
590601 } ;
591602 let Ok ( schematic) = eval_output. to_schematic ( ) else {
@@ -707,6 +718,17 @@ impl LspContext for LspEvalContext {
707718 let passes = self . create_lsp_diagnostic_passes ( & workspace_root) ;
708719 result. diagnostics . apply_passes ( & passes) ;
709720
721+ if let Some ( parsed) = result. output . as_ref ( ) {
722+ // Cache the eval output so viewer/getState and
723+ // on_save_diagnostics can reuse it without a redundant
724+ // full evaluation.
725+ self . set_last_eval_output ( path, parsed. eval_output . clone ( ) ) ;
726+ } else {
727+ // Don't let save diagnostics/viewer state reuse stale
728+ // output when the current parse/eval failed.
729+ self . clear_last_eval_output ( path) ;
730+ }
731+
710732 // Convert diagnostics to LSP format
711733 let diagnostics = result
712734 . diagnostics
@@ -716,7 +738,7 @@ impl LspContext for LspEvalContext {
716738
717739 LspEvalResult {
718740 diagnostics,
719- ast : result. output . flatten ( ) ,
741+ ast : result. output . map ( |parsed| parsed . ast ) ,
720742 }
721743 }
722744 _ => {
@@ -1009,26 +1031,38 @@ impl LspContext for LspEvalContext {
10091031 Ok ( params) => {
10101032 let state_json: Option < JsonValue > = match & params. uri {
10111033 LspUrl :: File ( path_buf) => {
1012- // Get contents from memory or disk
1013- let maybe_contents = self . get_load_contents ( & params. uri ) . ok ( ) . flatten ( ) ;
1014-
1015- // Evaluate the module
1016- let config = self . config_for ( path_buf) ;
1017- let ctx =
1018- EvalContext :: from_session_and_config ( Default :: default ( ) , config) ;
1019-
1020- let eval_result = if let Some ( contents) = maybe_contents {
1021- ctx. set_source_path ( path_buf. clone ( ) )
1022- . set_source_contents ( contents)
1023- . eval ( )
1034+ // Try the cached eval output first (populated during
1035+ // parse_file_with_contents) so we can return the
1036+ // schematic without a redundant full evaluation.
1037+ if let Some ( cached) = self . get_last_eval_output ( path_buf) {
1038+ cached
1039+ . to_schematic ( )
1040+ . ok ( )
1041+ . and_then ( |s| serde_json:: to_value ( & s) . ok ( ) )
10241042 } else {
1025- ctx. set_source_path ( path_buf. clone ( ) ) . eval ( )
1026- } ;
1027-
1028- eval_result
1029- . output
1030- . and_then ( |fmv| fmv. to_schematic ( ) . ok ( ) )
1031- . and_then ( |schematic| serde_json:: to_value ( & schematic) . ok ( ) )
1043+ // Fallback: evaluate from scratch using the
1044+ // shared session so loaded modules are cached.
1045+ let maybe_contents =
1046+ self . get_load_contents ( & params. uri ) . ok ( ) . flatten ( ) ;
1047+ let config = self . config_for ( path_buf) ;
1048+ let ctx = EvalContext :: from_session_and_config (
1049+ self . inner . session ( ) . clone ( ) ,
1050+ config,
1051+ ) ;
1052+
1053+ let eval_result = if let Some ( contents) = maybe_contents {
1054+ ctx. set_source_path ( path_buf. clone ( ) )
1055+ . set_source_contents ( contents)
1056+ . eval ( )
1057+ } else {
1058+ ctx. set_source_path ( path_buf. clone ( ) ) . eval ( )
1059+ } ;
1060+
1061+ eval_result
1062+ . output
1063+ . and_then ( |fmv| fmv. to_schematic ( ) . ok ( ) )
1064+ . and_then ( |schematic| serde_json:: to_value ( & schematic) . ok ( ) )
1065+ }
10321066 }
10331067 _ => None ,
10341068 } ;
0 commit comments