@@ -7,7 +7,7 @@ use tower_lsp::jsonrpc::Result;
77use tower_lsp:: lsp_types:: * ;
88use tower_lsp:: { Client , LanguageServer , LspService , Server } ;
99mod utils;
10- use utils:: { convert_severity, get_root_path, normalize_path , slashify_path } ;
10+ use utils:: { convert_severity, get_root_path, slashify_path , normalized_slash_path } ;
1111mod affected_files_store;
1212use affected_files_store:: AffectedFilesStore ;
1313
@@ -30,18 +30,17 @@ impl LanguageServer for Backend {
3030 self . client
3131 . log_message ( MessageType :: INFO , "Foundry server initializing!" )
3232 . await ;
33- let opt_path = get_root_path ( params. clone ( ) ) ;
34- if let Some ( path) = opt_path {
33+ if let Some ( root_path) = get_root_path ( params. clone ( ) ) {
3534 self . client
3635 . log_message (
3736 MessageType :: INFO ,
3837 & format ! (
3938 "Foundry server initializing with workspace path: {:?}" ,
40- path
39+ root_path
4140 ) ,
4241 )
4342 . await ;
44- self . load_workspace ( normalize_path ( path . as_str ( ) ) ) . await ;
43+ let _ = self . load_workspace ( root_path ) . await ;
4544 } else {
4645 self . client
4746 . log_message (
@@ -63,7 +62,7 @@ impl LanguageServer for Backend {
6362
6463 async fn initialized ( & self , _: InitializedParams ) {
6564 self . client
66- . log_message ( MessageType :: INFO , "foundryserver initialized!" )
65+ . log_message ( MessageType :: INFO , "Foundry server initialized!" )
6766 . await ;
6867 }
6968
@@ -74,10 +73,7 @@ impl LanguageServer for Backend {
7473 format ! ( "file opened!: {:}" , params. text_document. uri) ,
7574 )
7675 . await ;
77- if params. text_document . uri . path ( ) . contains ( "forge-std" ) {
78- return ;
79- }
80- self . compile ( slashify_path ( & normalize_path ( params. text_document . uri . path ( ) ) ) )
76+ let _ = self . compile ( normalized_slash_path ( params. text_document . uri . path ( ) ) )
8177 . await ;
8278 }
8379
@@ -88,7 +84,7 @@ impl LanguageServer for Backend {
8884 format ! ( "file changed!: {:}" , params. text_document. uri) ,
8985 )
9086 . await ;
91- self . compile ( slashify_path ( & normalize_path ( params. text_document . uri . path ( ) ) ) )
87+ let _ = self . compile ( normalized_slash_path ( params. text_document . uri . path ( ) ) )
9288 . await ;
9389 }
9490
@@ -98,21 +94,21 @@ impl LanguageServer for Backend {
9894}
9995
10096impl Backend {
101- pub async fn load_workspace ( & self , path : String ) {
97+ pub async fn load_workspace ( & self , path : String ) -> std :: result :: Result < ( ) , ( ) > {
10298 let mut state = self . state . lock ( ) . await ;
10399 match Compiler :: new_with_executable_check ( ) {
104100 Ok ( compiler) => state. compiler = Some ( compiler) ,
105101 Err ( Error :: FoundryExecutableNotFound ) => {
106102 self . client
107103 . show_message ( MessageType :: WARNING , "Foundry executable not found. Please install foundry and restart the extension." )
108104 . await ;
109- return ;
105+ return Err ( ( ) ) ;
110106 }
111107 Err ( Error :: InvalidFoundryVersion ) => {
112108 self . client
113109 . show_message ( MessageType :: WARNING , "Foundry executable version is not compatible with this extension. Please update foundry and restart the extension." )
114110 . await ;
115- return ;
111+ return Err ( ( ) ) ;
116112 }
117113 Err ( err) => {
118114 self . client
@@ -121,7 +117,7 @@ impl Backend {
121117 & format ! ( "Foundry server failed to initialize: {:?}" , err) ,
122118 )
123119 . await ;
124- return ;
120+ return Err ( ( ) ) ;
125121 }
126122 }
127123 if let Err ( err) = state. compiler . as_mut ( ) . unwrap ( ) . load_workspace ( path) {
@@ -131,37 +127,50 @@ impl Backend {
131127 & format ! ( "Foundry server failed to initialize: {:?}" , err) ,
132128 )
133129 . await ;
130+ return Err ( ( ) ) ;
134131 } else {
135132 state. initialized = true ;
136133 self . client
137134 . log_message ( MessageType :: INFO , "Foundry server initialized!" )
138135 . await ;
139136 }
140- drop ( state ) ;
137+ Ok ( ( ) )
141138 }
142139
143- pub async fn compile ( & self , filepath : String ) {
144- let mut state = self . state . lock ( ) . await ;
140+ /**
141+ * This function initializes the workspace if it is not already initialized.
142+ * @param {&str} filepath Filepath to compile
143+ * @returns {Result<(), ()>} Result of the initialization
144+ */
145+ async fn initialize_if_not ( & self , filepath : & str ) -> std:: result:: Result < ( ) , ( ) > {
146+ let state = self . state . lock ( ) . await ;
147+
145148 if !state. initialized {
146- // unlock the mutex before calling load_workspace
147- drop ( state ) ;
149+ drop ( state ) ; // unlock the mutex before calling load_workspace
150+
148151 self . client
149152 . log_message ( MessageType :: INFO , "Foundry server initializing!" )
150153 . await ;
151- let folder_path = Path :: new ( & filepath)
154+ let folder_path = Path :: new ( filepath)
152155 . parent ( )
153156 . unwrap ( )
154157 . to_str ( )
155158 . unwrap ( )
156159 . to_string ( ) ;
157- self . load_workspace ( folder_path) . await ;
158- state = self . state . lock ( ) . await ;
159- }
160+ self . load_workspace ( folder_path) . await ?
161+ }
162+ Ok ( ( ) )
163+ }
164+
165+ pub async fn compile ( & self , filepath : String ) -> std:: result:: Result < ( ) , ( ) > {
166+ self . initialize_if_not ( & filepath) . await ?;
167+ let mut state = self . state . lock ( ) . await ;
168+
160169 self . client
161170 . log_message ( MessageType :: INFO , "Foundry server compiling!" )
162171 . await ;
163- let output = state . compiler . as_mut ( ) . unwrap ( ) . compile ( & filepath ) ;
164- match output {
172+
173+ match state . compiler . as_mut ( ) . unwrap ( ) . compile ( & filepath ) {
165174 Ok ( ( project_path, output) ) => {
166175 /*self.client
167176 .log_message(MessageType::INFO, format!("Compile errors: {:?}", output.get_errors()))
@@ -179,66 +188,99 @@ impl Backend {
179188 . await ;
180189 }
181190 }
191+ Ok ( ( ) )
182192 }
183193
194+ /**
195+ * Generate and publish diagnostics from compilation errors
196+ * @param {String} project_path Project path
197+ * @param {String} filepath Filepath to compile
198+ * @param {ProjectCompileOutput} output Compilation output
199+ */
184200 pub async fn publish_errors_diagnostics (
185201 & self ,
186202 project_path : String ,
187203 filepath : String ,
188204 output : ProjectCompileOutput ,
189205 ) {
190- let mut diagnostics = HashMap :: < String , Vec < Diagnostic > > :: new ( ) ;
206+ let mut raised_diagnostics = HashMap :: < String , Vec < Diagnostic > > :: new ( ) ;
207+
191208 for error in output. get_errors ( ) {
192- eprintln ! ( "error: {:?}" , error) ;
193- let ( source_content_filepath, range) =
194- match self . extract_diagnostic_range ( & project_path, error) . await {
195- Some ( ( source_content_filepath, range) ) => ( source_content_filepath, range) ,
196- None => continue ,
197- } ;
198- let diagnostic = Diagnostic {
199- range : Range {
200- start : Position {
201- line : range. start . line ,
202- character : range. start . column ,
203- } ,
204- end : Position {
205- line : range. end . line ,
206- character : range. end . column ,
207- } ,
208- } ,
209- severity : Some ( convert_severity ( error. get_severity ( ) ) ) ,
210- code : None ,
211- code_description : None ,
212- source : Some ( "osmium-solidity-foundry-compiler" . to_string ( ) ) ,
213- message : error. get_message ( ) ,
214- related_information : None ,
215- tags : None ,
216- data : None ,
209+ // Generate diagnostic from compilation error
210+ let ( affected_file, diagnostic) = match self . extract_diagnostic ( & error, & project_path) . await {
211+ Some ( diagnostic) => diagnostic,
212+ None => continue ,
217213 } ;
218- let url = match source_content_filepath. to_str ( ) {
214+
215+ // Add diagnostic to the hashmap
216+ let url = match affected_file. to_str ( ) {
219217 Some ( source_path) => slashify_path ( source_path) ,
220218 None => continue ,
221219 } ;
222- if !diagnostics . contains_key ( & url) {
223- diagnostics . insert ( url. clone ( ) , vec ! [ diagnostic] ) ;
220+ if !raised_diagnostics . contains_key ( & url) {
221+ raised_diagnostics . insert ( url. clone ( ) , vec ! [ diagnostic] ) ;
224222 } else {
225- diagnostics . get_mut ( & url) . unwrap ( ) . push ( diagnostic) ;
223+ raised_diagnostics . get_mut ( & url) . unwrap ( ) . push ( diagnostic) ;
226224 }
227225 }
228226
229- self . add_not_affected_files ( project_path, filepath, & mut diagnostics)
230- . await ;
231- for ( uri, diags) in diagnostics. iter ( ) {
227+ self . reset_not_affected_files ( project_path, filepath, & raised_diagnostics) . await ;
228+ for ( uri, diags) in raised_diagnostics. iter ( ) {
232229 if let Ok ( url) = Url :: parse ( & format ! ( "file://{}" , & uri) ) {
233230 self . client
234231 . publish_diagnostics ( url, diags. clone ( ) , None )
235232 . await ;
236233 } else {
237- self . client . log_message ( MessageType :: ERROR , "error, cannot parse file uri" ) . await ;
234+ self . client . log_message ( MessageType :: ERROR , format ! ( "error, cannot parse file uri : {}" , uri ) ) . await ;
238235 }
239236 }
240237 }
241238
239+ /**
240+ * Extract diagnostic from compilation error
241+ * @param {CompilationError} compilation_error Compilation error
242+ * @param {String} project_path Project path
243+ * @returns {Option<(PathBuf, Diagnostic)>} Diagnostic
244+ * @returns {None} If the diagnostic cannot be extracted
245+ */
246+ async fn extract_diagnostic ( & self , compilation_error : & CompilationError , project_path : & str ) -> Option < ( PathBuf , Diagnostic ) > {
247+ eprintln ! ( "Compilation error: {:?}" , compilation_error) ;
248+ let ( source_content_filepath, range) =
249+ match self . extract_diagnostic_range ( & project_path, compilation_error) . await {
250+ Some ( ( source_content_filepath, range) ) => ( source_content_filepath, range) ,
251+ None => return None ,
252+ } ;
253+ let diagnostic = Diagnostic {
254+ range : Range {
255+ start : Position {
256+ line : range. start . line ,
257+ character : range. start . column ,
258+ } ,
259+ end : Position {
260+ line : range. end . line ,
261+ character : range. end . column ,
262+ } ,
263+ } ,
264+ severity : Some ( convert_severity ( compilation_error. get_severity ( ) ) ) ,
265+ code : None ,
266+ code_description : None ,
267+ source : Some ( "osmium-solidity-foundry-compiler" . to_string ( ) ) ,
268+ message : compilation_error. get_message ( ) ,
269+ related_information : None ,
270+ tags : None ,
271+ data : None ,
272+ } ;
273+ Some ( ( source_content_filepath, diagnostic) )
274+ }
275+
276+ /**
277+ * Extract diagnostic range from compilation error's source location
278+ * Open the file and get the range from the source location
279+ * @param {String} project_path Project path
280+ * @param {CompilationError} error Compilation error
281+ * @returns {Option<(PathBuf, osmium_libs_foundry_wrapper::Range)>} Diagnostic range
282+ * @returns {None} If the diagnostic range cannot be extracted
283+ */
242284 async fn extract_diagnostic_range (
243285 & self ,
244286 project_path : & str ,
@@ -251,9 +293,9 @@ impl Backend {
251293 complete_path
252294 }
253295 None => {
254- /* self.client
296+ self . client
255297 . log_message ( MessageType :: ERROR , format ! ( "error, cannot get filepath: {:?}" , error) )
256- .await;*/
298+ . await ;
257299 return None ;
258300 }
259301 } ;
@@ -287,36 +329,44 @@ impl Backend {
287329 Some ( ( source_content_filepath, range) )
288330 }
289331
290- async fn add_not_affected_files (
332+ /**
333+ * This function resets the diagnostics of the files that are not raising an error anymore.
334+ * @param {String} project_path Project path
335+ * @param {String} filepath Filepath to compile
336+ * @param {HashMap<String, Vec<Diagnostic>>} raised_diagnostics Raised diagnostics
337+ */
338+ async fn reset_not_affected_files (
291339 & self ,
292340 project_path : String ,
293341 filepath : String ,
294- raised_diagnostics : & mut HashMap < String , Vec < Diagnostic > > ,
342+ raised_diagnostics : & HashMap < String , Vec < Diagnostic > > ,
295343 ) {
296344 let mut state = self . state . lock ( ) . await ;
297345
298346 state
299347 . affected_files
300348 . add_project_file ( project_path. clone ( ) , filepath. clone ( ) ) ;
301-
302- let affected_files = state. affected_files . get_affected_files ( & project_path) ;
303- drop ( state) ;
304- let mut without_diagnostics = vec ! [ ] ;
305-
306- for file in affected_files {
307- if !raised_diagnostics. contains_key ( & file) { // if not potential not affected file is not in raised diags
308- if let std:: collections:: hash_map:: Entry :: Vacant ( e) = files. entry ( url) {
309- raised_diagnostics. insert ( file. clone ( ) , vec ! [ ] ) ;
310- without_diagnostics. push ( file) ;
311- }
312- }
349+ let raised_files = raised_diagnostics. keys ( ) . cloned ( ) . collect :: < Vec < String > > ( ) ;
350+ let without_diagnostics = state. affected_files . fill_affected_files ( raised_files, & project_path) ;
313351
314352 self . client
315353 . log_message (
316354 MessageType :: INFO ,
317355 format ! ( "files without diagnostic: {:?}" , without_diagnostics) ,
318356 )
319357 . await ;
358+
359+ for file in without_diagnostics. iter ( ) {
360+ if let Ok ( url) = Url :: parse ( & format ! ( "file://{}" , & file) ) {
361+ self . client
362+ . publish_diagnostics ( url, vec ! [ ] , None )
363+ . await ;
364+ } else {
365+ self . client . log_message ( MessageType :: ERROR , format ! ( "error, cannot parse file uri : {}" , file) ) . await ;
366+ }
367+ }
368+
369+
320370 }
321371}
322372
0 commit comments