1- use std:: { ops:: ControlFlow , sync:: Arc } ;
1+ use std:: { collections :: HashMap , ops:: ControlFlow , sync:: Arc } ;
22
3- use async_lsp:: { ClientSocket , ResponseError } ;
3+ use async_lsp:: { ClientSocket , LanguageClient , ResponseError } ;
44use lsp_types:: {
55 InitializeParams , InitializeResult , InitializedParams , LogMessageParams , MessageType ,
6- ServerCapabilities , ServerInfo , TextDocumentSyncCapability , TextDocumentSyncKind ,
7- TextDocumentSyncOptions , notification as notif,
6+ PublishDiagnosticsParams , ServerInfo ,
87} ;
98use solar_config:: version:: SHORT_VERSION ;
10- use solar_interface:: data_structures:: sync:: RwLock ;
9+ use solar_interface:: {
10+ Session ,
11+ data_structures:: sync:: RwLock ,
12+ diagnostics:: { DiagCtxt , InMemoryEmitter } ,
13+ source_map:: FileName ,
14+ } ;
15+ use solar_sema:: Compiler ;
16+ use tokio:: task:: JoinHandle ;
1117
12- use crate :: { NotifyResult , vfs:: Vfs } ;
18+ use crate :: { NotifyResult , config :: negotiate_capabilities , proto , vfs:: Vfs } ;
1319
1420pub ( crate ) struct GlobalState {
1521 client : ClientSocket ,
1622 pub ( crate ) vfs : Arc < RwLock < Vfs > > ,
23+ analysis_version : usize ,
1724}
1825
1926impl GlobalState {
2027 pub ( crate ) fn new ( client : ClientSocket ) -> Self {
21- Self { client, vfs : Arc :: new ( Default :: default ( ) ) }
28+ Self { client, vfs : Arc :: new ( Default :: default ( ) ) , analysis_version : 0 }
2229 }
2330
2431 pub ( crate ) fn on_initialize (
2532 & mut self ,
26- _ : InitializeParams ,
33+ params : InitializeParams ,
2734 ) -> impl Future < Output = Result < InitializeResult , ResponseError > > + use < > {
35+ let ( capabilities, _config) = negotiate_capabilities ( params) ;
36+
2837 std:: future:: ready ( Ok ( InitializeResult {
29- capabilities : ServerCapabilities {
30- text_document_sync : Some ( TextDocumentSyncCapability :: Options (
31- TextDocumentSyncOptions {
32- open_close : Some ( true ) ,
33- change : Some ( TextDocumentSyncKind :: INCREMENTAL ) ,
34- will_save : None ,
35- will_save_wait_until : None ,
36- ..Default :: default ( )
37- } ,
38- ) ) ,
39- ..Default :: default ( )
40- } ,
38+ capabilities,
4139 server_info : Some ( ServerInfo {
4240 name : "solar" . into ( ) ,
4341 version : Some ( SHORT_VERSION . into ( ) ) ,
@@ -46,32 +44,97 @@ impl GlobalState {
4644 }
4745
4846 pub ( crate ) fn on_initialized ( & mut self , _: InitializedParams ) -> NotifyResult {
49- self . info_msg ( "solar initialized" ) ;
47+ let _ = self . client . log_message ( LogMessageParams {
48+ typ : MessageType :: INFO ,
49+ message : "solar initialized" . into ( ) ,
50+ } ) ;
5051 ControlFlow :: Continue ( ( ) )
5152 }
52- }
5353
54- impl GlobalState {
55- #[ expect( unused) ]
56- fn warn_msg ( & self , message : impl Into < String > ) {
57- let _ = self . client . notify :: < notif:: LogMessage > ( LogMessageParams {
58- typ : MessageType :: WARNING ,
59- message : message. into ( ) ,
54+ /// Parses, lowers, and performs analysis on project files, including in-memory only files.
55+ ///
56+ /// Each time analysis is triggered, a version is assigned to the analysis. A snapshot is then
57+ /// taken of the global state ([`GlobalStateSnapshot`]) and analysis is performed on
58+ /// the entire project in a separate thread.
59+ ///
60+ /// Currently, Solar is sufficiently fast at parsing and lowering even large Solidity projects,
61+ /// so while analysing the entire project is relatively expensive compared to incremental
62+ /// analysis, it is still fast enough for most workloads. A potential improvement would be to
63+ /// enable incremental parsing and analysis in Solar using e.g. [`salsa`].
64+ ///
65+ /// [`salsa`]: https://docs.rs/salsa/latest/salsa/
66+ pub ( crate ) fn recompute ( & mut self ) {
67+ self . analysis_version += 1 ;
68+ let version = self . analysis_version ;
69+ self . spawn_with_snapshot ( move |mut snapshot| {
70+ // todo: if this errors, we should notify the user
71+ // todo: set base path to project root
72+ // todo: remappings
73+ let ( emitter, diag_buffer) = InMemoryEmitter :: new ( ) ;
74+ let sess = Session :: builder ( ) . dcx ( DiagCtxt :: new ( Box :: new ( emitter) ) ) . build ( ) ;
75+
76+ let mut compiler = Compiler :: new ( sess) ;
77+ let _ = compiler. enter_mut ( move |compiler| -> solar_interface:: Result < _ > {
78+ // Parse the files.
79+ let mut parsing_context = compiler. parse ( ) ;
80+ // todo: unwraps
81+ parsing_context. add_files ( snapshot. vfs . read ( ) . iter ( ) . map ( |( path, contents) | {
82+ compiler
83+ . sess ( )
84+ . source_map ( )
85+ . new_source_file (
86+ FileName :: real ( path. as_path ( ) . unwrap ( ) ) ,
87+ contents. to_string ( ) ,
88+ )
89+ . unwrap ( )
90+ } ) ) ;
91+
92+ parsing_context. parse ( ) ;
93+
94+ // Perform lowering and analysis.
95+ // We should never encounter `ControlFlow::Break` because we do not stop after
96+ // parsing, so we ignore the return.
97+ // todo: handle errors (currently this always errors?)
98+ let _ = compiler. lower_asts ( ) ;
99+ let _ = compiler. analysis ( ) ;
100+
101+ // todo handle diagnostic clearing
102+ // todo clean this mess up boya
103+ let diagnostics: HashMap < _ , Vec < _ > > = diag_buffer
104+ . read ( )
105+ . iter ( )
106+ . filter_map ( |diag| proto:: diagnostic ( compiler. sess ( ) . source_map ( ) , diag) )
107+ . fold ( HashMap :: new ( ) , |mut diags, ( path, diag) | {
108+ diags. entry ( path) . or_default ( ) . push ( diag) ;
109+ diags
110+ } ) ;
111+ for ( uri, diagnostics) in diagnostics. into_iter ( ) {
112+ let _ = snapshot. client . publish_diagnostics ( PublishDiagnosticsParams :: new (
113+ uri,
114+ diagnostics,
115+ None ,
116+ ) ) ;
117+ }
118+
119+ Ok ( ( ) )
120+ } ) ;
60121 } ) ;
61122 }
62123
63- #[ expect( unused) ]
64- fn error_msg ( & self , message : impl Into < String > ) {
65- let _ = self . client . notify :: < notif:: LogMessage > ( LogMessageParams {
66- typ : MessageType :: ERROR ,
67- message : message. into ( ) ,
68- } ) ;
124+ fn snapshot ( & self ) -> GlobalStateSnapshot {
125+ GlobalStateSnapshot { client : self . client . clone ( ) , vfs : self . vfs . clone ( ) }
69126 }
70127
71- fn info_msg ( & self , message : impl Into < String > ) {
72- let _ = self . client . notify :: < notif:: LogMessage > ( LogMessageParams {
73- typ : MessageType :: INFO ,
74- message : message. into ( ) ,
75- } ) ;
128+ fn spawn_with_snapshot < T : Send + ' static > (
129+ & self ,
130+ f : impl FnOnce ( GlobalStateSnapshot ) -> T + Send + ' static ,
131+ ) -> JoinHandle < T > {
132+ let snapshot = self . snapshot ( ) ;
133+ tokio:: task:: spawn_blocking ( move || f ( snapshot) )
76134 }
77135}
136+
137+ pub ( crate ) struct GlobalStateSnapshot {
138+ client : ClientSocket ,
139+ vfs : Arc < RwLock < Vfs > > ,
140+ }
0 commit comments