diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/LanguageServerWrapperTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/LanguageServerWrapperTest.java index 72f6223b9..4c2938792 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/LanguageServerWrapperTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/LanguageServerWrapperTest.java @@ -18,9 +18,10 @@ import static org.junit.Assert.assertTrue; import java.util.Collection; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -29,6 +30,7 @@ import org.eclipse.lsp4e.LanguageServerWrapper; import org.eclipse.lsp4e.LanguageServiceAccessor; import org.eclipse.lsp4e.test.utils.AbstractTestWithProject; +import org.eclipse.lsp4e.test.utils.MockConnectionProviderMultiRootFolders; import org.eclipse.lsp4e.test.utils.TestUtils; import org.eclipse.ui.IEditorPart; import org.junit.Before; @@ -73,44 +75,70 @@ public void testConnect() throws Exception { * @see https://github.com/eclipse/lsp4e/pull/688 */ @Test - public void testStopAndActive() throws CoreException, AssertionError, InterruptedException, ExecutionException { - if (System.getProperty("os.name").toLowerCase().startsWith("windows") || System.getenv("JENKINS_URL") != null) { - // FIXME temporarily disabling test on Windows and Jenkins because of https://github.com/eclipse/lsp4e/issues/1103 - System.err.println("Temporarily skipping test execution. See https://github.com/eclipse/lsp4e/issues/1103"); - return; - } + public void testStartStopAndActive() throws CoreException, AssertionError, InterruptedException, ExecutionException { + final int testCount= 100; + + MockConnectionProviderMultiRootFolders.resetCounts(); + IFile testFile1 = TestUtils.createFile(project, "shouldUseExtension.lsptWithMultiRoot", ""); IEditorPart editor1 = TestUtils.openEditor(testFile1); @NonNull Collection wrappers = LanguageServiceAccessor.getLSWrappers(testFile1, request -> true); assertEquals(1, wrappers.size()); LanguageServerWrapper wrapper = wrappers.iterator().next(); - final var started = new CountDownLatch(1); + + final int startingActiveThreads= ForkJoinPool.commonPool().getActiveThreadCount(); + + CompletableFuture startStop= CompletableFuture.runAsync(() -> { + for (int i= 0; i < testCount - 1; i++) { + wrapper.stop(); + wrapper.start(); + } + wrapper.stop(); + }); + + CompletableFuture testActive= CompletableFuture.runAsync(() -> { + while (!startStop.isDone()) { + wrapper.isActive(); + } + }); + try { - var startStopJob = ForkJoinPool.commonPool().submit(() -> { - started.countDown(); - while (!Thread.interrupted()) { - wrapper.stop(); - wrapper.start(); - } - }); + startStop.get(30, TimeUnit.SECONDS); + try { - started.await(); - for (int i = 0; i < 10000000; i++) { - // Should not throw - wrapper.isActive(); - if (startStopJob.isDone()) { - throw new AssertionError("Job should run indefinitely"); - } - } - } finally { - startStopJob.cancel(true); - if (!startStopJob.isCancelled()) { - startStopJob.get(); - } + testActive.get(1, TimeUnit.SECONDS); + } catch (Exception e) { + throw new AssertionError("testActive terminated with exception"); } + + } catch (Exception e) { + throw new AssertionError("test job terminated with exception"); + //TODO improve diagnostics: check for timeout + } finally { TestUtils.closeEditor(editor1, false); } + + // Give the various futures created time to execute. ForkJoinPool.commonPool.awaitQuiescence does not + // work here - other tests may not have cleaned up correctly. + long timeOut= System.currentTimeMillis() + 60_000; + do { + try { + Thread.sleep(1_000); + } catch (InterruptedException e) { + //ignore + } + } while (ForkJoinPool.commonPool().getActiveThreadCount() > startingActiveThreads && System.currentTimeMillis() < timeOut); + + if (ForkJoinPool.commonPool().getActiveThreadCount() > startingActiveThreads) { + throw new AssertionError("timeout waiting for ForkJoinPool.commonPool to go quiet"); + } else { + + Integer cpStartCount= MockConnectionProviderMultiRootFolders.getStartCount(); + Integer cpStopCount= MockConnectionProviderMultiRootFolders.getStopCount(); + + assertEquals("startCount == stopCount", cpStartCount, cpStopCount); + } } } diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/MockConnectionProviderMultiRootFolders.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/MockConnectionProviderMultiRootFolders.java index 35cdb9c89..a0036fede 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/MockConnectionProviderMultiRootFolders.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/MockConnectionProviderMultiRootFolders.java @@ -22,6 +22,14 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; import org.eclipse.lsp4e.server.StreamConnectionProvider; import org.eclipse.lsp4e.tests.mock.MockLanguageServer; @@ -30,32 +38,65 @@ import org.eclipse.lsp4j.launch.LSPLauncher; import org.eclipse.lsp4j.services.LanguageClient; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + public class MockConnectionProviderMultiRootFolders implements StreamConnectionProvider { + private static final Logger LOG = Logger.getLogger(MockConnectionProviderMultiRootFolders.class.getName()); + + static ExecutorService sharedExecutor = new ThreadPoolExecutor(0, Runtime.getRuntime().availableProcessors(), 0, + TimeUnit.SECONDS, new LinkedBlockingQueue(), + new ThreadFactoryBuilder().setNameFormat("mock-connection-provider-%d").build()); + + static private AtomicInteger startCount = new AtomicInteger(0); + static private AtomicInteger stopCount = new AtomicInteger(0); - private InputStream clientInputStream ; + static public void resetCounts() { + startCount.set(0); + stopCount.set(0); + } + + static public int getStartCount() { + return startCount.get(); + } + + static public int getStopCount() { + return stopCount.get(); + } + + private InputStream clientInputStream; private OutputStream clientOutputStream; private InputStream errorStream; private Collection streams = new ArrayList<>(4); + private Future launcherFuture; @Override public void start() throws IOException { - Pipe serverOutputToClientInput = Pipe.open(); - Pipe clientOutputToServerInput = Pipe.open(); - errorStream = new ByteArrayInputStream("Error output on console".getBytes(StandardCharsets.UTF_8)); - - InputStream serverInputStream = Channels.newInputStream(clientOutputToServerInput.source()); - OutputStream serverOutputStream = Channels.newOutputStream(serverOutputToClientInput.sink()); - Launcher launcher = LSPLauncher.createServerLauncher(MockLanguageServerMultiRootFolders.INSTANCE, serverInputStream, - serverOutputStream); - clientInputStream = Channels.newInputStream(serverOutputToClientInput.source()); - clientOutputStream = Channels.newOutputStream(clientOutputToServerInput.sink()); - launcher.startListening(); - MockLanguageServer.INSTANCE.addRemoteProxy(launcher.getRemoteProxy()); - streams.add(clientInputStream); - streams.add(clientOutputStream); - streams.add(serverInputStream); - streams.add(serverOutputStream); - streams.add(errorStream); + try { + Pipe serverOutputToClientInput = Pipe.open(); + Pipe clientOutputToServerInput = Pipe.open(); + errorStream = new ByteArrayInputStream("Error output on console".getBytes(StandardCharsets.UTF_8)); + + InputStream serverInputStream = Channels.newInputStream(clientOutputToServerInput.source()); + OutputStream serverOutputStream = Channels.newOutputStream(serverOutputToClientInput.sink()); + + Launcher launcher = LSPLauncher.createServerLauncher( + MockLanguageServerMultiRootFolders.INSTANCE, serverInputStream, serverOutputStream, sharedExecutor, + (c) -> c); + + clientInputStream = Channels.newInputStream(serverOutputToClientInput.source()); + clientOutputStream = Channels.newOutputStream(clientOutputToServerInput.sink()); + launcherFuture = launcher.startListening(); + MockLanguageServer.INSTANCE.addRemoteProxy(launcher.getRemoteProxy()); + streams.add(clientInputStream); + streams.add(clientOutputStream); + streams.add(serverInputStream); + streams.add(serverOutputStream); + streams.add(errorStream); + + startCount.incrementAndGet(); + } catch (Exception x) { + LOG.log(Level.SEVERE, "MockConnectionProvider#start", x); + } } @Override @@ -75,5 +116,9 @@ public InputStream getErrorStream() { @Override public void stop() { + stopCount.incrementAndGet(); + if (launcherFuture != null) { + launcherFuture.cancel(true); + } } } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java index 5e07ece93..f45838795 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java @@ -39,7 +39,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; @@ -152,27 +151,60 @@ public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) { }; + private static class LanguageServerContext { + boolean cancelled = false; + + @Nullable Future launcherFuture; + @Nullable StreamConnectionProvider lspStreamProvider; + @Nullable LanguageServer languageServer; + + synchronized void close() { + if (languageServer != null) { + CompletableFuture shutdown = languageServer.shutdown(); + try { + shutdown.get(5, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } catch (Exception ex) { + LanguageServerPlugin.logError(ex.getClass().getSimpleName() + " occurred during shutdown of " //$NON-NLS-1$ + + languageServer, ex); + } + } + + if (launcherFuture != null) { + launcherFuture.cancel(true); + } + + if (languageServer != null) { + languageServer.exit(); + } + + if (lspStreamProvider != null) { + lspStreamProvider.stop(); + } + + } + } + public final LanguageServerDefinition serverDefinition; public final @Nullable IProject initialProject; protected Map connectedDocuments; protected final @Nullable IPath initialPath; protected final InitializeParams initParams = new InitializeParams(); - protected @Nullable StreamConnectionProvider lspStreamProvider; - private @Nullable Future launcherFuture; private @Nullable CompletableFuture<@Nullable Void> initializeFuture; private final AtomicReference<@Nullable IProgressMonitor> initializeFutureMonitorRef = new AtomicReference<>(); private final int initializeFutureNumberOfStages = 7; - private @Nullable LanguageServer languageServer; private @Nullable LanguageClientImpl languageClient; private @Nullable ServerCapabilities serverCapabilities; private final Timer timer = new Timer("Stop Language Server Task Processor"); //$NON-NLS-1$ private @Nullable TimerTask stopTimerTask; - private AtomicBoolean stopping = new AtomicBoolean(false); private final ExecutorService dispatcher; private final ExecutorService listener; + private LanguageServerContext context = new LanguageServerContext(); + /** * Map containing unregistration handlers for dynamic capability registrations. */ @@ -273,7 +305,7 @@ public synchronized void restart() { */ private synchronized void start(boolean forceRestart) { final var filesToReconnect = new HashMap(); - if (this.languageServer != null) { + if (this.context.languageServer != null) { if (isActive() && !forceRestart) { return; } else { @@ -286,79 +318,90 @@ private synchronized void start(boolean forceRestart) { if (this.initializeFuture == null || forceRestart) { final URI rootURI = getRootURI(); final Job job = createInitializeLanguageServerJob(); - this.launcherFuture = new CompletableFuture<>(); + final LanguageServerContext workingContext = context; + this.initializeFuture = CompletableFuture.supplyAsync(() -> { - advanceInitializeFutureMonitor(); - final StreamConnectionProvider lspStreamProvider; - if (LoggingStreamConnectionProviderProxy.shouldLog(serverDefinition.id)) { - lspStreamProvider = this.lspStreamProvider = new LoggingStreamConnectionProviderProxy( - serverDefinition.createConnectionProvider(), serverDefinition.id); - } else { - lspStreamProvider = this.lspStreamProvider = serverDefinition.createConnectionProvider(); - } - initParams.setInitializationOptions(lspStreamProvider.getInitializationOptions(rootURI)); - try { - lspStreamProvider.start(); - } catch (IOException e) { - throw new UncheckedIOException(e); + synchronized (workingContext) { + markInitializationProgress(workingContext); + final StreamConnectionProvider lspStreamProvider; + if (LoggingStreamConnectionProviderProxy.shouldLog(serverDefinition.id)) { + lspStreamProvider = workingContext.lspStreamProvider = new LoggingStreamConnectionProviderProxy( + serverDefinition.createConnectionProvider(), serverDefinition.id); + } else { + lspStreamProvider = workingContext.lspStreamProvider = serverDefinition + .createConnectionProvider(); + } + initParams.setInitializationOptions(lspStreamProvider.getInitializationOptions(rootURI)); + try { + lspStreamProvider.start(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } return null; }).thenRun(() -> { - advanceInitializeFutureMonitor(); - final var languageClient = this.languageClient = serverDefinition.createLanguageClient(); - - initParams.setProcessId((int) ProcessHandle.current().pid()); + synchronized (workingContext) { + markInitializationProgress(workingContext); + final var languageClient = this.languageClient = serverDefinition.createLanguageClient(); - if (rootURI != null) { - initParams.setRootUri(rootURI.toString()); - initParams.setRootPath(rootURI.getPath()); - } + initParams.setProcessId((int) ProcessHandle.current().pid()); - UnaryOperator wrapper = consumer -> (message -> { - logMessage(message); - consumer.consume(message); - final var lspStreamProvider = this.lspStreamProvider; - if (lspStreamProvider != null && isActive() && languageServer != null) { - lspStreamProvider.handleMessage(message, languageServer, rootURI); + if (rootURI != null) { + initParams.setRootUri(rootURI.toString()); + initParams.setRootPath(rootURI.getPath()); } - }); - initParams.setWorkspaceFolders(getRelevantWorkspaceFolders()); - Launcher launcher = serverDefinition.createLauncherBuilder() // - .setLocalService(languageClient)// - .setRemoteInterface(serverDefinition.getServerInterface())// - .setInput(castNonNull(lspStreamProvider).getInputStream())// - .setOutput(castNonNull(lspStreamProvider).getOutputStream())// - .setExecutorService(listener)// - .wrapMessages(wrapper)// - .create(); - final var languageServer = this.languageServer = launcher.getRemoteProxy(); - languageClient.connect(languageServer, this); - this.launcherFuture = launcher.startListening(); - }) - .thenCompose(unused -> { - advanceInitializeFutureMonitor(); - return initServer(rootURI); - }) - .thenAccept(res -> { - advanceInitializeFutureMonitor(); - serverCapabilities = res.getCapabilities(); - this.initiallySupportsWorkspaceFolders = supportsWorkspaceFolders(serverCapabilities); + + UnaryOperator wrapper = consumer -> message -> { + logMessage(message); + consumer.consume(message); + final var lspStreamProvider = workingContext.lspStreamProvider; + final var languageServer = workingContext.languageServer; + if (lspStreamProvider != null && isActive() && languageServer != null) { + lspStreamProvider.handleMessage(message, languageServer, rootURI); + } + }; + initParams.setWorkspaceFolders(getRelevantWorkspaceFolders()); + final var lspStreamProvider= castNonNull(workingContext.lspStreamProvider); + Launcher launcher = serverDefinition.createLauncherBuilder() // + .setLocalService(languageClient)// + .setRemoteInterface(serverDefinition.getServerInterface())// + .setInput(lspStreamProvider.getInputStream())// + .setOutput(lspStreamProvider.getOutputStream())// + .setExecutorService(listener)// + .wrapMessages(wrapper)// + .create(); + final var languageServer = workingContext.languageServer = launcher.getRemoteProxy(); + languageClient.connect(languageServer, this); + workingContext.launcherFuture = launcher.startListening(); + } + }).thenCompose(unused -> { + markInitializationProgress(workingContext); + return initServer(rootURI); + }).thenAccept(res -> { + synchronized (workingContext) { + markInitializationProgress(workingContext); + serverCapabilities = res.getCapabilities(); + this.initiallySupportsWorkspaceFolders = supportsWorkspaceFolders(serverCapabilities); + } }).thenRun(() -> { - advanceInitializeFutureMonitor(); - castNonNull(languageServer).initialized(new InitializedParams()); + synchronized (workingContext) { + markInitializationProgress(workingContext); + castNonNull(workingContext.languageServer).initialized(new InitializedParams()); + } }).thenRun(() -> { - advanceInitializeFutureMonitor(); - final Map toReconnect = filesToReconnect; - castNonNull(initializeFuture).thenRunAsync(() -> { - watchProjects(); - for (Entry fileToReconnect : toReconnect.entrySet()) { - connect(fileToReconnect.getKey(), fileToReconnect.getValue()); - } - }); - FileBuffers.getTextFileBufferManager().addFileBufferListener(fileBufferListener); - advanceInitializeFutureMonitor(); + synchronized (workingContext) { + markInitializationProgress(workingContext); + final Map toReconnect = filesToReconnect; + castNonNull(initializeFuture).thenRunAsync(() -> { + watchProjects(); + for (Entry fileToReconnect : toReconnect.entrySet()) { + connect(fileToReconnect.getKey(), fileToReconnect.getValue()); + } + }); + FileBuffers.getTextFileBufferManager().addFileBufferListener(fileBufferListener); + } }).exceptionally(e -> { - shutdown(); + shutdown(workingContext); final Throwable cause = e.getCause(); if (cause instanceof CancellationException c) { throw c; @@ -368,15 +411,19 @@ private synchronized void start(boolean forceRestart) { } }); - if (this.initializeFuture.isCompletedExceptionally()) { - // This might happen if an exception occurred and stop() was called before this.initializeFuture was assigned - this.initializeFuture = null; - } else { + if (!this.initializeFuture.isCompletedExceptionally()) { job.schedule(); } } } + private void markInitializationProgress(LanguageServerContext context) { + if (context.cancelled) { + throw new CancellationException(); + } + advanceInitializeFutureMonitor(); + } + private void advanceInitializeFutureMonitor() { final var initializeFutureMonitor = initializeFutureMonitorRef.get(); if (initializeFutureMonitor != null) { @@ -428,17 +475,18 @@ private CompletableFuture initServer(final @Nullable URI rootU workspaceClientCapabilities, textDocumentClientCapabilities, windowClientCapabilities, - castNonNull(lspStreamProvider).getExperimentalFeaturesPOJO())); + castNonNull(context.lspStreamProvider).getExperimentalFeaturesPOJO())); initParams.setClientInfo(getClientInfo(name)); - initParams.setTrace(castNonNull(lspStreamProvider).getTrace(rootURI)); + initParams.setTrace(castNonNull(context.lspStreamProvider).getTrace(rootURI)); // no then...Async future here as we want this chain of operation to be sequential and "atomic"-ish - return castNonNull(languageServer).initialize(initParams); + return castNonNull(context.languageServer).initialize(initParams); + //FIXME race: this.context may not be what it is expected to be, should be parameter } @Nullable public ProcessHandle getProcessHandle() { - return Adapters.adapt(lspStreamProvider, ProcessHandle.class); + return Adapters.adapt(context.lspStreamProvider, ProcessHandle.class); } private ClientInfo getClientInfo(String name) { @@ -486,7 +534,7 @@ private void logMessage(Message message) { * @return whether the underlying connection to language server is still active */ public synchronized boolean isActive() { - final var launcherFuture = this.launcherFuture; + final var launcherFuture = context.launcherFuture; return launcherFuture != null && !launcherFuture.isDone(); } @@ -528,22 +576,25 @@ public void run() { * @return True if this is the wrapper for the given server */ boolean isWrapperFor(LanguageServer server) { - return server == this.languageServer; + return server == context.languageServer; } public synchronized void stop() { - if (this.initializeFuture != null) { + if (initializeFuture != null) { initializeFuture.cancel(true); - this.initializeFuture= null; + initializeFuture= null; } - shutdown(); - } - private void shutdown() { - final boolean alreadyStopping = this.stopping.getAndSet(true); - if (alreadyStopping) { - return; + LanguageServerContext contextToStop = context; + context = new LanguageServerContext(); + synchronized(contextToStop) { + contextToStop.cancelled = true; } + + shutdown(contextToStop); + } + + private void shutdown(LanguageServerContext workingContext) { removeStopTimerTask(); if (this.languageClient != null) { @@ -553,46 +604,15 @@ private void shutdown() { this.serverCapabilities = null; this.dynamicRegistrations.clear(); - final Future serverFuture = this.launcherFuture; - final StreamConnectionProvider provider = this.lspStreamProvider; - final LanguageServer languageServerInstance = this.languageServer; ResourcesPlugin.getWorkspace().removeResourceChangeListener(workspaceFolderUpdater); - Runnable shutdownKillAndStopFutureAndProvider = () -> { - if (languageServerInstance != null) { - CompletableFuture shutdown = languageServerInstance.shutdown(); - try { - shutdown.get(5, TimeUnit.SECONDS); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } catch (Exception ex) { - LanguageServerPlugin.logError(ex.getClass().getSimpleName() + " occurred during shutdown of " + languageServerInstance, ex); //$NON-NLS-1$ - } - } - - if (serverFuture != null) { - serverFuture.cancel(true); - } - - if (languageServerInstance != null) { - languageServerInstance.exit(); - } - - if (provider != null) { - provider.stop(); - } - this.stopping.set(false); - }; - - CompletableFuture.runAsync(shutdownKillAndStopFutureAndProvider); - - this.launcherFuture = null; - this.lspStreamProvider = null; + CompletableFuture.runAsync(() -> { + workingContext.close(); + }); while (!this.connectedDocuments.isEmpty()) { disconnect(this.connectedDocuments.keySet().iterator().next()); } - this.languageServer = null; FileBuffers.getTextFileBufferManager().removeFileBufferListener(fileBufferListener); } @@ -620,13 +640,13 @@ private void watchProjects() { if (!supportsWorkspaceFolderCapability()) { return; } - final LanguageServer currentLS = this.languageServer; + final LanguageServer currentLS = context.languageServer; new WorkspaceJob("Setting watch projects on server " + serverDefinition.label) { //$NON-NLS-1$ @Override public IStatus runInWorkspace(@Nullable IProgressMonitor monitor) throws CoreException { final var wsFolderEvent = new WorkspaceFoldersChangeEvent(); wsFolderEvent.getAdded().addAll(getRelevantWorkspaceFolders()); - if (currentLS != null && currentLS == LanguageServerWrapper.this.languageServer) { + if (currentLS != null && currentLS == context.languageServer) { currentLS.getWorkspaceService() .didChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams(wsFolderEvent)); } @@ -717,7 +737,7 @@ private boolean supportsWorkspaceFolderCapability() { } TextDocumentSyncKind syncKind = initializeFuture == null ? null : castNonNull(serverCapabilities).getTextDocumentSync().map(Functions.identity(), TextDocumentSyncOptions::getChange); - final var listener = new DocumentContentSynchronizer(this, castNonNull(languageServer), theDocument, syncKind); + final var listener = new DocumentContentSynchronizer(this, castNonNull(context.languageServer), theDocument, syncKind); theDocument.addPrenotifiedDocumentListener(listener); LanguageServerWrapper.this.connectedDocuments.put(uri, listener); } @@ -776,7 +796,7 @@ public boolean isConnectedTo(URI uri) { protected LanguageServer getServer() { CompletableFuture languagServerFuture = getInitializedServer(); if (Display.getCurrent() != null) { // UI Thread - return this.languageServer; + return context.languageServer; } else { return languagServerFuture.join(); } @@ -794,15 +814,16 @@ protected CompletableFuture getInitializedServer() { final CompletableFuture<@Nullable Void> currentInitializeFuture = initializeFuture; if (currentInitializeFuture != null && !currentInitializeFuture.isDone()) { - return currentInitializeFuture.thenApply(r -> castNonNull(this.languageServer)); + return currentInitializeFuture.thenApply(r -> castNonNull(context.languageServer)); } - return CompletableFuture.completedFuture(this.languageServer); + return CompletableFuture.completedFuture(context.languageServer); } /** * Sends a notification to the wrapped language server * - * @param fn LS notification to send + * @param fn + * LS notification to send */ public void sendNotification(Consumer fn) { // Enqueues a notification on the dispatch thread associated with the wrapped language server. This @@ -1129,7 +1150,7 @@ public void resourceChanged(IResourceChangeEvent event) { return; } // If shutting down, language server will be set to null, so ignore the event - final LanguageServer currentServer = LanguageServerWrapper.this.languageServer; + final LanguageServer currentServer = context.languageServer; if (currentServer != null) { currentServer.getWorkspaceService() .didChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams(workspaceFolderEvent));