diff --git a/libs/natls/build.gradle b/libs/natls/build.gradle index 1d25c1498..e693fb33e 100644 --- a/libs/natls/build.gradle +++ b/libs/natls/build.gradle @@ -11,6 +11,7 @@ dependencies { testImplementation project(':testhelpers') testImplementation libraries.slf4j_nop + testImplementation 'org.awaitility:awaitility:4.2.0' } shadowJar { diff --git a/libs/natls/src/main/java/org/amshove/natls/config/InitilizationConfiguration.java b/libs/natls/src/main/java/org/amshove/natls/config/InitilizationConfiguration.java new file mode 100644 index 000000000..686c3f09c --- /dev/null +++ b/libs/natls/src/main/java/org/amshove/natls/config/InitilizationConfiguration.java @@ -0,0 +1,16 @@ +package org.amshove.natls.config; + +public class InitilizationConfiguration +{ + private boolean async; + + public boolean isAsync() + { + return async; + } + + public void setAsync(boolean async) + { + this.async = async; + } +} diff --git a/libs/natls/src/main/java/org/amshove/natls/config/LSConfiguration.java b/libs/natls/src/main/java/org/amshove/natls/config/LSConfiguration.java index 00942eff2..c09f565bd 100644 --- a/libs/natls/src/main/java/org/amshove/natls/config/LSConfiguration.java +++ b/libs/natls/src/main/java/org/amshove/natls/config/LSConfiguration.java @@ -5,6 +5,7 @@ public class LSConfiguration private CompletionConfiguration completion; private InlayHintsConfiguration inlayhints; + private InitilizationConfiguration initialization; public static LSConfiguration createDefault() { @@ -18,6 +19,10 @@ public static LSConfiguration createDefault() inlay.setShowAssignmentTargetType(false); config.setInlayhints(inlay); + var init = new InitilizationConfiguration(); + init.setAsync(false); + config.setInitialization(init); + return config; } @@ -40,4 +45,14 @@ public void setInlayhints(InlayHintsConfiguration inlayhints) { this.inlayhints = inlayhints; } + + public InitilizationConfiguration getInitialization() + { + return initialization; + } + + public void setInitialization(InitilizationConfiguration initialization) + { + this.initialization = initialization; + } } diff --git a/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageServer.java b/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageServer.java index a31a1683e..de39b6b61 100644 --- a/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageServer.java +++ b/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageServer.java @@ -1,13 +1,13 @@ package org.amshove.natls.languageserver; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import org.amshove.natls.App; import org.amshove.natls.codeactions.CodeActionRegistry; +import org.amshove.natls.config.LSConfiguration; import org.amshove.natls.markupcontent.MarkdownContentBuilder; import org.amshove.natls.markupcontent.MarkupContentBuilderFactory; -import org.amshove.natls.progress.ClientProgressType; -import org.amshove.natls.progress.MessageProgressMonitor; -import org.amshove.natls.progress.ProgressTasks; -import org.amshove.natls.progress.WorkDoneProgressMonitor; +import org.amshove.natls.progress.*; import org.amshove.natparse.natural.project.NaturalFileType; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -40,6 +40,11 @@ public CompletableFuture initialize(InitializeParams params) log.info("Starting initialization"); var capabilities = new ServerCapabilities(); + var config = params.getInitializationOptions() != null + ? new Gson().fromJson((JsonObject) params.getInitializationOptions(), LSConfiguration.class) + : LSConfiguration.createDefault(); + NaturalLanguageService.setConfiguration(config); + capabilities.setWorkspaceSymbolProvider(true); capabilities.setDocumentSymbolProvider(new DocumentSymbolOptions("NatLS")); var hoverOptions = new HoverOptions(); @@ -124,7 +129,7 @@ public CompletableFuture initialize(InitializeParams params) } var startTime = System.currentTimeMillis(); - progressMonitor.progress("Begin indexing", 5); + progressMonitor.progress("Begin Indexing", 10); languageService.indexProject(Paths.get(URI.create(params.getRootUri())), progressMonitor); workspaceService.setLanguageService(languageService); documentService.setLanguageService(languageService); @@ -141,6 +146,7 @@ public CompletableFuture initialize(InitializeParams params) progressMonitor.progress("Initialization done in %dms".formatted(endTime - startTime), 100); } + BackgroundTasks.initialize(client); var lspName = App.class.getPackage().getImplementationTitle(); var lspVersion = App.class.getPackage().getImplementationVersion(); var initEnd = System.currentTimeMillis(); @@ -149,6 +155,33 @@ public CompletableFuture initialize(InitializeParams params) }); } + @Override + public void initialized(InitializedParams params) + { + log.info("initialized() called"); + if (NaturalLanguageService.getConfig().getInitialization().isAsync()) + { + client.showMessage(ClientMessage.info("Background initialization started")); + var fileReferences = languageService.parseFileReferencesAsync(); + var dataAreas = languageService.preparseDataAreasAsync(); + CompletableFuture.allOf(fileReferences, dataAreas) + .whenComplete((v, error) -> + { + if (error == null) + { + client.showMessage(ClientMessage.info("Background initialization done")); + client.refreshCodeLenses(); + } + else + { + client.showMessage(ClientMessage.error("Background initialization failed")); + } + languageService.setInitialized(); + }); + } + log.info("initialized() returned"); + } + @Override public CompletableFuture shutdown() { @@ -204,7 +237,7 @@ public CompletableFuture reparseReferences(Object params) { if (languageService.isInitialized()) { - return languageService.parseFileReferences(); + languageService.parseFileReferencesAsync(); } return CompletableFuture.completedFuture(null); diff --git a/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageService.java b/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageService.java index 9c2ec0b84..d2c9b201c 100644 --- a/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageService.java +++ b/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalLanguageService.java @@ -16,7 +16,9 @@ import org.amshove.natls.hover.HoverContext; import org.amshove.natls.hover.HoverProvider; import org.amshove.natls.inlayhints.InlayHintProvider; +import org.amshove.natls.progress.BackgroundTasks; import org.amshove.natls.progress.IProgressMonitor; +import org.amshove.natls.progress.NullProgressMonitor; import org.amshove.natls.progress.ProgressTasks; import org.amshove.natls.project.LanguageServerFile; import org.amshove.natls.project.LanguageServerProject; @@ -54,10 +56,12 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; import java.util.stream.Collectors; public class NaturalLanguageService implements LanguageClientAware { + private static final Logger log = Logger.getAnonymousLogger(); private static final CodeActionRegistry codeActionRegistry = CodeActionRegistry.INSTANCE; private NaturalProject project; // TODO: Replace private LanguageServerProject languageServerProject; @@ -79,26 +83,33 @@ public class NaturalLanguageService implements LanguageClientAware public void indexProject(Path workspaceRoot, IProgressMonitor progressMonitor) { this.workspaceRoot = workspaceRoot; + progressMonitor.progress("Reading project file", 20); var projectFile = new ActualFilesystem().findNaturalProjectFile(workspaceRoot); if (projectFile.isEmpty()) { throw new LanguageServerException("Could not load Natural project. .natural or _naturalBuild not found"); } var project = new BuildFileProjectReader().getNaturalProject(projectFile.get()); + progressMonitor.progress("Parsing .editorconfig", 30); var editorconfigPath = projectFile.get().getParent().resolve(".editorconfig"); if (editorconfigPath.toFile().exists()) { loadEditorConfig(editorconfigPath); } + progressMonitor.progress("Indexing Natural files", 40); var indexer = new NaturalProjectFileIndexer(); indexer.indexProject(project); this.project = project; languageServerProject = LanguageServerProject.fromProject(project); - parseFileReferences(progressMonitor); - preParseDataAreas(progressMonitor); - initialized = true; + if (!getConfig().getInitialization().isAsync()) + { + parseFileReferencesAsync(progressMonitor); + preParseDataAreas(progressMonitor); + initialized = true; + } hoverProvider = new HoverProvider(); + progressMonitor.progress("Initializing Services", 80); completionProvider = new CompletionProvider(new SnippetEngine(languageServerProject), hoverProvider); } @@ -406,9 +417,18 @@ public void parseAll(IProgressMonitor monitor) monitor.progress("Done", 100); } - public CompletableFuture parseFileReferences() + public CompletableFuture parseFileReferencesAsync() + { + // BackgroundTasks can't have a ProgressMonitor, because the progress would spam the communication + // and make the client wait for finish of the progress before sending new requests. + return BackgroundTasks.enqueue(() -> parseFileReferencesAsync(new NullProgressMonitor()), "Parsing file references"); + } + + public CompletableFuture preparseDataAreasAsync() { - return ProgressTasks.startNewVoid("Parsing file references", client, this::parseFileReferences); + // BackgroundTasks can't have a ProgressMonitor, because the progress would spam the communication + // and make the client wait for finish of the progress before sending new requests. + return BackgroundTasks.enqueue(() -> preParseDataAreas(new NullProgressMonitor()), "Parsing Data Areas"); } private void preParseDataAreas(IProgressMonitor monitor) @@ -416,11 +436,12 @@ private void preParseDataAreas(IProgressMonitor monitor) monitor.progress("Preparsing data areas", 0); languageServerProject.libraries().stream().flatMap(l -> l.files().stream().filter(f -> f.getType() == NaturalFileType.LDA || f.getType() == NaturalFileType.PDA)) .parallel() - .peek(f -> monitor.progress(f.getReferableName(), 0)) + .peek(f -> monitor.progress("Parsing data areas %s".formatted(f.getReferableName()))) .forEach(f -> f.parse(ParseStrategy.WITHOUT_CALLERS)); + log.info("preParseDataAreas done"); } - private void parseFileReferences(IProgressMonitor monitor) + private void parseFileReferencesAsync(IProgressMonitor monitor) { monitor.progress("Clearing current references", 0); var parser = new ModuleReferenceParser(); @@ -440,7 +461,7 @@ private void parseFileReferences(IProgressMonitor monitor) break; } var percentageDone = 100L * processedFiles / allFilesCount; - monitor.progress("Indexing %s.%s".formatted(library.name(), file.getReferableName()), (int) percentageDone); + monitor.progress("Parsing references %s.%s".formatted(library.name(), file.getReferableName()), (int) percentageDone); switch (file.getType()) { case PROGRAM, SUBPROGRAM, SUBROUTINE, FUNCTION, COPYCODE -> parser.parseReferences(file); @@ -450,6 +471,7 @@ private void parseFileReferences(IProgressMonitor monitor) processedFiles++; } } + log.info("parseFileReferences done"); } public boolean isInitialized() @@ -760,6 +782,11 @@ public LanguageServerProject getProject() return languageServerProject; } + public void setInitialized() + { + this.initialized = true; + } + private static T extractJsonObject(Object obj, Class clazz) { if (clazz.isInstance(obj)) diff --git a/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalWorkspaceService.java b/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalWorkspaceService.java index 989fecfc3..84d9ce89a 100644 --- a/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalWorkspaceService.java +++ b/libs/natls/src/main/java/org/amshove/natls/languageserver/NaturalWorkspaceService.java @@ -33,7 +33,7 @@ public void didChangeConfiguration(DidChangeConfigurationParams params) var settings = (JsonObject) params.getSettings(); var jsonObject = settings.getAsJsonObject("natls"); var configuration = new Gson().fromJson(jsonObject, LSConfiguration.class); - languageService.setConfiguration(configuration); + NaturalLanguageService.setConfiguration(configuration); } @Override diff --git a/libs/natls/src/main/java/org/amshove/natls/progress/BackgroundTasks.java b/libs/natls/src/main/java/org/amshove/natls/progress/BackgroundTasks.java new file mode 100644 index 000000000..8cf469db0 --- /dev/null +++ b/libs/natls/src/main/java/org/amshove/natls/progress/BackgroundTasks.java @@ -0,0 +1,45 @@ +package org.amshove.natls.progress; + +import org.amshove.natls.languageserver.ClientMessage; +import org.eclipse.lsp4j.services.LanguageClient; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BackgroundTasks +{ + private static final Logger log = Logger.getAnonymousLogger(); + private static final ExecutorService workpool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + private static LanguageClient client; + + private BackgroundTasks() + {} + + public static CompletableFuture enqueue(Runnable runnable, String description) + { + var future = new CompletableFuture(); + workpool.submit(() -> + { + try + { + runnable.run(); + future.complete(null); + } + catch (Exception e) + { + log.log(Level.SEVERE, "Background task <%s> threw an exception".formatted(description), e); + client.showMessage(ClientMessage.error("Background task <%s> failed".formatted(description))); + future.completeExceptionally(e); + } + }); + return future; + } + + public static void initialize(LanguageClient client) + { + BackgroundTasks.client = client; + } +} diff --git a/libs/natls/src/main/java/org/amshove/natls/progress/IProgressMonitor.java b/libs/natls/src/main/java/org/amshove/natls/progress/IProgressMonitor.java index e7fb77ca9..93263af4a 100644 --- a/libs/natls/src/main/java/org/amshove/natls/progress/IProgressMonitor.java +++ b/libs/natls/src/main/java/org/amshove/natls/progress/IProgressMonitor.java @@ -2,7 +2,15 @@ public interface IProgressMonitor { + /** + * Sends a progress notification with the given percentage. + */ void progress(String message, int percentage); + /** + * Increments the previous percentage (if below 100%) and sends a progress message. + */ + void progress(String message); + boolean isCancellationRequested(); } diff --git a/libs/natls/src/main/java/org/amshove/natls/progress/MessageProgressMonitor.java b/libs/natls/src/main/java/org/amshove/natls/progress/MessageProgressMonitor.java index 275249c9d..ceed2bde5 100644 --- a/libs/natls/src/main/java/org/amshove/natls/progress/MessageProgressMonitor.java +++ b/libs/natls/src/main/java/org/amshove/natls/progress/MessageProgressMonitor.java @@ -8,12 +8,19 @@ public class MessageProgressMonitor implements IProgressMonitor private final LanguageClient client; private int lastTenthPercentage = 0; + private int previousPercentage = 0; public MessageProgressMonitor(LanguageClient client) { this.client = client; } + @Override + public void progress(String message) + { + progress(message, ++previousPercentage); + } + @Override public void progress(String message, int percentage) { diff --git a/libs/natls/src/main/java/org/amshove/natls/progress/NullProgressMonitor.java b/libs/natls/src/main/java/org/amshove/natls/progress/NullProgressMonitor.java index bc9158090..d3ccc2f66 100644 --- a/libs/natls/src/main/java/org/amshove/natls/progress/NullProgressMonitor.java +++ b/libs/natls/src/main/java/org/amshove/natls/progress/NullProgressMonitor.java @@ -5,7 +5,13 @@ public class NullProgressMonitor implements IProgressMonitor @Override public void progress(String message, int percentage) { + // intentionally empty + } + @Override + public void progress(String message) + { + // intentionally empty } @Override diff --git a/libs/natls/src/main/java/org/amshove/natls/progress/WorkDoneProgressMonitor.java b/libs/natls/src/main/java/org/amshove/natls/progress/WorkDoneProgressMonitor.java index a8c9bfe4b..f0dfa7692 100644 --- a/libs/natls/src/main/java/org/amshove/natls/progress/WorkDoneProgressMonitor.java +++ b/libs/natls/src/main/java/org/amshove/natls/progress/WorkDoneProgressMonitor.java @@ -9,6 +9,7 @@ public class WorkDoneProgressMonitor implements IProgressMonitor { private final String taskId; private final LanguageClient client; + private int previousPercentage; public WorkDoneProgressMonitor(String taskId, LanguageClient client) { @@ -16,6 +17,12 @@ public WorkDoneProgressMonitor(String taskId, LanguageClient client) this.client = client; } + @Override + public void progress(String message) + { + progress(message, (previousPercentage < 100 ? ++previousPercentage : previousPercentage)); + } + @Override public void progress(String message, int percentage) { @@ -25,6 +32,7 @@ public void progress(String message, int percentage) report.setCancellable(true); client.notifyProgress(new ProgressParams(Either.forLeft(taskId), Either.forLeft(report))); + previousPercentage = percentage; } @Override diff --git a/libs/natls/src/test/java/org/amshove/natls/initialization/AsyncInitializationShould.java b/libs/natls/src/test/java/org/amshove/natls/initialization/AsyncInitializationShould.java new file mode 100644 index 000000000..9f80ae41a --- /dev/null +++ b/libs/natls/src/test/java/org/amshove/natls/initialization/AsyncInitializationShould.java @@ -0,0 +1,54 @@ +package org.amshove.natls.initialization; + +import org.amshove.natls.testlifecycle.LanguageServerTest; +import org.amshove.natls.testlifecycle.LspProjectName; +import org.amshove.natls.testlifecycle.LspTestContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.awaitility.Awaitility.await; + +class AsyncInitializationShould extends LanguageServerTest +{ + private LspTestContext context; + + @BeforeEach + void setUp(@LspProjectName(value = "modrefparser", config = "{\"initialization\": { \"async\": true}}") LspTestContext context) + { + this.context = context; + } + + @Test + void initializeWithBackgroundTasks() + { + waitForInitialization(); + var messages = context.getClient().getShownMessages(); + assertThat(messages) + .containsSubsequence( + "Background initialization started", + "Background initialization done" + ); + } + + @Test + void callRefreshCodeLensesAfterInitialization() + { + waitForInitialization(); + assertThat(getContext().getClient().getRefreshCodeLensesCalls()) + .isPositive(); + } + + private void waitForInitialization() + { + await().atMost(3, TimeUnit.SECONDS).until(() -> getContext().languageService().isInitialized()); + } + + @Override + protected LspTestContext getContext() + { + return context; + } +} diff --git a/libs/natls/src/test/java/org/amshove/natls/initialization/LanguageServerInitializationShould.java b/libs/natls/src/test/java/org/amshove/natls/initialization/LanguageServerInitializationShould.java new file mode 100644 index 000000000..2cd0b29b8 --- /dev/null +++ b/libs/natls/src/test/java/org/amshove/natls/initialization/LanguageServerInitializationShould.java @@ -0,0 +1,51 @@ +package org.amshove.natls.initialization; + +import org.amshove.natls.testlifecycle.LanguageServerTest; +import org.amshove.natls.testlifecycle.LspProjectName; +import org.amshove.natls.testlifecycle.LspTestContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class LanguageServerInitializationShould extends LanguageServerTest +{ + private LspTestContext context; + + @BeforeEach + void setUp(@LspProjectName(value = "modrefparser") LspTestContext context) + { + this.context = context; + } + + @Test + void fullyInitializeInInitializationRequest() + { + assertThat(getContext().languageService().isInitialized()) + .isTrue(); + } + + @Test + void sendMessagesAboutInitialization() + { + assertThat(getContext().getClient().getShownMessages()) + .containsSubsequence( + "10% Begin Indexing", + "20% Reading project file" + // ... + ); + } + + @Test + void parseFileReferences() + { + assertThat(getContext().getClient().getShownMessages()) + .anyMatch(m -> m.contains("Parsing references LIBONE.")); + } + + @Override + protected LspTestContext getContext() + { + return context; + } +} diff --git a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectName.java b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectName.java index d3eedcba5..37d1a20b6 100644 --- a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectName.java +++ b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectName.java @@ -10,4 +10,6 @@ public @interface LspProjectName { String value(); + + String config() default ""; } diff --git a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectNameResolver.java b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectNameResolver.java index 924e1d0c3..159286231 100644 --- a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectNameResolver.java +++ b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspProjectNameResolver.java @@ -1,5 +1,7 @@ package org.amshove.natls.testlifecycle; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import org.amshove.natls.languageserver.NaturalLanguageServer; import org.amshove.testhelpers.NaturalProjectResourceResolver; import org.eclipse.lsp4j.*; @@ -31,7 +33,8 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte { try { - var projectName = parameterContext.getParameter().getAnnotation(LspProjectName.class).value(); + var annotation = parameterContext.getParameter().getAnnotation(LspProjectName.class); + var projectName = annotation.value(); var tempDir = new NaturalProjectResourceResolver.AutoDeleteTempDirectory(projectName); extensionContext.getStore(NAMESPACE).put("tempdir", tempDir); TestProjectLoader.loadProjectFromResources(tempDir.getPath(), projectName); @@ -41,8 +44,16 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte params.setWorkspaceFolders(List.of(new WorkspaceFolder(tempDir.getPath().toUri().toString()))); params.setRootUri(tempDir.getPath().toUri().toString()); var client = new StubClient(); + + if (!annotation.config().isEmpty()) + { + var config = new Gson().fromJson(annotation.config(), JsonObject.class); + params.setInitializationOptions(config); + } + server.connect(client); server.initialize(params).get(1, TimeUnit.MINUTES); + server.initialized(new InitializedParams()); return new LspTestContext(server.getLanguageService().getProject(), client, server, server.getLanguageService()); } catch (InterruptedException | ExecutionException | TimeoutException e) diff --git a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspTestContext.java b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspTestContext.java index cb2f6260b..d6885c35f 100644 --- a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspTestContext.java +++ b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/LspTestContext.java @@ -28,4 +28,9 @@ public LanguageServerProject project() { return languageService.getProject(); } + + public StubClient getClient() + { + return client; + } } diff --git a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/StubClient.java b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/StubClient.java index fd899299d..b1c712cfc 100644 --- a/libs/natls/src/test/java/org/amshove/natls/testlifecycle/StubClient.java +++ b/libs/natls/src/test/java/org/amshove/natls/testlifecycle/StubClient.java @@ -3,11 +3,15 @@ import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.services.LanguageClient; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; public class StubClient implements LanguageClient { + private final List shownMessages = new ArrayList<>(); + private int refreshCodeLensesCalls = 0; + @Override public void telemetryEvent(Object object) { @@ -23,7 +27,7 @@ public void publishDiagnostics(PublishDiagnosticsParams diagnostics) @Override public void showMessage(MessageParams messageParams) { - + shownMessages.add(new ShownMessage(messageParams.getType(), messageParams.getMessage())); } @Override @@ -101,6 +105,23 @@ public CompletableFuture refreshSemanticTokens() @Override public CompletableFuture refreshCodeLenses() { - return new CompletableFuture<>(); + refreshCodeLensesCalls++; + return CompletableFuture.completedFuture(null); + } + + public List getShownMessages() + { + return shownMessages + .stream() + .map(ShownMessage::message) + .toList(); } + + public int getRefreshCodeLensesCalls() + { + return refreshCodeLensesCalls; + } + + record ShownMessage(MessageType type, String message) + {} }