|
16 | 16 |
|
17 | 17 | package vadl.lsp; |
18 | 18 |
|
| 19 | +import java.io.IOException; |
19 | 20 | import java.nio.file.Path; |
20 | 21 | import java.util.ArrayList; |
21 | 22 | import java.util.HashMap; |
|
31 | 32 | import org.eclipse.lsp4j.DidCloseTextDocumentParams; |
32 | 33 | import org.eclipse.lsp4j.DidOpenTextDocumentParams; |
33 | 34 | import org.eclipse.lsp4j.DidSaveTextDocumentParams; |
| 35 | +import org.eclipse.lsp4j.Position; |
34 | 36 | import org.eclipse.lsp4j.PublishDiagnosticsParams; |
35 | 37 | import org.eclipse.lsp4j.Range; |
36 | 38 | import org.eclipse.lsp4j.SemanticTokens; |
|
41 | 43 | import org.eclipse.lsp4j.services.TextDocumentService; |
42 | 44 | import org.slf4j.Logger; |
43 | 45 | import org.slf4j.LoggerFactory; |
44 | | -import vadl.ast.Ast; |
| 46 | +import vadl.ast.Frontend; |
45 | 47 | import vadl.ast.LspTokenizer; |
46 | | -import vadl.ast.ModelRemover; |
47 | | -import vadl.ast.TypeChecker; |
48 | | -import vadl.ast.Ungrouper; |
49 | | -import vadl.ast.VadlParser; |
50 | 48 | import vadl.error.Diagnostic.MsgType; |
51 | 49 | import vadl.error.DiagnosticList; |
52 | 50 | import vadl.utils.DiskVirtualFileSystem; |
@@ -190,71 +188,108 @@ private void publishDiagnostics(Document document) { |
190 | 188 | } |
191 | 189 |
|
192 | 190 | private void publishDiagnosticsForOneDocumentSnapshot(Document.Snapshot snapshot) { |
193 | | - List<Diagnostic> lspItems = new ArrayList<>(); |
194 | | - Path path = snapshot.getPath(); |
195 | | - try { |
196 | | - Ast ast = VadlParser.parse(snapshot.text(), snapshot.virtualFileSystem(), Map.of(), path); |
197 | | - new Ungrouper().ungroup(ast); |
198 | | - new ModelRemover().removeModels(ast); |
199 | | - new TypeChecker().verify(ast); |
200 | | - |
201 | | - } catch (DiagnosticList dl) { |
202 | | - log.debug("Raw diagnostics ({}): {}", snapshot.uri(), dl.getMessage()); |
203 | | - for (vadl.error.Diagnostic item : dl.items) { |
204 | | - // TODO Look into secondary locations too? Maybe as relatedInformation? Or to put a |
205 | | - // diagnostic message there as well? |
206 | | - SourceLocation location = item.multiLocation.primaryLocation().location(); |
207 | | - if (!Objects.equals(location.path(), path)) { |
208 | | - // Ignore errors for other files |
209 | | - // TODO this means that errors in included files are not reported unless that file is |
210 | | - // opened in the client, even though the Parser gives us diagnostics for them |
211 | | - continue; |
212 | | - } |
| 191 | + var unused = server.executor().submit(() -> { |
| 192 | + List<Diagnostic> lspItems = new ArrayList<>(); |
| 193 | + Path path = snapshot.getPath(); |
| 194 | + try { |
| 195 | + Frontend.compileToAst(path, snapshot.virtualFileSystem()); |
213 | 196 |
|
214 | | - Diagnostic lspItem = new Diagnostic(); |
215 | | - lspItem.setRange(new Range( |
216 | | - snapshot.calculateUtf16Position(location.begin(), false), |
217 | | - snapshot.calculateUtf16Position(location.end(), true) |
218 | | - )); |
219 | | - lspItem.setSeverity( |
220 | | - switch (item.level) { |
221 | | - case ERROR -> DiagnosticSeverity.Error; |
222 | | - case WARNING -> DiagnosticSeverity.Warning; |
| 197 | + } catch (IOException e) { |
| 198 | + log.error("Unexpected Exception occurred when parsing {}", snapshot.uri(), e); |
| 199 | + |
| 200 | + } catch (DiagnosticList dl) { |
| 201 | + log.debug("Raw diagnostics ({}): {}", snapshot.uri(), dl.getMessage()); |
| 202 | + List<String> importedFileErrors = new ArrayList<>(); |
| 203 | + for (vadl.error.Diagnostic item : dl.items) { |
| 204 | + Path itemPath = item.multiLocation.primaryLocation().location().path(); |
| 205 | + if (!Objects.equals(itemPath, path)) { |
| 206 | + if (itemPath == null) { |
| 207 | + continue; |
223 | 208 | } |
| 209 | + // Error in imported file |
| 210 | + importedFileErrors.add(relativePath(itemPath, snapshot.getPath())); |
| 211 | + continue; |
| 212 | + } |
| 213 | + |
| 214 | + lspItems.add(buildLspDiagnostic(item, snapshot)); |
| 215 | + } |
| 216 | + |
| 217 | + if (!importedFileErrors.isEmpty()) { |
| 218 | + // Putting one diagnostic at the top of the file, which points out which imported files |
| 219 | + // have errors |
| 220 | + Diagnostic importedFilesDiagnostic = new Diagnostic(); |
| 221 | + importedFilesDiagnostic.setRange(new Range(new Position(0, 0), |
| 222 | + new Position(0, 0))); |
| 223 | + // TODO Consider using different severity if all diagnostics represented by this are only |
| 224 | + // Warnings |
| 225 | + importedFilesDiagnostic.setSeverity(DiagnosticSeverity.Error); |
| 226 | + |
| 227 | + String message = importedFileErrors.size() == 1 |
| 228 | + ? "Errors in imported file: \n" + importedFileErrors.getFirst() |
| 229 | + : "Errors in imported files:\n- " + String.join("\n- ", importedFileErrors); |
| 230 | + importedFilesDiagnostic.setMessage(message); |
| 231 | + lspItems.addFirst(importedFilesDiagnostic); |
| 232 | + } |
| 233 | + } |
| 234 | + // TODO There may be diagnostics in DeferredDiagnosticStore, but that is a static list and |
| 235 | + // has no clear() method (i.e. outdated diagnostics remain visible) |
| 236 | + |
| 237 | + if (!documentVersionIsCurrent(snapshot)) { |
| 238 | + log.debug( |
| 239 | + "ABORT publishDiagnostics (after): outdated version {} of document {}", |
| 240 | + snapshot.version(), |
| 241 | + snapshot.uri() |
224 | 242 | ); |
225 | | - // labels (aka messages) per location |
226 | | - String labelsString = item.multiLocation.primaryLocation().labels().stream() |
227 | | - .map(vadl.error.Diagnostic.Message::content) |
228 | | - .collect(Collectors.joining("\n")); |
229 | | - // messages per Diagnostic - they may offer help or give additional notes |
230 | | - String messagesString = item.messages.stream() |
231 | | - .filter(m -> !m.type().equals(MsgType.PLAIN) |
232 | | - || !m.content().contains("parser got confused at this point")) |
233 | | - .map(vadl.error.Diagnostic.Message::content) |
234 | | - .collect(Collectors.joining("\n")); |
235 | | - |
236 | | - String fullMessage = item.reason + (!labelsString.isBlank() ? "\n" + labelsString : "") |
237 | | - + (!messagesString.isBlank() ? "\n" + messagesString : ""); |
238 | | - lspItem.setMessage(fullMessage); |
239 | | - lspItems.add(lspItem); |
| 243 | + return; |
240 | 244 | } |
241 | | - } |
242 | | - // TODO There may be diagnostics in DeferredDiagnosticStore, but that is a static list and |
243 | | - // has no clear() method (i.e. outdated diagnostics remain visible) |
244 | | - |
245 | | - if (!documentVersionIsCurrent(snapshot)) { |
246 | | - log.debug( |
247 | | - "ABORT publishDiagnostics (after): outdated version {} of document {}", |
248 | | - snapshot.version(), |
249 | | - snapshot.uri() |
250 | | - ); |
251 | | - return; |
252 | | - } |
253 | | - documentDependencies.setDependencies(snapshot.uri(), |
254 | | - snapshot.virtualFileSystem().getReadFiles()); |
255 | | - var data = new PublishDiagnosticsParams(snapshot.uri(), lspItems, snapshot.version()); |
256 | | - log.debug("<< publishDiagnostics ({}: {}", snapshot.uri(), data); |
257 | | - server.client().publishDiagnostics(data); |
| 245 | + documentDependencies.setDependencies(snapshot.uri(), |
| 246 | + snapshot.virtualFileSystem().getReadFiles()); |
| 247 | + var data = new PublishDiagnosticsParams(snapshot.uri(), lspItems, snapshot.version()); |
| 248 | + log.debug("<< publishDiagnostics ({}: {}", snapshot.uri(), data); |
| 249 | + server.client().publishDiagnostics(data); |
| 250 | + }); |
| 251 | + } |
| 252 | + |
| 253 | + private Diagnostic buildLspDiagnostic(vadl.error.Diagnostic vadlDiagnostic, |
| 254 | + Document.Snapshot snapshot) { |
| 255 | + // TODO Look into secondary locations too? Maybe as relatedInformation? Or to put a |
| 256 | + // diagnostic message there as well? |
| 257 | + SourceLocation location = vadlDiagnostic.multiLocation.primaryLocation().location(); |
| 258 | + |
| 259 | + Diagnostic lspDiagnostic = new Diagnostic(); |
| 260 | + lspDiagnostic.setRange(new Range( |
| 261 | + snapshot.calculateUtf16Position(location.begin(), false), |
| 262 | + snapshot.calculateUtf16Position(location.end(), true) |
| 263 | + )); |
| 264 | + lspDiagnostic.setSeverity( |
| 265 | + switch (vadlDiagnostic.level) { |
| 266 | + case ERROR -> DiagnosticSeverity.Error; |
| 267 | + case WARNING -> DiagnosticSeverity.Warning; |
| 268 | + } |
| 269 | + ); |
| 270 | + // labels (aka messages) per location |
| 271 | + String labelsString = vadlDiagnostic.multiLocation.primaryLocation().labels().stream() |
| 272 | + .map(vadl.error.Diagnostic.Message::content) |
| 273 | + .collect(Collectors.joining("\n")); |
| 274 | + // messages per Diagnostic - they may offer help or give additional notes |
| 275 | + String messagesString = vadlDiagnostic.messages.stream() |
| 276 | + .filter(m -> !m.type().equals(MsgType.PLAIN) |
| 277 | + || !m.content().contains("parser got confused at this point")) |
| 278 | + .map(vadl.error.Diagnostic.Message::content) |
| 279 | + .collect(Collectors.joining("\n")); |
| 280 | + |
| 281 | + String fullMessage = vadlDiagnostic.reason |
| 282 | + + (!labelsString.isBlank() ? "\n" + labelsString : "") |
| 283 | + + (!messagesString.isBlank() ? "\n" + messagesString : ""); |
| 284 | + lspDiagnostic.setMessage(fullMessage); |
| 285 | + |
| 286 | + return lspDiagnostic; |
| 287 | + } |
| 288 | + |
| 289 | + private String relativePath(Path path, Path relativeTo) { |
| 290 | + // relativeTo is a file, but we need its directory as base |
| 291 | + relativeTo = relativeTo.getParent() != null ? relativeTo.getParent() : relativeTo; |
| 292 | + return relativeTo.relativize(path).toString(); |
258 | 293 | } |
259 | 294 |
|
260 | 295 | private boolean documentVersionIsCurrent(Document.Snapshot snapshot) { |
|
0 commit comments