You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by lk...@apache.org on 2020/10/26 22:46:02 UTC
[netbeans] branch delivery updated: Infrastructure to display
simple confirmations/questions in LSP client. (#2493)
This is an automated email from the ASF dual-hosted git repository.
lkishalmi pushed a commit to branch delivery
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/delivery by this push:
new cee80c2 Infrastructure to display simple confirmations/questions in LSP client. (#2493)
cee80c2 is described below
commit cee80c2704dac3e35f71e5cefea67916a7ff7dc2
Author: Svatopluk Dedic <sv...@oracle.com>
AuthorDate: Mon Oct 26 23:45:51 2020 +0100
Infrastructure to display simple confirmations/questions in LSP client. (#2493)
* Added A11Y properties to the panel.
* Infrastructure to display simple confirmations/questions in LSP client.
* Only translate html-starting content.
* Leftover cleanup.
---
.../modules/gradle/execute/TrustProjectPanel.java | 6 +-
.../modules/project/ui/problems/Bundle.properties | 2 +-
.../nbcode/integration/nbproject/project.xml | 8 +
.../nbcode/integration/LspDialogDisplayer.java | 31 ++
java/java.lsp.server/nbproject/project.xml | 8 +
.../modules/java/lsp/server/LspServerUtils.java | 91 ++++++
.../lsp/server/protocol/NbCodeLanguageClient.java | 4 +
.../modules/java/lsp/server/protocol/Server.java | 229 +++++++++++----
.../lsp/server/protocol/WorkspaceUIContext.java | 9 +-
.../lsp/server/ui/AbstractDialogDisplayer.java | 68 +++++
.../lsp/server/ui/NotifyDescriptorAdapter.java | 325 +++++++++++++++++++++
.../modules/java/lsp/server/ui/UIContext.java | 39 ++-
.../lsp/server/ui/AbstractDialogDisplayerTest.java | 156 ++++++++++
13 files changed, 910 insertions(+), 66 deletions(-)
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/execute/TrustProjectPanel.java b/extide/gradle/src/org/netbeans/modules/gradle/execute/TrustProjectPanel.java
index e4bbfcf..910a70c 100644
--- a/extide/gradle/src/org/netbeans/modules/gradle/execute/TrustProjectPanel.java
+++ b/extide/gradle/src/org/netbeans/modules/gradle/execute/TrustProjectPanel.java
@@ -42,19 +42,23 @@ public class TrustProjectPanel extends javax.swing.JPanel {
+ " allows arbitrary code execution.</p>",
"TrustProjectPanel.INFO_UNKNOWN=<html><p>NetBeans is about to invoke a Gradle build process.</p>"
+ " <p>Executing Gradle can be potentially un-safe as it"
- + " allows arbitrary code execution.</p>"
+ + " allows arbitrary code execution.</p>",
+ "ProjectTrustDlg.TITLE=Not a Trusted Project"
})
public TrustProjectPanel(Project project) {
initComponents();
ProjectInformation info = project != null ? project.getLookup().lookup(ProjectInformation.class) : null;
+ getAccessibleContext().setAccessibleName(Bundle.ProjectTrustDlg_TITLE());
if (project == null) {
cbTrustProject.setEnabled(false);
cbTrustProject.setVisible(false);
}
if (info == null) {
lbTrustMessage.setText(Bundle.TrustProjectPanel_INFO_UNKNOWN());
+ getAccessibleContext().setAccessibleDescription(Bundle.TrustProjectPanel_INFO_UNKNOWN());
} else {
lbTrustMessage.setText(Bundle.TrustProjectPanel_INFO(info.getDisplayName()));
+ getAccessibleContext().setAccessibleDescription(Bundle.TrustProjectPanel_INFO(info.getDisplayName()));
}
}
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/problems/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/problems/Bundle.properties
index d022650..f9d5d34 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/problems/Bundle.properties
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/problems/Bundle.properties
@@ -20,7 +20,7 @@ ACSN_BrokenReferencesAlertPanel_notAgain=Do not show this message again
ACSD_BrokenReferencesAlertPanel_notAgain=N/A
MSG_Broken_References=<html>One or more project resources could not be found.<br>Right-click the project in the Projects window and choose<br><b>Resolve Project Problems</b> to find the missing resources.</html>
ACSN_BrokenReferencesAlertPanel=Broken Project Panel
-ACSD_BrokenReferencesAlertPanel=N/A
+ACSD_BrokenReferencesAlertPanel=The project contains broken references or other problems.
FMT_ProblemInProject={1} (in {0})
LBL_BrokenLinksCustomizer_List=Project &Problems:
diff --git a/java/java.lsp.server/nbcode/integration/nbproject/project.xml b/java/java.lsp.server/nbcode/integration/nbproject/project.xml
index ff60f27..3a36d95 100644
--- a/java/java.lsp.server/nbcode/integration/nbproject/project.xml
+++ b/java/java.lsp.server/nbcode/integration/nbproject/project.xml
@@ -51,6 +51,14 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.openide.dialogs</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>7.52</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.openide.util.lookup</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java
new file mode 100644
index 0000000..98e4416
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nbcode.integration;
+
+import org.netbeans.modules.java.lsp.server.ui.AbstractDialogDisplayer;
+import org.openide.DialogDisplayer;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+//@ServiceProvider(service = DialogDisplayer.class, position = 1000)
+public class LspDialogDisplayer extends AbstractDialogDisplayer {
+}
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index 89d6327..7d0d84b 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -303,6 +303,14 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.openide.dialogs</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>7.52</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.openide.filesystems</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java
new file mode 100644
index 0000000..ab14aed
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server;
+
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.Server;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author sdedic
+ */
+public class LspServerUtils {
+
+ /**
+ * Locates the client associated with the current context. Use this method as a
+ * last resort, for testing & all other practical purposes it is always better to
+ * have own LSP client reference or a context Lookup instance.
+ * @param context the processing context.
+ * @return LanguageClient instance or {@code null}, if no client associated with the context or thread.
+ */
+ @CheckForNull
+ public static final NbCodeLanguageClient findLspClient(Lookup context) {
+ NbCodeLanguageClient client = context != null ? context.lookup(NbCodeLanguageClient.class) : null;
+ if (client == null && context != Lookup.getDefault()) {
+ client = Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+ }
+ return client;
+ }
+
+ /**
+ * Locates the client associated with the current context. Use this method as a
+ * last resort, for testing & all other practical purposes it is always better to
+ * have own LSP client reference or a context Lookup instance.
+ * <p>
+ * This method always return a client, but if no real context is given, the client returns
+ * just stub values and logs all method calls as warnings.
+ *
+ * @param context the processing context.
+ * @return LanguageClient instance, never null.
+ */
+ @NonNull
+ public static final NbCodeLanguageClient requireLspClient(Lookup context) {
+ NbCodeLanguageClient client = findLspClient(context);
+ return client != null ? client : Server.getStubClient();
+ }
+
+ /**
+ * Checks whether the calling thread is the one serving client's communication.
+ * Such thread cannot be blocked. If {@code null} is passed, returns {@code true}
+ * if the calling thread serves any client.
+ *
+ * @param client client instance or {@code null}.
+ * @return true, if communication with the client would be broken
+ */
+ public static final boolean isClientResponseThread(NbCodeLanguageClient client) {
+ return Server.isClientResponseThread(client);
+ }
+
+ /**
+ * Ensures that the caller does not serve the client associated with the context. If it does,
+ * throws IllegalStateException. Call to avoid blocking communication with the client before
+ * waiting on some remote response.
+ *
+ * @param context execution context
+ */
+ public static final void avoidClientMessageThread(Lookup context) {
+ NbCodeLanguageClient client = LspServerUtils.findLspClient(context);
+ if (LspServerUtils.isClientResponseThread(client)) {
+ throw new IllegalStateException("Can not block LSP server message loop. Use RequestProcessor to run the calling code, or use notifyLater()");
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
index 10ad5a6..06177ae 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
@@ -46,4 +46,8 @@ public interface NbCodeLanguageClient extends LanguageClient {
* @return code capabilities.
*/
public NbCodeClientCapabilities getNbCodeCapabilities();
+
+ public default boolean isRequestDispatcherThread() {
+ return Boolean.TRUE.equals(Server.DISPATCHERS.get());
+ }
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index 91bf541..a67ad69 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -26,17 +26,24 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
+import org.eclipse.lsp4j.MessageActionItem;
+import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.ServerCapabilities;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
@@ -61,6 +68,7 @@ import org.netbeans.api.project.ui.OpenProjects;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
+import org.openide.util.RequestProcessor;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.Lookups;
@@ -71,13 +79,28 @@ import org.openide.util.lookup.ProxyLookup;
* @author lahvac
*/
public final class Server {
+ private static final Logger LOG = Logger.getLogger(Server.class.getName());
+
private Server() {
}
+ public static NbCodeLanguageClient getStubClient() {
+ return STUB_CLIENT;
+ }
+
+ public static boolean isClientResponseThread(NbCodeLanguageClient client) {
+ return client != null ?
+ DISPATCHERS.get() == client :
+ DISPATCHERS.get() != null;
+ }
+
public static void launchServer(InputStream in, OutputStream out) {
LanguageServerImpl server = new LanguageServerImpl();
- Launcher<NbCodeLanguageClient> serverLauncher = createLauncher(server, in, out);
- ((LanguageClientAware) server).connect(serverLauncher.getRemoteProxy());
+ ConsumeWithLookup msgProcessor = new ConsumeWithLookup(server.getSessionLookup());
+ Launcher<NbCodeLanguageClient> serverLauncher = createLauncher(server, in, out, msgProcessor::attachLookup);
+ NbCodeLanguageClient remote = serverLauncher.getRemoteProxy();
+ ((LanguageClientAware) server).connect(remote);
+ msgProcessor.attachClient(server.client);
Future<Void> runningServer = serverLauncher.startListening();
try {
runningServer.get();
@@ -86,39 +109,56 @@ public final class Server {
}
}
- private static Launcher<NbCodeLanguageClient> createLauncher(LanguageServerImpl server, InputStream in, OutputStream out) {
+ private static Launcher<NbCodeLanguageClient> createLauncher(LanguageServerImpl server, InputStream in, OutputStream out,
+ Function<MessageConsumer, MessageConsumer> processor) {
return new LSPLauncher.Builder<NbCodeLanguageClient>()
.setLocalService(server)
.setRemoteInterface(NbCodeLanguageClient.class)
.setInput(in)
.setOutput(out)
- .wrapMessages(new ConsumeWithLookup(server.getSessionLookup())::attachLookup)
+ .wrapMessages(processor)
.create();
}
+ static final ThreadLocal<NbCodeLanguageClient> DISPATCHERS = new ThreadLocal<>();
+
/**
* Processes message while the default Lookup is set to
* {@link LanguageServerImpl#getSessionLookup()}.
*/
private static class ConsumeWithLookup {
private final Lookup sessionLookup;
-
+ private NbCodeLanguageClient client;
+
public ConsumeWithLookup(Lookup sessionLookup) {
this.sessionLookup = sessionLookup;
}
+ synchronized void attachClient(NbCodeLanguageClient client) {
+ this.client = client;
+ }
+
public MessageConsumer attachLookup(MessageConsumer delegate) {
return new MessageConsumer() {
@Override
public void consume(Message msg) throws MessageIssueException, JsonRpcException {
- Lookups.executeWith(sessionLookup, () -> {
- delegate.consume(msg);
- });
+ try {
+ DISPATCHERS.set(client);
+ Lookups.executeWith(sessionLookup, () -> {
+ delegate.consume(msg);
+ });
+ } finally {
+ DISPATCHERS.remove();
+ }
}
};
}
}
+ // change to a greater throughput if the initialization waits on more processes than just (serialized) project open.
+ private static final RequestProcessor SERVER_INIT_RP = new RequestProcessor(LanguageServerImpl.class.getName());
+
+
private static class LanguageServerImpl implements LanguageServer, LanguageClientAware {
private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName());
@@ -135,62 +175,45 @@ public final class Server {
return sessionLookup;
}
- @Override
- public CompletableFuture<InitializeResult> initialize(InitializeParams init) {
- NbCodeClientCapabilities capa = NbCodeClientCapabilities.get(init);
- client.setClientCaps(capa);
- List<FileObject> projectCandidates = new ArrayList<>();
- List<WorkspaceFolder> folders = init.getWorkspaceFolders();
- if (folders != null) {
- for (WorkspaceFolder w : folders) {
- try {
- projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri()));
- } catch (MalformedURLException ex) {
- LOG.log(Level.FINE, null, ex);
+ private void asyncOpenSelectedProjects(CompletableFuture f, List<FileObject> projectCandidates) {
+ List<Project> projects = new ArrayList<>();
+ try {
+ for (FileObject candidate : projectCandidates) {
+ Project prj = FileOwnerQuery.getOwner(candidate);
+ if (prj != null) {
+ projects.add(prj);
}
}
- } else {
- String root = init.getRootUri();
-
- if (root != null) {
- try {
- projectCandidates.add(TextDocumentServiceImpl.fromUri(root));
- } catch (MalformedURLException ex) {
- LOG.log(Level.FINE, null, ex);
+ try {
+ Project[] previouslyOpened = OpenProjects.getDefault().openProjects().get();
+ if (previouslyOpened.length > 0) {
+ Level level = Level.FINEST;
+ assert (level = Level.CONFIG) != null;
+ for (Project p : previouslyOpened) {
+ LOG.log(level, "Previously opened project at {0}", p.getProjectDirectory());
+ }
}
- } else {
- //TODO: use getRootPath()?
+ } catch (InterruptedException | ExecutionException ex) {
+ throw new IllegalStateException(ex);
}
- }
- List<Project> projects = new ArrayList<>();
- for (FileObject candidate : projectCandidates) {
- Project prj = FileOwnerQuery.getOwner(candidate);
- if (prj != null) {
- projects.add(prj);
+ OpenProjects.getDefault().open(projects.toArray(new Project[0]), false);
+ try {
+ OpenProjects.getDefault().openProjects().get();
+ } catch (InterruptedException | ExecutionException ex) {
+ throw new IllegalStateException(ex);
}
- }
- try {
- Project[] previouslyOpened = OpenProjects.getDefault().openProjects().get();
- if (previouslyOpened.length > 0) {
- Level level = Level.FINEST;
- assert (level = Level.CONFIG) != null;
- for (Project p : previouslyOpened) {
- LOG.log(level, "Previously opened project at {0}", p.getProjectDirectory());
- }
+ for (Project prj : projects) {
+ //init source groups/FileOwnerQuery:
+ ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC);
}
- } catch (InterruptedException | ExecutionException ex) {
- throw new IllegalStateException(ex);
- }
- OpenProjects.getDefault().open(projects.toArray(new Project[0]), false);
- try {
- OpenProjects.getDefault().openProjects().get();
- } catch (InterruptedException | ExecutionException ex) {
- throw new IllegalStateException(ex);
- }
- for (Project prj : projects) {
- //init source groups/FileOwnerQuery:
- ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC);
+ Project[] prjs = projects.toArray(new Project[projects.size()]);
+ f.complete(prjs);
+ } catch (RuntimeException ex) {
+ f.completeExceptionally(ex);
}
+ }
+
+ private void showIndexingCompleted() {
try {
JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPath.EMPTY))
.runWhenScanFinished(cc -> {
@@ -204,6 +227,9 @@ public final class Server {
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
+ }
+
+ private InitializeResult constructInitResponse() {
ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental);
CompletionOptions completionOptions = new CompletionOptions();
@@ -216,7 +242,42 @@ public final class Server {
capabilities.setDocumentHighlightProvider(true);
capabilities.setReferencesProvider(true);
capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT)));
- return CompletableFuture.completedFuture(new InitializeResult(capabilities));
+ return new InitializeResult(capabilities);
+ }
+
+ @Override
+ public CompletableFuture<InitializeResult> initialize(InitializeParams init) {
+ NbCodeClientCapabilities capa = NbCodeClientCapabilities.get(init);
+ client.setClientCaps(capa);
+ List<FileObject> projectCandidates = new ArrayList<>();
+ List<WorkspaceFolder> folders = init.getWorkspaceFolders();
+ if (folders != null) {
+ for (WorkspaceFolder w : folders) {
+ try {
+ projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri()));
+ } catch (MalformedURLException ex) {
+ LOG.log(Level.FINE, null, ex);
+ }
+ }
+ } else {
+ String root = init.getRootUri();
+
+ if (root != null) {
+ try {
+ projectCandidates.add(TextDocumentServiceImpl.fromUri(root));
+ } catch (MalformedURLException ex) {
+ LOG.log(Level.FINE, null, ex);
+ }
+ } else {
+ //TODO: use getRootPath()?
+ }
+ }
+ CompletableFuture<Project[]> fProjects = new CompletableFuture<>();
+ SERVER_INIT_RP.post(() -> asyncOpenSelectedProjects(fProjects, projectCandidates));
+
+ return fProjects.
+ thenRun(this::showIndexingCompleted).
+ thenApply((v) -> constructInitResponse());
}
@Override
@@ -241,7 +302,7 @@ public final class Server {
@Override
public void connect(LanguageClient aClient) {
this.client = new NbCodeClientWrapper((NbCodeLanguageClient)aClient);
-
+ sessionServices.add(client);
sessionServices.add(new WorkspaceIOContext() {
@Override
protected LanguageClient client() {
@@ -254,8 +315,56 @@ public final class Server {
((LanguageClientAware) getWorkspaceService()).connect(aClient);
}
}
-
+
public static final String JAVA_BUILD_WORKSPACE = "java.build.workspace";
public static final String GRAALVM_PAUSE_SCRIPT = "graalvm.pause.script";
static final String INDEXING_COMPLETED = "Indexing completed.";
+
+ static final NbCodeLanguageClient STUB_CLIENT = new NbCodeLanguageClient() {
+ private final NbCodeClientCapabilities caps = new NbCodeClientCapabilities();
+
+ private void logWarning(Object... args) {
+ LOG.log(Level.WARNING, "LSP Client called without proper context with param(s): {0}",
+ Arrays.asList(args));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ logWarning(params);
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ logWarning();
+ return caps;
+ }
+
+ @Override
+ public void telemetryEvent(Object object) {
+ logWarning(object);
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams diagnostics) {
+ logWarning(diagnostics);
+ }
+
+ @Override
+ public void showMessage(MessageParams messageParams) {
+ logWarning(messageParams);
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams requestParams) {
+ logWarning(requestParams);
+ CompletableFuture<MessageActionItem> x = new CompletableFuture<>();
+ x.complete(null);
+ return x;
+ }
+
+ @Override
+ public void logMessage(MessageParams message) {
+ logWarning(message);
+ }
+ };
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceUIContext.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceUIContext.java
index c20575c..eb66a47 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceUIContext.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceUIContext.java
@@ -18,8 +18,10 @@
*/
package org.netbeans.modules.java.lsp.server.protocol;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.services.LanguageClient;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.netbeans.modules.java.lsp.server.ui.UIContext;
import org.openide.awt.StatusDisplayer;
@@ -40,6 +42,11 @@ class WorkspaceUIContext extends UIContext {
}
@Override
+ protected CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams msg) {
+ return client.showMessageRequest(msg);
+ }
+
+ @Override
protected void showMessage(MessageParams msg) {
client.showMessage(msg);
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java
new file mode 100644
index 0000000..35e05a0
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.ui;
+
+import java.awt.Dialog;
+import java.awt.HeadlessException;
+import org.netbeans.modules.java.lsp.server.LspServerUtils;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Lookup;
+
+/**
+ * Remoting implementation of {@link DialogDisplayer}. The implementation will refuse to
+ * display dialogs that block the message processing thread ({@link IllegalStateException} will be thrown.
+ *
+ * @author sdedic
+ */
+public class AbstractDialogDisplayer extends DialogDisplayer {
+ private final Lookup context;
+
+ public AbstractDialogDisplayer() {
+ this(Lookup.getDefault());
+ }
+
+ AbstractDialogDisplayer(Lookup context) {
+ this.context = context;
+ }
+
+ @Override
+ public Object notify(NotifyDescriptor descriptor) {
+ LspServerUtils.avoidClientMessageThread(context);
+ UIContext ctx = UIContext.find(context);
+ NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(descriptor, ctx);
+ return adapter.clientNotify();
+ }
+
+ @Override
+ public void notifyLater(final NotifyDescriptor descriptor) {
+ UIContext ctx = context.lookup(UIContext.class);
+ if (ctx == null) {
+ ctx = UIContext.find();
+ }
+ NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(descriptor, ctx);
+ adapter.clientNotifyLater();
+ }
+
+ @Override
+ public Dialog createDialog(DialogDescriptor descriptor) {
+ throw new HeadlessException();
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/NotifyDescriptorAdapter.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/NotifyDescriptorAdapter.java
new file mode 100644
index 0000000..f10318e
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/NotifyDescriptorAdapter.java
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.ui;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleContext;
+import javax.accessibility.AccessibleIcon;
+import javax.accessibility.AccessibleText;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import org.eclipse.lsp4j.MessageActionItem;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.openide.NotifyDescriptor;
+import org.openide.util.NbBundle;
+
+/**
+ * Adapts a {@link NotifyDescriptor} to a {@link ShowMessageRequestParams} call.
+ * @author sdedic
+ */
+class NotifyDescriptorAdapter {
+ private static final Logger LOG = Logger.getLogger(NotifyDescriptorAdapter.class.getName());
+
+ private final UIContext client;
+ private final NotifyDescriptor descriptor;
+ private final Map<MessageActionItem, Object> item2Option = new LinkedHashMap<>();
+ private final Map<String, Object> text2Option = new HashMap<>();
+ private final Map<Object, List<ActionListener>> optionListeners = new HashMap<>();
+ private final Map<Object, JButton> option2Button = new HashMap<>();
+
+ private static final Set<String> warnedClasses = new HashSet<>();
+
+ private ShowMessageRequestParams request;
+
+ private static final Object[] YES_NO_CANCEL = new Object[] {
+ NotifyDescriptor.YES_OPTION,
+ NotifyDescriptor.NO_OPTION,
+ NotifyDescriptor.CANCEL_OPTION
+ };
+
+ private static final Object[] YES_NO = new Object[] {
+ NotifyDescriptor.YES_OPTION,
+ NotifyDescriptor.NO_OPTION,
+ };
+
+ private static final Object[] OK_CANCEL = new Object[] {
+ NotifyDescriptor.OK_OPTION,
+ NotifyDescriptor.CANCEL_OPTION
+ };
+
+ private static final Object[] JUST_OK = new Object[] {
+ NotifyDescriptor.OK_OPTION
+ };
+
+ public NotifyDescriptorAdapter(NotifyDescriptor descriptor, UIContext client) {
+ this.descriptor = descriptor;
+ this.client = client;
+ }
+
+ private MessageType translateMessageType() {
+ switch(descriptor.getMessageType()) {
+ case NotifyDescriptor.ERROR_MESSAGE:
+ return MessageType.Error;
+ case NotifyDescriptor.WARNING_MESSAGE:
+ return MessageType.Warning;
+
+ case NotifyDescriptor.QUESTION_MESSAGE:
+ case NotifyDescriptor.PLAIN_MESSAGE:
+ case NotifyDescriptor.INFORMATION_MESSAGE:
+ return MessageType.Info;
+ default:
+ return MessageType.Log;
+ }
+ }
+
+ /**
+ * Strip HTML from the message; VSCode standard showMessage does not support HTML.
+ * @param original
+ * @return
+ */
+ private String translateText(String original) {
+ if (!original.startsWith("<html>")) { // NOI18N
+ return original;
+ }
+ String res =
+ original.replaceAll("<p/>|</p>|<br>", "\n"). // NOI18N
+ replaceAll( "<[^>]*>", "" ). // NOI18N
+ replaceAll( " ", " " ); // NOI18N
+ res = res.trim();
+ return res;
+ }
+
+ public String getAccessibleDescription(Object o) {
+ if (!(o instanceof Accessible)) {
+ return null;
+ }
+ AccessibleContext ac = ((Accessible)o).getAccessibleContext();
+ String s = ac.getAccessibleDescription();
+ if (s != null && !"N/A".equals(s)) {
+ return s;
+ }
+ return ac.getAccessibleName();
+ }
+
+ public ShowMessageRequestParams createShowMessageRequest() {
+ if (this.request != null) {
+ return request;
+ }
+ Object msg = descriptor.getMessage();
+ String displayText = null;
+
+ if (msg instanceof String) {
+ displayText = msg.toString();
+ } else {
+ displayText = getAccessibleDescription(msg);
+ }
+ if (displayText == null) {
+ return null;
+ }
+ mapDescriptorOptions();
+ ShowMessageRequestParams request = new ShowMessageRequestParams();
+ request.setMessage(translateText(displayText));
+ request.setActions(getActionItems());
+ request.setType(translateMessageType());
+ return this.request = request;
+ }
+
+ public Object actionToOption(MessageActionItem item) {
+ return item2Option.get(item);
+ }
+
+ public List<MessageActionItem> getActionItems() {
+ return new ArrayList<>(item2Option.keySet());
+ }
+
+ private void addMessageItem(MessageActionItem item, Object option) {
+ item2Option.put(item, option);
+ text2Option.put(item.getTitle(), option);
+ }
+
+ @NbBundle.Messages({
+ "OPTION_Yes=Yes",
+ "OPTION_No=No",
+ "OPTION_OK=OK",
+ "OPTION_Cancel=Cancel",
+ })
+ private String mapDescriptorOption(Object option) {
+ if (option == NotifyDescriptor.CANCEL_OPTION) {
+ return Bundle.OPTION_Cancel();
+ } else if (option == NotifyDescriptor.NO_OPTION) {
+ return Bundle.OPTION_No();
+ } else if (option == NotifyDescriptor.YES_OPTION) {
+ return Bundle.OPTION_Yes();
+ } else if (option == NotifyDescriptor.OK_OPTION) {
+ return Bundle.OPTION_OK();
+ }
+ if (option instanceof Component) {
+ return null;
+ }
+ if (option != null) {
+ return option.toString();
+ }
+ return null;
+ }
+
+ private void mapDescriptorOptions() {
+ Object[] options = descriptor.getOptions();
+ if (options == null) {
+ switch (descriptor.getOptionType()) {
+ case NotifyDescriptor.DEFAULT_OPTION:
+ case NotifyDescriptor.OK_CANCEL_OPTION:
+ options = OK_CANCEL; break;
+ case NotifyDescriptor.YES_NO_CANCEL_OPTION:
+ options = YES_NO_CANCEL; break;
+ case NotifyDescriptor.YES_NO_OPTION:
+ options = YES_NO; break;
+ default:
+ options = JUST_OK;
+ break;
+ }
+ }
+ for (Object o : options) {
+ String text;
+
+ if (o instanceof JButton) {
+ text = addButtonItem((JButton)o);
+ } else if (o instanceof Icon) {
+ text = addIconItem((Icon)o);
+ } else {
+ text = mapDescriptorOption(o);
+ }
+ if (text != null) {
+ addMessageItem(new MessageActionItem(text), o);
+ } else {
+ reportUnknownOption(o);
+ }
+ }
+ }
+
+ private void reportUnknownOption(Object o) {
+ Throwable t = new Throwable();
+ StackTraceElement[] stack = t.getStackTrace();
+ if (stack.length >= 7) {
+ String callerClass = stack[6].getClassName();
+ synchronized (warnedClasses) {
+ if (!warnedClasses.add(callerClass)) {
+ return;
+ }
+ }
+ }
+ LOG.log(Level.WARNING, new Throwable(),
+ () -> "Unhandled option " + o + " for descriptor: " + descriptor);
+ }
+
+ private String addButtonItem(JButton button) {
+ if (!button.isVisible()) {
+ return null;
+ }
+ String t = button.getText();
+ List<ActionListener> ll = Arrays.asList(button.getActionListeners());
+ if (!ll.isEmpty()) {
+ optionListeners.put(button, ll);
+ }
+ return t;
+ }
+
+ private String addIconItem(Icon icon) {
+ if (icon instanceof AccessibleIcon) {
+ return ((AccessibleIcon)icon).getAccessibleIconDescription();
+ } else {
+ return null;
+ }
+ }
+
+ public Object clientNotify() {
+ try {
+ return clientNotifyLater().get();
+ } catch (InterruptedException | ExecutionException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public CompletableFuture<Object> clientNotifyLater() {
+ ShowMessageRequestParams params = createShowMessageRequest();
+ if (params == null) {
+ CompletableFuture<Object> x = new CompletableFuture<>();
+ x.complete(NotifyDescriptor.CLOSED_OPTION);
+ return x;
+ }
+ CompletableFuture<MessageActionItem> resultItem = client.showMessageRequest(request);
+ return resultItem /*.exceptionally(this::handleClientException) */.thenApply(this::processActivatedOption);
+ }
+
+ MessageActionItem handleClientException(Throwable t) {
+ // TBD
+ return null;
+ }
+
+ Object processActivatedOption(MessageActionItem item) {
+ Object option = selectActivatedOption(item);
+ List<ActionListener> ll = optionListeners.get(option);
+ if (ll != null) {
+ ActionEvent e = new ActionEvent(option, ActionEvent.ACTION_PERFORMED, item.getTitle());
+ for (ActionListener l : ll) {
+ try {
+ l.actionPerformed(e);
+ } catch (RuntimeException ex) {
+ LOG.log(Level.SEVERE, "Error occurred during actionListener dispatch", ex);
+ }
+ }
+ }
+ return option;
+ }
+
+ Object selectActivatedOption(MessageActionItem item) {
+ if (item == null) {
+ return NotifyDescriptor.CLOSED_OPTION;
+ }
+ Object option = item2Option.get(item);
+ if (option == null) {
+ option = text2Option.get(item.getTitle());
+ }
+ if (option == null) {
+ LOG.log(Level.WARNING, "Unknown client response received: {0}, the valid options were: {1}", new Object[] {
+ item.getTitle(),
+ item2Option.keySet().stream().map(MessageActionItem::getTitle).collect(Collectors.toList())
+ });
+ return NotifyDescriptor.CLOSED_OPTION;
+ }
+
+ return option;
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/UIContext.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/UIContext.java
index 781d04e..d820f4e 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/UIContext.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/UIContext.java
@@ -20,17 +20,37 @@ package org.netbeans.modules.java.lsp.server.ui;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams;
import org.openide.awt.StatusDisplayer.Message;
import org.openide.util.Lookup;
public abstract class UIContext {
private static Reference<UIContext> lastCtx = new WeakReference<>(null);
-
- public static synchronized UIContext find() {
- UIContext ctx = Lookup.getDefault().lookup(UIContext.class);
+
+ /**
+ * Allows to pass Lookup as a context to locate UIContext implementation; can be useful for tests. If not found
+ * in the context `lkp', will be searched in the default Lookup (if lkp is not the default one).
+ * @param lkp context lookup
+ * @return UIContext.
+ */
+ @NonNull
+ public static synchronized UIContext find(Lookup lkp) {
+ UIContext ctx = lkp.lookup(UIContext.class);
+ if (ctx != null) {
+ return ctx;
+ }
+ Lookup def = Lookup.getDefault();
+ if (lkp != def) {
+ ctx = def.lookup(UIContext.class);
+ }
if (ctx == null) {
+ // PENDING: better context transfer between threads is needed; this way the UIContext can remote to a bad
+ // LSP client window
ctx = lastCtx.get();
if (ctx != null && !ctx.isValid()) {
lastCtx.clear();
@@ -49,8 +69,14 @@ public abstract class UIContext {
return ctx;
}
+ @NonNull
+ public static synchronized UIContext find() {
+ return find(Lookup.getDefault());
+ }
+
protected abstract boolean isValid();
protected abstract void showMessage(MessageParams msg);
+ protected abstract CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams msg);
protected abstract void logMessage(MessageParams msg);
protected abstract Message showStatusMessage(ShowStatusMessageParams msg);
@@ -62,6 +88,13 @@ public abstract class UIContext {
}
@Override
+ protected CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams msg) {
+ System.err.println(msg.getType() + ": " + msg.getMessage());
+ CompletableFuture<MessageActionItem> ai = CompletableFuture.completedFuture(null);
+ return ai;
+ }
+
+ @Override
protected void showMessage(MessageParams msg) {
System.err.println(msg.getType() + ": " + msg.getMessage());
}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayerTest.java
new file mode 100644
index 0000000..9d9ec81
--- /dev/null
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.ui;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import javax.swing.JPanel;
+import static junit.framework.TestCase.fail;
+import org.eclipse.lsp4j.MessageActionItem;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams;
+import org.openide.NotifyDescriptor;
+import org.openide.awt.StatusDisplayer;
+
+/**
+ *
+ * @author sdedic
+ */
+public class AbstractDialogDisplayerTest extends NbTestCase {
+
+ public AbstractDialogDisplayerTest(String name) {
+ super(name);
+ }
+
+ private static class MockUIContext extends UIContext {
+ @Override
+ protected boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected void showMessage(MessageParams msg) {
+ }
+
+ @Override
+ protected CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams msg) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ protected void logMessage(MessageParams msg) {
+ }
+
+ @Override
+ protected StatusDisplayer.Message showStatusMessage(ShowStatusMessageParams msg) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks that component-based dialogs will just return CLOSED.
+ * @throws Exception
+ */
+ public void testUnsupportedDialogWithPanel() throws Exception {
+ MockUIContext client = new MockUIContext() {
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams requestParams) {
+ fail();
+ return null;
+ }
+
+ @Override
+ public void showMessage(MessageParams messageParams) {
+ fail();
+ }
+ };
+
+ NotifyDescriptor nd = new NotifyDescriptor(new JPanel(), "Unused",
+ NotifyDescriptor.OK_CANCEL_OPTION,
+ NotifyDescriptor.WARNING_MESSAGE,
+ null, null);
+
+ NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(nd, client);
+ assertSame(NotifyDescriptor.CLOSED_OPTION, adapter.clientNotify());
+ }
+
+ private static final Map<Integer, MessageType> MESSAGE_TYPES = new HashMap<>();
+
+ {
+ MESSAGE_TYPES.put(NotifyDescriptor.PLAIN_MESSAGE, MessageType.Info);
+ MESSAGE_TYPES.put(NotifyDescriptor.QUESTION_MESSAGE, MessageType.Info);
+ MESSAGE_TYPES.put(NotifyDescriptor.INFORMATION_MESSAGE, MessageType.Info);
+ MESSAGE_TYPES.put(NotifyDescriptor.WARNING_MESSAGE, MessageType.Warning);
+ MESSAGE_TYPES.put(NotifyDescriptor.ERROR_MESSAGE, MessageType.Error);
+ }
+
+ /**
+ * Checks that showMessage receives an appropriate message type, for different
+ * ND's {@code messageTy[e} value/
+ * @throws Exception
+ */
+ public void testCheckMessageTypes() throws Exception {
+ for (int i : MESSAGE_TYPES.keySet()) {
+ MockUIContext cl = new MockUIContext() {
+ MessageType check = MESSAGE_TYPES.get(i);
+
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams requestParams) {
+ assertEquals(check, requestParams.getType());
+ CompletableFuture<MessageActionItem> x = new CompletableFuture<>();
+ x.complete(null);
+ return x;
+ }
+ };
+ NotifyDescriptor nd = new NotifyDescriptor("Hello, LSP client", "Unused",
+ NotifyDescriptor.OK_CANCEL_OPTION,
+ i,
+ null, null);
+ NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(nd, cl);
+ adapter.clientNotify();
+ }
+ }
+
+ /**
+ * Checks that yes-no-cancel items will be presented at the client
+ */
+ public void testYesNoCancelItems() {
+ MockUIContext cl = new MockUIContext() {
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams requestParams) {
+ assertEquals(3, requestParams.getActions().size());
+ assertEquals("Yes", requestParams.getActions().get(0).getTitle());
+ assertEquals("No", requestParams.getActions().get(1).getTitle());
+ assertEquals("Cancel", requestParams.getActions().get(2).getTitle());
+
+ CompletableFuture<MessageActionItem> x = new CompletableFuture<>();
+ x.complete(requestParams.getActions().get(0));
+ return x;
+ }
+ };
+ NotifyDescriptor nd = new NotifyDescriptor("Hello, LSP client", "Unused",
+ NotifyDescriptor.YES_NO_CANCEL_OPTION,
+ NotifyDescriptor.QUESTION_MESSAGE,
+ null, null);
+ NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(nd, cl);
+ assertEquals(NotifyDescriptor.YES_OPTION, adapter.clientNotify());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists