Merge pull request #2664 from JaroslavTulach/ReadmeChangelogVersionForRelease12.2.1
Readme, changelog and version for release 12.2.1
diff --git a/.gitignore b/.gitignore
index d9d0aa9..3c10d76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@
/nbi/engine/native/*/*/dist/
/nb-javac/
/java.source.nbjavac/test/test-nb-javac/nbproject/private/
+/java/java.lsp.server/vscode/.vscode-test/
/harness/apisupport.harness/windows-launcher-src/*.exe
/harness/apisupport.harness/windows-launcher-src/*.res
/ide/versioning/test/qa-functional/data/tck.properties
diff --git a/.travis.yml b/.travis.yml
index 8ccec65..24ff19a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -747,7 +747,7 @@
- name: Build the Visual Studio Code extension for Java
jdk: openjdk8
before_install:
- - nvm install 8
+ - nvm install 12.14.1
env:
- OPTS="-Dmetabuild.jsonurl=https://raw.githubusercontent.com/apache/netbeans-jenkins-lib/master/meta/netbeansrelease.json -quiet -Dcluster.config=java -Djavac.compilerargs=-nowarn -Dbuild.compiler.deprecation=false -Dtest-unit-sys-prop.ignore.random.failures=true"
before_script:
@@ -755,6 +755,7 @@
- ant $OPTS build
script:
- (cd java/java.lsp.server; ant build-vscode-ext)
+ - (cd java/java.lsp.server; ant test-vscode-ext)
- name: "GraalVM Tests (latest)"
jdk: openjdk8
diff --git a/cpplite/cpplite.editor/nbproject/project.xml b/cpplite/cpplite.editor/nbproject/project.xml
index db43b93..58f824f 100644
--- a/cpplite/cpplite.editor/nbproject/project.xml
+++ b/cpplite/cpplite.editor/nbproject/project.xml
@@ -26,6 +26,15 @@
<code-name-base>org.netbeans.modules.cpplite.editor</code-name-base>
<module-dependencies>
<dependency>
+ <code-name-base>org.netbeans.api.java.classpath</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.63</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.api.templates</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -66,7 +75,6 @@
<compile-dependency/>
<run-dependency>
<release-version>0</release-version>
- <!--<implementation-version/>-->
</run-dependency>
</dependency>
<dependency>
@@ -79,6 +87,14 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.parsing.indexing</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>9.17</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.projectapi</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java
new file mode 100644
index 0000000..2436f94
--- /dev/null
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.cpplite.editor;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.modules.cpplite.editor.file.MIMETypes;
+import org.netbeans.modules.parsing.spi.indexing.PathRecognizer;
+import org.openide.util.lookup.ServiceProvider;
+
+@ServiceProvider(service=PathRecognizer.class)
+public class PathRecognizerImpl extends PathRecognizer{
+
+ @Override
+ public Set<String> getSourcePathIds() {
+ return Collections.singleton(ClassPath.SOURCE);
+ }
+
+ @Override
+ public Set<String> getLibraryPathIds() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getBinaryLibraryPathIds() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getMimeTypes() {
+ return new HashSet<>(Arrays.asList(MIMETypes.C, MIMETypes.CPP, MIMETypes.H, MIMETypes.HPP));
+ }
+
+}
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java
index bbaa61d..eb6c211 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java
@@ -100,8 +100,13 @@
@ActionReference(
path = "Editors/" + MIMETypes.CPP + "/Popup",
id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
- position = 1400,
- separatorAfter = 1450
+ position = 1400
+ ),
+ @ActionReference(
+ path = "Editors/" + MIMETypes.CPP + "/Popup",
+ id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+ position = 1500,
+ separatorAfter = 1550
)
})
@GrammarRegistration(grammar="resources/c.tmLanguage.json", mimeType=MIMETypes.C)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java
index b9fada4..294ab0a 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java
@@ -100,8 +100,13 @@
@ActionReference(
path = "Editors/" + MIMETypes.CPP + "/Popup",
id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
- position = 1400,
- separatorAfter = 1450
+ position = 1400
+ ),
+ @ActionReference(
+ path = "Editors/" + MIMETypes.CPP + "/Popup",
+ id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+ position = 1500,
+ separatorAfter = 1550
)
})
@GrammarRegistration(grammar="resources/cpp.tmLanguage.json", mimeType=MIMETypes.CPP)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java
index df473e3..be8d498 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java
@@ -100,8 +100,13 @@
@ActionReference(
path = "Editors/" + MIMETypes.CPP + "/Popup",
id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
- position = 1400,
- separatorAfter = 1450
+ position = 1400
+ ),
+ @ActionReference(
+ path = "Editors/" + MIMETypes.CPP + "/Popup",
+ id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+ position = 1500,
+ separatorAfter = 1550
)
})
@GrammarRegistration(grammar="resources/c.tmLanguage.json", mimeType=MIMETypes.H)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java
index ea6349d..1222141 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java
@@ -100,8 +100,13 @@
@ActionReference(
path = "Editors/" + MIMETypes.CPP + "/Popup",
id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
- position = 1400,
- separatorAfter = 1450
+ position = 1400
+ ),
+ @ActionReference(
+ path = "Editors/" + MIMETypes.CPP + "/Popup",
+ id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+ position = 1500,
+ separatorAfter = 1550
)
})
@GrammarRegistration(grammar="resources/cpp.tmLanguage.json", mimeType=MIMETypes.HPP)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java
index 52478eb..27a3971 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java
@@ -66,7 +66,7 @@
private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName());
- private Map<Project, LanguageServerDescription> prj2Server = new HashMap<>();
+ private static final Map<Project, LanguageServerDescription> prj2Server = new HashMap<>();
@Override
public LanguageServerDescription startServer(Lookup lookup) {
@@ -117,8 +117,18 @@
command.add("--clang-tidy");
command.add("--completion-style=detailed");
}
- Process process = new ProcessBuilder(command).redirectError(Redirect.INHERIT).start();
- return LanguageServerDescription.create(new CopyInput(process.getInputStream(), System.err), new CopyOutput(process.getOutputStream(), System.err), process);
+ ProcessBuilder builder = new ProcessBuilder(command);
+ if (LOG.isLoggable(Level.FINEST)) {
+ builder.redirectError(Redirect.INHERIT);
+ }
+ Process process = builder.start();
+ InputStream in = process.getInputStream();
+ OutputStream out = process.getOutputStream();
+ if (LOG.isLoggable(Level.FINEST)) {
+ in = new CopyInput(in, System.err);
+ out = new CopyOutput(out, System.err);
+ }
+ return LanguageServerDescription.create(in, out, process);
}
return null;
} catch (IOException ex) {
diff --git a/cpplite/cpplite.project/nbproject/project.xml b/cpplite/cpplite.project/nbproject/project.xml
index a2d843f..993b7dc 100644
--- a/cpplite/cpplite.project/nbproject/project.xml
+++ b/cpplite/cpplite.project/nbproject/project.xml
@@ -26,6 +26,15 @@
<code-name-base>org.netbeans.modules.cpplite.project</code-name-base>
<module-dependencies>
<dependency>
+ <code-name-base>org.netbeans.api.java.classpath</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.63</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.api.templates</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java
index 4182887..e14fc4c 100644
--- a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java
+++ b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java
@@ -18,17 +18,24 @@
*/
package org.netbeans.modules.cpplite.project;
+import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
+import javax.swing.Icon;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.modules.cpplite.project.ui.customizer.CustomizerProviderImpl;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.project.ProjectFactory;
import org.netbeans.spi.project.ProjectFactory2;
import org.netbeans.spi.project.ProjectState;
import org.netbeans.spi.project.ui.PrivilegedTemplates;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.netbeans.spi.project.ui.RecommendedTemplates;
import org.openide.filesystems.FileObject;
import org.openide.util.ImageUtilities;
@@ -92,6 +99,8 @@
new CPPLiteCProjectConfigurationProvider(getRootPreferences(projectDirectory)),
new RecommendedTemplatesImpl(),
new PrivilegedTemplatesImpl(),
+ new ProjectInfo(this),
+ new ProjectOpenHookImpl(this),
this);
buildConfigurations.set(BuildConfiguration.read(getBuildPreferences(projectDirectory)));
}
@@ -130,6 +139,10 @@
getRootPreferences(projectDirectory).put(KEY_COMPILE_COMMANDS_EXECUTABLE, compileCommandsExecutable);
}
+ private static Icon loadProjectIcon() {
+ return ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/cpplite/project/resources/project.gif"));
+ }
+
@ServiceProvider(service=ProjectFactory.class)
public static final class FactoryImpl implements ProjectFactory2 {
@@ -137,7 +150,7 @@
public ProjectManager.Result isProject2(FileObject projectDirectory) {
Preferences prefs = getRootPreferences(projectDirectory, false);
if (prefs != null && prefs.getBoolean(KEY_IS_PROJECT, false)) {
- return new ProjectManager.Result(ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/cpplite/project/resources/project.gif")));
+ return new ProjectManager.Result(loadProjectIcon());
}
return null;
}
@@ -187,4 +200,60 @@
return TEMPLATES;
}
}
+
+ private static final class ProjectInfo implements ProjectInformation {
+
+ private final Project prj;
+
+ public ProjectInfo(Project prj) {
+ this.prj = prj;
+ }
+
+ @Override
+ public String getName() {
+ return prj.getProjectDirectory().getNameExt();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return prj.getProjectDirectory().getNameExt();
+ }
+
+ @Override
+ public Icon getIcon() {
+ return loadProjectIcon();
+ }
+
+ @Override
+ public Project getProject() {
+ return prj;
+ }
+
+ @Override
+ public void addPropertyChangeListener(PropertyChangeListener listener) {}
+
+ @Override
+ public void removePropertyChangeListener(PropertyChangeListener listener) {}
+
+ }
+
+ private static final class ProjectOpenHookImpl extends ProjectOpenedHook {
+
+ private final ClassPath source;
+
+ public ProjectOpenHookImpl(Project prj) {
+ this.source = ClassPathSupport.createClassPath(prj.getProjectDirectory());
+ }
+
+ @Override
+ protected void projectOpened() {
+ GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {source});
+ }
+
+ @Override
+ protected void projectClosed() {
+ GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {source});
+ }
+
+ }
}
diff --git a/extide/gradle/nbproject/project.xml b/extide/gradle/nbproject/project.xml
index 69cf6d2..60dadee 100644
--- a/extide/gradle/nbproject/project.xml
+++ b/extide/gradle/nbproject/project.xml
@@ -57,15 +57,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.47.1</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.47.1</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java b/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java
index 710c0a4..22a2b75 100644
--- a/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java
+++ b/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java
@@ -57,7 +57,6 @@
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.gradle.api.NbGradleProject;
import org.netbeans.modules.gradle.spi.GradleFiles;
import org.openide.awt.Notification;
@@ -500,7 +499,7 @@
})
public DownloadTask(GradleDistribution dist) {
this.dist = dist;
- handle = ProgressHandleFactory.createSystemHandle(Bundle.TIT_Download_Gradle(dist.getVersion()));
+ handle = ProgressHandle.createSystemHandle(Bundle.TIT_Download_Gradle(dist.getVersion()), null);
notification = NotificationDisplayer.getDefault().notify(
Bundle.TIT_Download_Gradle(dist.getVersion()),
NbGradleProject.getIcon(),
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java b/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
index 410b324..219be06 100644
--- a/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
+++ b/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
@@ -51,7 +51,6 @@
import org.gradle.tooling.ProgressEvent;
import org.gradle.tooling.ProjectConnection;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.gradle.NbGradleProjectImpl;
@@ -91,7 +90,7 @@
@SuppressWarnings("LeakingThisInConstructor")
public GradleDaemonExecutor(RunConfig config) {
super(config);
- handle = ProgressHandleFactory.createHandle(config.getTaskDisplayName(), this, new AbstractAction() {
+ handle = ProgressHandle.createHandle(config.getTaskDisplayName(), this, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
diff --git a/ide/editor.lib2/nbproject/project.properties b/ide/editor.lib2/nbproject/project.properties
index 4069275..cb51f55 100644
--- a/ide/editor.lib2/nbproject/project.properties
+++ b/ide/editor.lib2/nbproject/project.properties
@@ -18,7 +18,7 @@
is.autoload=true
javac.source=1.8
javac.compilerargs=-Xlint:unchecked
-spec.version.base=2.30.0
+spec.version.base=2.30.1
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/ide/extexecution/nbproject/project.xml b/ide/extexecution/nbproject/project.xml
index 7119b1a..2eeb431 100644
--- a/ide/extexecution/nbproject/project.xml
+++ b/ide/extexecution/nbproject/project.xml
@@ -40,15 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java b/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java
index 11a5acf..1c1de51 100644
--- a/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java
+++ b/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java
@@ -34,7 +34,6 @@
import javax.swing.AbstractAction;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.extexecution.ExecutionDescriptor.InputProcessorFactory;
import org.netbeans.api.extexecution.ExecutionDescriptor.InputProcessorFactory2;
import org.netbeans.api.extexecution.ExecutionDescriptor.LineConvertorFactory;
@@ -379,7 +378,7 @@
return null;
}
- ProgressHandle handle = ProgressHandleFactory.createHandle(displayName,
+ ProgressHandle handle = ProgressHandle.createHandle(displayName,
cancellable, new ProgressAction(inputOutput));
handle.setInitialDelay(0);
diff --git a/ide/lsp.client/nbproject/project.xml b/ide/lsp.client/nbproject/project.xml
index 14c73d2..182caec 100644
--- a/ide/lsp.client/nbproject/project.xml
+++ b/ide/lsp.client/nbproject/project.xml
@@ -111,6 +111,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.55</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.editor.indent</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -165,6 +174,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.jumpto</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.64</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.lexer</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -183,6 +201,14 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.parsing.indexing</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>9.17</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.projectapi</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -192,6 +218,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.queries</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.53</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.refactoring.api</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
index 27d5d18..b65920a 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
@@ -51,6 +51,7 @@
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.ResourceOperationKind;
import org.eclipse.lsp4j.ServerCapabilities;
+import org.eclipse.lsp4j.SymbolCapabilities;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.SymbolKindCapabilities;
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
@@ -61,6 +62,7 @@
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
+import org.eclipse.lsp4j.util.Preconditions;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.progress.*;
import org.netbeans.api.project.FileOwnerQuery;
@@ -89,6 +91,11 @@
*/
public class LSPBindings {
+ static {
+ //Don't perform null checks. The servers may not adhere to the specification, and send illegal nulls.
+ Preconditions.enableNullChecks(false);
+ }
+
private static final RequestProcessor WORKER = new RequestProcessor(LanguageClientImpl.class.getName(), 1, false, false);
private static final int DELAY = 500;
@@ -110,43 +117,50 @@
break;
}
}
+
+ String mimeType = FileUtil.getMIMEType(file);
Project prj = FileOwnerQuery.getOwner(file);
+
+ if (mimeType == null) {
+ return null;
+ }
+
+ return getBindingsImpl(prj, file, mimeType, true);
+ }
+
+ public static void ensureServerRunning(Project prj, String mimeType) {
+ getBindingsImpl(prj, prj.getProjectDirectory(), mimeType, false);
+ }
+
+ public static synchronized LSPBindings getBindingsImpl(Project prj, FileObject file, String mimeType, boolean forceBindings) {
FileObject dir;
+
if (prj == null) {
dir = file.getParent();
} else {
dir = prj.getProjectDirectory();
}
+
URI uri = dir.toURI();
- String mimeType = FileUtil.getMIMEType(file);
-
- if (mimeType == null) {
- return null;
- }
-
boolean[] created = new boolean[1];
LSPBindings bindings =
project2MimeType2Server.computeIfAbsent(uri, p -> new HashMap<>())
.computeIfAbsent(mimeType, mt -> {
MimeTypeInfo mimeTypeInfo = new MimeTypeInfo(mt);
- Reference<Project> prjRef = new WeakReference<>(prj);
ServerRestarter restarter = () -> {
synchronized (LSPBindings.class) {
- Project p = prjRef.get();
- if (p != null) {
- LSPBindings b = project2MimeType2Server.getOrDefault(uri, Collections.emptyMap()).remove(mimeType);
+ LSPBindings b = project2MimeType2Server.getOrDefault(uri, Collections.emptyMap()).remove(mimeType);
- if (b != null) {
- try {
- b.server.shutdown().get();
- } catch (InterruptedException | ExecutionException ex) {
- LOG.log(Level.FINE, null, ex);
- }
- if (b.process != null) {
- b.process.destroy();
- }
+ if (b != null) {
+ try {
+ b.server.shutdown().get();
+ } catch (InterruptedException | ExecutionException ex) {
+ LOG.log(Level.FINE, null, ex);
+ }
+ if (b.process != null) {
+ b.process.destroy();
}
}
}
@@ -181,9 +195,12 @@
}
}
}
- return new LSPBindings(null, null, null);
+ return forceBindings ? new LSPBindings(null, null, null) : null;
});
+ if (bindings == null) {
+ return null;
+ }
if (bindings.process != null && !bindings.process.isAlive()) {
//XXX: what now
return null;
@@ -246,6 +263,8 @@
wcc.setWorkspaceEdit(new WorkspaceEditCapabilities());
wcc.getWorkspaceEdit().setDocumentChanges(true);
wcc.getWorkspaceEdit().setResourceOperations(Arrays.asList(ResourceOperationKind.Create, ResourceOperationKind.Delete, ResourceOperationKind.Rename));
+ SymbolCapabilities sc = new SymbolCapabilities(new SymbolKindCapabilities(Arrays.asList(SymbolKind.values())));
+ wcc.setSymbol(sc);
initParams.setCapabilities(new ClientCapabilities(wcc, tdcc, null));
CompletableFuture<InitializeResult> initResult = server.initialize(initParams);
while (true) {
@@ -261,6 +280,21 @@
}
}
+ public static Set<LSPBindings> getAllBindings() {
+ Set<LSPBindings> allBindings = Collections.newSetFromMap(new IdentityHashMap<>());
+
+ project2MimeType2Server.values()
+ .stream()
+ .flatMap(n -> n.values().stream())
+ .forEach(allBindings::add);
+ workspace2Extension2Server.values()
+ .stream()
+ .flatMap(n -> n.values().stream())
+ .forEach(allBindings::add);
+
+ return allBindings;
+ }
+
private final LanguageServer server;
private final InitializeResult initResult;
private final Process process;
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java
new file mode 100644
index 0000000..97ba03d
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java
@@ -0,0 +1,206 @@
+/*
+ * 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.lsp.client.bindings;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.swing.Icon;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.WorkspaceSymbolParams;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectInformation;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.spi.jumpto.support.NameMatcher;
+import org.netbeans.spi.jumpto.support.NameMatcherFactory;
+import org.netbeans.spi.jumpto.type.SearchType;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author lahvac
+ */
+public class BaseSymbolProvider {
+
+ private static final Pattern WORD_START = Pattern.compile("(^|[^\\p{L}])(\\p{L})");
+ private final AtomicBoolean cancel = new AtomicBoolean();
+ private CompletableFuture<List<? extends SymbolInformation>> currentQuery;
+
+ public String name() {
+ return "lsp-client";
+ }
+
+ public void computeSymbolNames(SearchType searchType, String searchText, BiConsumer<SymbolInformation, String> found) {
+ cancel.set(false);
+
+ List<CompletableFuture<List<? extends SymbolInformation>>> queries = new ArrayList<>();
+
+ try {
+ for (LSPBindings b : LSPBindings.getAllBindings()) {
+ if (cancel.get()) {
+ return ;
+ }
+ queries.add(b.getWorkspaceService().symbol(new WorkspaceSymbolParams(searchText)));
+ }
+
+ NameMatcher matcher = NameMatcherFactory.createNameMatcher(searchText, searchType);
+
+ while (!queries.isEmpty()) {
+ if (cancel.get()) {
+ return ;
+ }
+
+
+ try {
+ currentQuery = queries.remove(queries.size() - 1);
+
+ List<? extends SymbolInformation> infos = currentQuery.get();
+
+ currentQuery = null;
+
+ if (infos != null) {
+ for (SymbolInformation info : infos) {
+ if (cancel.get()) {
+ return ;
+ }
+ Matcher wordStartMatcher = WORD_START.matcher(info.getName());
+ while (wordStartMatcher.find()) {
+ int nameStart = wordStartMatcher.start(2);
+ String namePart = info.getName().substring(nameStart);
+ if (matcher.accept(namePart)) {
+ found.accept(info, namePart);
+ }
+ }
+ }
+ }
+ } catch (InterruptedException ex) {
+ //ignore?
+ } catch (CancellationException ex) {
+ return ;
+ } catch (ExecutionException ex) {
+ LOG.log(Level.FINE, null, ex);
+ }
+ }
+ } finally {
+ if (cancel.get()) {
+ if (currentQuery != null) {
+ currentQuery.cancel(true);
+ }
+ queries.forEach(cf -> cf.cancel(true));
+ }
+ currentQuery = null;
+ }
+ }
+
+ private static final Logger LOG = Logger.getLogger(BaseSymbolProvider.class.getName());
+
+ public void cancel() {
+ cancel.set(true);
+ if (currentQuery != null) {
+ currentQuery.cancel(true);
+ }
+ }
+
+ public void cleanup() {
+ }
+
+ public static interface BaseSymbolDescriptor {
+
+ public SymbolInformation getInfo();
+
+ public default Icon getIcon() {
+ return Icons.getSymbolIcon(getInfo().getKind());
+ }
+
+ public default String getSymbolName() {
+ return getInfo().getName();
+ }
+
+ public default String getOwnerName() {
+ String container = getInfo().getContainerName();
+
+ if (container == null || "".equals(container)) {
+ String uri = getInfo().getLocation().getUri();
+
+ container = uri.substring(uri.lastIndexOf('/') + 1);
+ }
+
+ return container;
+ }
+
+ public default String getProjectName() {
+ return getProjectInformation().map(pi -> pi.getDisplayName()).orElse(null);
+ }
+
+ public default Icon getProjectIcon() {
+ return getProjectInformation().map(pi -> pi.getIcon()).orElse(null);
+ }
+
+ //XXX: should be private:
+ public default Optional<ProjectInformation> getProjectInformation() {
+ FileObject file = getFileObject();
+
+ if (file != null) {
+ Project owningProject = FileOwnerQuery.getOwner(file);
+
+ if (owningProject != null) {
+ return Optional.of(ProjectUtils.getInformation(owningProject));
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ public default FileObject getFileObject() {
+ try {
+ URI target = URI.create(getInfo().getLocation().getUri());
+
+ return URLMapper.findFileObject(target.toURL());
+ } catch (MalformedURLException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ return null;
+ }
+
+ public default int getOffset() {
+ return -1; //XXX
+ }
+
+ public default void open() {
+ Utils.open(getInfo().getLocation().getUri(), getInfo().getLocation().getRange());
+ }
+
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java
new file mode 100644
index 0000000..7c5c989
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java
@@ -0,0 +1,143 @@
+/*
+ * 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.lsp.client.bindings;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.parsing.spi.indexing.Context;
+import org.netbeans.modules.parsing.spi.indexing.CustomIndexer;
+import org.netbeans.modules.parsing.spi.indexing.CustomIndexerFactory;
+import org.netbeans.modules.parsing.spi.indexing.Indexable;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.EditableProperties;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author lahvac
+ */
+public class CustomIndexerImpl extends CustomIndexer {
+
+ private static final RequestProcessor WORKER = new RequestProcessor(CustomIndexerImpl.class.getName(), 1, false, false);
+
+ @Override
+ protected void index(Iterable<? extends Indexable> files, Context context) {
+ handleStoredFiles(context, props -> {
+ FileObject root = context.getRoot();
+ for (Indexable i : files) {
+ FileObject file = root.getFileObject(i.getRelativePath());
+ if (file != null) {
+ props.setProperty(i.getRelativePath(), FileUtil.getMIMEType(file));
+ }
+ }
+
+ Set<String> mimeTypes = new HashSet<>(props.values());
+ System.err.println("mimeTypes=" + mimeTypes);
+ Project prj = FileOwnerQuery.getOwner(root);
+
+ if (prj != null) {
+ WORKER.post(() -> {
+ for (String mimeType : mimeTypes) {
+ LSPBindings.ensureServerRunning(prj, mimeType);
+ }
+ });
+ }
+ });
+ }
+
+ private static final String INDEX_FILE_NAME = "index.properties";
+
+ private static void handleStoredFiles(Context context, Consumer<EditableProperties> handleProperties) {
+ EditableProperties props = new EditableProperties(true);
+ FileObject index = context.getIndexFolder().getFileObject(INDEX_FILE_NAME);
+
+ if (index != null) {
+ try (InputStream in = index.getInputStream()) {
+ props.load(in);
+ } catch (IOException ex) {
+ //ignore...
+ }
+ }
+
+ EditableProperties old = props.cloneProperties();
+
+ handleProperties.accept(props);
+
+ if (!old.equals(props)) {
+ try {
+ if (index == null) {
+ index = context.getIndexFolder().createData(INDEX_FILE_NAME);
+ }
+ try (OutputStream out = index.getOutputStream()) {
+ props.store(out);
+ }
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ }
+
+ @MimeRegistration(mimeType="", service=CustomIndexerFactory.class)
+ public static final class FactoryImpl extends CustomIndexerFactory {
+
+ @Override
+ public CustomIndexer createIndexer() {
+ return new CustomIndexerImpl();
+ }
+
+ @Override
+ public boolean supportsEmbeddedIndexers() {
+ return true;
+ }
+
+ @Override
+ public void filesDeleted(Iterable<? extends Indexable> deleted, Context context) {
+ handleStoredFiles(context, props -> {
+ for (Indexable d : deleted) {
+ props.remove(d.getRelativePath());
+ }
+ });
+ }
+
+ @Override
+ public void filesDirty(Iterable<? extends Indexable> dirty, Context context) {
+ }
+
+ @Override
+ public String getIndexerName() {
+ return "lsp-indexer";
+ }
+
+ @Override
+ public int getIndexVersion() {
+ return 0;
+ }
+
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java
index 6570b06..8de64f7 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java
@@ -75,4 +75,8 @@
}
return null;
}
+
+ public static Icon getSymbolIcon(SymbolKind symbolKind) {
+ return ImageUtilities.loadImageIcon(getSymbolIconBase(symbolKind), false);
+ }
}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java
new file mode 100644
index 0000000..250b783
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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.lsp.client.bindings;
+
+import javax.swing.Icon;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.netbeans.spi.jumpto.symbol.SymbolDescriptor;
+import org.netbeans.spi.jumpto.symbol.SymbolProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+@ServiceProvider(service=SymbolProvider.class)
+public class SymbolProviderImpl extends BaseSymbolProvider implements SymbolProvider {
+
+ @Override
+ @Messages("DN_getDisplayName=Language Server Symbol Provider")
+ public String getDisplayName() {
+ return Bundle.DN_Symbols();
+ }
+
+ @Override
+ public void computeSymbolNames(Context context, Result result) {
+ computeSymbolNames(context.getSearchType(), context.getText(), (info, simpleName) -> result.addResult(new SymbolDescriptorImpl(info, simpleName)));
+ }
+
+ public static class SymbolDescriptorImpl extends SymbolDescriptor implements BaseSymbolDescriptor {
+
+ private final SymbolInformation info;
+ private final String simpleName;
+
+ public SymbolDescriptorImpl(SymbolInformation info, String simpleName) {
+ this.info = info;
+ this.simpleName = simpleName;
+ }
+
+ @Override
+ public SymbolInformation getInfo() {
+ return info;
+ }
+
+ @Override
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return BaseSymbolDescriptor.super.getIcon();
+ }
+
+ @Override
+ public String getSymbolName() {
+ return BaseSymbolDescriptor.super.getSymbolName();
+ }
+
+ @Override
+ public String getOwnerName() {
+ return BaseSymbolDescriptor.super.getOwnerName();
+ }
+
+ @Override
+ public String getProjectName() {
+ return BaseSymbolDescriptor.super.getProjectName();
+ }
+
+ @Override
+ public Icon getProjectIcon() {
+ return BaseSymbolDescriptor.super.getProjectIcon();
+ }
+
+ @Override
+ public FileObject getFileObject() {
+ return BaseSymbolDescriptor.super.getFileObject();
+ }
+
+ @Override
+ public int getOffset() {
+ return BaseSymbolDescriptor.super.getOffset();
+ }
+
+ @Override
+ public void open() {
+ BaseSymbolDescriptor.super.open();
+ }
+
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java
new file mode 100644
index 0000000..498cdb3
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java
@@ -0,0 +1,127 @@
+/*
+ * 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.lsp.client.bindings;
+
+import java.util.EnumSet;
+import java.util.Set;
+import javax.swing.Icon;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.SymbolKind;
+import org.netbeans.spi.jumpto.type.TypeDescriptor;
+import org.netbeans.spi.jumpto.type.TypeProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+@ServiceProvider(service=TypeProvider.class)
+public class TypeProviderImpl extends BaseSymbolProvider implements TypeProvider {
+
+ private static final Set<SymbolKind> TYPE_KINDS = EnumSet.of(
+ SymbolKind.Class, SymbolKind.Enum, SymbolKind.Interface,
+ SymbolKind.Struct
+ );
+
+ @Override
+ @Messages("DN_TypeProviderImpl=Language Server Type Provider")
+ public String getDisplayName() {
+ return Bundle.DN_TypeProviderImpl();
+ }
+
+ @Override
+ public void computeTypeNames(Context context, Result result) {
+ computeSymbolNames(context.getSearchType(),
+ context.getText(),
+ (info, simpleName) -> {
+ if (TYPE_KINDS.contains(info.getKind())) {
+ result.addResult(new TypeDescriptorImpl(info, simpleName));
+ }
+ });
+ }
+
+ public static class TypeDescriptorImpl extends TypeDescriptor implements BaseSymbolDescriptor {
+
+ private final SymbolInformation info;
+ private final String simpleName;
+
+ public TypeDescriptorImpl(SymbolInformation info, String simpleName) {
+ this.info = info;
+ this.simpleName = simpleName;
+ }
+
+ @Override
+ public SymbolInformation getInfo() {
+ return info;
+ }
+
+ @Override
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return BaseSymbolDescriptor.super.getIcon();
+ }
+
+ @Override
+ public String getTypeName() {
+ return BaseSymbolDescriptor.super.getSymbolName();
+ }
+
+ @Override
+ public String getProjectName() {
+ return BaseSymbolDescriptor.super.getProjectName();
+ }
+
+ @Override
+ public Icon getProjectIcon() {
+ return BaseSymbolDescriptor.super.getProjectIcon();
+ }
+
+ @Override
+ public FileObject getFileObject() {
+ return BaseSymbolDescriptor.super.getFileObject();
+ }
+
+ @Override
+ public int getOffset() {
+ return BaseSymbolDescriptor.super.getOffset();
+ }
+
+ @Override
+ public void open() {
+ BaseSymbolDescriptor.super.open();
+ }
+
+ @Override
+ public String getOuterName() {
+ return null;
+ }
+
+ @Override
+ public String getContextName() {
+ return null;
+ }
+
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties
new file mode 100644
index 0000000..00f04bf
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties
@@ -0,0 +1,19 @@
+# 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.
+
+RenamePanel.jLabel1.text=Name:
+RenamePanel.name.text=
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/ModificationResult.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/ModificationResult.java
new file mode 100644
index 0000000..f70816d
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/ModificationResult.java
@@ -0,0 +1,430 @@
+/*
+ * 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.lsp.client.bindings.refactoring;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.StyledDocument;
+import org.netbeans.api.queries.FileEncodingQuery;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataObject;
+import org.openide.text.NbDocument;
+import org.openide.text.PositionRef;
+import org.openide.util.Parameters;
+
+/**Copied from csl.api
+ * Class that collects changes built during a modification task run.
+ *
+ * @author Dusan Balek
+ */
+public final class ModificationResult implements org.netbeans.modules.refactoring.spi.ModificationResult {
+
+ private boolean committed;
+ private final Map<FileObject, List<Difference>> diffs = new HashMap<FileObject, List<Difference>>();
+
+ private static final Comparator<Difference> COMPARATOR = new Comparator<Difference>() {
+ public int compare(Difference d1, Difference d2) {
+ return d1.getStartPosition().getOffset() - d2.getStartPosition().getOffset();
+ }
+ };
+
+ /** Creates a new instance of ModificationResult */
+ public ModificationResult() {
+ }
+
+ // API of the class --------------------------------------------------------
+
+ public void addDifferences(FileObject fo, List<Difference> differences) {
+ List<Difference> fileDiffs = diffs.get(fo);
+ if (fileDiffs == null) {
+ fileDiffs = new ArrayList<Difference>();
+ diffs.put(fo, fileDiffs);
+ }
+ fileDiffs.addAll(differences);
+
+ // Sort the diffs, if applicable
+ if (fileDiffs.size() > 0) {
+ Collections.sort(fileDiffs, COMPARATOR);
+ }
+ }
+
+ public Set<? extends FileObject> getModifiedFileObjects() {
+ return diffs.keySet();
+ }
+
+ public List<? extends Difference> getDifferences(FileObject fo) {
+ return diffs.get(fo);
+ }
+
+ public Set<File> getNewFiles() {
+ Set<File> newFiles = new HashSet<File>();
+ for (List<Difference> ds:diffs.values()) {
+ for (Difference d: ds) {
+ if (d.getKind() == Difference.Kind.CREATE) {
+ newFiles.add(((CreateFileDifference) d).getFile());
+ }
+ }
+ }
+ return newFiles;
+ }
+
+ /**
+ * Once all of the changes have been collected, this method can be used
+ * to commit the changes to the source files
+ */
+ public void commit() throws IOException {
+ if (this.committed) {
+ throw new IllegalStateException ("Calling commit on already committed Modificationesult."); //NOI18N
+ }
+ try {
+ for (Map.Entry<FileObject, List<Difference>> me : diffs.entrySet()) {
+ commit(me.getKey(), me.getValue(), null);
+ }
+ } finally {
+ this.committed = true;
+ }
+ }
+
+ private void commit (final FileObject fo, final List<Difference> differences, final Writer out) throws IOException {
+ DataObject dObj = DataObject.find(fo);
+ EditorCookie ec = dObj != null ? dObj.getCookie(org.openide.cookies.EditorCookie.class) : null;
+ // if editor cookie was found and user does not provided his own
+ // writer where he wants to see changes, commit the changes to
+ // found document.
+ if (ec != null && out == null) {
+ final StyledDocument doc = ec.getDocument();
+ if (doc != null) {
+ final IOException[] exceptions = new IOException [1];
+ NbDocument.runAtomic(doc, new Runnable () {
+ public void run () {
+ try {
+ commit2 (doc, differences, out);
+ } catch (IOException ex) {
+ exceptions [0] = ex;
+ }
+ }
+ });
+ if (exceptions [0] != null)
+ throw exceptions [0];
+ return;
+ }
+ }
+ InputStream ins = null;
+ ByteArrayOutputStream baos = null;
+ Reader in = null;
+ Writer out2 = out;
+ try {
+ Charset encoding = FileEncodingQuery.getEncoding(fo);
+ ins = fo.getInputStream();
+ baos = new ByteArrayOutputStream();
+ FileUtil.copy(ins, baos);
+
+ ins.close();
+ ins = null;
+ byte[] arr = baos.toByteArray();
+ int arrLength = convertToLF(arr);
+ baos.close();
+ baos = null;
+ in = new InputStreamReader(new ByteArrayInputStream(arr, 0, arrLength), encoding);
+ // initialize standard commit output stream, if user
+ // does not provide his own writer
+ boolean ownOutput = out != null;
+ if (out2 == null) {
+ out2 = new OutputStreamWriter(fo.getOutputStream(), encoding);
+ }
+ int offset = 0;
+ for (Difference diff : differences) {
+ if (diff.isExcluded())
+ continue;
+ if (Difference.Kind.CREATE == diff.getKind()) {
+ if (!ownOutput) {
+ createUnit((CreateFileDifference)diff, null);
+ }
+ continue;
+ }
+ int pos = diff.getStartPosition().getOffset();
+ int toread = pos - offset;
+ char[] buff = new char[toread];
+ int n;
+ int rc = 0;
+ while ((n = in.read(buff,0, toread - rc)) > 0 && rc < toread) {
+ out2.write(buff, 0, n);
+ rc+=n;
+ offset += n;
+ }
+ switch (diff.getKind()) {
+ case INSERT:
+ out2.write(diff.getNewText());
+ break;
+ case REMOVE:
+ int len = diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset();
+ in.skip(len);
+ offset += len;
+ break;
+ case CHANGE:
+ len = diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset();
+ in.skip(len);
+ offset += len;
+ out2.write(diff.getNewText());
+ break;
+ }
+ }
+ char[] buff = new char[1024];
+ int n;
+ while ((n = in.read(buff)) > 0)
+ out2.write(buff, 0, n);
+ } finally {
+ if (ins != null)
+ ins.close();
+ if (baos != null)
+ baos.close();
+ if (in != null)
+ in.close();
+ if (out2 != null)
+ out2.close();
+ }
+ }
+
+ private void commit2 (final StyledDocument doc, final List<Difference> differences, Writer out) throws IOException {
+ for (Difference diff : differences) {
+ if (diff.isExcluded())
+ continue;
+ switch (diff.getKind()) {
+ case INSERT:
+ case REMOVE:
+ case CHANGE:
+ processDocument(doc, diff);
+ break;
+ case CREATE:
+ createUnit((CreateFileDifference)diff, out);
+ break;
+ }
+ }
+ }
+
+ private void processDocument(final StyledDocument doc, final Difference diff) throws IOException {
+ final BadLocationException[] blex = new BadLocationException[1];
+ Runnable task = new Runnable() {
+
+ public void run() {
+ try {
+ processDocumentLocked(doc, diff);
+ } catch (BadLocationException ex) {
+ blex[0] = ex;
+ }
+ }
+ };
+ if (diff.isCommitToGuards()) {
+ NbDocument.runAtomic(doc, task);
+ } else {
+ try {
+ NbDocument.runAtomicAsUser(doc, task);
+ } catch (BadLocationException ex) {
+ blex[0] = ex;
+ }
+ }
+ if (blex[0] != null) {
+ IOException ioe = new IOException();
+ ioe.initCause(blex[0]);
+ throw ioe;
+ }
+ }
+
+ private void processDocumentLocked(Document doc, Difference diff) throws BadLocationException {
+ switch (diff.getKind()) {
+ case INSERT:
+ doc.insertString(diff.getStartPosition().getOffset(), diff.getNewText(), null);
+ break;
+ case REMOVE:
+ doc.remove(diff.getStartPosition().getOffset(), diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset());
+ break;
+ case CHANGE:
+ doc.remove(diff.getStartPosition().getOffset(), diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset());
+ doc.insertString(diff.getStartPosition().getOffset(), diff.getNewText(), null);
+ break;
+ }
+ }
+
+ private void createUnit(CreateFileDifference diff, Writer out) {
+ Writer w = out;
+ try {
+ if (w == null) {
+ w = new FileWriter(diff.getFile());
+ }
+ w.append(diff.getNewText());
+ } catch (IOException e) {
+ Logger.getLogger(ModificationResult.class.getName()).log(Level.SEVERE, e.getMessage(), e);
+ } finally {
+ if (w != null) {
+ try {
+ w.close();
+ } catch (IOException e) {
+ Logger.getLogger(ModificationResult.class.getName()).log(Level.SEVERE, e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+ private int convertToLF(byte[] buff) {
+ int j = 0;
+ for (int i = 0; i < buff.length; i++) {
+ if (buff[i] != '\r') {
+ buff[j++] = buff[i];
+ }
+ }
+ return j;
+ }
+
+ /**
+ * Returned string represents preview of resulting source. No difference
+ * really is applied. Respects {@code isExcluded()} flag of difference.
+ *
+ * @param there can be more resulting source, user has to specify
+ * which wants to preview.
+ * @return if changes are applied source looks like return string
+ * @throws IllegalArgumentException if the provided {@link FileObject} is not
+ * modified in this {@link ModificationResult}
+ */
+ public String getResultingSource(FileObject fileObject) throws IOException, IllegalArgumentException {
+ Parameters.notNull("fileObject", fileObject);
+
+ if (!getModifiedFileObjects().contains(fileObject)) {
+ throw new IllegalArgumentException("File: " + FileUtil.getFileDisplayName(fileObject) + " is not modified in this ModificationResult");
+ }
+
+ StringWriter writer = new StringWriter();
+ commit(fileObject, diffs.get(fileObject), writer);
+
+ return writer.toString();
+ }
+
+ public static class Difference {
+ Kind kind;
+ PositionRef startPos;
+ PositionRef endPos;
+ String oldText;
+ String newText;
+ String description;
+ private boolean excluded;
+ private boolean ignoreGuards = false;
+
+ public Difference(Kind kind, PositionRef startPos, PositionRef endPos, String oldText, String newText, String description) {
+ this.kind = kind;
+ this.startPos = startPos;
+ this.endPos = endPos;
+ this.oldText = oldText;
+ this.newText = newText;
+ this.description = description;
+ this.excluded = false;
+ }
+
+ public Difference(Kind kind, PositionRef startPos, PositionRef endPos, String oldText, String newText) {
+ this(kind, startPos, endPos, oldText, newText, null);
+ }
+
+ public Kind getKind() {
+ return kind;
+ }
+
+ public PositionRef getStartPosition() {
+ return startPos;
+ }
+
+ public PositionRef getEndPosition() {
+ return endPos;
+ }
+
+ public String getOldText() {
+ return oldText;
+ }
+
+ public String getNewText() {
+ return newText;
+ }
+
+ public boolean isExcluded() {
+ return excluded;
+ }
+
+ public void exclude(boolean b) {
+ excluded = b;
+ }
+
+ /**
+ * Gets flag if it is possible to write to guarded sections.
+ * @return {@code true} in case the difference may be written even into
+ * guarded sections.
+ * @see #guards(boolean)
+ * @since 0.33
+ */
+ public boolean isCommitToGuards() {
+ return ignoreGuards;
+ }
+
+ /**
+ * Sets flag if it is possible to write to guarded sections.
+ * @param b flag if it is possible to write to guarded sections
+ * @since 0.33
+ */
+ public void setCommitToGuards(boolean b) {
+ ignoreGuards = b;
+ }
+
+ @Override
+ public String toString() {
+ return kind + "<" + startPos.getOffset() + ", " + endPos.getOffset() + ">: " + oldText + " -> " + newText;
+ }
+ public String getDescription() {
+ return description;
+ }
+
+ public static enum Kind {
+ INSERT,
+ REMOVE,
+ CHANGE,
+ CREATE;
+ }
+ } // End of Difference class
+
+ public static class CreateFileDifference extends Difference {
+ private final File file;
+
+ public CreateFileDifference(File file, String text) {
+ super(Kind.CREATE, null, null, null, text, "Create file " + file.getPath());
+ this.file = file;
+ }
+
+ public final File getFile() {
+ return file;
+ }
+
+ @Override
+ public String toString() {
+ return kind + "Create File: " + file.getName() + "; contents = \"\n" + newText + "\"";
+ }
+ } // End of CreateFileDifference class
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
index c267eb8..7ebc214 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
@@ -19,16 +19,49 @@
package org.netbeans.modules.lsp.client.bindings.refactoring;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.text.BadLocationException;
import javax.swing.text.Position;
import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.CreateFile;
+import org.eclipse.lsp4j.DeleteFile;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
+import org.eclipse.lsp4j.ResourceOperationKind;
+import org.eclipse.lsp4j.TextDocumentEdit;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.modules.lsp.client.LSPBindings;
import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult.Difference;
+import org.netbeans.modules.lsp.client.bindings.refactoring.tree.DiffElement;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
import org.netbeans.modules.refactoring.api.WhereUsedQuery;
+import org.netbeans.modules.refactoring.spi.BackupFacility;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.netbeans.modules.refactoring.spi.RefactoringPlugin;
import org.netbeans.modules.refactoring.spi.RefactoringPluginFactory;
@@ -36,17 +69,25 @@
import org.openide.cookies.EditorCookie;
import org.openide.cookies.LineCookie;
import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.loaders.DataObject;
+import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.CloneableEditorSupport;
import org.openide.text.Line;
import org.openide.text.PositionBounds;
+import org.openide.text.PositionRef;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.Pair;
import org.openide.util.lookup.ServiceProvider;
/**
*
* @author lahvac
*/
+@NbBundle.Messages("TXT_Canceled=Canceled")
public class Refactoring {
private static final class WhereUsedRefactoringPlugin implements RefactoringPlugin {
@@ -54,6 +95,8 @@
private final WhereUsedQuery query;
private final LSPBindings bindings;
private final ReferenceParams params;
+ private final AtomicBoolean cancel = new AtomicBoolean();
+ private volatile CompletableFuture<List<? extends Location>> runningRequest;
public WhereUsedRefactoringPlugin(WhereUsedQuery query, LSPBindings bindings, ReferenceParams params) {
this.query = query;
@@ -78,28 +121,35 @@
@Override
public void cancelRequest() {
- throw new UnsupportedOperationException("Not supported yet.");
+ cancel.set(true);
+ CompletableFuture localRunningRequest = runningRequest;
+ if(localRunningRequest != null) {
+ localRunningRequest.cancel(true);
+ }
}
@Override
public Problem prepare(RefactoringElementsBag refactoringElements) {
try {
- for (Location l : bindings.getTextDocumentService().references(params).get()) {
+ runningRequest = bindings.getTextDocumentService().references(params);
+ for (Location l : runningRequest.get()) {
+ if(cancel.get()) {
+ break;
+ }
FileObject file = Utils.fromURI(l.getUri());
- PositionBounds boundsTemp = null;
if (file != null) {
+ PositionBounds bounds;
try {
CloneableEditorSupport es = file.getLookup().lookup(CloneableEditorSupport.class);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
StyledDocument doc = ec.openDocument();
- boundsTemp = new PositionBounds(es.createPositionRef(Utils.getOffset(doc, l.getRange().getStart()), Position.Bias.Forward),
+ bounds = new PositionBounds(es.createPositionRef(Utils.getOffset(doc, l.getRange().getStart()), Position.Bias.Forward),
es.createPositionRef(Utils.getOffset(doc, l.getRange().getEnd()), Position.Bias.Forward));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
- boundsTemp = null;
+ bounds = null;
}
- PositionBounds bounds = boundsTemp;
LineCookie lc = file.getLookup().lookup(LineCookie.class);
Line startLine = lc.getLineSet().getCurrent(l.getRange().getStart().getLine());
String lineText = startLine.getText();
@@ -110,14 +160,210 @@
refactoringElements.add(query, new LSPRefactoringElementImpl(annotatedLine, file, bounds));
}
}
+ runningRequest = null;
return null;
+ } catch (CancellationException ex) {
+ return new Problem(false, Bundle.TXT_Canceled());
} catch (InterruptedException | ExecutionException ex) {
+ Exceptions.printStackTrace(ex);
return new Problem(true, ex.getLocalizedMessage());
}
}
}
+ private static final class RenameRefactoringPlugin implements RefactoringPlugin {
+
+ private final RenameRefactoring refactoring;
+ private final LSPBindings bindings;
+ private final RenameParams params;
+ private final AtomicBoolean cancel = new AtomicBoolean();
+ private volatile CompletableFuture<WorkspaceEdit> runningRequest;
+
+ public RenameRefactoringPlugin(RenameRefactoring refactoring, LSPBindings bindings, RenameParams params) {
+ this.refactoring = refactoring;
+ this.bindings = bindings;
+ this.params = params;
+ }
+
+ @Override
+ public Problem preCheck() {
+ return null;
+ }
+
+ @Override
+ public Problem checkParameters() {
+ return null;
+ }
+
+ @Override
+ public Problem fastCheckParameters() {
+ return null;
+ }
+
+ @Override
+ public void cancelRequest() {
+ cancel.set(true);
+ CompletableFuture localRunningRequest = runningRequest;
+ if(localRunningRequest != null) {
+ localRunningRequest.cancel(true);
+ }
+ }
+
+ @Override
+ public Problem prepare(RefactoringElementsBag refactoringElements) {
+ if (cancel.get()) {
+ return new Problem(false, Bundle.TXT_Canceled());
+ }
+ Problem p = null;
+ try {
+ runningRequest = bindings.getTextDocumentService().rename(params);
+ WorkspaceEdit edit = runningRequest.get();
+ List<Either<TextDocumentEdit, ResourceOperation>> documentChanges = edit.getDocumentChanges();
+ ModificationResult result = new ModificationResult();
+ Map<FileObject, List<Difference>> file2Diffs = new HashMap<>();
+ Map<String, String> newURI2Old = new HashMap<>();
+ Map<String, String> newFileURI2Content = new HashMap<>();
+
+ if (documentChanges != null) {
+ for (Either<TextDocumentEdit, ResourceOperation> part : documentChanges) {
+ if(cancel.get()) {
+ break;
+ }
+ if (part.isLeft()) {
+ String uri = part.getLeft().getTextDocument().getUri();
+ uri = newURI2Old.getOrDefault(uri, uri);
+ FileObject file = Utils.fromURI(uri);
+
+ if (file != null) {
+ for (TextEdit te : part.getLeft().getEdits()) {
+ Difference diff = textEdit2Difference(file, te);
+ file2Diffs.computeIfAbsent(file, f -> new ArrayList<>())
+ .add(diff);
+ }
+ } else if (newFileURI2Content.containsKey(uri)) {
+ FileObject temp = FileUtil.createMemoryFileSystem().getRoot().createData("temp.txt");
+ try (OutputStream out = temp.getOutputStream()) {
+ out.write(newFileURI2Content.get(uri).getBytes()); //TODO: encoding - native, OK?
+ }
+ List<Difference> diffs = new ArrayList<>();
+ for (TextEdit te : part.getLeft().getEdits()) {
+ diffs.add(textEdit2Difference(temp, te));
+ }
+ ModificationResult tempResult = new ModificationResult();
+ tempResult.addDifferences(temp, diffs);
+ newFileURI2Content.put(uri, tempResult.getResultingSource(temp));
+ } else {
+ //XXX: problem...
+ }
+ } else {
+ switch (part.getRight().getKind()) {
+ case ResourceOperationKind.Rename: {
+ RenameFile rename = (RenameFile) part.getRight();
+ FileObject file = Utils.fromURI(rename.getOldUri());
+ refactoringElements.addFileChange(refactoring, new LSPRenameFile(file, rename.getNewUri()));
+ newURI2Old.put(rename.getNewUri(), rename.getOldUri());
+ break;
+ }
+ case ResourceOperationKind.Delete: {
+ DeleteFile delete = (DeleteFile) part.getRight();
+ FileObject file = Utils.fromURI(delete.getUri());
+ refactoringElements.addFileChange(refactoring, new LSPDeleteFile(file));
+ break;
+ }
+ case ResourceOperationKind.Create: {
+ CreateFile create = (CreateFile) part.getRight();
+ String uri = create.getUri();
+ newFileURI2Content.put(uri, "");
+ break;
+ }
+ default:
+ p = chain(new Problem(true, "Unknown file operation: " + part.getRight().getKind()), p);
+ break;
+ }
+ }
+ }
+ } else {
+ for (Entry<String, List<TextEdit>> fileAndChanges : edit.getChanges().entrySet()) {
+ if(cancel.get()) {
+ break;
+ }
+ //TODO: errors:
+ FileObject file = Utils.fromURI(fileAndChanges.getKey());
+
+ for (TextEdit te : fileAndChanges.getValue()) {
+ Difference diff = textEdit2Difference(file, te);
+ file2Diffs.computeIfAbsent(file, f -> new ArrayList<>())
+ .add(diff);
+ }
+ }
+ }
+
+ if (cancel.get()) {
+ p = chain(new Problem(false, Bundle.TXT_Canceled()), p);
+ } else {
+
+ file2Diffs.entrySet()
+ .forEach(e -> {
+ e.getValue()
+ .forEach(diff -> refactoringElements.add(refactoring, DiffElement.create(diff, e.getKey(), result)));
+ result.addDifferences(e.getKey(), e.getValue());
+ });
+
+ newFileURI2Content.entrySet()
+ .forEach(e -> {
+ refactoringElements.add(refactoring, new LSPCreateFile(e.getKey(), e.getValue()));
+ });
+ refactoringElements.registerTransaction(new RefactoringCommit(Collections.singletonList(result)));
+
+ if (cancel.get()) {
+ p = chain(new Problem(false, Bundle.TXT_Canceled()), p);
+ }
+ }
+
+ return p;
+ } catch (CancellationException ex) {
+ return chain(new Problem(false, Bundle.TXT_Canceled()), p);
+ } catch (InterruptedException | ExecutionException | IOException ex) {
+ return chain(new Problem(true, ex.getLocalizedMessage()), p);
+ } finally {
+ runningRequest = null;
+ }
+ }
+
+ private Problem chain(Problem current, Problem existing) {
+ if (existing != null) {
+ current.setNext(existing);
+ }
+ return current;
+ }
+
+ private Difference textEdit2Difference(FileObject file, TextEdit edit) {
+ if (file != null) {
+ try {
+ EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+ StyledDocument doc = ec.openDocument();
+ CloneableEditorSupport es = file.getLookup().lookup(CloneableEditorSupport.class);
+
+ PositionRef start = es.createPositionRef(Utils.getOffset(doc, edit.getRange().getStart()), Position.Bias.Forward);
+ PositionRef end = es.createPositionRef(Utils.getOffset(doc, edit.getRange().getEnd()), Position.Bias.Forward);
+ PositionBounds bounds = new PositionBounds(start, end);
+ return new Difference(Difference.Kind.CHANGE, start, end, bounds.getText(), edit.getNewText());
+ } catch (IOException | BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ private static String uri2SimpleName(String uri) {
+ int dot = uri.lastIndexOf('/');
+
+ return uri.substring(dot + 1);
+ }
+
public static class LSPRefactoringElementImpl extends SimpleRefactoringElementImplementation {
private final String annotatedLine;
@@ -142,6 +388,8 @@
@Override
public void performChange() {
+ // Currently the LSPRefactoringElementImpl is only used for the
+ // WhereUsedRefactoring, which is not doing changes
throw new UnsupportedOperationException();
}
@@ -161,6 +409,258 @@
}
}
+ public static class LSPRenameFile extends SimpleRefactoringElementImplementation {
+
+ private final FileObject fo;
+ private final String newUri;
+ public LSPRenameFile(FileObject fo, String newUri) {
+ this.fo = fo;
+ this.oldUri = fo.toURI().toString();
+ this.newUri = newUri;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "# {0} - current name of the file",
+ "TXT_RenameFile=Rename file {0}",
+ "# {0} - current name of the folders",
+ "TXT_RenameFolder=Rename folder {0}"
+ })
+ public String getText() {
+ return fo.isFolder()? Bundle.TXT_RenameFolder(fo.getNameExt()) :
+ Bundle.TXT_RenameFile(fo.getNameExt());
+ }
+
+ @Override
+ public String getDisplayText() {
+ return getText();
+ }
+
+ private String oldUri;
+
+ @Override
+ public void performChange() {
+ oldUri = fo.getName();
+ doRename(newUri);
+ }
+
+ @Override
+ public void undoChange(){
+// if (!fo.isValid()) {
+// throw new CannotUndoRefactoring(Collections.singleton(fo.getPath()));
+// }
+ doRename(oldUri);
+ }
+
+ private void doRename(String uri) {
+ try {
+ //XXX: different path, not only the name:
+ String newName = uri.substring(uri.lastIndexOf('/') + 1);
+ DataObject.find(fo).rename(newName);
+ } catch (DataObjectNotFoundException ex) {
+ throw new IllegalStateException(ex);
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @Override
+ public Lookup getLookup() {
+ return Lookup.EMPTY;
+ }
+
+ @Override
+ public FileObject getParentFile() {
+ return fo;
+ }
+
+ @Override
+ public PositionBounds getPosition() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return uri2SimpleName(fo.toURI().toString()) + "=>" + uri2SimpleName(newUri);
+ }
+
+ }
+
+ public static class LSPDeleteFile extends SimpleRefactoringElementImplementation {
+ private final URL res;
+ private final String filename;
+
+ private BackupFacility.Handle id;
+
+ /**
+ *
+ * @param fo
+ * @param session
+ */
+ public LSPDeleteFile(FileObject fo) {
+ this.res = fo.toURL();
+ this.filename = fo.getNameExt();
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "# {0} - name of the file to be deleted",
+ "TXT_DeleteFile=Delete file {0}"
+ })
+ public String getText() {
+ return Bundle.TXT_DeleteFile(filename);
+ }
+
+ @Override
+ public String getDisplayText() {
+ return getText();
+ }
+
+ @Override
+ public void performChange() {
+ try {
+ FileObject fo = URLMapper.findFileObject(res);
+ if (fo == null) {
+ throw new IOException(res.toString());
+ }
+ id = BackupFacility.getDefault().backup(fo);
+ DataObject.find(fo).delete();
+ } catch (DataObjectNotFoundException ex) {
+ Exceptions.printStackTrace(ex);
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ @Override
+ public void undoChange() {
+ try {
+ FileObject f = URLMapper.findFileObject(res);
+ if (f != null) {
+// throw new CannotUndoRefactoring(Collections.singleton(f.getPath()));
+ }
+ id.restore();
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ @Override
+ public Lookup getLookup() {
+ return Lookup.EMPTY;
+ }
+
+ @Override
+ public FileObject getParentFile() {
+ return URLMapper.findFileObject(res);
+ }
+
+ @Override
+ public PositionBounds getPosition() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return uri2SimpleName(res.toString()) + "=>";
+ }
+
+ }
+
+ public static class LSPCreateFile extends SimpleRefactoringElementImplementation {
+ private final String uri;
+ private final String content;
+
+ /**
+ *
+ * @param fo
+ * @param session
+ */
+ public LSPCreateFile(String uri, String content) {
+ this.uri = uri;
+ this.content = content;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "# {0} - name of the newly created file",
+ "TXT_CreateFile=Create file {0}"
+ })
+ public String getText() {
+ return Bundle.TXT_CreateFile(uri2SimpleName(uri));
+ }
+
+ @Override
+ public String getDisplayText() {
+ return getText();
+ }
+
+ private FileObject target;
+
+ @Override
+ public void performChange() {
+ try {
+ Pair<FileObject, String> p = fileAndRemainingPath(uri);
+ target = FileUtil.createData(p.first(), p.second());
+ try (Writer w = new OutputStreamWriter(target.getOutputStream(), FileEncodingQuery.getEncoding(target))) {
+ w.write(content);
+ }
+ } catch (IOException | URISyntaxException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ @Override
+ public void undoChange() {
+ try {
+ target.delete();
+ target = null;
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ @Override
+ public Lookup getLookup() {
+ return Lookup.EMPTY;
+ }
+
+ @Override
+ public FileObject getParentFile() {
+ try {
+ return fileAndRemainingPath(uri).first();
+ } catch (URISyntaxException | MalformedURLException ex) {
+ Exceptions.printStackTrace(ex);
+ return null;
+ }
+ }
+
+ @SuppressWarnings({"NestedAssignment", "AssignmentToMethodParameter"})
+ private static Pair<FileObject, String> fileAndRemainingPath(String uri) throws URISyntaxException, MalformedURLException {
+ StringBuilder path = new StringBuilder();
+ FileObject existing;
+ while ((existing = URLMapper.findFileObject(new URI(uri).toURL())) == null) {
+ int slash = uri.lastIndexOf('/');
+ if (path.length() > 0) {
+ path.insert(0, '/');
+ }
+ path.insert(0, uri.substring(slash + 1));
+ uri = uri.substring(0, slash);
+ }
+ return Pair.of(existing, path.toString());
+ }
+
+ @Override
+ public PositionBounds getPosition() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "=>" + uri2SimpleName(uri) + "(" + content + ")";
+ }
+
+ }
@ServiceProvider(service=RefactoringPluginFactory.class)
public static class FactoryImpl implements RefactoringPluginFactory {
@@ -174,6 +674,13 @@
if (bindings != null && params != null) {
return new WhereUsedRefactoringPlugin(q, bindings, params);
}
+ } else if (refactoring instanceof RenameRefactoring) {
+ RenameRefactoring r = (RenameRefactoring) refactoring;
+ LSPBindings bindings = r.getRefactoringSource().lookup(LSPBindings.class);
+ RenameParams params = r.getRefactoringSource().lookup(RenameParams.class);
+ if (bindings != null && params != null) {
+ return new RenameRefactoringPlugin(r, bindings, params);
+ }
}
return null;
}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java
index c1d72ed..5eff44b 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java
@@ -22,12 +22,15 @@
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
+import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.ReferenceContext;
import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameOptions;
import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.editor.NbEditorUtilities;
@@ -64,6 +67,7 @@
try {
JEditorPane c = ec.getOpenedPanes()[0];
Document doc = c.getDocument();
+ AbstractDocument abstractDoc = (doc instanceof AbstractDocument) ? ((AbstractDocument) doc) : null;
FileObject file = NbEditorUtilities.getFileObject(doc);
LSPBindings bindings = LSPBindings.getBindings(file);
int caretPos = c.getCaretPosition();
@@ -72,16 +76,70 @@
params.setTextDocument(new TextDocumentIdentifier(Utils.toURI(file)));
params.setPosition(pos);
params.setContext(new ReferenceContext(false)); //(could be an option?)
- TokenSequence<?> ts = TokenHierarchy.get(doc).tokenSequence();
+
String name = Bundle.NM_Unknown();
- if (ts != null) {
- ts.move(caretPos);
- if (ts.moveNext()) {
- name = ts.token().text().toString();
+
+ if (abstractDoc != null) {
+ abstractDoc.readLock();
+ }
+ try {
+ TokenSequence<?> ts = TokenHierarchy.get(doc).tokenSequence();
+ if (ts != null) {
+ ts.move(caretPos);
+ if (ts.moveNext()) {
+ name = ts.token().text().toString();
+ }
+ }
+ } finally {
+ if (abstractDoc != null) {
+ abstractDoc.readUnlock();
}
}
- UI.openRefactoringUI(new RefactoringUIImpl(bindings, params, name),
+ UI.openRefactoringUI(new WhereUsedRefactoringUIImpl(bindings, params, name),
+ TopComponent.getRegistry().getActivated());
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ };
+ SwingUtilities.invokeLater(start);
+ }
+
+ @Override
+ public void doRename(Lookup lookup) {
+ Runnable start = () -> {
+ EditorCookie ec = lookup.lookup(EditorCookie.class);
+
+ if (isFromEditor(ec)) {
+ try {
+ JEditorPane c = ec.getOpenedPanes()[0];
+ Document doc = c.getDocument();
+ AbstractDocument abstractDoc = (doc instanceof AbstractDocument) ? ((AbstractDocument) doc) : null;
+ FileObject file = NbEditorUtilities.getFileObject(doc);
+ LSPBindings bindings = LSPBindings.getBindings(file);
+ int caretPos = c.getCaretPosition();
+ Position pos = Utils.createPosition(doc, caretPos);
+ String name;
+ if(abstractDoc != null) {
+ abstractDoc.readLock();
+ }
+ try {
+ TokenSequence<?> ts = TokenHierarchy.get(doc).tokenSequence();
+ name = "";
+ if (ts != null) {
+ ts.move(caretPos);
+ if (ts.moveNext()) {
+ name = ts.token().text().toString();
+ }
+ }
+ } finally {
+ if (abstractDoc != null) {
+ abstractDoc.readUnlock();
+ }
+ }
+
+ UI.openRefactoringUI(new RenameRefactoringUIImpl(bindings, file, pos, name),
TopComponent.getRegistry().getActivated());
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
@@ -104,7 +162,17 @@
return false;
}
Boolean hasReferences = bindings.getInitResult().getCapabilities().getReferencesProvider();
- return hasReferences != null && hasReferences;
+ return Utils.isTrue(hasReferences);
+ }
+
+ @Override
+ public boolean canRename(Lookup lookup) {
+ LSPBindings bindings = getBindings(lookup);
+ if (bindings == null) {
+ return false;
+ }
+ Either<Boolean, RenameOptions> hasRename = bindings.getInitResult().getCapabilities().getRenameProvider();
+ return hasRename != null && ((hasRename.isLeft() && Utils.isTrue(hasRename.getLeft())) || hasRename.isRight());
}
private LSPBindings getBindings(Lookup lookup) {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.form b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.form
new file mode 100644
index 0000000..93da9c9
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.form
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+
+ 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.
+
+-->
+
+<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+ <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ </AuxValues>
+
+ <Layout>
+ <DimensionLayout dim="0">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="102" alignment="0" attributes="0">
+ <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
+ <EmptySpace max="-2" attributes="0"/>
+ <Component id="name" pref="355" max="32767" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ <DimensionLayout dim="1">
+ <Group type="103" groupAlignment="0" attributes="0">
+ <Group type="103" groupAlignment="3" attributes="0">
+ <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+ <Component id="name" alignment="3" min="-2" max="-2" attributes="0"/>
+ </Group>
+ </Group>
+ </DimensionLayout>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel1">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties" key="RenamePanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
+ </Property>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="name">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties" key="RenamePanel.name.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
+ </Property>
+ </Properties>
+ </Component>
+ </SubComponents>
+</Form>
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.java
new file mode 100644
index 0000000..b26e608
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.java
@@ -0,0 +1,81 @@
+/*
+ * 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.lsp.client.bindings.refactoring;
+
+/**
+ *
+ * @author lahvac
+ */
+public class RenamePanel extends javax.swing.JPanel {
+
+ /**
+ * Creates new form RenamePanel
+ */
+ public RenamePanel() {
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ jLabel1 = new javax.swing.JLabel();
+ name = new javax.swing.JTextField();
+
+ org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(RenamePanel.class, "RenamePanel.jLabel1.text")); // NOI18N
+
+ name.setText(org.openide.util.NbBundle.getMessage(RenamePanel.class, "RenamePanel.name.text")); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(jLabel1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(name, javax.swing.GroupLayout.DEFAULT_SIZE, 355, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jLabel1)
+ .addComponent(name, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+ }// </editor-fold>//GEN-END:initComponents
+
+ public void setName(String name) {
+ this.name.setText(name);
+ this.name.setSelectionStart(0);
+ this.name.setSelectionEnd(name.length());
+ }
+
+ public String getName() {
+ return this.name.getText();
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JTextField name;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringUIImpl.java
similarity index 68%
copy from ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
copy to ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringUIImpl.java
index 665e5cd..2cfb717 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringUIImpl.java
@@ -19,15 +19,18 @@
package org.netbeans.modules.lsp.client.bindings.refactoring;
import java.awt.Component;
-import javax.swing.JPanel;
import javax.swing.event.ChangeListener;
-import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.Utils;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
-import org.netbeans.modules.refactoring.api.WhereUsedQuery;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
import org.netbeans.modules.refactoring.spi.ui.CustomRefactoringPanel;
import org.netbeans.modules.refactoring.spi.ui.RefactoringUI;
+import org.openide.filesystems.FileObject;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.Lookups;
@@ -36,16 +39,21 @@
*
* @author lahvac
*/
-public class RefactoringUIImpl implements RefactoringUI {
+public class RenameRefactoringUIImpl implements RefactoringUI {
private final LSPBindings bindings;
- private final ReferenceParams params;
+ private final FileObject file;
+ private final Position position;
private final String name;
+ private final RenameParams params;
+ private RenamePanel panel;
- public RefactoringUIImpl(LSPBindings binding, ReferenceParams params, String name) {
+ public RenameRefactoringUIImpl(LSPBindings binding, FileObject file, Position position, String name) {
this.bindings = binding;
- this.params = params;
+ this.file = file;
+ this.position = position;
this.name = name;
+ this.params = new RenameParams();
}
@Override
@@ -56,19 +64,23 @@
@Override
@Messages({
"# {0} - identifier",
- "DESC_Usages=Usages of {0}"
+ "DESC_Rename=Renaming {0}"
})
public String getDescription() {
- return Bundle.DESC_Usages(name);
+ return Bundle.DESC_Rename(name);
}
@Override
public boolean isQuery() {
- return true;
+ return false;
}
@Override
public CustomRefactoringPanel getPanel(ChangeListener parent) {
+ if (panel == null) {
+ panel = new RenamePanel();
+ }
+ panel.setName(name);
return new CustomRefactoringPanel() {
@Override
public void initialize() {
@@ -76,13 +88,16 @@
@Override
public Component getComponent() {
- return new JPanel();
+ return panel;
}
};
}
@Override
public Problem setParameters() {
+ params.setTextDocument(new TextDocumentIdentifier(Utils.toURI(file)));
+ params.setPosition(position);
+ params.setNewName(panel.getName());
return null;
}
@@ -93,12 +108,12 @@
@Override
public boolean hasParameters() {
- return false;
+ return true;
}
@Override
public AbstractRefactoring getRefactoring() {
- return new WhereUsedQuery(Lookups.fixed(bindings, params));
+ return new RenameRefactoring(Lookups.fixed(bindings, params));
}
@Override
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/WhereUsedRefactoringUIImpl.java
similarity index 94%
rename from ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
rename to ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/WhereUsedRefactoringUIImpl.java
index 665e5cd..bc16cc4 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/WhereUsedRefactoringUIImpl.java
@@ -36,13 +36,13 @@
*
* @author lahvac
*/
-public class RefactoringUIImpl implements RefactoringUI {
+public class WhereUsedRefactoringUIImpl implements RefactoringUI {
private final LSPBindings bindings;
private final ReferenceParams params;
private final String name;
- public RefactoringUIImpl(LSPBindings binding, ReferenceParams params, String name) {
+ public WhereUsedRefactoringUIImpl(LSPBindings binding, ReferenceParams params, String name) {
this.bindings = binding;
this.params = params;
this.name = name;
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/tree/DiffElement.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/tree/DiffElement.java
new file mode 100644
index 0000000..a9c3d25
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/tree/DiffElement.java
@@ -0,0 +1,150 @@
+/*
+ * 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.lsp.client.bindings.refactoring.tree;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import javax.swing.text.Position;
+import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult;
+import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult.Difference;
+import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
+import org.openide.filesystems.FileObject;
+import org.openide.text.PositionBounds;
+import org.openide.text.PositionRef;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.Lookups;
+
+/**
+ * Implementatation of RefactoringElementImplementation specific to refactoring
+ * in java files.
+ *
+ * @author Jan Becicka
+ */
+ public final class DiffElement extends SimpleRefactoringElementImplementation {
+ private PositionBounds bounds;
+ private String displayText;
+ private FileObject parentFile;
+ private Difference diff;
+ private ModificationResult modification;
+ private WeakReference<String> newFileContent;
+
+ @Messages("LBL_NotAvailable=Update")
+ private DiffElement(Difference diff, PositionBounds bounds, FileObject parentFile, ModificationResult modification) {
+ this.bounds = bounds;
+ final String description = diff.getDescription();
+ if (description == null) {
+ displayText = NbBundle.getMessage(DiffElement.class, "LBL_NotAvailable");
+ } else {
+ this.displayText = description;
+ }
+ this.parentFile = parentFile;
+ this.diff = diff;
+ this.modification = modification;
+ }
+
+ @Override
+ public String getDisplayText() {
+ return displayText;
+ }
+
+ @Override
+ public Lookup getLookup() {
+ Object composite = null;
+ if (bounds!=null) {
+// composite = ElementGripFactory.getDefault().get(parentFile, bounds.getBegin().getOffset());
+ }
+ if (composite==null) {
+ composite = parentFile;
+ }
+ return Lookups.fixed(composite, diff);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ diff.exclude(!enabled);
+ newFileContent = null;
+ super.setEnabled(enabled);
+ }
+
+ @Override
+ public PositionBounds getPosition() {
+ return bounds;
+ }
+
+ @Override
+ public String getText() {
+ return displayText;
+ }
+
+ @Override
+ public void performChange() {
+ }
+
+ @Override
+ public FileObject getParentFile() {
+ if (diff.getKind() == Difference.Kind.CREATE) {
+ return parentFile.getParent();
+ }
+ return parentFile;
+ }
+
+ @Override
+ protected String getNewFileContent() {
+ String result;
+ if (newFileContent !=null) {
+ result = newFileContent.get();
+ if (result!=null) {
+ return result;
+ }
+ }
+ try {
+ if (diff.getKind()==Difference.Kind.CREATE) {
+ result = diff.getNewText();
+ } else {
+ result = modification.getResultingSource(parentFile);
+ }
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ return null;
+ }
+ newFileContent = new WeakReference<String>(result);
+ return result;
+ }
+
+ /**
+ * Factory method for DiffElement
+ * @param diff diff instance corresponding to thid Element
+ * @param fileObject fileObject corresponding to this Element
+ * @param modification
+ * @return ModificationResult corresponding to this change
+ */
+ public static DiffElement create(Difference diff, FileObject fileObject, ModificationResult modification) {
+ Position start = diff.getStartPosition();
+ Position end = diff.getEndPosition();
+ PositionBounds bounds = null;
+ if (diff.getKind() != Difference.Kind.CREATE) {
+ // FIXME - unnecessary dependency on openide.text
+ bounds = new PositionBounds((PositionRef)start, (PositionRef)end);
+ }
+ return new DiffElement(diff, bounds, fileObject, modification);
+ }
+}
diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/TestUtils.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/TestUtils.java
new file mode 100644
index 0000000..4ce03d3
--- /dev/null
+++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/TestUtils.java
@@ -0,0 +1,202 @@
+/*
+ * 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.lsp.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.function.Supplier;
+import org.eclipse.lsp4j.DidChangeConfigurationParams;
+import org.eclipse.lsp4j.DidChangeTextDocumentParams;
+import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
+import org.eclipse.lsp4j.DidCloseTextDocumentParams;
+import org.eclipse.lsp4j.DidOpenTextDocumentParams;
+import org.eclipse.lsp4j.DidSaveTextDocumentParams;
+import org.eclipse.lsp4j.launch.LSPLauncher;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.lsp4j.services.TextDocumentService;
+import org.eclipse.lsp4j.services.WorkspaceService;
+import static org.junit.Assert.assertTrue;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.modules.editor.NbEditorKit;
+import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
+import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.MIMEResolver;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author lahvac
+ */
+public class TestUtils {
+
+ public static final String MIME_TYPE = "application/mock-txt";
+ public static final String TEXT_PLAIN = "text/plain";
+
+ public static final class MimeDataProviderImpl implements MimeDataProvider {
+ @Override
+ public Lookup getLookup(MimePath mp) {
+ switch (mp.getPath()) {
+ case MIME_TYPE:
+ return Lookups.fixed(new MockLSP(), new NbEditorKit() {
+ @Override
+ public String getContentType() {
+ return MIME_TYPE;
+ }
+ });
+ case TEXT_PLAIN:
+ return Lookups.fixed(new NbEditorKit() {
+ @Override
+ public String getContentType() {
+ return TEXT_PLAIN;
+ }
+ });
+ case "": return Lookup.EMPTY;
+ default: throw new AssertionError(mp.getPath());
+ }
+ }
+ }
+
+ public static class MockLSP implements LanguageServerProvider {
+ public static Supplier<LanguageServer> createServer = () -> { throw new UnsupportedOperationException(); };
+ @Override
+ public LanguageServerProvider.LanguageServerDescription startServer(Lookup lookup) {
+ try {
+ final MockProcess process = new MockProcess();
+ ServerSocket srv = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
+ Thread serverThread = new Thread(() -> {
+ try {
+ Socket server = srv.accept();
+
+ LSPLauncher.createServerLauncher(createServer.get(), server.getInputStream(), server.getOutputStream()).startListening().get();
+ } catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ });
+ serverThread.start();
+ Socket client = new Socket(srv.getInetAddress(), srv.getLocalPort());
+
+ return LanguageServerProvider.LanguageServerDescription.create(client.getInputStream(), client.getOutputStream(), process);
+ } catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+ }
+
+ public final static class MockMimeResolver extends MIMEResolver {
+
+ public MockMimeResolver() {
+ }
+
+ @Override
+ public String findMIMEType(FileObject fo) {
+ switch (fo.getExt()) {
+ case "mock-txt": return MIME_TYPE;
+ case "txt": return TEXT_PLAIN;
+ default: return null;
+ }
+ }
+ }
+
+ static final class MockProcess extends Process {
+ final ByteArrayInputStream in;
+ final ByteArrayOutputStream out;
+
+ public MockProcess() {
+ this.in = new ByteArrayInputStream(new byte[0]);
+ this.out = new ByteArrayOutputStream();
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return out;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return in;
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return in;
+ }
+
+ @Override
+ public int waitFor() throws InterruptedException {
+ throw new InterruptedException();
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public int exitValue() {
+ return 0;
+ }
+
+ @Override
+ public void destroy() {
+ }
+ }
+
+ public static class BaseTextDocumentServiceImpl implements TextDocumentService {
+ @Override
+ public void didOpen(DidOpenTextDocumentParams params) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void didChange(DidChangeTextDocumentParams params) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void didClose(DidCloseTextDocumentParams params) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void didSave(DidSaveTextDocumentParams params) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public static class BaseWorkspaceServiceImpl implements WorkspaceService {
+
+ @Override
+ public void didChangeConfiguration(DidChangeConfigurationParams params) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+}
diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java
index d7fe5a7..52cd005 100644
--- a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java
+++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java
@@ -18,13 +18,6 @@
*/
package org.netbeans.modules.lsp.client.bindings;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -35,9 +28,7 @@
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
-import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
-import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
@@ -50,28 +41,25 @@
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
-import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.junit.Test;
-import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.junit.MockServices;
-import org.netbeans.modules.editor.NbEditorKit;
import org.netbeans.modules.editor.lib2.EditorApiPackageAccessor;
import org.netbeans.modules.lsp.client.Utils;
-import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
-import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
-import org.openide.filesystems.MIMEResolver;
import org.openide.loaders.DataObject;
import org.openide.text.CloneableEditorSupport;
import org.openide.text.NbDocument;
-import org.openide.util.Lookup;
-import org.openide.util.lookup.Lookups;
import static org.junit.Assert.*;
+import org.netbeans.modules.lsp.client.TestUtils.BaseWorkspaceServiceImpl;
+import static org.netbeans.modules.lsp.client.TestUtils.MIME_TYPE;
+import org.netbeans.modules.lsp.client.TestUtils.MimeDataProviderImpl;
+import org.netbeans.modules.lsp.client.TestUtils.MockLSP;
+import org.netbeans.modules.lsp.client.TestUtils.MockMimeResolver;
/**
*
@@ -79,11 +67,11 @@
*/
public class TextDocumentSyncServerCapabilityHandlerTest {
- private static final String MIME_TYPE = "application/mock-txt";
private static final List<String> eventLog = new ArrayList<>();
@Test
public void testOpenClose() throws Exception {
+ MockLSP.createServer = () -> new TestLanguageServer();
MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class);
new TextDocumentSyncServerCapabilityHandler.Init().run();
@@ -171,99 +159,6 @@
}
}
- public static final class MimeDataProviderImpl implements MimeDataProvider {
- @Override
- public Lookup getLookup(MimePath mp) {
- assertEquals("application/mock-txt", mp.getPath());
- return Lookups.fixed(new MockLSP(), new NbEditorKit() {
- @Override
- public String getContentType() {
- return "application/mock-txt";
- }
- });
- }
- }
-
- public static final class MockLSP implements LanguageServerProvider {
- @Override
- public LanguageServerProvider.LanguageServerDescription startServer(Lookup lookup) {
- try {
- final MockProcess process = new MockProcess();
- ServerSocket srv = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
- Thread serverThread = new Thread(() -> {
- try {
- Socket server = srv.accept();
-
- LSPLauncher.createServerLauncher(new TestLanguageServer(), server.getInputStream(), server.getOutputStream()).startListening().get();
- } catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- });
- serverThread.start();
- Socket client = new Socket(srv.getInetAddress(), srv.getLocalPort());
-
- return LanguageServerProvider.LanguageServerDescription.create(client.getInputStream(), client.getOutputStream(), process);
- } catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- }
- }
-
- public final static class MockMimeResolver extends MIMEResolver {
-
- public MockMimeResolver() {
- }
-
- @Override
- public String findMIMEType(FileObject fo) {
- return fo.hasExt("mock-txt") ? "application/mock-txt" : null;
- }
- }
-
- static final class MockProcess extends Process {
- final ByteArrayInputStream in;
- final ByteArrayOutputStream out;
-
- public MockProcess() {
- this.in = new ByteArrayInputStream(new byte[0]);
- this.out = new ByteArrayOutputStream();
- }
-
- @Override
- public OutputStream getOutputStream() {
- return out;
- }
-
- @Override
- public InputStream getInputStream() {
- return in;
- }
-
- @Override
- public InputStream getErrorStream() {
- return in;
- }
-
- @Override
- public int waitFor() throws InterruptedException {
- throw new InterruptedException();
- }
-
- @Override
- public boolean isAlive() {
- return true;
- }
-
- @Override
- public int exitValue() {
- return 0;
- }
-
- @Override
- public void destroy() {
- }
- }
-
private static final class TestLanguageServer implements LanguageServer {
@Override
@@ -327,17 +222,7 @@
@Override
public WorkspaceService getWorkspaceService() {
- return new WorkspaceService() {
- @Override
- public void didChangeConfiguration(DidChangeConfigurationParams params) {
- throw new IllegalStateException("Should not be called.");
- }
-
- @Override
- public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
- throw new IllegalStateException("Should not be called.");
- }
- };
+ return new BaseWorkspaceServiceImpl();
}
}
diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringTest.java
new file mode 100644
index 0000000..c6fbdd5
--- /dev/null
+++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringTest.java
@@ -0,0 +1,392 @@
+/*
+ * 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.lsp.client.bindings.refactoring;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import org.eclipse.lsp4j.CreateFile;
+import org.eclipse.lsp4j.DeleteFile;
+import org.eclipse.lsp4j.InitializeParams;
+import org.eclipse.lsp4j.InitializeResult;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.RenameOptions;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
+import org.eclipse.lsp4j.ServerCapabilities;
+import org.eclipse.lsp4j.TextDocumentEdit;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.lsp4j.services.TextDocumentService;
+import org.eclipse.lsp4j.services.WorkspaceService;
+import org.junit.Test;
+import org.netbeans.junit.MockServices;
+import org.netbeans.modules.lsp.client.Utils;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.text.CloneableEditorSupport;
+import static org.junit.Assert.*;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.editor.NbEditorKit;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.TestUtils.BaseTextDocumentServiceImpl;
+import org.netbeans.modules.lsp.client.TestUtils.BaseWorkspaceServiceImpl;
+import static org.netbeans.modules.lsp.client.TestUtils.MIME_TYPE;
+import org.netbeans.modules.lsp.client.TestUtils.MimeDataProviderImpl;
+import org.netbeans.modules.lsp.client.TestUtils.MockLSP;
+import org.netbeans.modules.lsp.client.TestUtils.MockMimeResolver;
+import org.netbeans.modules.lsp.client.bindings.refactoring.Refactoring.LSPCreateFile;
+import org.netbeans.modules.lsp.client.bindings.refactoring.Refactoring.LSPDeleteFile;
+import org.netbeans.modules.lsp.client.bindings.refactoring.Refactoring.LSPRenameFile;
+import org.netbeans.modules.lsp.client.bindings.refactoring.tree.DiffElement;
+import org.netbeans.modules.refactoring.api.Problem;
+import org.netbeans.modules.refactoring.api.RefactoringElement;
+import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
+import org.netbeans.modules.refactoring.api.impl.APIAccessor;
+import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.impl.UndoableWrapper;
+import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
+import org.openide.text.PositionRef;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author lahvac
+ */
+public class RenameRefactoringTest {
+
+ @Test
+ public void testSimpleRename() throws Exception {
+ MockLSP.createServer = () -> new TestLanguageServer();
+ MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class, RootMimeDataProviderImpl.class);
+
+ FileObject folder = FileUtil.createMemoryFileSystem().getRoot().createFolder("myfolder");
+ FileObject file1 = createFile(folder, "data1.mock-txt");
+
+ try (OutputStream out = file1.getOutputStream()) {
+ out.write((" test other\n" +
+ " other test\n").getBytes(StandardCharsets.UTF_8));
+ }
+
+ FileObject file2 = createFile(folder, "data2.mock-txt");
+
+ try (OutputStream out = file2.getOutputStream()) {
+ out.write((" 2test other\n" +
+ " 2other test\n").getBytes(StandardCharsets.UTF_8));
+ }
+
+ String uri = Utils.toURI(file1);
+
+ LSPBindings bindings = LSPBindings.getBindings(file1);
+ RenameParams renameParams = new RenameParams(new TextDocumentIdentifier(uri), new Position(1, 8), "newName");
+ List<Function<RenameParams, WorkspaceEdit>> renameFunctions = Arrays.asList(
+ params -> {
+ assertEquals(uri, params.getTextDocument().getUri());
+ assertEquals("newName", params.getNewName());
+
+ WorkspaceEdit result = new WorkspaceEdit();
+ String file1URI = params.getTextDocument().getUri();
+ TextDocumentEdit file1Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file1URI, -1),
+ Arrays.asList(new TextEdit(new Range(new Position(0, 2), new Position(0, 6)), "newName"),
+ new TextEdit(new Range(new Position(1, 8), new Position(1, 12)), "newName")));
+ String file2URI = file1URI.replace("data1", "data2");
+ TextDocumentEdit file2Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file2URI, -1),
+ Arrays.asList(new TextEdit(new Range(new Position(0, 3), new Position(0, 7)), "newName"),
+ new TextEdit(new Range(new Position(1, 9), new Position(1, 13)), "newName")));
+ result.setDocumentChanges(Arrays.asList(Either.forLeft(file1Edits), Either.forLeft(file2Edits)));
+ return result;
+ },
+ params -> {
+ assertEquals(uri, params.getTextDocument().getUri());
+ assertEquals("newName", params.getNewName());
+
+ WorkspaceEdit result = new WorkspaceEdit();
+ Map<String, List<TextEdit>> file2Edits = new HashMap<>();
+ String file1URI = params.getTextDocument().getUri();
+ file2Edits.put(file1URI, Arrays.asList(new TextEdit(new Range(new Position(0, 2), new Position(0, 6)), "newName"),
+ new TextEdit(new Range(new Position(1, 8), new Position(1, 12)), "newName")));
+ String file2URI = file1URI.replace("data1", "data2");
+ file2Edits.put(file2URI, Arrays.asList(new TextEdit(new Range(new Position(0, 3), new Position(0, 7)), "newName"),
+ new TextEdit(new Range(new Position(1, 9), new Position(1, 13)), "newName")));
+
+ result.setChanges(file2Edits);
+ return result;
+ }
+ );
+ for (Function<RenameParams, WorkspaceEdit> renameFunc : renameFunctions) {
+ renameFunction = renameFunc;
+
+ RenameRefactoring refactoring = new RenameRefactoring(Lookups.fixed(bindings, renameParams));
+
+ RefactoringSession session = RefactoringSession.create("test rename");
+ assertNull(refactoring.checkParameters());
+ assertNull(refactoring.preCheck());
+ assertNull(refactoring.prepare(session));
+
+ Set<String> elements = new HashSet<>();
+
+ for (RefactoringElement re : session.getRefactoringElements()) {
+ RefactoringElementImplementation impl =
+ APIAccessor.DEFAULT.getRefactoringElementImplementation(re);
+ Method getNewFileContent = impl.getClass().getDeclaredMethod("getNewFileContent");
+
+ getNewFileContent.setAccessible(true);
+
+ String newFileContent = (String) getNewFileContent.invoke(impl);
+ String element = positionToString(re.getPosition().getBegin()) + "-" +
+ positionToString(re.getPosition().getEnd()) + ":" +
+ newFileContent;
+
+ elements.add(element);
+ }
+
+ Set<String> expectedElements = new HashSet<>(Arrays.asList(
+ "1:9-1:13: 2newName other\n" +
+ " 2other newName\n",
+ "0:3-0:7: 2newName other\n" +
+ " 2other newName\n",
+ "1:8-1:12: newName other\n" +
+ " other newName\n",
+ "0:2-0:6: newName other\n" +
+ " other newName\n"
+ ));
+
+ assertEquals(expectedElements, elements);
+
+ session.doRefactoring(true);
+
+ assertFile(file1, " newName other\n" +
+ " other newName\n");
+ assertFile(file2, " 2newName other\n" +
+ " 2other newName\n");
+
+ session.undoRefactoring(true);
+
+ assertFile(file1, " test other\n" +
+ " other test\n");
+ assertFile(file2, " 2test other\n" +
+ " 2other test\n");
+ }
+ }
+
+ @Test
+ public void testFileOperations() throws Exception {
+ MockLSP.createServer = () -> new TestLanguageServer();
+ MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class, RootMimeDataProviderImpl.class);
+
+ FileObject folder = FileUtil.createMemoryFileSystem().getRoot().createFolder("myfolder");
+ FileObject file1 = createFile(folder, "data1.mock-txt");
+
+ try (OutputStream out = file1.getOutputStream()) {
+ out.write((" test other\n" +
+ " other test\n").getBytes(StandardCharsets.UTF_8));
+ }
+
+ FileObject file2 = createFile(folder, "data2.mock-txt");
+
+ try (OutputStream out = file2.getOutputStream()) {
+ out.write((" 2test other\n" +
+ " 2other test\n").getBytes(StandardCharsets.UTF_8));
+ }
+
+ String uri = Utils.toURI(file1);
+
+ LSPBindings bindings = LSPBindings.getBindings(file1);
+ RenameParams renameParams = new RenameParams(new TextDocumentIdentifier(uri), new Position(1, 8), "newName");
+ renameFunction = params -> {
+ assertEquals(uri, params.getTextDocument().getUri());
+ assertEquals("newName", params.getNewName());
+
+ WorkspaceEdit result = new WorkspaceEdit();
+ String file1URI = params.getTextDocument().getUri();
+ TextDocumentEdit file1Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file1URI, -1),
+ Arrays.asList(new TextEdit(new Range(new Position(1, 8), new Position(1, 12)), "newName")));
+ String file1aURI = file1URI.replace("data1", "data1a");
+ ResourceOperation file1Operation = new RenameFile(file1URI, file1aURI);
+ TextDocumentEdit file1aEdits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file1aURI, -1),
+ Arrays.asList(new TextEdit(new Range(new Position(0, 2), new Position(0, 6)), "newName")));
+ String file2URI = file1URI.replace("data1", "data2");
+ ResourceOperation file2Operation = new DeleteFile(file2URI);
+ String file3URI = file1URI.replace("data1", "data3");
+ ResourceOperation file3Operation = new CreateFile(file3URI);
+ TextDocumentEdit file3Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file3URI, -1),
+ Arrays.asList(new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), "newName content\n")));
+ result.setDocumentChanges(Arrays.asList(Either.forLeft(file1Edits),
+ Either.forRight(file1Operation),
+ Either.forLeft(file1aEdits),
+ Either.forRight(file2Operation),
+ Either.forRight(file3Operation),
+ Either.forLeft(file3Edits))
+ );
+ return result;
+ };
+
+ RenameRefactoring refactoring = new RenameRefactoring(Lookups.fixed(bindings, renameParams));
+
+ RefactoringSession session = RefactoringSession.create("test rename");
+ assertNull(refactoring.checkParameters());
+ assertNull(refactoring.preCheck());
+ Problem problem = refactoring.prepare(session);
+ assertNull(problem2String(problem), problem);
+
+ Set<String> elements = new HashSet<>();
+
+ for (RefactoringElement re : session.getRefactoringElements()) {
+ RefactoringElementImplementation impl =
+ APIAccessor.DEFAULT.getRefactoringElementImplementation(re);
+ if (impl instanceof DiffElement) {
+ Method getNewFileContent = impl.getClass().getDeclaredMethod("getNewFileContent");
+
+ getNewFileContent.setAccessible(true);
+
+ String newFileContent = (String) getNewFileContent.invoke(impl);
+ String element = positionToString(re.getPosition().getBegin()) + "-" +
+ positionToString(re.getPosition().getEnd()) + ":" +
+ newFileContent;
+
+ elements.add(element);
+ } else if (impl instanceof LSPRenameFile ||
+ impl instanceof LSPDeleteFile ||
+ impl instanceof LSPCreateFile) {
+ elements.add(impl.toString());
+ } else {
+ fail("Unknown element class: " + impl.getClass());
+ }
+ }
+
+ Set<String> expectedElements = new HashSet<>(Arrays.asList(
+ "data2.mock-txt=>",
+ "data1.mock-txt=>data1a.mock-txt",
+ "1:8-1:12: newName other\n" +
+ " other newName\n",
+ "0:2-0:6: newName other\n" +
+ " other newName\n",
+ "=>data3.mock-txt(newName content\n)"
+ ));
+
+ assertEquals(expectedElements, elements);
+
+ session.doRefactoring(true);
+
+ assertEquals("data1a.mock-txt", file1.getNameExt());
+ assertFile(file1, " newName other\n" +
+ " other newName\n");
+ assertFalse(file2.isValid());
+
+ //Backup facility does not handle non file:// URLs:
+// session.undoRefactoring(true);
+//
+// assertFile(file1, " test other\n" +
+// " other test\n");
+// assertFile(file2, " 2test other\n" +
+// " 2other test\n");
+ }
+
+ private FileObject createFile(FileObject folder, String name) throws Exception {
+ FileObject file = folder.createData(name);
+ EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+ ((CloneableEditorSupport) ec).setMIMEType(MIME_TYPE);
+
+ return file;
+ }
+
+ private String positionToString(PositionRef p) throws IOException {
+ return "" + p.getLine() + ":" + p.getColumn();
+ }
+
+ private void assertFile(FileObject file, String expectedContent) throws IOException {
+ assertEquals(expectedContent, file.asText());
+ }
+
+ private String problem2String(Problem p) {
+ if (p == null){
+ return null;
+ }
+ return p.getMessage() + ":" + p.isFatal() + (p.getNext() != null ? "[" + problem2String(p.getNext()) + "]" : "");
+ }
+
+ private static Function<RenameParams, WorkspaceEdit> renameFunction;
+
+ private static final class TestLanguageServer implements LanguageServer {
+
+ @Override
+ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
+ ServerCapabilities caps = new ServerCapabilities();
+ RenameOptions renameOptions = new RenameOptions();
+ caps.setRenameProvider(renameOptions);
+ InitializeResult initResult = new InitializeResult(caps);
+ return CompletableFuture.completedFuture(initResult);
+ }
+
+ @Override
+ public CompletableFuture<Object> shutdown() {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public void exit() {
+ }
+
+ @Override
+ public TextDocumentService getTextDocumentService() {
+ return new BaseTextDocumentServiceImpl() {
+ @Override
+ public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
+ WorkspaceEdit result = renameFunction.apply(params);
+ return CompletableFuture.completedFuture(result);
+ }
+ };
+ }
+
+ @Override
+ public WorkspaceService getWorkspaceService() {
+ return new BaseWorkspaceServiceImpl();
+ }
+
+ }
+
+ public static final class RootMimeDataProviderImpl implements MimeDataProvider {
+ @Override
+ public Lookup getLookup(MimePath mp) {
+ if ("".equals(mp.getPath())) {
+ return Lookups.fixed(new UndoableWrapper());
+ }
+ return Lookup.EMPTY;
+ }
+ }
+}
diff --git a/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java b/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java
index 95d076d..6fd7899 100644
--- a/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java
+++ b/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java
@@ -537,7 +537,6 @@
@NonNull final Lookup context) {
synchronized (Source.class) {
final Source source = SourceFactory.getDefault().createSource(fileObject, mimeType, context);
- assert source.context == context;
return source;
}
}
diff --git a/ide/parsing.lucene/nbproject/project.properties b/ide/parsing.lucene/nbproject/project.properties
index 882f805..53ccf16 100644
--- a/ide/parsing.lucene/nbproject/project.properties
+++ b/ide/parsing.lucene/nbproject/project.properties
@@ -19,7 +19,7 @@
javadoc.apichanges=${basedir}/apichanges.xml
javac.compilerargs=-Xlint -Xlint:-serial
-spec.version.base=2.45.0
+spec.version.base=2.45.1
test.config.stableBTD.includes=**/*Test.class
test.config.stableBTD.excludes=\
**/LuceneIndexTest.class
diff --git a/ide/parsing.lucene/nbproject/project.xml b/ide/parsing.lucene/nbproject/project.xml
index d9e096f..988a845 100644
--- a/ide/parsing.lucene/nbproject/project.xml
+++ b/ide/parsing.lucene/nbproject/project.xml
@@ -85,6 +85,7 @@
</test-dependencies>
<friend-packages>
<friend>org.netbeans.modules.cnd.indexing</friend>
+ <friend>org.netbeans.modules.java.lsp.server</friend>
<friend>org.netbeans.modules.java.source.base</friend>
<friend>org.netbeans.modules.java.sourceui</friend>
<friend>org.netbeans.modules.jumpto</friend>
diff --git a/ide/projectui/nbproject/project.xml b/ide/projectui/nbproject/project.xml
index e426523..7fe0cc7 100644
--- a/ide/projectui/nbproject/project.xml
+++ b/ide/projectui/nbproject/project.xml
@@ -40,15 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java
index 0c4ec5e..7d4bf8a 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java
@@ -59,7 +59,6 @@
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
@@ -337,7 +336,7 @@
action = a;
currentFiles = Utilities.actionsGlobalContext().lookupResult(FileObject.class);
currentFiles.addLookupListener(WeakListeners.create(LookupListener.class, this, currentFiles));
- progress = ProgressHandleFactory.createHandle(CAP_Opening_Projects());
+ progress = ProgressHandle.createHandle(CAP_Opening_Projects());
}
final boolean waitFinished(long timeout) {
@@ -658,7 +657,7 @@
}
}
final Cancellation cancellation = new Cancellation();
- final ProgressHandle handle = ProgressHandleFactory.createHandle(CAP_Opening_Projects(), cancellation);
+ final ProgressHandle handle = ProgressHandle.createHandle(CAP_Opening_Projects(), cancellation);
handle.start();
handle.progress(projects[0].getProjectDirectory().getNameExt());
OPENING_RP.post(new Runnable() {
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java
index 04eb24b..3ed3214 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java
@@ -46,7 +46,6 @@
import java.util.prefs.Preferences;
import javax.swing.SwingUtilities;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
@@ -563,7 +562,7 @@
} else {
handleLabel = Group_open_handle(NONE_GROUP);
}
- final ProgressHandle h = ProgressHandleFactory.createHandle(handleLabel);
+ final ProgressHandle h = ProgressHandle.createHandle(handleLabel);
try {
h.start(200);
ProjectUtilities.WaitCursor.show();
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java
index 5a31f4c..47a8410 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java
@@ -24,7 +24,6 @@
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -51,7 +50,6 @@
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ui.OpenProjects;
@@ -135,7 +133,7 @@
})
private static boolean build(File root, File zip) throws IOException {
final AtomicBoolean canceled = new AtomicBoolean();
- ProgressHandle handle = ProgressHandleFactory.createHandle(MSG_building(zip.getName()), new Cancellable() {
+ ProgressHandle handle = ProgressHandle.createHandle(MSG_building(zip.getName()), new Cancellable() {
@Override public boolean cancel() {
return canceled.compareAndSet(false, true);
}
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java
index 5bbe7cf..de83bb2 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java
@@ -45,7 +45,6 @@
import javax.swing.JPanel;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
@@ -130,7 +129,7 @@
private static void unpackAndOpen(File zip, File root) throws IOException {
final AtomicBoolean canceled = new AtomicBoolean();
List<Project> projects = new ArrayList<Project>();
- ProgressHandle handle = ProgressHandleFactory.createHandle(MSG_unpacking(zip.getName()), new Cancellable() {
+ ProgressHandle handle = ProgressHandle.createHandle(MSG_unpacking(zip.getName()), new Cancellable() {
@Override public boolean cancel() {
return canceled.compareAndSet(false, true);
}
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java
index b61b8bc..d356f0b 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java
@@ -19,11 +19,13 @@
package org.netbeans.modules.refactoring.api;
import java.util.Collection;
+import java.util.List;
import org.netbeans.modules.refactoring.api.impl.APIAccessor;
import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
import org.netbeans.modules.refactoring.spi.GuardedBlockHandler;
import org.netbeans.modules.refactoring.spi.ProblemDetailsImplementation;
import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
import org.netbeans.modules.refactoring.spi.ui.FiltersDescription;
/**
@@ -87,5 +89,15 @@
public boolean isFinished(RefactoringSession session) {
return session.isFinished();
}
+
+ @Override
+ public List<Transaction> getCommits(RefactoringSession session) {
+ return session.getCommits();
+ }
+
+ @Override
+ public List<RefactoringElementImplementation> getFileChanges(RefactoringSession session) {
+ return session.getFileChanges();
+ }
}
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java
index 22a8a06..64ffcfa 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java
@@ -188,7 +188,7 @@
}
return null;
}
-
+
private class ProgressL implements ProgressListener {
private float progressStep;
@@ -383,6 +383,14 @@
}
}
+
+ List<Transaction> getCommits() {
+ return SPIAccessor.DEFAULT.getCommits(bag);
+ }
+
+ List<RefactoringElementImplementation> getFileChanges() {
+ return SPIAccessor.DEFAULT.getFileChanges(bag);
+ }
private class ElementsCollection extends AbstractCollection<RefactoringElement> {
@Override
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java
index c96c6a2..abe8ad9 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java
@@ -19,6 +19,7 @@
package org.netbeans.modules.refactoring.api.impl;
import java.util.Collection;
+import java.util.List;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.api.ProblemDetails;
@@ -26,7 +27,9 @@
import org.netbeans.modules.refactoring.api.RefactoringSession;
import org.netbeans.modules.refactoring.spi.GuardedBlockHandler;
import org.netbeans.modules.refactoring.spi.ProblemDetailsImplementation;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
import org.netbeans.modules.refactoring.spi.ui.FiltersDescription;
/**
@@ -56,5 +59,7 @@
public abstract FiltersDescription getFiltersDescription(AbstractRefactoring refactoring);
public abstract void resetFiltersDescription(AbstractRefactoring refactoring);
public abstract boolean isFinished(RefactoringSession session);
+ public abstract List<Transaction> getCommits(RefactoringSession session);
+ public abstract List<RefactoringElementImplementation> getFileChanges(RefactoringSession session);
}
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java
index c40b3cd..9240a03 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java
@@ -22,6 +22,8 @@
import java.util.Collection;
import java.util.List;
import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.spi.ModificationResult;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
@@ -52,5 +54,6 @@
public abstract boolean hasChangesInReadOnlyFiles(RefactoringElementsBag bag);
public abstract void check(Transaction commit, boolean undo);
public abstract void sum(Transaction commit);
+ public abstract Collection<? extends ModificationResult> getTransactions(RefactoringCommit c);
}
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java
index fce8666..7a5a78f 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java
@@ -72,7 +72,7 @@
public void cancelRequest() {
}
- private class RenameFile extends SimpleRefactoringElementImplementation {
+ public class RenameFile extends SimpleRefactoringElementImplementation {
private FileObject fo;
public RenameFile(FileObject fo, RefactoringElementsBag bag) {
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java
index 875afe7..db11ab4 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java
@@ -80,4 +80,10 @@
((RefactoringCommit) commit).sum();
}
}
+
+ @Override
+ public Collection<? extends ModificationResult> getTransactions(RefactoringCommit c) {
+ return c.results;
+ }
+
}
diff --git a/ide/spi.debugger.ui/apichanges.xml b/ide/spi.debugger.ui/apichanges.xml
index 0703882..90170b2 100644
--- a/ide/spi.debugger.ui/apichanges.xml
+++ b/ide/spi.debugger.ui/apichanges.xml
@@ -261,6 +261,21 @@
<class package="org.netbeans.spi.debugger.ui" name="DebuggingView"/>
</change>
+ <change id="DVFrameSourceMimeType">
+ <api name="DebuggerCoreSPI"/>
+ <summary>Source MIME type added to DVFrame.</summary>
+ <version major="2" minor="67"/>
+ <date day="30" month="11" year="2020"/>
+ <author login="mentlicher"/>
+ <compatibility binary="compatible" source="compatible" addition="yes" semantic="compatible"/>
+ <description>
+ <p>
+ <code>DebuggingView.DVFrame</code> provides source MIME type.
+ </p>
+ </description>
+ <class package="org.netbeans.spi.debugger.ui" name="DebuggingView"/>
+ </change>
+
</changes>
<!-- Now the surrounding HTML text and document structure: -->
diff --git a/ide/spi.debugger.ui/manifest.mf b/ide/spi.debugger.ui/manifest.mf
index c410f70..414b804 100644
--- a/ide/spi.debugger.ui/manifest.mf
+++ b/ide/spi.debugger.ui/manifest.mf
@@ -2,6 +2,6 @@
OpenIDE-Module: org.netbeans.spi.debugger.ui/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/ui/Bundle.properties
OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml
-OpenIDE-Module-Specification-Version: 2.65
+OpenIDE-Module-Specification-Version: 2.65.1
OpenIDE-Module-Provides: org.netbeans.spi.debugger.ui
OpenIDE-Module-Install: org/netbeans/modules/debugger/ui/DebuggerModule.class
diff --git a/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java b/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java
index 263297d..dd085bf 100644
--- a/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java
+++ b/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java
@@ -477,13 +477,23 @@
void makeCurrent();
/**
- * Gen URI of the source file associated with this frame, if any.
+ * Get URI of the source file associated with this frame, if any.
* @return a source URI, or <code>null</code> if the file is unknown.
* @since 2.65
*/
URI getSourceURI();
/**
+ * Get the source MIME type, if known.
+ * @return the source MIME type, or <code>null</code> if the source, or
+ * its MIME type is unknown.
+ * @since 2.67
+ */
+ default String getSourceMimeType() {
+ return null;
+ }
+
+ /**
* Line location of the frame in the source code at {@link #getSourceURI()}.
*
* @return the line number, or <code>-1</code> if the line is unknown
diff --git a/java/debugger.jpda.truffle/nbproject/project.xml b/java/debugger.jpda.truffle/nbproject/project.xml
index aff8c0d..f731b8d 100644
--- a/java/debugger.jpda.truffle/nbproject/project.xml
+++ b/java/debugger.jpda.truffle/nbproject/project.xml
@@ -58,7 +58,7 @@
<compile-dependency/>
<run-dependency>
<release-version>2</release-version>
- <specification-version>1.98</specification-version>
+ <specification-version>1.117.1</specification-version>
</run-dependency>
</dependency>
<dependency>
@@ -130,7 +130,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>2.65</specification-version>
+ <specification-version>2.65.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java
index 2340344..d769294 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java
@@ -52,26 +52,27 @@
@Override
public String getURL(String relativePath, boolean global) {
if (TRUFFLE_ACCESSOR_PATH.equals(relativePath)) {
- JPDAThread currentThread = debugger.getCurrentThread();
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(currentThread);
- if (currentPCInfo != null) {
- return currentPCInfo.getSourcePosition().getSource().getUrl().toExternalForm();
- }
+ return getCurrentURL();
}
return null;
}
public String getURL(JPDAClassType clazz, String stratum) {
if (TRUFFLE_ACCESSOR_CLASS_NAME.equals(clazz.getName())) {
- JPDAThread currentThread = debugger.getCurrentThread();
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(currentThread);
- if (currentPCInfo != null) {
- Source source = currentPCInfo.getSourcePosition().getSource();
- if (source != null) {
- URL url = source.getUrl();
- if (url != null) {
- return url.toExternalForm();
- }
+ return getCurrentURL();
+ }
+ return null;
+ }
+
+ private String getCurrentURL() {
+ JPDAThread currentThread = debugger.getCurrentThread();
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(currentThread);
+ if (currentPCInfo != null) {
+ Source source = currentPCInfo.getSourcePosition().getSource();
+ if (source != null) {
+ URL url = source.getUrl();
+ if (url != null) {
+ return url.toExternalForm();
}
}
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java
index c1dcc60..d9e4904 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java
@@ -19,6 +19,7 @@
package org.netbeans.modules.debugger.jpda.truffle.access;
+import com.sun.jdi.AbsentInformationException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.ref.Reference;
@@ -27,6 +28,7 @@
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.api.debugger.jpda.LocalVariable;
+import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
import org.netbeans.modules.debugger.jpda.truffle.ast.TruffleNode;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackInfo;
@@ -93,6 +95,13 @@
}
public void setSelectedStackFrame(TruffleStackFrame selectedStackFrame) {
+ if (selectedStackFrame != null) {
+ ((JPDADebuggerImpl) selectedStackFrame.getDebugger()).setCurrentCallStackFrame(null);
+// try {
+// selectedStackFrame.getThread().getCallStack(0, 1)[0].makeCurrent();
+// } catch (AbsentInformationException ex) {}
+ selectedStackFrame.getDebugger().getSession().setCurrentLanguage(TruffleStrataProvider.TRUFFLE_STRATUM);
+ }
TruffleStackFrame old = this.selectedStackFrame;
this.selectedStackFrame = selectedStackFrame;
if (old != selectedStackFrame) {
@@ -101,6 +110,9 @@
}
public TruffleNode getAST(TruffleStackFrame frame) {
+ if (frame == null) {
+ return null;
+ }
return truffleNodes.apply(frame.getDepth());
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java
index 9a7787d..e45aff4 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java
@@ -22,6 +22,7 @@
import org.netbeans.api.debugger.jpda.Field;
import org.netbeans.api.debugger.jpda.LocalVariable;
import org.netbeans.api.debugger.jpda.ObjectVariable;
+import org.netbeans.api.debugger.jpda.Variable;
/**
* Halted information from the backend <code>JPDATruffleAccessor.executionHalted()</code>.
@@ -33,22 +34,24 @@
final boolean haltedBefore;
final ObjectVariable returnValue;
final ObjectVariable frameInfo;
+ final boolean supportsJavaFrames;
final ObjectVariable[] breakpointsHit;
final ObjectVariable[] breakpointConditionExceptions;
final LocalVariable stepCmd;
- private ExecutionHaltedInfo(LocalVariable[] vars) {
+ private ExecutionHaltedInfo(Variable[] vars) {
this.debugManager = (ObjectVariable) vars[0];
this.sourcePositions = (ObjectVariable) vars[1];
this.haltedBefore = (Boolean) vars[2].createMirrorObject();
this.returnValue = (ObjectVariable) vars[3];
this.frameInfo = (ObjectVariable) vars[4];
- this.breakpointsHit = getObjectArray((ObjectVariable) vars[5]);
- this.breakpointConditionExceptions = getObjectArray((ObjectVariable) vars[6]);
- this.stepCmd = vars[7];
+ this.supportsJavaFrames = (Boolean) vars[5].createMirrorObject();
+ this.breakpointsHit = vars.length > 6 ? getObjectArray((ObjectVariable) vars[6]) : null;
+ this.breakpointConditionExceptions = vars.length > 7 ? getObjectArray((ObjectVariable) vars[7]) : null;
+ this.stepCmd = vars.length > 8 ? (LocalVariable) vars[8] : null;
}
- static ExecutionHaltedInfo get(LocalVariable[] vars) {
+ static ExecutionHaltedInfo get(Variable[] vars) {
return new ExecutionHaltedInfo(vars);
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java
index de1ebc8..6560dd5 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java
@@ -22,18 +22,29 @@
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ClassType;
import com.sun.jdi.InvocationException;
+import com.sun.jdi.Method;
+import com.sun.jdi.ObjectReference;
import com.sun.jdi.StringReference;
+import com.sun.jdi.ThreadReference;
+import com.sun.jdi.Value;
+import com.sun.jdi.event.ClassPrepareEvent;
+import com.sun.jdi.event.Event;
+import com.sun.jdi.event.EventSet;
+import com.sun.jdi.event.LocatableEvent;
+import com.sun.jdi.request.EventRequest;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.InvalidObjectException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.Lock;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -46,15 +57,28 @@
import org.netbeans.api.debugger.jpda.JPDAClassType;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.debugger.jpda.JPDAThread;
+import org.netbeans.api.debugger.jpda.LineBreakpoint;
import org.netbeans.api.debugger.jpda.LocalVariable;
import org.netbeans.api.debugger.jpda.MethodBreakpoint;
import org.netbeans.api.debugger.jpda.ObjectVariable;
import org.netbeans.api.debugger.jpda.Variable;
import org.netbeans.api.debugger.jpda.event.JPDABreakpointEvent;
import org.netbeans.api.debugger.jpda.event.JPDABreakpointListener;
+import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated;
import org.netbeans.modules.debugger.jpda.expr.JDIVariable;
+import org.netbeans.modules.debugger.jpda.jdi.ClassTypeWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.IllegalThreadStateExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.ObjectCollectedExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.ThreadReferenceWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.ClassPrepareEventWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.EventSetWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.EventWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.LocatableEventWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.request.EventRequestWrapper;
import org.netbeans.modules.debugger.jpda.models.JPDAClassTypeImpl;
import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
import org.netbeans.modules.debugger.jpda.truffle.LanguageName;
@@ -69,6 +93,7 @@
import org.netbeans.modules.debugger.jpda.truffle.vars.impl.TruffleScope;
import org.netbeans.modules.debugger.jpda.truffle.vars.impl.TruffleStackVariable;
import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable;
+import org.netbeans.modules.debugger.jpda.util.Executor;
import org.netbeans.modules.debugger.jpda.util.WeakHashMapActive;
import org.openide.util.Exceptions;
@@ -89,7 +114,9 @@
private static final String VAR_FRAME = "frame"; // NOI18N
private static final String VAR_SRC_ID = "id"; // NOI18N
private static final String VAR_SRC_URI = "uri"; // NOI18N
+ private static final String VAR_SRC_MIMETYPE = "mimeType"; // NOI18N
private static final String VAR_SRC_NAME = "name"; // NOI18N
+ private static final String VAR_SRC_HOST_METHOD = "hostMethodName"; // NOI18N
private static final String VAR_SRC_PATH = "path"; // NOI18N
private static final String VAR_SRC_SOURCESECTION = "sourceSection"; // NOI18N
private static final String VAR_SRC_CODE = "code";
@@ -102,12 +129,15 @@
private static final String METHOD_GET_VARIABLES_SGN = "(Lcom/oracle/truffle/api/debug/DebugStackFrame;)[Ljava/lang/Object;"; // NOI18N
private static final String METHOD_GET_SCOPE_VARIABLES = "getScopeVariables";// NOI18N
private static final String METHOD_GET_SCOPE_VARIABLES_SGN = "(Lcom/oracle/truffle/api/debug/DebugScope;)[Ljava/lang/Object;"; // NOI18N
+ private static final String METHOD_SUSPEND_HERE = "suspendHere"; // NOI18N
+ private static final String METHOD_SUSPEND_HERE_SGN = "()[Ljava/lang/Object;";// NOI18N
private static final String METHOD_SET_UNWIND = "setUnwind";// NOI18N
private static final String METHOD_SET_UNWIND_SGN = "(I)Z"; // NOI18N
private static final String METHOD_GET_AST = "getTruffleAST"; // NOI18N
private static final String METHOD_GET_AST_SGN = "(I)[Ljava/lang/Object;";
private static final Map<JPDAThread, ThreadInfo> currentPCInfos = new WeakHashMap<>();
+ private static final Map<JPDAThread, ThreadInfo> suspendHerePCInfos = new WeakHashMap<>();
private static final PropertyChangeListener threadResumeListener = new ThreadResumeListener();
private static final TruffleAccess DEFAULT = new TruffleAccess();
@@ -147,6 +177,14 @@
}
public static CurrentPCInfo getCurrentPCInfo(JPDAThread thread) {
+ CurrentPCInfo cpi = getCurrentGuestPCInfo(thread);
+ if (cpi == null) {
+ cpi = getCurrentSuspendHereInfo(thread);
+ }
+ return cpi;
+ }
+
+ public static CurrentPCInfo getCurrentGuestPCInfo(JPDAThread thread) {
ThreadInfo info;
synchronized (currentPCInfos) {
info = currentPCInfos.get(thread);
@@ -172,6 +210,142 @@
return null;
}
+ public static CurrentPCInfo getCurrentSuspendHereInfo(JPDAThread thread) {
+ synchronized (suspendHerePCInfos) {
+ ThreadInfo info = suspendHerePCInfos.get(thread);
+ if (info != null) {
+ return info.cpi;
+ }
+ }
+ return null;
+ }
+
+ public static CurrentPCInfo getSuspendHere(JPDAThread thread) {
+ if (getCurrentGuestPCInfo(thread) != null) {
+ // Suspended in the guest language already.
+ return null;
+ }
+ ThreadInfo info;
+ synchronized (suspendHerePCInfos) {
+ info = suspendHerePCInfos.get(thread);
+ if (info != null) {
+ if (info.cpi != null) {
+ return info.cpi;
+ } else if (info.noCpi) {
+ return null;
+ }
+ }
+ if (info == null) {
+ info = new ThreadInfo();
+ suspendHerePCInfos.put(thread, info);
+ }
+ }
+ synchronized (info) {
+ if (info.cpi != null) {
+ return info.cpi;
+ }
+ if (info.noCpi) {
+ return null;
+ }
+ JPDADebugger debugger = ((JPDAThreadImpl) thread).getDebugger();
+ ClassType debugAccessorClass = TruffleDebugManager.getDebugAccessorClass(debugger);
+ if (debugAccessorClass == null) {
+ info.noCpi = true;
+ return null;
+ }
+ ObjectVariable haltInfo = invokeSuspendHere(debugAccessorClass, thread);
+ if (haltInfo == null) {
+ info.noCpi = true;
+ return null;
+ }
+ CurrentPCInfo cpi = getCurrentPosition(debugger, thread, ((ObjectVariable) haltInfo).getFields(0, Integer.MAX_VALUE));
+ cpi.setSelectedStackFrame(null);
+ info.cpi = cpi;
+ return cpi;
+ }
+ }
+
+ private static ObjectVariable invokeSuspendHere(ClassType debugAccessorClass, JPDAThread thread) {
+ JPDAThreadImpl threadImpl = (JPDAThreadImpl) thread;
+ ThreadReference tr = threadImpl.getThreadReference();
+ try {
+ Method suspendHereMethod = ClassTypeWrapper.concreteMethodByName(debugAccessorClass, METHOD_SUSPEND_HERE, METHOD_SUSPEND_HERE_SGN);
+ JPDADebuggerImpl debugger = threadImpl.getDebugger();
+ Value haltInfo;
+ Lock writeLock = threadImpl.accessLock.writeLock();
+ Function<EventSet, Boolean> breakpointEventInterceptor = null;
+ try {
+ writeLock.lock();
+ breakpointEventInterceptor = skipSuspendedEventClearLeakingReferences(debugger, thread);
+ haltInfo = ClassTypeWrapper.invokeMethod(debugAccessorClass, tr, suspendHereMethod, Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED);
+ } finally {
+ ((JPDADebuggerImpl) debugger).getOperator().removeEventInterceptor(breakpointEventInterceptor);
+ writeLock.unlock();
+ }
+ if (haltInfo instanceof ObjectReference) {
+ return (ObjectVariable) debugger.getVariable(haltInfo);
+ }
+ } catch (InvocationException ex) {
+ Exceptions.printStackTrace(ex);
+ } catch (Exception ex) {
+ LOG.log(Level.CONFIG, "Invoking " + METHOD_SUSPEND_HERE, ex);
+ }
+ return null;
+ }
+
+ private static Function<EventSet, Boolean> skipSuspendedEventClearLeakingReferences(JPDADebugger debugger, JPDAThread thread) {
+ ThreadReference tr = ((JPDAThreadImpl) thread).getThreadReference();
+ MethodBreakpoint clearLeakingReferencesBreakpoint = MethodBreakpoint.create("com.oracle.truffle.api.debug.SuspendedEvent", "clearLeakingReferences");
+ clearLeakingReferencesBreakpoint.setBreakpointType(MethodBreakpoint.TYPE_METHOD_ENTRY);
+ clearLeakingReferencesBreakpoint.setThreadFilters(debugger, new JPDAThread[] { thread });
+ clearLeakingReferencesBreakpoint.setHidden(true);
+ Function<EventSet, Boolean> breakpointEventInterceptor = eventSet -> {
+ ThreadReference etr = null;
+ try {
+ for (Event e: eventSet) {
+ if (e instanceof ClassPrepareEvent) {
+ etr = ClassPrepareEventWrapper.thread((ClassPrepareEvent) e);
+ } else if (e instanceof LocatableEvent) {
+ etr = LocatableEventWrapper.thread((LocatableEvent) e);
+ }
+ }
+ if (tr.equals(etr)) {
+ boolean resume = true;
+ for (Event e: eventSet) {
+ EventRequest r = EventWrapper.request(e);
+ Executor exec = (r != null) ? (Executor) EventRequestWrapper.getProperty (r, "executor") : null;
+ resume = resume & exec.exec(e);
+ }
+ if (resume) {
+ try {
+ EventSetWrapper.resume(eventSet);
+ } catch (IllegalThreadStateExceptionWrapper | ObjectCollectedExceptionWrapper ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ } catch (InternalExceptionWrapper | VMDisconnectedExceptionWrapper ex) {
+ return false;
+ }
+ };
+ clearLeakingReferencesBreakpoint.addJPDABreakpointListener(event -> {
+ DebuggerManager.getDebuggerManager().removeBreakpoint(clearLeakingReferencesBreakpoint);
+ try {
+ ThreadReferenceWrapper.forceEarlyReturn(tr, tr.virtualMachine().mirrorOfVoid());
+ } catch (Exception ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ event.resume();
+ ((JPDADebuggerImpl) debugger).getOperator().removeEventInterceptor(breakpointEventInterceptor);
+ });
+ ((JPDADebuggerImpl) debugger).getOperator().addEventInterceptor(breakpointEventInterceptor);
+ DebuggerManager.getDebuggerManager().addBreakpoint(clearLeakingReferencesBreakpoint);
+ return breakpointEventInterceptor;
+ }
+
@Override
public void breakpointReached(JPDABreakpointEvent event) {
Object bp = event.getSource();
@@ -213,12 +387,20 @@
}
}
- private CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread) {
+ private static CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread) {
try {
CallStackFrame csf = thread.getCallStack(0, 1)[0];
LocalVariable[] localVariables = csf.getLocalVariables();
- ExecutionHaltedInfo haltedInfo = ExecutionHaltedInfo.get(localVariables);
- //JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
+ return getCurrentPosition(debugger, thread, localVariables);
+ } catch (AbsentInformationException | IllegalStateException ex) {
+ Exceptions.printStackTrace(ex);
+ return null;
+ }
+ }
+
+ private static CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread, Variable[] haltVars) {
+ try {
+ ExecutionHaltedInfo haltedInfo = ExecutionHaltedInfo.get(haltVars);
ObjectVariable sourcePositionVar = haltedInfo.sourcePositions;
SourcePosition sp = getSourcePosition(debugger, sourcePositionVar);
@@ -230,11 +412,11 @@
String topFrameDescription = (String) frameInfoVar.getField(VAR_TOP_FRAME).createMirrorObject();
ObjectVariable thisObject = null;// TODO: (ObjectVariable) frameInfoVar.getField("thisObject");
TruffleStackFrame topFrame = new TruffleStackFrame(debugger, thread, 0, frame, topFrameDescription, null/*code*/, scopes, thisObject, true);
- TruffleStackInfo stack = new TruffleStackInfo(debugger, thread, stackTrace);
+ TruffleStackInfo stack = new TruffleStackInfo(debugger, thread, stackTrace, haltedInfo.supportsJavaFrames);
return new CurrentPCInfo(haltedInfo.stepCmd, thread, sp, scopes, topFrame, stack, depth -> {
return getTruffleAST(debugger, (JPDAThreadImpl) thread, depth, sp, stack);
});
- } catch (AbsentInformationException | IllegalStateException ex) {
+ } catch (IllegalStateException ex) {
Exceptions.printStackTrace(ex);
return null;
}
@@ -306,10 +488,12 @@
Source src = Source.getExistingSource(debugger, id);
if (src == null) {
String name = (String) sourcePositionVar.getField(VAR_SRC_NAME).createMirrorObject();
+ String hostMethodName = (String) sourcePositionVar.getField(VAR_SRC_HOST_METHOD).createMirrorObject();
String path = (String) sourcePositionVar.getField(VAR_SRC_PATH).createMirrorObject();
URI uri = (URI) sourcePositionVar.getField(VAR_SRC_URI).createMirrorObject();
+ String mimeType = (String) sourcePositionVar.getField(VAR_SRC_MIMETYPE).createMirrorObject();
StringReference codeRef = (StringReference) ((JDIVariable) sourcePositionVar.getField(VAR_SRC_CODE)).getJDIValue();
- src = Source.getSource(debugger, id, name, path, uri, codeRef);
+ src = Source.getSource(debugger, id, name, hostMethodName, path, uri, mimeType, codeRef);
}
return new SourcePosition(debugger, id, src, sourceSection);
}
@@ -406,8 +590,10 @@
}
int sourceId;
String sourceName;
+ String hostMethodName;
String sourcePath;
URI sourceURI;
+ String mimeType;
String sourceSection;
try {
int i1 = 0;
@@ -418,6 +604,9 @@
sourceName = sourceDef.substring(i1, i2);
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
+ hostMethodName = sourceDef.substring(i1, i2);
+ i1 = i2 + 1;
+ i2 = sourceDef.indexOf('\n', i1);
sourcePath = sourceDef.substring(i1, i2);
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
@@ -428,6 +617,9 @@
}
i1 = i2 + 1;
i2 = sourceDef.indexOf('\n', i1);
+ mimeType = sourceDef.substring(i1, i2);
+ i1 = i2 + 1;
+ i2 = sourceDef.indexOf('\n', i1);
if (i2 < 0) {
i2 = sourceDef.length();
}
@@ -435,7 +627,7 @@
} catch (IndexOutOfBoundsException ioob) {
throw new IllegalStateException("var source definition='"+sourceDef+"'", ioob);
}
- Source src = Source.getSource(debugger, sourceId, sourceName, sourcePath, sourceURI, codeRef);
+ Source src = Source.getSource(debugger, sourceId, sourceName, hostMethodName, sourcePath, sourceURI, mimeType, codeRef);
return new SourcePosition(debugger, sourceId, src, sourceSection);
}
@@ -556,6 +748,7 @@
private static final class ThreadInfo {
volatile CurrentPCInfo cpi;
+ volatile boolean noCpi;
}
private static final class ThreadResumeListener implements PropertyChangeListener {
@@ -565,13 +758,19 @@
JPDAThreadImpl t = (JPDAThreadImpl) evt.getSource();
if (!(Boolean) evt.getNewValue() && !t.isMethodInvoking()) { // not suspended, resumed
synchronized (currentPCInfos) {
- ThreadInfo info = currentPCInfos.get(t);
- if (info != null) {
- info.cpi = null;
- }
+ clear(currentPCInfos.get(t));
+ }
+ synchronized (suspendHerePCInfos) {
+ clear(suspendHerePCInfos.get(t));
}
}
}
+ private void clear(ThreadInfo info) {
+ if (info != null) {
+ info.cpi = null;
+ info.noCpi = false;
+ }
+ }
}
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java
index 08c0e9c..7f1bf07 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java
@@ -26,7 +26,9 @@
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.api.debugger.jpda.ObjectVariable;
import org.netbeans.api.debugger.jpda.Variable;
+import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated;
import org.netbeans.modules.debugger.jpda.truffle.TruffleDebugManager;
+import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
@@ -48,7 +50,11 @@
if (currentPCInfo == null) {
throw new InvalidExpressionException(Bundle.MSG_NoSuspend());
}
- ObjectVariable stackFrameInstance = currentPCInfo.getSelectedStackFrame().getStackFrameInstance();
+ TruffleStackFrame selectedStackFrame = currentPCInfo.getSelectedStackFrame();
+ if (selectedStackFrame == null) {
+ throw new InvalidExpressionException(Bundle.MSG_NoSuspend());
+ }
+ ObjectVariable stackFrameInstance = selectedStackFrame.getStackFrameInstance();
JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
try {
Variable mirrorExpression = debugger.createMirrorVar(expression);
@@ -58,6 +64,13 @@
new Variable[] { stackFrameInstance,
mirrorExpression });
return valueVar;
+ } catch (InvalidExpressionException ex) {
+ Throwable targetException = ex.getTargetException();
+ if (targetException instanceof InvocationExceptionTranslated) {
+ // We do not want to prepend Java exception message:
+ ((InvocationExceptionTranslated) targetException).resetInvocationMessage();
+ }
+ throw ex;
} catch (InvalidObjectException | NoSuchMethodException ex) {
try {
return debugger.createMirrorVar(ex.getLocalizedMessage());
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java
index 8a50fed..464b610 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java
@@ -61,7 +61,7 @@
@Override
public int getStrataLineNumber(CallStackFrameImpl csf, String stratum) {
if (TRUFFLE_STRATUM.equals(stratum) && isInTruffleAccessPoint(csf)) {
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(csf.getThread());
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(csf.getThread());
if (currentPCInfo != null) {
return currentPCInfo.getSourcePosition().getStartLine();
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java
index 4dfc2f6..3e695ef 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java
@@ -95,7 +95,7 @@
setEnabled(NAME, hasServiceClass);
if (hasServiceClass) {
JPDAThread currentThread = debugger.getCurrentThread();
- if (currentThread != null && TruffleAccess.getCurrentPCInfo(currentThread) != null) {
+ if (currentThread != null && TruffleAccess.getCurrentGuestPCInfo(currentThread) != null) {
suspendState = false;
}
updateActionState(true, suspendState);
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java
index 2ca211a..51a99f9 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java
@@ -73,8 +73,8 @@
ActionsManager.ACTION_CONTINUE
})));
- private static final String STEP2JAVA_CLASS = "com.oracle.truffle.polyglot.HostMethodDesc$SingleMethod$MHBase";
- private static final String STEP2JAVA_METHOD = "invokeHandle";
+ public static final String STEP2JAVA_CLASS = "com.oracle.truffle.polyglot.HostMethodDesc$SingleMethod$MHBase";
+ public static final String STEP2JAVA_METHOD = "invokeHandle";
public StepActionProvider (ContextProvider lookupProvider) {
super (
@@ -92,7 +92,7 @@
i.next (),
(debuggerState == JPDADebugger.STATE_STOPPED) &&
(currentThread != null) &&
- (TruffleAccess.getCurrentPCInfo(currentThread) != null)
+ (TruffleAccess.getCurrentGuestPCInfo(currentThread) != null)
);
}
}
@@ -102,7 +102,7 @@
LOG.fine("doAction("+action+")");
JPDADebuggerImpl debugger = getDebuggerImpl();
JPDAThread currentThread = debugger.getCurrentThread();
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(currentThread);
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(currentThread);
int stepCmd = 0;
if (ActionsManager.ACTION_CONTINUE.equals(action)) {
stepCmd = 0;
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java
index 532bba9..0c244e2 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java
@@ -21,6 +21,7 @@
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ArrayReference;
+import com.sun.jdi.BooleanValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.IncompatibleThreadStateException;
@@ -192,6 +193,12 @@
if (bp.isEnabled()) {
bpImpl = setLineBreakpoint(debugManager, t, uri, bp.getLineNumber(),
getIgnoreCount(bp), bp.getCondition());
+ // Find out whether the breakpoint was resolved already during the submission:
+ try {
+ updateResolved(bp, bpImpl, t.getThreadReference());
+ } catch (Exception ex) {
+ Exceptions.printStackTrace(Exceptions.attachMessage(ex, "Testing resolved breakpoint at "+uri+":"+bp.getLineNumber()));
+ }
} else {
bpImpl = null;
}
@@ -210,6 +217,15 @@
}
}
+ private void updateResolved(JSLineBreakpoint breakpoint, ObjectReference bp, ThreadReference tr) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ClassNotPreparedExceptionWrapper, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException, ObjectCollectedExceptionWrapper {
+ ClassType breakpointClass = (ClassType) bp.referenceType();
+ Method isResolvedMethod = ClassTypeWrapper.concreteMethodByName(breakpointClass, "isResolved", "()Z");
+ BooleanValue isResolvedValue = (BooleanValue) ObjectReferenceWrapper.invokeMethod(bp, tr, isResolvedMethod, Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED);
+ if (isResolvedValue.value()) {
+ JSBreakpointStatus.setValid(breakpoint, "resolved");
+ }
+ }
+
private static int getIgnoreCount(JSLineBreakpoint bp) {
int ignoreCount = 0;
if (Breakpoint.HIT_COUNT_FILTERING_STYLE.GREATER.equals(bp.getHitCountFilteringStyle())) {
@@ -301,10 +317,16 @@
ObjectReference.INVOKE_SINGLE_THREADED);
ret.disableCollection();
bpRef[0] = ret;
+ // Find out whether the breakpoint was resolved already during the submission:
+ for (Value v : ret.getValues()) {
+ if (v instanceof ObjectReference) {
+ updateResolved(bp, (ObjectReference) v, tr);
+ }
+ }
} catch (InvalidTypeException | ClassNotLoadedException |
IncompatibleThreadStateException | UnsupportedOperationExceptionWrapper |
InternalExceptionWrapper | VMDisconnectedExceptionWrapper |
- ObjectCollectedExceptionWrapper ex) {
+ ObjectCollectedExceptionWrapper | ClassNotPreparedExceptionWrapper ex) {
Exceptions.printStackTrace(Exceptions.attachMessage(ex, "Setting breakpoint to "+uri+":"+line));
} finally {
persistents.collect();
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java
index ac74193..3b3f91b 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java
@@ -56,13 +56,17 @@
private final int sourceId;
private final String sourceName;
+ private final String hostClassName;
+ private final String hostMethodName;
private final String sourcePath;
private final URI sourceURI;
+ private final String mimeType;
private final String sourceSection;
private final StringReference codeRef;
private TruffleScope[] scopes;
private final ObjectVariable thisObject;
private final boolean isInternal;
+ private final boolean isHost;
public TruffleStackFrame(JPDADebugger debugger, JPDAThread thread, int depth,
ObjectVariable frameInstance,
@@ -90,6 +94,9 @@
methodName = frameDefinition.substring(i1, i2);
i1 = i2 + 1;
i2 = frameDefinition.indexOf('\n', i1);
+ isHost = Boolean.valueOf(frameDefinition.substring(i1, i2));
+ i1 = i2 + 1;
+ i2 = frameDefinition.indexOf('\n', i1);
language = LanguageName.parse(frameDefinition.substring(i1, i2));
i1 = i2 + 1;
i2 = frameDefinition.indexOf('\n', i1);
@@ -105,12 +112,21 @@
sourcePath = frameDefinition.substring(i1, i2);
i1 = i2 + 1;
i2 = frameDefinition.indexOf('\n', i1);
+ hostClassName = frameDefinition.substring(i1, i2);
+ i1 = i2 + 1;
+ i2 = frameDefinition.indexOf('\n', i1);
+ hostMethodName = frameDefinition.substring(i1, i2);
+ i1 = i2 + 1;
+ i2 = frameDefinition.indexOf('\n', i1);
try {
sourceURI = new URI(frameDefinition.substring(i1, i2));
} catch (URISyntaxException usex) {
throw new IllegalStateException("Bad URI: "+frameDefinition.substring(i1, i2), usex);
}
i1 = i2 + 1;
+ i2 = frameDefinition.indexOf('\n', i1);
+ mimeType = frameDefinition.substring(i1, i2);
+ i1 = i2 + 1;
if (includeInternal) {
i2 = frameDefinition.indexOf('\n', i1);
sourceSection = frameDefinition.substring(i1, i2);
@@ -140,6 +156,14 @@
return depth;
}
+ public String getHostClassName() {
+ return hostClassName;
+ }
+
+ public String getHostMethodName() {
+ return hostMethodName;
+ }
+
public String getMethodName() {
return methodName;
}
@@ -163,7 +187,7 @@
public SourcePosition getSourcePosition() {
Source src = Source.getExistingSource(debugger, sourceId);
if (src == null) {
- src = Source.getSource(debugger, sourceId, sourceName, sourcePath, sourceURI, codeRef);
+ src = Source.getSource(debugger, sourceId, sourceName, hostMethodName, sourcePath, sourceURI, mimeType, codeRef);
}
SourcePosition sp = new SourcePosition(debugger, sourceId, src, sourceSection);
return sp;
@@ -184,7 +208,7 @@
if (depth > 0) {
boolean unwindScheduled = TruffleAccess.unwind(debugger, thread, depth - 1);
if (unwindScheduled) {
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(thread);
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(thread);
try {
currentPCInfo.getStepCommandVar().setFromMirrorObject(-1);
thread.resume();
@@ -203,4 +227,7 @@
return isInternal;
}
+ public boolean isHost() {
+ return isHost;
+ }
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java
index 0502462..deb0ffb 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java
@@ -41,19 +41,25 @@
public final class TruffleStackInfo {
private static final String METHOD_GET_FRAMES_INFO = "getFramesInfo"; // NOI18N
- private static final String METHOD_GET_FRAMES_INFO_SIG = "([Lcom/oracle/truffle/api/debug/DebugStackFrame;Z)[Ljava/lang/Object;"; // NOI18N
+ private static final String METHOD_GET_FRAMES_INFO_SIG = "([Lcom/oracle/truffle/api/debug/DebugStackFrame;ZZ)[Ljava/lang/Object;"; // NOI18N
private final JPDADebugger debugger;
private final JPDAThread thread;
private final ObjectVariable stackTrace;
+ private final boolean supportsJavaFrames;
private TruffleStackFrame[] stackFrames;
private boolean includedInternalFrames;
private boolean areInternalFrames;
public TruffleStackInfo(JPDADebugger debugger, JPDAThread thread, ObjectVariable stackTrace) {
+ this(debugger, thread, stackTrace, false);
+ }
+
+ public TruffleStackInfo(JPDADebugger debugger, JPDAThread thread, ObjectVariable stackTrace, boolean supportsJavaFrames) {
this.debugger = debugger;
this.thread = thread;
this.stackTrace = stackTrace;
+ this.supportsJavaFrames = supportsJavaFrames;
}
public TruffleStackFrame[] getStackFrames(boolean includeInternal) {
@@ -67,15 +73,21 @@
public boolean hasInternalFrames() {
return areInternalFrames;
}
-
+
+ public boolean hasJavaFrames() {
+ return supportsJavaFrames;
+ }
+
private TruffleStackFrame[] loadStackFrames(boolean includeInternal) {
JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
try {
Variable internalVar = debugger.createMirrorVar(includeInternal, true);
+ Variable javaFramesVar = debugger.createMirrorVar(supportsJavaFrames, true);
Variable framesVar = debugAccessor.invokeMethod(METHOD_GET_FRAMES_INFO,
METHOD_GET_FRAMES_INFO_SIG,
new Variable[] { stackTrace,
- internalVar });
+ internalVar,
+ javaFramesVar });
Field[] framesInfos = ((ObjectVariable) framesVar).getFields(0, Integer.MAX_VALUE);
String framesDesc = (String) framesInfos[0].createMirrorObject();
Field[] codes = ((ObjectVariable) framesInfos[1]).getFields(0, Integer.MAX_VALUE);
@@ -88,7 +100,8 @@
int i2;
int depth = 1;
List<TruffleStackFrame> truffleFrames = new ArrayList<>();
- while ((i2 = framesDesc.indexOf("\n\n", i1)) > 0) {
+ String frameSeparator = "\n\t\n";
+ while ((i2 = framesDesc.indexOf(frameSeparator, i1)) > 0) {
StringReference codeRef = (StringReference) ((JDIVariable) codes[depth-1]).getJDIValue();
ObjectVariable frameInstance = (ObjectVariable) stackTrace.getFields(0, Integer.MAX_VALUE)[depth - 1];
TruffleStackFrame tsf = new TruffleStackFrame(
@@ -98,7 +111,7 @@
if (includeInternal && tsf.isInternal()) {
areInternalFrames = true;
}
- i1 = i2 + 2;
+ i1 = i2 + frameSeparator.length();
depth++;
}
return truffleFrames.toArray(new TruffleStackFrame[truffleFrames.size()]);
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java
index dc0acf1..324fbfd 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java
@@ -19,15 +19,26 @@
package org.netbeans.modules.debugger.jpda.truffle.frames.models;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
import javax.swing.Action;
import javax.swing.SwingUtilities;
+import org.netbeans.api.debugger.jpda.CallStackFrame;
+import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.modules.debugger.jpda.EditorContextBridge;
+import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.netbeans.modules.debugger.jpda.truffle.options.TruffleOptions;
import org.netbeans.modules.debugger.jpda.truffle.source.SourcePosition;
+import org.netbeans.modules.debugger.jpda.util.WeakCacheMap;
import org.netbeans.spi.debugger.ContextProvider;
import org.netbeans.spi.debugger.DebuggerServiceRegistration;
@@ -39,6 +50,7 @@
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
+import org.openide.util.WeakListeners;
@DebuggerServiceRegistration(path="netbeans-JPDASession/DebuggingView",
types=NodeActionsProviderFilter.class,
@@ -50,26 +62,40 @@
private final Action POP_TO_HERE_ACTION;
private final Action SHOW_INTERNAL_ACTION;
private final Action HIDE_INTERNAL_ACTION;
+ private final Action SHOW_HOST_ACTION;
+ private final Action HIDE_HOST_ACTION;
+ private final RequestProcessor requestProcessor;
+
+ private static final Map<DebuggingView.DVThread, Boolean> SHOWING_ALL_HOST_FRAMES = Collections.synchronizedMap(new WeakHashMap<>());
+ private static final PropertyChangeSupport SHOWING_ALL_HOST_FRAMES_CHANGE = new PropertyChangeSupport(new Object());
public DebuggingTruffleActionsProvider(ContextProvider lookupProvider) {
- RequestProcessor requestProcessor = lookupProvider.lookupFirst(null, RequestProcessor.class);
+ requestProcessor = lookupProvider.lookupFirst(null, RequestProcessor.class);
MAKE_CURRENT_ACTION = createMAKE_CURRENT_ACTION(requestProcessor);
GO_TO_SOURCE_ACTION = createGO_TO_SOURCE_ACTION(requestProcessor);
POP_TO_HERE_ACTION = createPOP_TO_HERE_ACTION(requestProcessor);
SHOW_INTERNAL_ACTION = createSHOW_INTERNAL_ACTION();
HIDE_INTERNAL_ACTION = createHIDE_INTERNAL_ACTION();
+ SHOW_HOST_ACTION = createSHOW_HOST_ACTION();
+ HIDE_HOST_ACTION = createHIDE_HOST_ACTION();
}
@Override
public void performDefaultAction(NodeActionsProvider original, Object node) throws UnknownTypeException {
if (node instanceof TruffleStackFrame) {
- TruffleStackFrame f = (TruffleStackFrame) node;
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(f.getThread());
+ requestProcessor.post(() ->{
+ TruffleStackFrame f = (TruffleStackFrame) node;
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(f.getThread());
+ if (currentPCInfo != null) {
+ currentPCInfo.setSelectedStackFrame(f);
+ goToSource(f);
+ }
+ });
+ } else if (node instanceof CallStackFrame) {
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(((CallStackFrame) node).getThread());
if (currentPCInfo != null) {
- currentPCInfo.setSelectedStackFrame(f);
- goToSource(f);
+ currentPCInfo.setSelectedStackFrame(null);
}
- } else {
original.performDefaultAction(node);
}
}
@@ -96,20 +122,60 @@
}
return actions;*/
} else {
- return original.getActions(node);
+ Action[] actions = original.getActions(node);
+ if (node instanceof DebuggingView.DVThread && hasGuestInfo(node)) {
+ Action showAction;
+ if (!isShowAllHostFrames((DebuggingView.DVThread) node)) {
+ showAction = SHOW_HOST_ACTION;
+ } else {
+ showAction = HIDE_HOST_ACTION;
+ }
+ int pos = actions.length;
+ actions = Arrays.copyOf(actions, pos + 2);
+ actions[pos++] = null;
+ actions[pos] = showAction;
+ }
+ return actions;
}
}
+ private static boolean hasGuestInfo(Object node) {
+ JPDAThread thread = ((WeakCacheMap.KeyedValue<JPDAThread>) node).getKey();
+ return TruffleAccess.getCurrentPCInfo(thread) != null;
+ }
+
+ static boolean isShowAllHostFrames(DebuggingView.DVThread thread) {
+ Boolean is = SHOWING_ALL_HOST_FRAMES.get(thread);
+ return Boolean.TRUE.equals(is);
+ }
+
+ static void onShowAllHostFramesChange(PropertyChangeListener listener) {
+ SHOWING_ALL_HOST_FRAMES_CHANGE.addPropertyChangeListener(WeakListeners.propertyChange(listener, SHOWING_ALL_HOST_FRAMES_CHANGE));
+ }
+
private static void goToSource(final TruffleStackFrame f) {
final SourcePosition sourcePosition = f.getSourcePosition();
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
- EditorContextBridge.getContext().showSource (
- sourcePosition.getSource().getUrl().toExternalForm(),
- sourcePosition.getStartLine(),
- f.getDebugger()
- );
+ if (sourcePosition.getSource().getHostMethodName() == null) {
+ EditorContextBridge.getContext().showSource (
+ sourcePosition.getSource().getUrl().toExternalForm(),
+ sourcePosition.getStartLine(),
+ f.getDebugger()
+ );
+ } else {
+ String path = sourcePosition.getSource().getPath();
+ if (path != null) {
+ path = path.replace (File.separatorChar, '/');
+ String url = ((JPDADebuggerImpl) f.getDebugger()).getEngineContext().getURL(path, true);
+ EditorContextBridge.getContext().showSource (
+ url,
+ sourcePosition.getStartLine(),
+ f.getDebugger()
+ );
+ }
+ }
}
});
}
@@ -125,12 +191,7 @@
TruffleStackFrame f = (TruffleStackFrame) node;
CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(f.getThread());
if (currentPCInfo != null) {
- TruffleStackFrame topFrame = currentPCInfo.getTopFrame();
- if (topFrame == null) {
- return f.getDepth() > 0;
- } else {
- return f != topFrame;
- }
+ return f != currentPCInfo.getSelectedStackFrame();
}
}
return false;
@@ -230,6 +291,56 @@
);
}
+ @NbBundle.Messages("CTL_StackFrameAction_ShowHost_Label=Show All Host Frames")
+ static final Action createSHOW_HOST_ACTION() {
+ return Models.createAction (
+ Bundle.CTL_StackFrameAction_ShowHost_Label(),
+ new Models.ActionPerformer () {
+ @Override
+ public boolean isEnabled (Object node) {
+ if (!(node instanceof DebuggingView.DVThread)) {
+ return false;
+ }
+ return !isShowAllHostFrames((DebuggingView.DVThread) node);
+ }
+
+ @Override
+ public void perform (final Object[] nodes) {
+ for (Object node : nodes) {
+ SHOWING_ALL_HOST_FRAMES.put((DebuggingView.DVThread) node, Boolean.TRUE);
+ }
+ SHOWING_ALL_HOST_FRAMES_CHANGE.firePropertyChange("allHostFrames", false, true);
+ }
+ },
+ Models.MULTISELECTION_TYPE_ALL
+ );
+ }
+
+ @NbBundle.Messages("CTL_StackFrameAction_HideHost_Label=Show Graal Guest Frames")
+ static final Action createHIDE_HOST_ACTION() {
+ return Models.createAction (
+ Bundle.CTL_StackFrameAction_HideHost_Label(),
+ new Models.ActionPerformer () {
+ @Override
+ public boolean isEnabled (Object node) {
+ if (!(node instanceof DebuggingView.DVThread)) {
+ return false;
+ }
+ return isShowAllHostFrames((DebuggingView.DVThread) node);
+ }
+
+ @Override
+ public void perform (final Object[] nodes) {
+ for (Object node : nodes) {
+ SHOWING_ALL_HOST_FRAMES.remove((DebuggingView.DVThread) node);
+ }
+ SHOWING_ALL_HOST_FRAMES_CHANGE.firePropertyChange("allHostFrames", true, false);
+ }
+ },
+ Models.MULTISELECTION_TYPE_ALL
+ );
+ }
+
@NbBundle.Messages("CTL_StackFrameAction_PopToHere_Label=Pop To Here")
private Action createPOP_TO_HERE_ACTION(RequestProcessor requestProcessor) {
return Models.createAction (
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java
index 7e85cd9..af95f4b 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java
@@ -31,7 +31,8 @@
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
-import org.netbeans.modules.debugger.jpda.truffle.access.TruffleStrataProvider;
+import static org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess.BASIC_CLASS_NAME;
+import org.netbeans.modules.debugger.jpda.truffle.actions.StepActionProvider;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.netbeans.modules.debugger.jpda.truffle.options.TruffleOptions;
import org.netbeans.modules.debugger.jpda.ui.debugging.JPDADVFrame;
@@ -47,15 +48,15 @@
import org.netbeans.spi.viewmodel.TreeModelFilter;
import org.netbeans.spi.viewmodel.UnknownTypeException;
-@DebuggerServiceRegistration(path="netbeans-JPDASession/"+TruffleStrataProvider.TRUFFLE_STRATUM+"/DebuggingView",
- types={ TreeModelFilter.class })
+@DebuggerServiceRegistration(path="netbeans-JPDASession/DebuggingView",
+ types={ TreeModelFilter.class }, position=25000)
public class DebuggingTruffleTreeModel implements TreeModelFilter {
private static final Predicate<String> PREDICATE1 = Pattern.compile("^((com|org)\\.\\p{Alpha}*\\.truffle|(com|org)(\\.graalvm|\\.truffleruby))\\..*$").asPredicate();
private static final String FILTER1 = "com.[A-z]*.truffle."; // NOI18N
private static final String FILTER2 = "com.oracle.graal."; // NOI18N
private static final String FILTER3 = "org.netbeans.modules.debugger.jpda.backend."; // NOI18N
-
+
private final JPDADebugger debugger;
private final List<ModelListener> listeners = new ArrayList<>();
private final PropertyChangeListener propListenerHolder; // Not to have the listener collected
@@ -73,6 +74,7 @@
}
};
TruffleOptions.onLanguageDeveloperModeChange(propListenerHolder);
+ DebuggingTruffleActionsProvider.onShowAllHostFramesChange(propListenerHolder);
}
@Override
@@ -83,12 +85,25 @@
@Override
public Object[] getChildren(TreeModel original, Object parent, int from, int to) throws UnknownTypeException {
Object[] children = original.getChildren(parent, from, to);
- if (parent instanceof DebuggingView.DVThread && children.length > 0) {
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(((WeakCacheMap.KeyedValue<JPDAThread>) parent).getKey());
+ if (parent instanceof DebuggingView.DVThread && children.length > 0 &&
+ !DebuggingTruffleActionsProvider.isShowAllHostFrames((DebuggingView.DVThread) parent)) {
+
+ JPDAThread thread = ((WeakCacheMap.KeyedValue<JPDAThread>) parent).getKey();
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(thread);
+ boolean inGuest = isInGuest(children[0]);
+ boolean haveTopHostFrames = false;
+ if (currentPCInfo == null) {
+ currentPCInfo = inGuest ? TruffleAccess.getCurrentSuspendHereInfo(thread) : TruffleAccess.getSuspendHere(thread);
+ haveTopHostFrames = true;
+ }
if (currentPCInfo != null) {
boolean showInternalFrames = TruffleOptions.isLanguageDeveloperMode();
TruffleStackFrame[] stackFrames = currentPCInfo.getStack().getStackFrames(showInternalFrames);
- children = filterAndAppend(children, stackFrames, currentPCInfo.getTopFrame());
+ if (inGuest && !currentPCInfo.getStack().hasJavaFrames()) {
+ children = filterAndAppend(children, stackFrames, currentPCInfo.getTopFrame());
+ } else {
+ children = mergeFrames(children, stackFrames, currentPCInfo.getTopFrame(), haveTopHostFrames);
+ }
}
}
return children;
@@ -122,18 +137,27 @@
}
}
+ private static boolean isInGuest(Object child) {
+ if (child instanceof CallStackFrame) {
+ CallStackFrame csf = (CallStackFrame) child;
+ return BASIC_CLASS_NAME.equals(csf.getClassName());
+ } else {
+ return false;
+ }
+ }
+
private static Object[] filterAndAppend(Object[] children, TruffleStackFrame[] stackFrames,
- TruffleStackFrame topFrame) {
+ TruffleStackFrame topFrame) {
List<Object> newChildren = new ArrayList<>(children.length);
//newChildren.addAll(Arrays.asList(children));
for (Object ch : children) {
- if (ch instanceof CallStackFrame) {
+ if (ch instanceof CallStackFrame) {
String className = ((CallStackFrame) ch).getClassName();
if (PREDICATE1.test(className) ||
className.startsWith(FILTER2) ||
className.startsWith(FILTER3)) {
- continue;
+ continue;
}
}
newChildren.add(ch);
@@ -146,6 +170,61 @@
return newChildren.toArray();
}
+ private static TruffleStackFrame[] join(TruffleStackFrame[] stackFrames,
+ TruffleStackFrame topFrame) {
+ TruffleStackFrame[] joined = new TruffleStackFrame[stackFrames.length + 1];
+ joined[0] = topFrame;
+ System.arraycopy(stackFrames, 0, joined, 1, stackFrames.length);
+ return joined;
+ }
+
+ static Object[] mergeFrames(Object[] children, TruffleStackFrame[] stackFrames,
+ TruffleStackFrame topFrame, boolean haveTopHostFrames) {
+ List<Object> newChildren = new ArrayList<>(children.length);
+ stackFrames = join(stackFrames, topFrame);
+ int chi = 0;
+ if (haveTopHostFrames) {
+ for (; chi < children.length; chi++) {
+ Object ch = children[chi];
+ if (ch instanceof CallStackFrame) {
+ CallStackFrame csf = (CallStackFrame) ch;
+ if (StepActionProvider.STEP2JAVA_CLASS.equals(csf.getClassName()) &&
+ StepActionProvider.STEP2JAVA_METHOD.equals(csf.getMethodName())) {
+ break;
+ }
+ }
+ newChildren.add(ch);
+ }
+ }
+ for (TruffleStackFrame tframe : stackFrames) {
+ if (tframe.isHost()) {
+ for (; chi < children.length; chi++) {
+ Object ch = children[chi];
+ if (ch instanceof CallStackFrame) {
+ CallStackFrame csf = (CallStackFrame) ch;
+ String className = csf.getClassName();
+ if (className.startsWith(FILTER3)) {
+ continue;
+ }
+ if (equalFrames(csf, tframe)) {
+ newChildren.add(csf);
+ chi++;
+ break;
+ }
+ } else {
+ newChildren.add(ch);
+ }
+ }
+ } else {
+ newChildren.add(tframe);
+ }
+ }
+ for (; chi < children.length; chi++) {
+ newChildren.add(children[chi]);
+ }
+ return newChildren.toArray();
+ }
+
static List<DVFrame> filterAndAppend(JPDADVThread thread, List<DVFrame> children,
TruffleStackFrame[] stackFrames,
TruffleStackFrame topFrame) {
@@ -157,8 +236,8 @@
className.startsWith(FILTER2) ||
className.startsWith(FILTER3)) {
- continue;
- }
+ continue;
+ }
}
newChildren.add(ch);
}
@@ -170,4 +249,58 @@
return Collections.unmodifiableList(newChildren);
}
+ static List<DVFrame> mergeFrames(JPDADVThread thread, List<DVFrame> children,
+ TruffleStackFrame[] stackFrames,
+ TruffleStackFrame topFrame, boolean haveTopHostFrames) {
+ List<DVFrame> newChildren = new ArrayList<>(children.size());
+ stackFrames = join(stackFrames, topFrame);
+ int chi = 0;
+ if (haveTopHostFrames) {
+ for (; chi < children.size(); chi++) {
+ DVFrame ch = children.get(chi);
+ CallStackFrame csf = ((JPDADVFrame) ch).getCallStackFrame();
+ if (StepActionProvider.STEP2JAVA_CLASS.equals(csf.getClassName()) &&
+ StepActionProvider.STEP2JAVA_METHOD.equals(csf.getMethodName())) {
+ break;
+ }
+ newChildren.add(ch);
+ }
+ }
+ for (TruffleStackFrame tframe : stackFrames) {
+ if (tframe.isHost()) {
+ for (; chi < children.size(); chi++) {
+ DVFrame ch = children.get(chi);
+ CallStackFrame csf = ((JPDADVFrame) ch).getCallStackFrame();
+ String className = csf.getClassName();
+ if (className.startsWith(FILTER3)) {
+ continue;
+ }
+ if (equalFrames(csf, tframe)) {
+ newChildren.add(ch);
+ chi++;
+ break;
+ }
+ }
+ } else {
+ newChildren.add(new TruffleDVFrame(thread, tframe));
+ }
+ }
+ for (; chi < children.size(); chi++) {
+ newChildren.add(children.get(chi));
+ }
+ return Collections.unmodifiableList(newChildren);
+ }
+
+ private static boolean equalFrames(CallStackFrame csf, TruffleStackFrame tframe) {
+ if (!csf.getClassName().equals(tframe.getHostClassName())) {
+ return false;
+ }
+ if (!csf.getMethodName().equals(tframe.getHostMethodName())) {
+ return false;
+ }
+ int linej = csf.getLineNumber(null);
+ int linet = tframe.getSourcePosition().getStartLine();
+ return (linej == linet || linej == 0);
+ }
+
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java
index b55c647..7391ae6 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java
@@ -21,11 +21,15 @@
import java.util.Collections;
import java.util.List;
+import org.netbeans.api.debugger.jpda.CallStackFrame;
+import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
+import static org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess.BASIC_CLASS_NAME;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.netbeans.modules.debugger.jpda.truffle.options.TruffleOptions;
import org.netbeans.modules.debugger.jpda.ui.debugging.DebuggingViewSupportImpl;
+import org.netbeans.modules.debugger.jpda.ui.debugging.JPDADVFrame;
import org.netbeans.modules.debugger.jpda.ui.debugging.JPDADVThread;
import org.netbeans.spi.debugger.ContextProvider;
import org.netbeans.spi.debugger.ui.DebuggingView;
@@ -55,11 +59,26 @@
@Override
protected List<DVFrame> getFrames(JPDADVThread thread, int from, int to) {
List<DVFrame> frames = super.getFrames(thread, 0, Integer.MAX_VALUE);
- CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(thread.getKey());
+ if (frames.isEmpty()) {
+ return frames;
+ }
+ JPDAThread jt = thread.getKey();
+ CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(jt);
+ boolean inGuest = isInGuest(frames.get(0));
+ boolean haveTopHostFrames = false;
+ if (currentPCInfo == null) {
+ currentPCInfo = inGuest ? TruffleAccess.getCurrentSuspendHereInfo(jt) : TruffleAccess.getSuspendHere(jt);
+ haveTopHostFrames = true;
+ }
if (currentPCInfo != null) {
boolean showInternalFrames = TruffleOptions.isLanguageDeveloperMode();
TruffleStackFrame[] stackFrames = currentPCInfo.getStack().getStackFrames(showInternalFrames);
- frames = DebuggingTruffleTreeModel.filterAndAppend(thread, frames, stackFrames, currentPCInfo.getTopFrame());
+
+ if (inGuest && !currentPCInfo.getStack().hasJavaFrames()) {
+ frames = DebuggingTruffleTreeModel.filterAndAppend(thread, frames, stackFrames, currentPCInfo.getTopFrame());
+ } else {
+ frames = DebuggingTruffleTreeModel.mergeFrames(thread, frames, stackFrames, currentPCInfo.getTopFrame(), haveTopHostFrames);
+ }
}
if (from >= frames.size()) {
return Collections.emptyList();
@@ -68,6 +87,11 @@
return frames.subList(from, to);
}
+ private static boolean isInGuest(DVFrame child) {
+ CallStackFrame csf = ((JPDADVFrame) child).getCallStackFrame();
+ return BASIC_CLASS_NAME.equals(csf.getClassName());
+ }
+
@Override
public String getDisplayName(DVFrame frame) {
if (frame instanceof TruffleDVFrame) {
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java
index a194c63..15cf338 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java
@@ -19,9 +19,11 @@
package org.netbeans.modules.debugger.jpda.truffle.frames.models;
import java.net.URI;
+import java.net.URISyntaxException;
import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
+import org.netbeans.modules.debugger.jpda.truffle.source.Source;
import org.netbeans.spi.debugger.ui.DebuggingView.DVFrame;
import org.netbeans.spi.debugger.ui.DebuggingView.DVThread;
@@ -63,7 +65,22 @@
@Override
public URI getSourceURI() {
- return truffleFrame.getSourcePosition().getSource().getURI();
+ Source source = truffleFrame.getSourcePosition().getSource();
+ URI uri = source.getURI();
+ if (uri != null && "file".equalsIgnoreCase(uri.getScheme())) {
+ return uri;
+ }
+ try {
+ return source.getUrl().toURI();
+ } catch (URISyntaxException ex) {
+ return null;
+ }
+ }
+
+ @Override
+ public String getSourceMimeType() {
+ Source source = truffleFrame.getSourcePosition().getSource();
+ return source.getMimeType();
}
@Override
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java
index 426642f..25289f2 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java
@@ -49,33 +49,41 @@
private final StringReference codeRef;
private final String name;
+ private final String hostMethodName;
+ private final String path;
private final URI uri; // The original source URI
private final URL url; // The source
+ private final String mimeType;
private final long hash;
private String content;
- private Source(JPDADebugger jpda, String name, URI uri, long hash, StringReference codeRef) {
+ private Source(JPDADebugger jpda, String name, String hostMethodName, String path, URI uri, String mimeType, long hash, StringReference codeRef) {
this.name = name;
+ this.hostMethodName = hostMethodName;
+ this.path = path;
this.codeRef = codeRef;
URL url = null;
- if (uri == null || !"file".equalsIgnoreCase(uri.getScheme())) {
- try {
- url = SourceFilesCache.get(jpda).getSourceFile(name, hash, uri, getContent());
- } catch (IOException ex) {
- Exceptions.printStackTrace(ex);
+ if (hostMethodName == null) {
+ if (uri == null || !"file".equalsIgnoreCase(uri.getScheme())) {
+ try {
+ url = SourceFilesCache.get(jpda).getSourceFile(name, hash, uri, getContent());
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ } else {
+ uri = SourceBinaryTranslator.binary2Source(uri);
}
- } else {
- uri = SourceBinaryTranslator.binary2Source(uri);
- }
- if (url == null) {
- try {
- url = uri.toURL();
- } catch (MalformedURLException muex) {
- Exceptions.printStackTrace(muex);
+ if (url == null) {
+ try {
+ url = uri.toURL();
+ } catch (MalformedURLException muex) {
+ Exceptions.printStackTrace(muex);
+ }
}
}
this.url = url;
this.uri = uri;
+ this.mimeType = mimeType;
this.hash = hash;
}
@@ -119,6 +127,25 @@
String path,
URI uri,
StringReference codeRef) {
+ return getSource(debugger, id, name, path, uri, null, codeRef);
+ }
+
+ public static Source getSource(JPDADebugger debugger, long id,
+ String name,
+ String path,
+ URI uri,
+ String mimeType,
+ StringReference codeRef) {
+ return getSource(debugger, id, name, null, path, uri, null, codeRef);
+ }
+
+ public static Source getSource(JPDADebugger debugger, long id,
+ String name,
+ String hostMethodName,
+ String path,
+ URI uri,
+ String mimeType,
+ StringReference codeRef) {
synchronized (KNOWN_SOURCES) {
Map<Long, Source> dbgSources = KNOWN_SOURCES.get(debugger);
if (dbgSources != null) {
@@ -128,23 +155,27 @@
}
}
}
- return getTheSource(debugger, id, name, path, uri, codeRef);
+ return getTheSource(debugger, id, name, hostMethodName, path, uri, mimeType, codeRef);
}
private static Source getTheSource(JPDADebugger debugger, long id,
String name,
+ String hostMethodName,
String path,
URI uri,
+ String mimeType,
StringReference codeRef) {
- Source src = new Source(debugger, name, uri, id, codeRef);
- synchronized (KNOWN_SOURCES) {
- Map<Long, Source> dbgSources = KNOWN_SOURCES.get(debugger);
- if (dbgSources == null) {
- dbgSources = new HashMap<>();
- KNOWN_SOURCES.put(debugger, dbgSources);
+ Source src = new Source(debugger, name, hostMethodName, path, uri, mimeType, id, codeRef);
+ if (id >= 0) {
+ synchronized (KNOWN_SOURCES) {
+ Map<Long, Source> dbgSources = KNOWN_SOURCES.get(debugger);
+ if (dbgSources == null) {
+ dbgSources = new HashMap<>();
+ KNOWN_SOURCES.put(debugger, dbgSources);
+ }
+ dbgSources.put(id, src);
}
- dbgSources.put(id, src);
}
return src;
}
@@ -153,6 +184,14 @@
return name;
}
+ public String getHostMethodName() {
+ return hostMethodName;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
public URL getUrl() {
return url;
}
@@ -161,6 +200,10 @@
return uri;
}
+ public String getMimeType() {
+ return mimeType;
+ }
+
public long getHash() {
return hash;
}
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java
index 163a3bd..0b9d202 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java
@@ -29,6 +29,7 @@
import org.netbeans.modules.debugger.jpda.truffle.LanguageName;
import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
+import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
import org.netbeans.modules.debugger.jpda.truffle.source.SourcePosition;
import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable;
import org.openide.util.Exceptions;
@@ -209,8 +210,9 @@
static ObjectVariable setValue(JPDADebugger debugger, ObjectVariable guestObject, String newExpression) {
CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(debugger.getCurrentThread());
- if (currentPCInfo != null) {
- ObjectVariable selectedFrame = currentPCInfo.getSelectedStackFrame().getStackFrameInstance();
+ TruffleStackFrame selectedStackFrame;
+ if (currentPCInfo != null && (selectedStackFrame = currentPCInfo.getSelectedStackFrame()) != null) {
+ ObjectVariable selectedFrame = selectedStackFrame.getStackFrameInstance();
try {
Variable retVar = guestObject.invokeMethod(METHOD_SET_VALUE, METHOD_SET_VALUE_SIG,
new Variable[] { selectedFrame, debugger.createMirrorVar(newExpression) });
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java
index 81c304d..bcdd633 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java
@@ -52,7 +52,8 @@
public Object[] getChildren(TreeModel original, Object parent, int from, int to) throws UnknownTypeException {
if (parent == original.getRoot()) {
CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(getDebugger().getCurrentThread());
- if (currentPCInfo != null) {
+ TruffleStackFrame selectedStackFrame;
+ if (currentPCInfo != null && (selectedStackFrame = currentPCInfo.getSelectedStackFrame()) != null) {
synchronized (cpisListening) {
if (!cpisListening.contains(currentPCInfo)) {
currentPCInfo.addPropertyChangeListener(
@@ -60,7 +61,6 @@
cpisListening.add(currentPCInfo);
}
}
- TruffleStackFrame selectedStackFrame = currentPCInfo.getSelectedStackFrame();
TruffleScope[] scopes = selectedStackFrame.getScopes();
if (scopes.length == 0) {
return new Object[] {};
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java
index d776869..15eb018 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java
@@ -153,7 +153,7 @@
CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(debugger.getCurrentThread());
if (currentPCInfo != null) {
TruffleStackFrame selectedStackFrame = currentPCInfo.getSelectedStackFrame();
- frameLanguage = selectedStackFrame.getLanguage();
+ frameLanguage = selectedStackFrame != null ? selectedStackFrame.getLanguage() : LanguageName.NONE;
}
LanguageName valueLanguage = tv.getLanguage();
if (!LanguageName.NONE.equals(valueLanguage) && !frameLanguage.equals(valueLanguage)) {
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java
index 47c2b05..6d148e4 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java
@@ -21,9 +21,12 @@
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.debug.DebugStackFrame;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
+import java.lang.reflect.Method;
+
/**
*
* @author Martin
@@ -47,12 +50,24 @@
}
/** <File name>:<line number> */
- static String getSourceLocation(SourceSection ss) {
- if (ss == null) {
- //System.err.println("No source section for node "+n);
- return "unknown";
+ static String getSourceLocation(DebugStackFrame sf, boolean isHost) {
+ if (!isHost) {
+ SourceSection ss = sf.getSourceSection();
+ if (ss == null) {
+ //System.err.println("No source section for node "+n);
+ return "unknown";
+ }
+ return ss.getSource().getName() + ":" + ss.getStartLine();
+ } else {
+ try {
+ Method getHostTraceElementMethod = DebugStackFrame.class.getMethod("getHostTraceElement");
+ StackTraceElement ste = (StackTraceElement) getHostTraceElementMethod.invoke(sf);
+ return ste.getFileName() + ":" + ste.getLineNumber();
+ } catch (Exception ex) {
+ LangErrors.exception("getHostTraceElement", ex);
+ return null;
+ }
}
- return ss.getSource().getName() + ":" + ss.getStartLine();
}
}
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java
index 61c7167..03c4e41 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java
@@ -33,15 +33,16 @@
* @author martin
*/
final class FrameInfo {
+
final DebugStackFrame frame; // the top frame instance
final DebugStackFrame[] stackTrace; // All but the top frame
final String topFrame;
final Object[] topVariables;
// TODO: final Object[] thisObjects;
- FrameInfo(DebugStackFrame topStackFrame, Iterable<DebugStackFrame> stackFrames) {
+ FrameInfo(DebugStackFrame topStackFrame, Iterable<DebugStackFrame> stackFrames, boolean supportsJavaFrames) {
SourceSection topSS = topStackFrame.getSourceSection();
- SourcePosition position = new SourcePosition(topSS);
+ SourcePosition position = new SourcePosition(topSS, topStackFrame.getLanguage());
ArrayList<DebugStackFrame> stackFramesArray = new ArrayList<>();
for (DebugStackFrame sf : stackFrames) {
if (sf == topStackFrame) {
@@ -49,7 +50,8 @@
}
SourceSection ss = sf.getSourceSection();
// Ignore frames without sources:
- if (ss == null || ss.getSource() == null) {
+ boolean isHost = supportsJavaFrames && isHost(sf);
+ if (!isHost && (ss == null || ss.getSource() == null)) {
continue;
}
stackFramesArray.add(sf);
@@ -57,12 +59,14 @@
frame = topStackFrame;
stackTrace = stackFramesArray.toArray(new DebugStackFrame[stackFramesArray.size()]);
LanguageInfo sfLang = topStackFrame.getLanguage();
- topFrame = topStackFrame.getName() + "\n" +
+ boolean isHost = supportsJavaFrames && isHost(topStackFrame);
+ topFrame = topStackFrame.getName() + "\n" + isHost + "\n" +
((sfLang != null) ? sfLang.getId() + " " + sfLang.getName() : "") + "\n" +
- DebuggerVisualizer.getSourceLocation(topSS) + "\n" +
+ DebuggerVisualizer.getSourceLocation(topStackFrame, isHost) + "\n" +
position.id + "\n" + position.name + "\n" + position.path + "\n" +
- position.uri.toString() + "\n" + position.sourceSection +/* "," + position.startColumn + "," +
- position.endLine + "," + position.endColumn +*/ "\n" + isInternal(topStackFrame);
+ position.hostClassName + "\n" + position.hostMethodName + "\n" +
+ position.uri.toString() + "\n" + position.mimeType + "\n" + position.sourceSection + "\n" +
+ isInternal(topStackFrame);
topVariables = JPDATruffleAccessor.getVariables(topStackFrame);
}
@@ -90,4 +94,21 @@
return isInternal;
}
+ static boolean isHost(DebugStackFrame sf) {
+ try {
+ Method isHostMethod = DebugStackFrame.class.getMethod("isHost");
+ return (Boolean) isHostMethod.invoke(sf);
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+
+ static StackTraceElement getHostTraceElement(DebugStackFrame sf) {
+ try {
+ Method getHostTraceElementMethod = DebugStackFrame.class.getMethod("getHostTraceElement");
+ return (StackTraceElement) getHostTraceElementMethod.invoke(sf);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
}
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java
index 8b159a1..dedb852 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java
@@ -145,7 +145,7 @@
SourceSection sourceLocation = value.getSourceLocation();
//System.err.println("\nSOURCE of "+value.getName()+" is: "+sourceLocation);
if (sourceLocation != null && sourceLocation.isAvailable()) {
- sp = new SourcePosition(sourceLocation);
+ sp = new SourcePosition(sourceLocation, value.getOriginalLanguage());
}
} catch (ThreadDeath td) {
throw td;
@@ -159,7 +159,7 @@
SourceSection sourceLocation = metaObject.getSourceLocation();
//System.err.println("\nSOURCE of metaobject "+metaObject+" is: "+sourceLocation);
if (sourceLocation != null && sourceLocation.isAvailable()) {
- sp = new SourcePosition(sourceLocation);
+ sp = new SourcePosition(sourceLocation, value.getOriginalLanguage());
}
} catch (ThreadDeath td) {
throw td;
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java
index 2de2dd0..134d880 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java
@@ -155,6 +155,7 @@
boolean haltedBefore,
DebugValue returnValue,
FrameInfo frameInfo,
+ boolean supportsJavaFrames,
Breakpoint[] breakpointsHit,
Throwable[] breakpointConditionExceptions,
int stepCmd) {
@@ -193,11 +194,31 @@
}
/**
+ * Tries to suspend immediately at the current location of the current execution thread.
+ * When called from a suspension in a Java code, it reveals the guest code execution.
+ *
+ * @return the halted info, the array corresponds to the arguments of
+ * {@link #executionHalted(JPDATruffleDebugManager, SourcePosition, boolean, DebugValue, FrameInfo, boolean, Breakpoint[], Throwable[], int)},
+ * or <code>null</code>, when the suspend wasn't successful.
+ */
+ static Object[] suspendHere() {
+ synchronized (debugManagers) {
+ for (JPDATruffleDebugManager tdm : debugManagers.values()) {
+ Object[] haltedInfo = tdm.suspendHere();
+ if (haltedInfo != null) {
+ return haltedInfo;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* @param frames The array of stack frame infos
* @return An array of two elements: a String of frame information and
* an array of code contents.
*/
- static Object[] getFramesInfo(DebugStackFrame[] frames, boolean includeInternal) {
+ static Object[] getFramesInfo(DebugStackFrame[] frames, boolean includeInternal, boolean supportsJavaFrames) {
trace("getFramesInfo({0})",includeInternal);
int n = frames.length;
StringBuilder frameInfos = new StringBuilder();
@@ -211,17 +232,20 @@
if (!includeInternal && isInternal) {
continue;
}
+ boolean isHost = supportsJavaFrames && FrameInfo.isHost(sf);
String sfName = sf.getName();
if (sfName == null) {
sfName = "";
}
frameInfos.append(sfName);
frameInfos.append('\n');
+ frameInfos.append(isHost);
+ frameInfos.append('\n');
LanguageInfo sfLang = sf.getLanguage();
String sfLangId = (sfLang != null) ? sfLang.getId() + " " + sfLang.getName() : "";
frameInfos.append(sfLangId);
frameInfos.append('\n');
- frameInfos.append(DebuggerVisualizer.getSourceLocation(sf.getSourceSection()));
+ frameInfos.append(DebuggerVisualizer.getSourceLocation(sf, isHost));
frameInfos.append('\n');
/*if (fi.getCallNode() == null) {
/* frames with null call nodes are filtered out by JPDATruffleDebugManager.FrameInfo
@@ -231,14 +255,20 @@
System.err.println("frameInfos = "+frameInfos);
*//*
}*/
- SourcePosition position = new SourcePosition(sf.getSourceSection());
+ SourcePosition position;
+ if (isHost) {
+ StackTraceElement ste = FrameInfo.getHostTraceElement(sf);
+ position = new SourcePosition(ste);
+ } else {
+ position = new SourcePosition(sf.getSourceSection(), sf.getLanguage());
+ }
frameInfos.append(createPositionIdentificationString(position));
if (includeInternal) {
frameInfos.append('\n');
frameInfos.append(isInternal);
}
- frameInfos.append("\n\n");
+ frameInfos.append("\n\t\n");
codes[j] = position.code;
j++;
@@ -259,8 +289,14 @@
str.append('\n');
str.append(position.path);
str.append('\n');
+ str.append(position.hostClassName);
+ str.append('\n');
+ str.append(position.hostMethodName);
+ str.append('\n');
str.append(position.uri.toString());
str.append('\n');
+ str.append(position.mimeType);
+ str.append('\n');
str.append(position.sourceSection);
return str.toString();
}
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java
index f3fd297..36b4a06 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java
@@ -26,9 +26,12 @@
import com.oracle.truffle.api.debug.SuspendedCallback;
import com.oracle.truffle.api.debug.SuspendedEvent;
import com.oracle.truffle.api.debug.SuspensionFilter;
+import com.oracle.truffle.api.nodes.Node;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
/**
*
@@ -39,17 +42,35 @@
private final Reference<Debugger> debugger;
private final Reference<DebuggerSession> session;
private final ThreadLocal<SuspendedEvent> suspendedEvents = new ThreadLocal<>();
+ private final ThreadLocal<Object[]> suspendHere = new ThreadLocal<>();
+ private final boolean supportsJavaFrames;
public JPDATruffleDebugManager(Debugger debugger, boolean includeInternal, boolean doStepInto) {
this.debugger = new WeakReference<>(debugger);
DebuggerSession debuggerSession = debugger.startSession(this);
debuggerSession.setSteppingFilter(createSteppingFilter(includeInternal));
+ supportsJavaFrames = supportsJavaFrames(debuggerSession);
if (doStepInto) {
debuggerSession.suspendNextExecution();
}
this.session = new WeakReference<>(debuggerSession);
}
+ private static boolean supportsJavaFrames(DebuggerSession debuggerSession) {
+ try {
+ Method setShowHostStackFramesMethod = DebuggerSession.class.getMethod("setShowHostStackFrames", Boolean.TYPE);
+ try {
+ setShowHostStackFramesMethod.invoke(debuggerSession, true);
+ return true;
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ LangErrors.exception("setShowHostStackFrames", ex);
+ return false;
+ }
+ } catch (NoSuchMethodException | SecurityException ex) {
+ return false;
+ }
+ }
+
static SuspensionFilter createSteppingFilter(boolean includeInternal) {
return SuspensionFilter.newBuilder().ignoreLanguageContextInitialization(true).includeInternal(includeInternal).build();
}
@@ -99,6 +120,19 @@
@Override
public void onSuspend(SuspendedEvent event) {
JPDATruffleAccessor.trace("JPDATruffleDebugManager.onSuspend({0})", event);
+ if (suspendHere.get() != null) {
+ // A special 'suspendHere':
+ SourcePosition position = new SourcePosition(event.getSourceSection(), event.getTopStackFrame().getLanguage());
+ Object[] haltInfo = new Object[]{
+ this, position,
+ event.getSuspendAnchor() == SuspendAnchor.BEFORE,
+ event.getReturnValue(),
+ new FrameInfo(event.getTopStackFrame(), event.getStackFrames(), supportsJavaFrames),
+ supportsJavaFrames
+ };
+ suspendHere.set(haltInfo);
+ return ;
+ }
Breakpoint[] breakpointsHit = new Breakpoint[event.getBreakpoints().size()];
breakpointsHit = event.getBreakpoints().toArray(breakpointsHit);
Throwable[] breakpointConditionExceptions = new Throwable[breakpointsHit.length];
@@ -107,12 +141,13 @@
}
suspendedEvents.set(event);
try {
- SourcePosition position = new SourcePosition(event.getSourceSection());
+ SourcePosition position = new SourcePosition(event.getSourceSection(), event.getTopStackFrame().getLanguage());
int stepCmd = JPDATruffleAccessor.executionHalted(
this, position,
event.getSuspendAnchor() == SuspendAnchor.BEFORE,
event.getReturnValue(),
- new FrameInfo(event.getTopStackFrame(), event.getStackFrames()),
+ new FrameInfo(event.getTopStackFrame(), event.getStackFrames(), supportsJavaFrames),
+ supportsJavaFrames,
breakpointsHit,
breakpointConditionExceptions,
0);
@@ -133,5 +168,28 @@
suspendedEvents.remove();
}
}
+
+ Object[] suspendHere() {
+ DebuggerSession debuggerSession = session.get();
+ if (debuggerSession == null) {
+ return null;
+ }
+ try {
+ Method suspendHereMethod = DebuggerSession.class.getMethod("suspendHere", Node.class);
+ try {
+ suspendHere.set(new Object[]{});
+ boolean success = (Boolean) suspendHereMethod.invoke(debuggerSession, new Object[]{null});
+ if (success) {
+ return suspendHere.get();
+ }
+ } finally {
+ suspendHere.remove();
+ }
+ } catch (NoSuchMethodException ex) {
+ } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ LangErrors.exception("suspendHere", ex);
+ }
+ return null;
+ }
}
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java
index d4321b0..1e93a19 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java
@@ -19,6 +19,7 @@
package org.netbeans.modules.debugger.jpda.backend.truffle;
+import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
@@ -37,15 +38,20 @@
final long id;
final String name;
+ final String hostClassName;
+ final String hostMethodName;
final String path;
final String sourceSection;
final String code;
final URI uri;
+ final String mimeType;
- public SourcePosition(SourceSection sourceSection) {
+ public SourcePosition(SourceSection sourceSection, LanguageInfo languageInfo) {
Source source = sourceSection.getSource();
this.id = getId(source);
this.name = source.getName();
+ this.hostClassName = null;
+ this.hostMethodName = null;
String sourcePath = source.getPath();
if (sourcePath == null) {
sourcePath = name;
@@ -54,6 +60,27 @@
this.sourceSection = sourceSection.getStartLine() + "," + sourceSection.getStartColumn() + "," + sourceSection.getEndLine() + "," + sourceSection.getEndColumn();
this.code = source.getCharacters().toString();
this.uri = source.getURI();
+ this.mimeType = findMIMEType(source, languageInfo);
+ }
+
+ public SourcePosition(StackTraceElement ste) {
+ this.id = -1;
+ this.name = ste.getFileName() != null ? ste.getFileName() : ste.getClassName();
+ this.hostClassName = ste.getClassName();
+ this.hostMethodName = ste.getMethodName();
+ this.path = ste.getFileName();
+ this.sourceSection = ste.getLineNumber() + "," + 0 + "," + ste.getLineNumber() + "," + 0;
+ this.code = null;
+ this.uri = URI.create("");
+ this.mimeType = null;
+ }
+
+ private String findMIMEType(Source source, LanguageInfo languageInfo) {
+ String mimeType = source.getMimeType();
+ if (mimeType == null && languageInfo != null) {
+ mimeType = languageInfo.getDefaultMimeType();
+ }
+ return mimeType;
}
private static synchronized long getId(Source s) {
diff --git a/java/debugger.jpda/nbproject/project.properties b/java/debugger.jpda/nbproject/project.properties
index e94b671..6e859b4 100644
--- a/java/debugger.jpda/nbproject/project.properties
+++ b/java/debugger.jpda/nbproject/project.properties
@@ -22,7 +22,7 @@
javac.source=1.8
javadoc.arch=${basedir}/arch.xml
requires.nb.javac=true
-spec.version.base=1.117.0
+spec.version.base=1.117.1
test-unit-sys-prop.test.dir.src=${basedir}/test/unit/src/
test-unit-sys-prop.netbeans.user=${basedir}/work/nb_user_dir
test.unit.cp.extra=../java.source.nbjavac/build/test-nb-javac/cluster/modules/org-netbeans-modules-java-source-nbjavac-test.jar
diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java
index 0444242..7dc72e9 100644
--- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java
+++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java
@@ -173,7 +173,6 @@
private AbstractDICookie attachingCookie;
private JavaEngineProvider javaEngineProvider;
private Set<String> languages;
- private String lastStratumn;
private ContextProvider lookupProvider;
private ObjectTranslation threadsTranslation;
private ObjectTranslation localsTranslation;
@@ -2387,12 +2386,9 @@
languages.add (language);
}
} // for
- if ( (stratum != null) &&
- (!stratum.equals (lastStratumn))
- ) {
+ if (stratum != null) {
javaEngineProvider.getSession ().setCurrentLanguage (stratum);
}
- lastStratumn = stratum;
}
private Set<JSR45DebuggerEngineProvider> jsr45EngineProviders;
diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java
index d0f824d..5610df2 100644
--- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java
+++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java
@@ -95,6 +95,10 @@
this.createdAt = new Throwable().fillInStackTrace();
}
+ public void resetInvocationMessage() {
+ this.invocationMessage = null;
+ }
+
public void setPreferredThread(JPDAThreadImpl preferredThread) {
this.preferredThread = preferredThread;
}
diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java
index c5cff2b..1b74b1c 100644
--- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java
+++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java
@@ -27,6 +27,7 @@
import com.sun.jdi.request.StepRequest;
import com.sun.jdi.ThreadReference;
import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -37,6 +38,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.debugger.DebuggerManager;
@@ -107,6 +109,7 @@
private final List<EventSet> parallelEvents = new LinkedList<EventSet>();
private boolean haveParallelEventsToProcess = false;
private final Map<EventSet, Set<ThreadReference>> threadsResumedForEvents = new WeakHashMap<EventSet, Set<ThreadReference>>();
+ private final List<Function<EventSet, Boolean>> eventsInterceptors = new LinkedList<>();
private final LoopControl loopControl;
/**
@@ -262,7 +265,22 @@
}
private boolean processEvents(EventSet eventSet, Executor starter,
- SuspendControllersSupport scs, SuspendCount suspendCount) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ObjectCollectedExceptionWrapper, IllegalThreadStateExceptionWrapper {
+ SuspendControllersSupport scs, SuspendCount suspendCount) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ObjectCollectedExceptionWrapper, IllegalThreadStateExceptionWrapper {
+ List<Function<EventSet, Boolean>> interceptors;
+ synchronized (eventsInterceptors) {
+ if (eventsInterceptors.isEmpty()) {
+ interceptors = null;
+ } else {
+ interceptors = new ArrayList<>(eventsInterceptors);
+ }
+ }
+ if (interceptors != null) {
+ for (Function<EventSet, Boolean> interceptor : interceptors) {
+ if (interceptor.apply(eventSet)) {
+ return true;
+ }
+ }
+ }
boolean silent = eventSet.size() > 0;
for (Event e: eventSet) {
EventRequest r = EventWrapper.request(e);
@@ -751,6 +769,28 @@
}
/**
+ * Add an interceptor for JDWP events. Use when absolutely necessary only,
+ * for instance when events that are crucial to process come during an ongoing
+ * method invocation.
+ * @param eventsInterceptor a function that gets the event and returns <code>true</code>
+ * when it intercepted and processed the event.
+ */
+ public void addEventInterceptor(Function<EventSet, Boolean> eventsInterceptor) {
+ synchronized (eventsInterceptors) {
+ eventsInterceptors.add(eventsInterceptor);
+ }
+ }
+
+ /**
+ * Remove an interceptor for JDWP events.
+ */
+ public void removeEventInterceptor(Function<EventSet, Boolean> eventsInterceptor) {
+ synchronized (eventsInterceptors) {
+ eventsInterceptors.remove(eventsInterceptor);
+ }
+ }
+
+ /**
* Stop the operator thread.
*/
public void stop() {
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java b/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java
index f50ccdb..0d83bbf 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java
@@ -201,7 +201,7 @@
Context resolved = resolveContext(controller, doc, offset, goToSource, false);
if (resolved == null) {
- target[0] = new GoToTarget(-1, -1, null, null, null, false);
+ target[0] = new GoToTarget(-1, -1, null, null, null, null, false);
return;
}
@@ -210,7 +210,7 @@
if (url != null) {
HtmlBrowser.URLDisplayer.getDefault().showURL(url);
} else {
- target[0] = new GoToTarget(-1, -1, null, null, null, false);
+ target[0] = new GoToTarget(-1, -1, null, null, null, null, false);
}
} else {
target[0] = computeGoToTarget(controller, resolved, offset);
@@ -255,7 +255,7 @@
if (startPos != (-1)) {
//check if the caret is inside the declaration itself, as jump in this case is not very usefull:
if (isCaretInsideDeclarationName(controller, tree, elpath, offset)) {
- return new GoToTarget(-1, -1, null, null, null, false);
+ return new GoToTarget(-1, -1, null, null, null, null, false);
} else {
long endPos = controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), tree);
//#71272: it is necessary to translate the offset:
@@ -263,17 +263,20 @@
controller.getSnapshot().getOriginalOffset((int) endPos),
null,
null,
+ null,
controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
true);
}
} else {
- return new GoToTarget(-1, -1, null, null, null, false);
+ return new GoToTarget(-1, -1, null, null, null, null, false);
}
} else {
+ TypeElement te = resolved.resolved != null ? controller.getElementUtilities().outermostTypeElement(resolved.resolved) : null;
return new GoToTarget(-1,
-1,
controller.getClasspathInfo(),
ElementHandle.create(resolved.resolved),
+ te != null ? te.getQualifiedName().toString().replace('.', '/') + ".class" : null,
controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
true);
}
@@ -284,14 +287,16 @@
public final int endPos;
public final ClasspathInfo cpInfo;
public final ElementHandle elementToOpen;
+ public final String resourceName;
public final String displayNameForError;
public final boolean success;
- public GoToTarget(int offsetToOpen, int endPos, ClasspathInfo cpInfo, ElementHandle elementToOpen, String displayNameForError, boolean success) {
+ public GoToTarget(int offsetToOpen, int endPos, ClasspathInfo cpInfo, ElementHandle elementToOpen, String resourceName, String displayNameForError, boolean success) {
this.offsetToOpen = offsetToOpen;
this.endPos = endPos;
this.cpInfo = cpInfo;
this.elementToOpen = elementToOpen;
+ this.resourceName = resourceName;
this.displayNameForError = displayNameForError;
this.success = success;
}
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
index c4b3277..6b95763 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
@@ -299,7 +299,7 @@
return ElementNode.Description.create(descriptions);
}
- static void generateDelegatingMethods(WorkingCopy wc, TreePath path, VariableElement delegate, Iterable<? extends ExecutableElement> methods, int offset) {
+ public static void generateDelegatingMethods(WorkingCopy wc, TreePath path, VariableElement delegate, Iterable<? extends ExecutableElement> methods, int offset) {
assert TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind());
TypeElement te = (TypeElement)wc.getTrees().getElement(path);
if (te != null) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
index 08ebbac..8795d63 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
@@ -393,8 +393,8 @@
generateEqualsAndHashCode(wc, path, e, h, -1);
}
-
- static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iterable<? extends VariableElement> equalsFields, Iterable<? extends VariableElement> hashCodeFields, int offset) {
+
+ public static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iterable<? extends VariableElement> equalsFields, Iterable<? extends VariableElement> hashCodeFields, int offset) {
assert TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind());
TypeElement te = (TypeElement)wc.getTrees().getElement(path);
if (te != null) {
@@ -419,7 +419,7 @@
members.add(createEqualsMethod(wc, equalsFields, dt, scope));
}
wc.rewrite(nue, GeneratorUtils.insertClassMembers(wc, nue, members, offset));
- }
+ }
}
private static MethodTree createEqualsMethod(WorkingCopy wc, Iterable<? extends VariableElement> equalsFields, DeclaredType type, Scope scope) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
index bf91dcd..7b11f99 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
@@ -152,7 +152,7 @@
}
}
- private static VariableTree createLoggerField(TreeMaker make, ClassTree cls, CharSequence name, Set<Modifier> mods) {
+ public static VariableTree createLoggerField(TreeMaker make, ClassTree cls, CharSequence name, Set<Modifier> mods) {
ModifiersTree modifiers = make.Modifiers(mods, Collections.<AnnotationTree>emptyList());
final List<ExpressionTree> none = Collections.<ExpressionTree>emptyList();
IdentifierTree className = make.Identifier(cls.getSimpleName());
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
index e0ccca1..c855d3c 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
@@ -209,7 +209,7 @@
}
}
- private static MethodTree createToStringMethod(WorkingCopy wc, Iterable<? extends VariableElement> fields, String typeName, boolean useStringBuilder) {
+ public static MethodTree createToStringMethod(WorkingCopy wc, Iterable<? extends VariableElement> fields, String typeName, boolean useStringBuilder) {
TreeMaker make = wc.getTreeMaker();
Set<Modifier> mods = EnumSet.of(Modifier.PUBLIC);
List<AnnotationTree> annotations = new LinkedList<>();
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java
index 86e7245..fe031d7 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java
@@ -21,6 +21,7 @@
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.Tree;
@@ -57,7 +58,6 @@
import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
import org.netbeans.spi.editor.hints.ChangeInfo;
import org.netbeans.spi.editor.hints.EnhancedFix;
-import org.netbeans.spi.editor.hints.Fix;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
@@ -68,18 +68,20 @@
*
* @author Jan lahoda
*/
-public abstract class CreateClassFix implements EnhancedFix {
+public abstract class CreateClassFix extends CreateFixBase implements EnhancedFix {
+ protected ClasspathInfo cpInfo;
protected Set<Modifier> modifiers;
protected List<TypeMirrorHandle> argumentTypes; //if a specific constructor should be created
protected List<String> argumentNames; //dtto.
private Integer prio;
- private List<TypeMirrorHandle> superTypes;
+ protected List<TypeMirrorHandle> superTypes;
protected ElementKind kind;
- private int numTypeParameters;
+ protected int numTypeParameters;
protected List<? extends TypeMirror> argumentTypeMirrors;
public CreateClassFix(CompilationInfo info, Set<Modifier> modifiers, List<? extends TypeMirror> argumentTypes, List<String> argumentNames, TypeMirror superType, ElementKind kind, int numTypeParameters) {
+ this.cpInfo = info.getClasspathInfo();
this.modifiers = modifiers;
if (argumentTypes != null && argumentNames != null) {
@@ -267,6 +269,7 @@
this.rootName = rootName;
}
+ @Override
public String getText() {
if (argumentNames == null || argumentNames.isEmpty()) {
return NbBundle.getMessage(CreateClassFix.class, "FIX_CreateClassInPackage", simpleName, packageName, valueForBundle(kind), rootName);
@@ -292,6 +295,85 @@
}
}
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
+ //use the original cp-info so it is "sure" that the target can be resolved:
+ JavaSource js = JavaSource.create(cpInfo);
+ return js.runModificationTask(new Task<WorkingCopy>() {
+ public void run(final WorkingCopy working) throws IOException {
+ working.toPhase(Phase.RESOLVED);
+ TreeMaker make = working.getTreeMaker();
+ List<Tree> members = new ArrayList<>();
+ if (argumentNames != null) {
+ List<VariableTree> argTypes = new ArrayList<VariableTree>();
+ Iterator<TypeMirrorHandle> typeIt = argumentTypes.iterator();
+ Iterator<String> nameIt = argumentNames.iterator();
+ while (typeIt.hasNext() && nameIt.hasNext()) {
+ TypeMirrorHandle tmh = typeIt.next();
+ String argName = nameIt.next();
+ argTypes.add(make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), argName, make.Type(tmh.resolve(working)), null));
+ }
+ members.add(make.Method(make.Modifiers(EnumSet.of(Modifier.PUBLIC/*!!!*/)), "<init>", null, Collections.<TypeParameterTree>emptyList(), argTypes, Collections.<ExpressionTree>emptyList(), "{}" /*XXX*/, null)); // NOI18N
+ }
+ Tree extendsClause = null;
+ List<Tree> implementsClause = Collections.<Tree>emptyList();
+ if (superTypes != null) {
+ DeclaredType extendsType = null;
+ List<DeclaredType> implementsTypes = new LinkedList<DeclaredType>();
+ for (TypeMirrorHandle h : superTypes) {
+ TypeMirror tm = h.resolve(working);
+ if (tm == null) {
+ continue;
+ }
+ if (tm.getKind() != TypeKind.DECLARED) {
+ continue;
+ }
+ DeclaredType dt = (DeclaredType) tm;
+ if (dt.asElement().getKind().isClass()) {
+ extendsType = dt;
+ } else {
+ implementsTypes.add(dt);
+ }
+ }
+ if (extendsType != null && !"java.lang.Object".equals(((TypeElement) extendsType.asElement()).getQualifiedName().toString())) { // NOI18N
+ extendsClause = make.Type(extendsType);
+ }
+ if (!implementsTypes.isEmpty()) {
+ implementsClause = new LinkedList<Tree>();
+ for (DeclaredType dt : implementsTypes) {
+ implementsClause.add(make.Type(dt));
+ }
+ }
+ }
+ ModifiersTree nueModifiers = make.Modifiers(modifiers);
+ List<TypeParameterTree> typeParameters = new LinkedList<TypeParameterTree>();
+ for (int cntr = 0; cntr < numTypeParameters; cntr++) {
+ typeParameters.add(make.TypeParameter(numTypeParameters == 1 ? "T" : "T" + cntr, Collections.<ExpressionTree>emptyList())); // NOI18N
+ }
+ ClassTree source;
+ switch (kind) {
+ case CLASS:
+ source = make.Class(nueModifiers, simpleName, typeParameters, extendsClause, implementsClause, members);
+ break;
+ case INTERFACE:
+ source = make.Interface(nueModifiers, simpleName, typeParameters, implementsClause, members);
+ break;
+ case ANNOTATION_TYPE:
+ source = make.AnnotationType(nueModifiers, simpleName, members);
+ break;
+ case ENUM:
+ source = make.Enum(nueModifiers, simpleName, implementsClause, members);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ CompilationUnitTree cut = make.CompilationUnit(targetSourceRoot, packageName.replace('.', '/') + '/' + simpleName + ".java", Collections.<ImportTree>emptyList(), Collections.singletonList(source));
+ working.rewrite(null, cut);
+ }
+ });
+ }
+
+ @Override
public ChangeInfo implement() throws IOException {
FileObject pack = FileUtil.createFolder(targetSourceRoot, packageName.replace('.', '/')); // NOI18N
FileObject classTemplate/*???*/ = FileUtil.getConfigFile(template(kind));
@@ -383,7 +465,6 @@
private FileObject targetFile;
private ElementHandle<TypeElement> target;
- private ClasspathInfo cpInfo;
private String name;
private String inFQN;
@@ -396,15 +477,23 @@
this.targetFile = targetFile;
}
+ @Override
public String getText() {
return NbBundle.getMessage(CreateClassFix.class, "FIX_CreateInnerClass", name, inFQN, valueForBundle(kind));
}
+ @Override
public ChangeInfo implement() throws Exception {
+ ModificationResult diff = getModificationResult();
+ return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
+ }
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
//use the original cp-info so it is "sure" that the target can be resolved:
JavaSource js = JavaSource.create(cpInfo, targetFile);
- ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
+ return js.runModificationTask(new Task<WorkingCopy>() {
public void run(final WorkingCopy working) throws IOException {
working.toPhase(Phase.RESOLVED);
@@ -431,8 +520,6 @@
working.rewrite(targetTree.getLeaf(), GeneratorUtilities.get(working).insertClassMember((ClassTree)targetTree.getLeaf(), innerClass));
}
});
-
- return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
}
public String toDebugString(CompilationInfo info) {
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java
index b8af510..191c771 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java
@@ -57,7 +57,7 @@
*
* @author Max Sauer
*/
-class CreateEnumConstant implements Fix {
+class CreateEnumConstant extends CreateFixBase implements Fix {
private String name;
private String inFQN;
@@ -85,14 +85,22 @@
}
}
+ @Override
public String getText() {
return NbBundle.getMessage(CreateEnumConstant.class, "LBL_FIX_Create_Enum_Constant", name, inFQN);
}
+ @Override
public ChangeInfo implement() throws Exception {
+ ModificationResult diff = getModificationResult();
+ return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
+ }
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
JavaSource js = JavaSource.create(cpInfo, targetFile);
- ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
+ return js.runModificationTask(new Task<WorkingCopy>() {
public void run(final WorkingCopy working) throws IOException {
working.toPhase(Phase.RESOLVED);
TypeElement targetType = target.resolve(working);
@@ -132,8 +140,6 @@
working.rewrite(targetTree, enumm);
}
});
-
- return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
}
String toDebugString(CompilationInfo info) {
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java
index 0cda238..2e84ce7 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java
@@ -41,7 +41,6 @@
import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
import org.netbeans.spi.editor.hints.ChangeInfo;
import org.netbeans.spi.editor.hints.EnhancedFix;
-import org.netbeans.spi.editor.hints.Fix;
import org.openide.filesystems.FileObject;
import org.openide.util.NbBundle;
@@ -49,7 +48,7 @@
*
* @author Jan Lahoda
*/
-public final class CreateFieldFix implements EnhancedFix {
+public final class CreateFieldFix extends CreateFixBase implements EnhancedFix {
private FileObject targetFile;
private ElementHandle<TypeElement> target;
@@ -75,15 +74,27 @@
this.proposedType = proposedType == null ? null : TypeMirrorHandle.create(proposedType);
this.remote = !org.openide.util.Utilities.compareObjects(info.getFileObject(), targetFile);
}
-
+
+ @Override
public String getText() {
return NbBundle.getMessage(CreateFieldFix.class, "LBL_FIX_Create_Field", name, inFQN);
}
-
+
+ @Override
public ChangeInfo implement() throws IOException {
+ ModificationResult diff = getModificationResult();
+ ChangeInfo ci = Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
+ if (remote) {
+ return ci;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
//use the original cp-info so it is "sure" that the proposedType can be resolved:
JavaSource js = JavaSource.create(cpInfo, targetFile);
-
ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
public void run(final WorkingCopy working) throws IOException {
working.toPhase(Phase.RESOLVED);
@@ -115,16 +126,9 @@
working.rewrite(targetTree, decl);
}
});
-
- ChangeInfo ci = Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
-
- if (remote) {
- return ci;
- } else {
- return null;
- }
+ return diff;
}
-
+
String toDebugString(CompilationInfo info) {
return "CreateFieldFix:" + name + ":" + target.getQualifiedName() + ":" + proposedType.resolve(info).toString() + ":" + modifiers; // NOI18N
}
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFixBase.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFixBase.java
new file mode 100644
index 0000000..ec8b160
--- /dev/null
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFixBase.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hints.errors;
+
+import java.io.IOException;
+import org.netbeans.api.java.source.ModificationResult;
+import org.netbeans.spi.editor.hints.Fix;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class CreateFixBase implements Fix {
+
+ public abstract ModificationResult getModificationResult() throws IOException;
+}
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java
index bf92eac..89a09b2 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java
@@ -67,7 +67,7 @@
*
* @author Jan lahoda
*/
-public final class CreateMethodFix implements Fix {
+public final class CreateMethodFix extends CreateFixBase implements Fix {
private FileObject targetFile;
private ElementHandle<TypeElement> target;
@@ -134,7 +134,8 @@
this.methodDisplayName = methodDisplayName.toString();
}
-
+
+ @Override
public String getText() {
if(target.getKind() == ElementKind.ANNOTATION_TYPE)
return NbBundle.getMessage(CreateMethodFix.class, "LBL_FIX_Create_Annotation_Element", methodDisplayName, inFQN );
@@ -144,17 +145,25 @@
return NbBundle.getMessage(CreateMethodFix.class, "LBL_FIX_Create_Constructor", methodDisplayName, inFQN );
}
}
+
+ // tag used for selection
+ final String methodBodyTag = "mbody"; //NOI18N
+ @Override
public ChangeInfo implement() throws IOException {
+ ModificationResult diff = getModificationResult();
+ return Utilities.commitAndComputeChangeInfo(targetFile, diff, methodBodyTag);
+ }
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
//use the original cp-info so it is "sure" that the proposedType can be resolved:
JavaSource js = JavaSource.create(cpInfo, targetFile);
if (js == null) {
return null;
}
- // tag used for selection
- final String methodBodyTag = "mbody"; //NOI18N
- ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
+ return js.runModificationTask(new Task<WorkingCopy>() {
public void run(final WorkingCopy working) throws IOException {
working.toPhase(Phase.RESOLVED);
TypeElement targetType = target.resolve(working);
@@ -236,8 +245,6 @@
working.rewrite(targetTree.getLeaf(), decl);
}
});
-
- return Utilities.commitAndComputeChangeInfo(targetFile, diff, methodBodyTag);
}
private void addArguments(CompilationInfo info, StringBuilder value) {
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java
index 4f4369d..452ee25 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java
@@ -22,12 +22,13 @@
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
-import com.sun.source.tree.Scope;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
+import java.awt.GraphicsEnvironment;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -47,6 +48,7 @@
import javax.swing.text.Document;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.TreePathHandle;
@@ -147,7 +149,7 @@
allIfaces.set(allInterfaces);
return targets;
}
-
+
private final TypeMirrorHandle returnType;
private final List<TreePathHandle> parameters;
private final Set<TypeMirrorHandle> thrownTypes;
@@ -192,7 +194,26 @@
final TargetDescription target = panel.getSelectedTarget();
final MemberSearchResult searchResult = val.getResult();
final boolean redoReferences = panel.isRefactorExisting();
- js.runModificationTask(new Task<WorkingCopy>() {
+ getModificationResult(name, target, replaceOther, access, redoReferences, searchResult).commit();
+ return null;
+ }
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
+ ModificationResult result = null;
+ int counter = 0;
+ do {
+ try {
+ result = getModificationResult("method" + (counter != 0 ? String.valueOf(counter) : ""), targets.iterator().next(), true, EnumSet.of(Modifier.PRIVATE), false, null);
+ } catch (Exception e) {
+ counter++;
+ }
+ } while (result == null && counter < 10);
+ return result;
+ }
+
+ private ModificationResult getModificationResult(final String name, final TargetDescription target, final boolean replaceOther, final Set<Modifier> access, final boolean redoReferences, final MemberSearchResult searchResult) throws IOException {
+ return js.runModificationTask(new Task<WorkingCopy>() {
public void run(WorkingCopy copy) throws Exception {
copy.toPhase(JavaSource.Phase.RESOLVED);
TreePath expression = IntroduceExpressionBasedMethodFix.this.handle.resolve(copy);
@@ -211,6 +232,7 @@
returnType = Utilities.convertIfAnonymous(Utilities.resolveTypeForDeclaration(copy, returnType));
final TreeMaker make = copy.getTreeMaker();
Tree returnTypeTree = make.Type(returnType);
+ copy.tag(returnTypeTree, TYPE_TAG);
List<VariableElement> parameters = IntroduceHint.resolveVariables(copy, IntroduceExpressionBasedMethodFix.this.parameters);
List<ExpressionTree> realArguments = IntroduceHint.realArguments(make, parameters);
ExpressionTree invocation = make.MethodInvocation(Collections.<ExpressionTree>emptyList(), make.Identifier(name), realArguments);
@@ -243,11 +265,11 @@
//handle duplicates
Document doc = copy.getDocument();
Pattern p = Pattern.createPatternWithRemappableVariables(expression, parameters, true);
- for (Occurrence desc : Matcher.create(copy).setCancel(new AtomicBoolean()).match(p)) {
+ for (Occurrence desc : Matcher.create(copy).setSearchRoot(pathToClass).setCancel(new AtomicBoolean()).match(p)) {
TreePath firstLeaf = desc.getOccurrenceRoot();
int startOff = (int) copy.getTrees().getSourcePositions().getStartPosition(copy.getCompilationUnit(), firstLeaf.getLeaf());
int endOff = (int) copy.getTrees().getSourcePositions().getEndPosition(copy.getCompilationUnit(), firstLeaf.getLeaf());
- if (!IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
+ if (!GraphicsEnvironment.isHeadless() && !IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
continue;
}
//XXX:
@@ -282,13 +304,12 @@
if (redoReferences) {
new ReferenceTransformer(
- copy, ElementKind.METHOD, searchResult,
- name,
- targetType).scan(pathToClass, null);
+ copy, ElementKind.METHOD, searchResult,
+ name,
+ targetType).scan(pathToClass, null);
}
}
- }).commit();
- return null;
+ });
}
-
+
}
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java
index cc36bbc..21d0fd0c 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java
@@ -48,6 +48,7 @@
import javax.swing.JButton;
import javax.swing.text.BadLocationException;
import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreeMaker;
@@ -132,6 +133,14 @@
return pathToClass;
}
+ private TreePath findOutermostClass(WorkingCopy copy, TreePath resolved) {
+ TreePath pathToClass = resolved;
+ while (pathToClass != null && (!TreeUtilities.CLASS_TREE_KINDS.contains(pathToClass.getLeaf().getKind()) || pathToClass.getParentPath().getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT)) {
+ pathToClass = pathToClass.getParentPath();
+ }
+ return pathToClass;
+ }
+
@Override
public ChangeInfo implement() throws IOException, BadLocationException {
JButton btnOk = new JButton(NbBundle.getMessage(IntroduceHint.class, "LBL_Ok"));
@@ -157,7 +166,12 @@
panel.isRefactorExisting())).commit();
return null;
}
-
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
+ return js.runModificationTask(new Worker(guessedName, permitDuplicates, false, EnumSet.of(Modifier.PRIVATE), IntroduceFieldPanel.INIT_FIELD, null, false));
+ }
+
/**
* The actual modification. Some javac related data are recorded in fields, inner class prevents
* unintentional leak if someone keeps a reference to the Fix
@@ -294,7 +308,7 @@
allNewUses.add(resolved.getLeaf());
Collection<TreePath> duplicates = new ArrayList<>();
if (replaceAll) {
- for (TreePath p : SourceUtils.computeDuplicates(parameter, resolved, new TreePath(parameter.getCompilationUnit()), new AtomicBoolean())) {
+ for (TreePath p : SourceUtils.computeDuplicates(parameter, resolved, findOutermostClass(parameter, resolved), new AtomicBoolean())) {
if (variableRewrite) {
IntroduceHint.removeFromParent(parameter, p);
} else {
@@ -317,7 +331,9 @@
VariableTree field;
expressionStatementRewrite = parentTree.getKind() == Tree.Kind.EXPRESSION_STATEMENT;
if (!variableRewrite) {
- field = make.Variable(modsTree, name, make.Type(tm), initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+ Tree varType = make.Type(tm);
+ field = make.Variable(modsTree, name, varType, initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+ parameter.tag(varType, TYPE_TAG);
if (!expressionStatementRewrite) {
Tree nueParent = parameter.getTreeUtilities().translate(parentTree, Collections.singletonMap(resolved.getLeaf(), make.Identifier(name)));
parameter.rewrite(parentTree, nueParent);
@@ -326,8 +342,9 @@
toRemoveFromParent = resolved.getParentPath();
}
} else {
- VariableTree originalVar = (VariableTree) original;
- field = make.Variable(modsTree, name, originalVar.getType(), initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+ Tree originalVarType = ((VariableTree) original).getType();
+ field = make.Variable(modsTree, name, originalVarType, initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+ parameter.tag(originalVarType, TYPE_TAG);
toRemoveFromParent = resolved;
}
nueClass = IntroduceHint.insertField(parameter, (ClassTree) pathToClass.getLeaf(), field, allNewUses, offset);
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java
index 22de63d..181affd 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java
@@ -18,7 +18,9 @@
*/
package org.netbeans.modules.java.hints.introduce;
+import java.io.IOException;
import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.spi.editor.hints.Fix;
@@ -28,8 +30,10 @@
* @author sdedic
*/
public abstract class IntroduceFixBase implements Fix {
+
+ protected static final String TYPE_TAG = "typeTag";
protected final JavaSource js;
- protected final TreePathHandle handle;
+ protected final TreePathHandle handle;
protected final int duplicatesCount;
protected final int offset;
protected boolean targetIsInterface;
@@ -45,4 +49,10 @@
this.targetIsInterface = f;
}
+ public abstract ModificationResult getModificationResult() throws IOException;
+
+ public int getNameOffset(ModificationResult result) {
+ int[] span = result.getSpan(TYPE_TAG);
+ return span != null ? span[1] + 1 : -1;
+ }
}
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java
index 043f682..0e4fc7c 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java
@@ -29,13 +29,13 @@
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.ReturnTree;
-import com.sun.source.tree.Scope;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
+import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -59,18 +59,14 @@
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.swing.JButton;
import javax.swing.text.Document;
-import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
-import org.netbeans.api.java.source.ElementHandle;
-import org.netbeans.api.java.source.ElementUtilities.ElementAcceptor;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.TreePathHandle;
@@ -440,7 +436,21 @@
js.runModificationTask(new TaskImpl(access, name, target, replaceOther, val.getResult(), redoReferences)).commit();
return null;
}
-
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
+ ModificationResult result = null;
+ int counter = 0;
+ do {
+ try {
+ result = js.runModificationTask(new TaskImpl(EnumSet.of(Modifier.PRIVATE), "method" + (counter != 0 ? String.valueOf(counter) : ""), targets.iterator().next(), true, null, false));
+ } catch (Exception e) {
+ counter++;
+ }
+ } while (result == null && counter < 10);
+ return result;
+ }
+
static class OccurrencePositionComparator implements Comparator<Occurrence> {
final CompilationUnitTree cut;
final SourcePositions positions;
@@ -569,7 +579,8 @@
}
ModifiersTree mods = make.Modifiers(modifiers);
MethodTree method = make.Method(mods, name, returnTypeTree, typeVars, formalArguments, thrown, make.Block(methodStatements, false), null);
-
+ copy.tag(returnTypeTree, TYPE_TAG);
+
return method;
}
/**
@@ -824,7 +835,7 @@
statementsPaths.add(new TreePath(firstStatement.getParentPath(), t));
}
Pattern p = Pattern.createPatternWithRemappableVariables(statementsPaths, parameters, true);
- List<? extends Occurrence> occurrences = new ArrayList<Occurrence>(Matcher.create(copy).setCancel(new AtomicBoolean()).match(p));
+ List<? extends Occurrence> occurrences = new ArrayList<Occurrence>(Matcher.create(copy).setSearchRoot(pathToClass).setCancel(new AtomicBoolean()).match(p));
Collections.sort(occurrences, new OccurrencePositionComparator(copy.getCompilationUnit(), copy.getTrees().getSourcePositions()));
for (Occurrence desc :occurrences ) {
TreePath firstLeaf = desc.getOccurrenceRoot();
@@ -862,7 +873,7 @@
int startOff = (int) copy.getTrees().getSourcePositions().getStartPosition(copy.getCompilationUnit(), firstSt);
int endOff = (int) copy.getTrees().getSourcePositions().getEndPosition(copy.getCompilationUnit(), lastSt);
- if (usedAfter || !IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
+ if (usedAfter || !GraphicsEnvironment.isHeadless() && !IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
continue;
}
List<StatementTree> newStatements = new LinkedList<StatementTree>();
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java
index a227018..6e40a51 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java
@@ -19,7 +19,6 @@
package org.netbeans.modules.java.hints.introduce;
import com.sun.source.tree.ExpressionTree;
-import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
@@ -29,7 +28,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -41,9 +39,9 @@
import javax.swing.JButton;
import javax.swing.text.BadLocationException;
import org.netbeans.api.java.source.CompilationInfo;
-import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreeMaker;
@@ -127,9 +125,9 @@
public ChangeInfo implement() throws IOException, BadLocationException {
JButton btnOk = new JButton(NbBundle.getMessage(IntroduceHint.class, "LBL_Ok"));
JButton btnCancel = new JButton(NbBundle.getMessage(IntroduceHint.class, "LBL_Cancel"));
- IntroduceFieldPanel panel = new IntroduceFieldPanel(guessedName, null, duplicatesCount,
- true, handle.getKind() == Tree.Kind.VARIABLE,
- IntroduceFieldPanel.VARIABLE,
+ IntroduceFieldPanel panel = new IntroduceFieldPanel(guessedName, null, duplicatesCount,
+ true, handle.getKind() == Tree.Kind.VARIABLE,
+ IntroduceFieldPanel.VARIABLE,
"introduceVariable", btnOk);
String caption = NbBundle.getMessage(IntroduceHint.class, "CAP_" + getKeyExt()); //NOI18N
DialogDescriptor dd = new DialogDescriptor(panel, caption, true, new Object[]{btnOk, btnCancel}, btnOk, DialogDescriptor.DEFAULT_ALIGN, null, null);
@@ -145,7 +143,17 @@
final boolean replaceAll = panel.isReplaceAll();
final boolean declareFinal = panel.isDeclareFinal();
final MemberSearchResult search = val.getLastResult();
- js.runModificationTask(new Task<WorkingCopy>() {
+ getModificationResult(replaceAll, name, declareFinal, refactor, search).commit();
+ return null;
+ }
+
+ @Override
+ public ModificationResult getModificationResult() throws IOException {
+ return getModificationResult(true, guessedName, false, false, null);
+ }
+
+ private ModificationResult getModificationResult(final boolean replaceAll, final String name, final boolean declareFinal, final boolean refactor, final MemberSearchResult search) throws IOException {
+ return js.runModificationTask(new Task<WorkingCopy>() {
public void run(WorkingCopy parameter) throws Exception {
parameter.toPhase(JavaSource.Phase.RESOLVED);
TreePath resolved = handle.resolve(parameter);
@@ -158,7 +166,7 @@
}
tm = Utilities.convertIfAnonymous(Utilities.resolveTypeForDeclaration(parameter, tm));
if (!Utilities.isValidType(tm)) {
- return; // TODO...
+ return; // TODO...
}
Element targetEl = null;
TreePath targetPath = null;
@@ -212,7 +220,9 @@
List<StatementTree> nueStatements;
GeneratorUtilities.get(parameter).importComments(IntroduceHint.getStatementOrBlock(statement).getLeaf(), parameter.getCompilationUnit());
mods = make.Modifiers(declareFinal ? EnumSet.of(Modifier.FINAL) : EnumSet.noneOf(Modifier.class));
- VariableTree newVariable = make.Variable(mods, name, make.Type(tm), expression);
+ Tree varType = make.Type(tm);
+ VariableTree newVariable = make.Variable(mods, name, varType, expression);
+ parameter.tag(varType, TYPE_TAG);
nueStatements = new ArrayList<>();
nueStatements.add(make.asReplacementOf(newVariable, resolved.getLeaf(), true));
if (expressionStatement) {
@@ -220,20 +230,19 @@
} else {
Utilities.insertStatement(parameter, statement, nueStatements, null);
}
-
+
if (!expressionStatement) {
Tree origParent = resolved.getParentPath().getLeaf();
- Tree newParent = parameter.getTreeUtilities().translate(origParent, Collections.singletonMap(resolved.getLeaf(),
+ Tree newParent = parameter.getTreeUtilities().translate(origParent, Collections.singletonMap(resolved.getLeaf(),
make.asNew(make.Identifier(name))));
parameter.rewrite(origParent, newParent);
}
-
+
if (refactor) {
- new ReferenceTransformer(parameter, ElementKind.LOCAL_VARIABLE,
+ new ReferenceTransformer(parameter, ElementKind.LOCAL_VARIABLE,
search, name, targetEl).scan(statement.getParentPath(), null);
}
}
- }).commit();
- return null;
+ });
}
}
diff --git a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java
index e600684..ea3a48f 100644
--- a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java
+++ b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java
@@ -205,7 +205,7 @@
.map((xt) -> xt.toString())
.orElse(pkg);
}
- } catch (IOException ioe) {
+ } catch (Exception e) {
//TODO: Log & pass
}
return pkg;
diff --git a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java
index 2b3d632..1c2dcc9 100644
--- a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java
+++ b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java
@@ -18,14 +18,17 @@
*/
package org.netbeans.modules.java.j2seplatform.queries;
+import java.awt.GraphicsEnvironment;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import org.netbeans.api.annotations.common.CheckForNull;
@@ -34,7 +37,6 @@
import org.netbeans.api.java.queries.SourceJavadocAttacher.AttachmentListener;
import org.netbeans.spi.java.project.support.JavadocAndSourceRootDetection;
import org.netbeans.spi.java.queries.SourceJavadocAttacherImplementation;
-import org.netbeans.spi.java.queries.SourceJavadocAttacherImplementation.Definer;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
@@ -111,23 +113,36 @@
assert root != null;
assert browseCall != null;
assert convertor != null;
- final SelectRootsPanel selectSources = new SelectRootsPanel(
- SelectRootsPanel.SOURCES,
- root,
- attachedRoots,
- browseCall,
- convertor,
- plugin);
- final DialogDescriptor dd = new DialogDescriptor(selectSources, Bundle.TXT_SelectSources());
- dd.setButtonListener(selectSources);
- if (DialogDisplayer.getDefault().notify(dd) == DialogDescriptor.OK_OPTION) {
- try {
- return selectSources.getRoots();
- } catch (Exception e) {
- DialogDisplayer.getDefault().notify(
- new NotifyDescriptor.Message(
- Bundle.TXT_InvalidSourceRoot(),
- NotifyDescriptor.ERROR_MESSAGE));
+ if (GraphicsEnvironment.isHeadless()) {
+ List<? extends URI> sources = plugin.getSources(root, () -> false).stream().map(url -> {
+ try {
+ return url.toURI();
+ } catch (URISyntaxException ex) {
+ }
+ return null;
+ }).filter(uri -> uri != null).collect(Collectors.toList());
+ if (!sources.isEmpty()) {
+ return sources;
+ }
+ } else {
+ final SelectRootsPanel selectSources = new SelectRootsPanel(
+ SelectRootsPanel.SOURCES,
+ root,
+ attachedRoots,
+ browseCall,
+ convertor,
+ plugin);
+ final DialogDescriptor dd = new DialogDescriptor(selectSources, Bundle.TXT_SelectSources());
+ dd.setButtonListener(selectSources);
+ if (DialogDisplayer.getDefault().notify(dd) == DialogDescriptor.OK_OPTION) {
+ try {
+ return selectSources.getRoots();
+ } catch (Exception e) {
+ DialogDisplayer.getDefault().notify(
+ new NotifyDescriptor.Message(
+ Bundle.TXT_InvalidSourceRoot(),
+ NotifyDescriptor.ERROR_MESSAGE));
+ }
}
}
return null;
diff --git a/java/java.lexer/manifest.mf b/java/java.lexer/manifest.mf
index bd61e4c..9aee443 100644
--- a/java/java.lexer/manifest.mf
+++ b/java/java.lexer/manifest.mf
@@ -1,5 +1,5 @@
OpenIDE-Module: org.netbeans.modules.java.lexer/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/java/lexer/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.45
+OpenIDE-Module-Specification-Version: 1.45.1
OpenIDE-Module-Layer: org/netbeans/lib/java/lexer/layer.xml
diff --git a/java/java.lsp.server/build.xml b/java/java.lsp.server/build.xml
index 5034869..48e9311 100644
--- a/java/java.lsp.server/build.xml
+++ b/java/java.lsp.server/build.xml
@@ -129,4 +129,16 @@
<zip destfile="${build.dir}/apache-netbeans-java-${vsix.version}.vsix" basedir="${build.dir}/vscode-mandatory/" update="true" />
</target>
+ <target name="test-vscode-ext" description="Tests the Visual Studio Code extension built by 'build-vscode-ext' target.">
+ <exec executable="npm" failonerror="true" dir="vscode">
+ <arg value="--allow-same-version"/>
+ <arg value="run" />
+ <arg value="nbjavac" />
+ </exec>
+ <exec executable="npm" failonerror="true" dir="vscode">
+ <arg value="--allow-same-version"/>
+ <arg value="run" />
+ <arg value="test" />
+ </exec>
+ </target>
</project>
diff --git a/java/java.lsp.server/external/binaries-list b/java/java.lsp.server/external/binaries-list
index 05e0a74..39d428f 100644
--- a/java/java.lsp.server/external/binaries-list
+++ b/java/java.lsp.server/external/binaries-list
@@ -15,11 +15,11 @@
# specific language governing permissions and limitations
# under the License.
-4FF29B93C50F6768C9A73040AE818E96EB2E68DB org.eclipse.lsp4j:org.eclipse.lsp4j:0.9.0
-E5F9B4D2A6DDE53FA2CEAD6F0D51CABC73E3C850 org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.9.0
-DA453B2A6C9BBD9369DE41365C59E88574EC1F0B org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.9.0
-1CF7FCE1E2F4DFA76C3B91E4C2D72EA0C6899945 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.9.0
-DAF59D5711756C35B6BF9537EB1A6EDDF52EF452 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.9.0
+271665493A1E35FB4C2D1DBABF19363C8AD27819 org.eclipse.lsp4j:org.eclipse.lsp4j:0.10.0
+18621C63F8D8587C0CC5EEB9CB945E49E9B5C900 org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.10.0
+ED2D6D876578B139AEA24289422435148FEF6B45 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.10.0
+42CA44A49315A490804A3DED67163123748C1F19 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.10.0
+30A70C7AC5E722F25843669F124CDACA9772B9C8 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.10.0
751B324BED8C077BF6B7B8C2055595EB8C28509D org.eclipse.xtend:org.eclipse.xtend.lib:2.19.0
F3626A047A3ABFA47CF62D76BDEF1D246F7985F6 org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.19.0
997BA9935D0069B3F1CD2F93B56B91D24E9C60A0 org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.19.0
diff --git a/java/java.lsp.server/external/lsp4j-0.9.0-license.txt b/java/java.lsp.server/external/lsp4j-0.10.0-license.txt
similarity index 98%
rename from java/java.lsp.server/external/lsp4j-0.9.0-license.txt
rename to java/java.lsp.server/external/lsp4j-0.10.0-license.txt
index 90fb325..d90f316 100644
--- a/java/java.lsp.server/external/lsp4j-0.9.0-license.txt
+++ b/java/java.lsp.server/external/lsp4j-0.10.0-license.txt
@@ -1,10 +1,10 @@
Name: Eclipse Language Server Protocol Library
Origin: Eclipse
-Version: 0.9.0
+Version: 0.10.0
License: EPL-v20
URL: http://www.eclipse.org/
Description: Eclipse Language Server Protocol Library
-Files: org.eclipse.lsp4j-0.9.0.jar org.eclipse.lsp4j.debug-0.9.0.jar org.eclipse.lsp4j.generator-0.9.0.jar org.eclipse.lsp4j.jsonrpc-0.9.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar
+Files: org.eclipse.lsp4j-0.10.0.jar org.eclipse.lsp4j.debug-0.10.0.jar org.eclipse.lsp4j.generator-0.10.0.jar org.eclipse.lsp4j.jsonrpc-0.10.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar
Eclipse Public License - v 2.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
diff --git a/java/java.lsp.server/nbcode/integration/licenseinfo.xml b/java/java.lsp.server/nbcode/integration/licenseinfo.xml
new file mode 100644
index 0000000..13f4a57
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/licenseinfo.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<licenseinfo>
+ <fileset>
+ <file>src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template</file>
+ <file>src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template</file>
+ <file>src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template</file>
+ <license ref="Apache-2.0-ASF" />
+ <comment type="TEMPLATE_MINIMAL_IP"/>
+ </fileset>
+</licenseinfo>
diff --git a/java/java.lsp.server/nbcode/integration/nbproject/project.xml b/java/java.lsp.server/nbcode/integration/nbproject/project.xml
index 3a36d95..78bfd93 100644
--- a/java/java.lsp.server/nbcode/integration/nbproject/project.xml
+++ b/java/java.lsp.server/nbcode/integration/nbproject/project.xml
@@ -35,6 +35,23 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.api.templates</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>1.18</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.netbeans.api.progress</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.57.1</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.java.lsp.server</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -59,6 +76,14 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.openide.util</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>9.17</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
index 98e4416..df3f217 100644
--- 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
@@ -26,6 +26,6 @@
*
* @author sdedic
*/
-//@ServiceProvider(service = DialogDisplayer.class, position = 1000)
+@ServiceProvider(service = DialogDisplayer.class, position = 1000)
public class LspDialogDisplayer extends AbstractDialogDisplayer {
}
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspProgressEnvironment.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspProgressEnvironment.java
new file mode 100644
index 0000000..a357eea
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspProgressEnvironment.java
@@ -0,0 +1,32 @@
+/*
+ * 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.AbstractProgressEnvironment;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+@ServiceProvider(service = ProgressEnvironment.class, position = 10000)
+public class LspProgressEnvironment extends AbstractProgressEnvironment {
+
+}
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/package-info.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/package-info.java
new file mode 100644
index 0000000..80fbe04
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+@TemplateRegistrations({
+ @TemplateRegistration(folder = "Classes/Code", position = 100, content = "resources/GeneratedMethodBody.template", scriptEngine = "freemarker", displayName = "#GeneratedMethodBody", category = "java-code-snippet"),
+ @TemplateRegistration(folder = "Classes/Code", position = 200, content = "resources/OverriddenMethodBody.template", scriptEngine = "freemarker", displayName = "#OverriddenMethodBody", category = "java-code-snippet"),
+ @TemplateRegistration(folder = "Classes/Code", position = 300, content = "resources/LambdaBody.template", scriptEngine = "freemarker", displayName = "#LambdaBody", category = "java-code-snippet")
+})
+@NbBundle.Messages({
+ "GeneratedMethodBody=Generated Method Body", "OverriddenMethodBody=Overridden Method Body", "LambdaBody=Generated Lambda Body" //NOI18N
+})
+package org.netbeans.modules.nbcode.integration;
+
+import org.netbeans.api.templates.TemplateRegistration;
+import org.netbeans.api.templates.TemplateRegistrations;
+import org.openide.util.NbBundle;
+
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template
new file mode 100644
index 0000000..8e8e003
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template
@@ -0,0 +1,13 @@
+<#--
+A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
+filling the body of methods generated by the IDE. When editing the template,
+the following predefined variables, that will be then expanded into
+the corresponding values, could be used together with Java expressions and
+comments:
+${method_return_type} a return type of a created method
+${default_return_value} a value returned by the method by default
+${method_name} name of the created method
+${class_name} qualified name of the enclosing class
+${simple_class_name} simple name of the enclosing class
+-->
+throw new java.lang.UnsupportedOperationException("Not supported yet.");
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template
new file mode 100644
index 0000000..f4c0584
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template
@@ -0,0 +1,14 @@
+<#--
+A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
+filling the body of lambda expressions generated by the IDE. When editing
+the template, the following predefined variables, that will be then expanded
+into the corresponding values, could be used together with Java expressions and
+comments:
+${method_return_type} a return type of the functional interface method
+${default_return_value} a value returned by the method by default
+${method_name} name of the functional interface method
+-->
+<#if method_return_type?? && method_return_type != "void">
+return ${default_return_value};
+<#else>
+</#if>
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template
new file mode 100644
index 0000000..0122d0d
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template
@@ -0,0 +1,18 @@
+<#--
+A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
+filling the body of overridden methods generated by the IDE. When editing
+the template, the following predefined variables, that will be then expanded
+into the corresponding values, could be used together with Java expressions and
+comments:
+${super_method_call} a super method call
+${method_return_type} a return type of a created method
+${default_return_value} a value returned by the method by default
+${method_name} name of the created method
+${class_name} qualified name of the enclosing class
+${simple_class_name} simple name of the enclosing class
+-->
+<#if method_return_type?? && method_return_type != "void">
+return ${super_method_call};
+<#else>
+${super_method_call};
+</#if>
diff --git a/java/java.lsp.server/nbproject/project.properties b/java/java.lsp.server/nbproject/project.properties
index a4153f3..45081a0 100644
--- a/java/java.lsp.server/nbproject/project.properties
+++ b/java/java.lsp.server/nbproject/project.properties
@@ -17,17 +17,17 @@
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
-spec.version.base=1.6.0
+spec.version.base=1.6.1
requires.nb.javac=true
lsp.build.dir=vscode/nbcode
test.unit.run.cp.extra=
cp.extra=${tools.jar}
-release.external/org.eclipse.lsp4j-0.9.0.jar=modules/ext/org.eclipse.lsp4j-0.9.0.jar
-release.external/org.eclipse.lsp4j.debug-0.9.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.9.0.jar
-release.external/org.eclipse.lsp4j.generator-0.9.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.9.0.jar
-release.external/org.eclipse.lsp4j.jsonrpc-0.9.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.9.0.jar
-release.external/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar
+release.external/org.eclipse.lsp4j-0.10.0.jar=modules/ext/org.eclipse.lsp4j-0.10.0.jar
+release.external/org.eclipse.lsp4j.debug-0.10.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.10.0.jar
+release.external/org.eclipse.lsp4j.generator-0.10.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.10.0.jar
+release.external/org.eclipse.lsp4j.jsonrpc-0.10.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.10.0.jar
+release.external/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar
release.external/org.eclipse.xtend.lib-2.19.0.jar=modules/ext/org.eclipse.xtend.lib-2.19.0.jar
release.external/org.eclipse.xtend.lib.macro-2.19.0.jar=modules/ext/org.eclipse.xtend.lib.macro-2.19.0.jar
release.external/org.eclipse.xtext.xbase.lib-2.19.0.jar=modules/ext/org.eclipse.xtext.xbase.lib-2.19.0.jar
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index 7d0d84b..f0071f2 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -26,12 +26,20 @@
<code-name-base>org.netbeans.modules.java.lsp.server</code-name-base>
<module-dependencies>
<dependency>
+ <code-name-base>com.google.gson</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>2.7</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.api.annotations.common</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.34</specification-version>
+ <specification-version>1.36</specification-version>
</run-dependency>
</dependency>
<dependency>
@@ -79,6 +87,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.api.progress</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.57.1</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.libs.flexmark</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -130,6 +147,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>2.30.1</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -173,6 +199,23 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.java.lexer</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.45.1</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.netbeans.modules.java.source</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <implementation-version/>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.java.source.base</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -199,6 +242,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.jumpto</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.64</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.lexer</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -221,7 +273,16 @@
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>9.13</specification-version>
+ <implementation-version/>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.netbeans.modules.parsing.lucene</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>2</release-version>
+ <specification-version>2.45.1</specification-version>
</run-dependency>
</dependency>
<dependency>
@@ -243,11 +304,29 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.modules.queries</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.52</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.refactoring.api</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>1.50</specification-version>
+ <implementation-version/>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.netbeans.modules.refactoring.java</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <implementation-version/>
</run-dependency>
</dependency>
<dependency>
@@ -265,7 +344,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>2.63</specification-version>
+ <specification-version>2.65.1</specification-version>
</run-dependency>
</dependency>
<dependency>
@@ -374,14 +453,6 @@
<specification-version>9.14</specification-version>
</run-dependency>
</dependency>
- <dependency>
- <code-name-base>com.google.gson</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>2.7</specification-version>
- </run-dependency>
- </dependency>
</module-dependencies>
<test-dependencies>
<test-type>
@@ -483,24 +554,24 @@
<package>org.netbeans.modules.java.lsp.server.ui</package>
</public-packages>
<class-path-extension>
- <runtime-relative-path>ext/org.eclipse.lsp4j.debug-0.9.0.jar</runtime-relative-path>
- <binary-origin>external/org.eclipse.lsp4j.debug-0.9.0.jar</binary-origin>
+ <runtime-relative-path>ext/org.eclipse.lsp4j.debug-0.10.0.jar</runtime-relative-path>
+ <binary-origin>external/org.eclipse.lsp4j.debug-0.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/org.eclipse.xtend.lib.macro-2.19.0.jar</runtime-relative-path>
<binary-origin>external/org.eclipse.xtend.lib.macro-2.19.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
- <runtime-relative-path>ext/org.eclipse.lsp4j.generator-0.9.0.jar</runtime-relative-path>
- <binary-origin>external/org.eclipse.lsp4j.generator-0.9.0.jar</binary-origin>
+ <runtime-relative-path>ext/org.eclipse.lsp4j.generator-0.10.0.jar</runtime-relative-path>
+ <binary-origin>external/org.eclipse.lsp4j.generator-0.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
- <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc-0.9.0.jar</runtime-relative-path>
- <binary-origin>external/org.eclipse.lsp4j.jsonrpc-0.9.0.jar</binary-origin>
+ <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc-0.10.0.jar</runtime-relative-path>
+ <binary-origin>external/org.eclipse.lsp4j.jsonrpc-0.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
- <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar</runtime-relative-path>
- <binary-origin>external/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar</binary-origin>
+ <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar</runtime-relative-path>
+ <binary-origin>external/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/org.eclipse.xtend.lib-2.19.0.jar</runtime-relative-path>
@@ -511,8 +582,8 @@
<binary-origin>external/org.eclipse.xtext.xbase.lib-2.19.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
- <runtime-relative-path>ext/org.eclipse.lsp4j-0.9.0.jar</runtime-relative-path>
- <binary-origin>external/org.eclipse.lsp4j-0.9.0.jar</binary-origin>
+ <runtime-relative-path>ext/org.eclipse.lsp4j-0.10.0.jar</runtime-relative-path>
+ <binary-origin>external/org.eclipse.lsp4j-0.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/commons-io-2.5.jar</runtime-relative-path>
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
index ab14aed..c98193b 100644
--- 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
@@ -85,7 +85,7 @@
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()");
+ throw new IllegalStateException("Cannot 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/Utils.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
new file mode 100644
index 0000000..f79bcb8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
@@ -0,0 +1,209 @@
+/*
+ * 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 com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.tree.Tree;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.Properties;
+import javax.lang.model.element.ElementKind;
+import javax.swing.text.Document;
+import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.SymbolKind;
+import org.netbeans.api.editor.document.LineDocument;
+import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.modules.Places;
+import org.openide.text.NbDocument;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author lahvac
+ */
+public class Utils {
+
+ public static SymbolKind elementKind2SymbolKind(ElementKind kind) {
+ switch (kind) {
+ case PACKAGE:
+ return SymbolKind.Package;
+ case ENUM:
+ return SymbolKind.Enum;
+ case CLASS:
+ return SymbolKind.Class;
+ case ANNOTATION_TYPE:
+ return SymbolKind.Interface;
+ case INTERFACE:
+ return SymbolKind.Interface;
+ case ENUM_CONSTANT:
+ return SymbolKind.EnumMember;
+ case FIELD:
+ return SymbolKind.Field; //TODO: constant
+ case PARAMETER:
+ return SymbolKind.Variable;
+ case LOCAL_VARIABLE:
+ return SymbolKind.Variable;
+ case EXCEPTION_PARAMETER:
+ return SymbolKind.Variable;
+ case METHOD:
+ return SymbolKind.Method;
+ case CONSTRUCTOR:
+ return SymbolKind.Constructor;
+ case TYPE_PARAMETER:
+ return SymbolKind.TypeParameter;
+ case RESOURCE_VARIABLE:
+ return SymbolKind.Variable;
+ case MODULE:
+ return SymbolKind.Module;
+ case STATIC_INIT:
+ case INSTANCE_INIT:
+ case OTHER:
+ default:
+ return SymbolKind.File; //XXX: what here?
+ }
+ }
+
+ public static Range treeRange(CompilationInfo info, Tree tree) {
+ long start = info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tree);
+ long end = info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), tree);
+ if (end == (-1)) {
+ end = start;
+ }
+ return new Range(createPosition(info.getCompilationUnit(), (int) start),
+ createPosition(info.getCompilationUnit(), (int) end));
+ }
+
+ public static Position createPosition(CompilationUnitTree cut, int offset) {
+ return createPosition(cut.getLineMap(), offset);
+ }
+
+ public static Position createPosition(LineMap lm, int offset) {
+ return new Position((int) lm.getLineNumber(offset) - 1,
+ (int) lm.getColumnNumber(offset) - 1);
+ }
+
+ public static Position createPosition(FileObject file, int offset) {
+ try {
+ EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+ StyledDocument doc = ec.openDocument();
+ int line = NbDocument.findLineNumber(doc, offset);
+ int column = NbDocument.findLineColumn(doc, offset);
+
+ return new Position(line, column);
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ public static int getOffset(Document doc, Position pos) {
+ return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter();
+ }
+
+ public static synchronized String toUri(FileObject file) {
+ if (FileUtil.isArchiveArtifact(file)) {
+ //VS code cannot open jar:file: URLs, workaround:
+ File cacheDir = getCacheDir();
+ cacheDir.mkdirs();
+ File segments = new File(cacheDir, "segments");
+ Properties props = new Properties();
+
+ try (InputStream in = new FileInputStream(segments)) {
+ props.load(in);
+ } catch (IOException ex) {
+ //OK, may not exist yet
+ }
+ FileObject archive = FileUtil.getArchiveFile(file);
+ String archiveString = archive.toURL().toString();
+ File foundSegment = null;
+ for (String segment : props.stringPropertyNames()) {
+ if (archiveString.equals(props.getProperty(segment))) {
+ foundSegment = new File(cacheDir, segment);
+ break;
+ }
+ }
+ if (foundSegment == null) {
+ int i = 0;
+ while (props.getProperty("s" + i) != null)
+ i++;
+ foundSegment = new File(cacheDir, "s" + i);
+ props.put("s" + i, archiveString);
+ try (OutputStream in = new FileOutputStream(segments)) {
+ props.store(in, "");
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ File cache = new File(foundSegment, FileUtil.getRelativePath(FileUtil.getArchiveRoot(archive), file));
+ cache.getParentFile().mkdirs();
+ try (OutputStream out = new FileOutputStream(cache)) {
+ out.write(file.asBytes());
+ return cache.toURI().toString();
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ return file.toURI().toString();
+ }
+
+ public static synchronized FileObject fromUri(String uri) throws MalformedURLException {
+ File cacheDir = getCacheDir();
+ URI uriUri = URI.create(uri);
+ URI relative = cacheDir.toURI().relativize(uriUri);
+ if (relative != null && new File(cacheDir, relative.toString()).canRead()) {
+ String segmentAndPath = relative.toString();
+ int slash = segmentAndPath.indexOf('/');
+ String segment = segmentAndPath.substring(0, slash);
+ String path = segmentAndPath.substring(slash + 1);
+ File segments = new File(cacheDir, "segments");
+ Properties props = new Properties();
+
+ try (InputStream in = new FileInputStream(segments)) {
+ props.load(in);
+ String archiveUri = props.getProperty(segment);
+ FileObject archive = URLMapper.findFileObject(URI.create(archiveUri).toURL());
+ archive = archive != null ? FileUtil.getArchiveRoot(archive) : null;
+ FileObject file = archive != null ? archive.getFileObject(path) : null;
+ if (file != null) {
+ return file;
+ }
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ return URLMapper.findFileObject(URI.create(uri).toURL());
+ }
+
+ private static File getCacheDir() {
+ return Places.getCacheSubfile("java-server");
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java
index 73c6dc2..7d4c2e7 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java
@@ -33,6 +33,9 @@
import org.netbeans.modules.java.lsp.server.debugging.breakpoints.BreakpointsManager;
import org.netbeans.modules.java.lsp.server.debugging.launch.NbDebugSession;
+import org.netbeans.modules.java.lsp.server.progress.LspInternalHandle;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.openide.util.Pair;
public final class DebugAdapterContext {
@@ -49,9 +52,11 @@
private Charset debuggeeEncoding;
private boolean isVmStopOnEntry = false;
private boolean isDebugMode = true;
+ private InternalHandle processExecutorHandle;
private final AtomicInteger lastSourceReferenceId = new AtomicInteger(0);
- private final Map<Integer, String> sourceReferences = new ConcurrentHashMap<>();
+ private final Map<Integer, Pair<URI, String>> sourcesById = new ConcurrentHashMap<>();
+ private final Map<URI, Integer> sourceReferences = new ConcurrentHashMap<>();
private final NBConfigurationSemaphore configurationSemaphore = new NBConfigurationSemaphore();
private final NbSourceProvider sourceProvider = new NbSourceProvider(this);
@@ -114,6 +119,25 @@
void setClientPathsAreUri(boolean clientPathsAreUri) {
this.clientPathsAreUri = clientPathsAreUri;
}
+
+ public boolean requestProcessTermination() {
+ InternalHandle ih;
+ synchronized (this) {
+ ih = processExecutorHandle;
+ }
+ if (ih != null) {
+ ((LspInternalHandle)ih).forceRequestCancel();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void setProcessExecutorHandle(InternalHandle h) {
+ synchronized (this) {
+ this.processExecutorHandle = h;
+ }
+ }
public String getClientPath(String debuggerPath) {
if (clientPathsAreUri == debuggerPathsAreUri) {
@@ -179,13 +203,27 @@
this.sourcePaths = sourcePaths;
}
- public String getSourceUri(int sourceReference) {
- return sourceReferences.get(sourceReference);
+ public URI getSourceUri(int sourceReference) {
+ Pair<URI, String> sourceInfo = sourcesById.get(sourceReference);
+ if (sourceInfo != null) {
+ return sourceInfo.first();
+ } else {
+ return null;
+ }
}
- public int createSourceReference(String uri) {
- int id = lastSourceReferenceId.incrementAndGet();
- sourceReferences.put(id, uri);
+ public String getSourceMimeType(int sourceReference) {
+ Pair<URI, String> sourceInfo = sourcesById.get(sourceReference);
+ if (sourceInfo != null) {
+ return sourceInfo.second();
+ } else {
+ return null;
+ }
+ }
+
+ public int createSourceReference(URI uri, String mimeType) {
+ int id = sourceReferences.computeIfAbsent(uri, u -> lastSourceReferenceId.incrementAndGet());
+ sourcesById.put(id, Pair.of(uri, mimeType));
return id;
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java
index 81374ad..2f91d13 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java
@@ -24,8 +24,18 @@
import java.util.concurrent.Future;
import org.eclipse.lsp4j.debug.launch.DSPLauncher;
import org.eclipse.lsp4j.debug.services.IDebugProtocolClient;
+import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.Launcher;
+import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
+import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
+import org.eclipse.lsp4j.jsonrpc.messages.Message;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.AbstractLookup;
+import org.openide.util.lookup.InstanceContent;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
/**
*
@@ -39,7 +49,9 @@
public static void startDebugger(InputStream in, OutputStream out) {
final DebugAdapterContext context = new DebugAdapterContext();
NbProtocolServer server = new NbProtocolServer(context);
- Launcher<IDebugProtocolClient> serverLauncher = DSPLauncher.createServerLauncher(server, in, out);
+
+ Launcher<IDebugProtocolClient> serverLauncher = DSPLauncher.createServerLauncher(
+ server, in, out, null, ConsumeWithLookup::new);
context.setClient(serverLauncher.getRemoteProxy());
Future<Void> runningServer = serverLauncher.startListening();
try {
@@ -48,4 +60,41 @@
Exceptions.printStackTrace(ex);
}
}
+
+ private static class ConsumeWithLookup implements MessageConsumer {
+ private final MessageConsumer delegate;
+ private OperationContext topContext;
+
+ public ConsumeWithLookup(MessageConsumer delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void consume(Message message) throws MessageIssueException, JsonRpcException {
+ InstanceContent ic = new InstanceContent();
+ ProxyLookup ll = new ProxyLookup(new AbstractLookup(ic), Lookup.getDefault());
+ // HACK: piggyback on LSP's client.
+ if (topContext == null) {
+ topContext = OperationContext.find(null);
+ }
+ final OperationContext ctx;
+
+ if (topContext != null) {
+ ctx = topContext.operationContext();
+ ctx.disableCancels();
+ ic.add(ctx);
+ } else {
+ ctx = null;
+ }
+ Lookups.executeWith(ll, () -> {
+ try {
+ delegate.consume(message);
+ } finally {
+ if (ctx != null) {
+ ctx.stop();
+ }
+ }
+ });
+ }
+ }
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
index bc6793b..73d83fc 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
@@ -18,7 +18,11 @@
*/
package org.netbeans.modules.java.lsp.server.debugging;
+import java.io.File;
+import java.net.MalformedURLException;
import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
@@ -286,11 +290,33 @@
stackFrame.setId(frameId);
stackFrame.setName(frame.getName());
URI sourceURI = frame.getSourceURI();
- if (sourceURI != null && sourceURI.getPath() != null) {
+ if (sourceURI != null) {
Source source = new Source();
- source.setName(Paths.get(sourceURI).getFileName().toString());
- source.setPath(sourceURI.getPath());
- source.setSourceReference(0);
+ String scheme = sourceURI.getScheme();
+ if (null == scheme || scheme.isEmpty() || "file".equalsIgnoreCase(scheme)) {
+ source.setName(Paths.get(sourceURI).getFileName().toString());
+ source.setPath(sourceURI.getPath());
+ source.setSourceReference(0);
+ } else {
+ int ref = context.createSourceReference(sourceURI, frame.getSourceMimeType());
+ String path = sourceURI.getPath();
+ if (path == null) {
+ path = sourceURI.getSchemeSpecificPart();
+ }
+ if (path != null) {
+ int sepIndex = Math.max(path.lastIndexOf('/'), path.lastIndexOf(File.separatorChar));
+ source.setName(path.substring(sepIndex + 1));
+ if ("jar".equalsIgnoreCase(scheme)) {
+ try {
+ path = new URI(path).getPath();
+ } catch (URISyntaxException ex) {
+ // ignore, we just tried
+ }
+ }
+ source.setPath(path);
+ }
+ source.setSourceReference(ref);
+ }
stackFrame.setSource(source);
}
stackFrame.setLine(line);
@@ -343,10 +369,10 @@
if (sourceReference <= 0) {
ErrorUtilities.completeExceptionally(future, "SourceRequest: property 'sourceReference' is missing, null, or empty", ResponseErrorCode.InvalidParams);
} else {
- String uri = context.getSourceUri(sourceReference);
+ URI uri = context.getSourceUri(sourceReference);
NbSourceProvider sourceProvider = context.getSourceProvider();
SourceResponse response = new SourceResponse();
- response.setMimeType("text/x-java"); // Set mimeType to tell clients to recognize the source contents as java source
+ response.setMimeType(context.getSourceMimeType(sourceReference));
response.setContent(sourceProvider.getSourceContents(uri));
future.complete(response);
}
@@ -379,13 +405,13 @@
String expression = args.getExpression();
if (StringUtils.isBlank(expression)) {
throw ErrorUtilities.createResponseErrorException(
- "Failed to evaluate. Reason: Empty expression cannot be evaluated.",
+ "Empty expression cannot be evaluated.",
ResponseErrorCode.InvalidParams);
}
NbFrame stackFrame = (NbFrame) context.getThreadsProvider().getThreadObjects().getObject(args.getFrameId());
if (stackFrame == null) {
throw ErrorUtilities.createResponseErrorException(
- "Failed to evaluate. Reason: Unknown frame " + args.getFrameId(),
+ "Unknown frame " + args.getFrameId(),
ResponseErrorCode.InvalidParams);
}
stackFrame.getDVFrame().makeCurrent(); // The evaluation is always performed with respect to the current frame
@@ -397,7 +423,7 @@
variable = debugger.evaluate(expression);
} catch (InvalidExpressionException ex) {
throw ErrorUtilities.createResponseErrorException(
- "Failed to evaluate. Reason: " + ex.getLocalizedMessage(),
+ ex.getLocalizedMessage(),
ResponseErrorCode.ParseError);
}
EvaluateResponse response = new EvaluateResponse();
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java
index e4d792b..6303248 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java
@@ -19,7 +19,13 @@
package org.netbeans.modules.java.lsp.server.debugging;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URISyntaxException;
+import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -67,9 +73,25 @@
});
}
- public String getSourceContents(String arg0) {
- LOG.log(Level.INFO, "SourceContent {0}", arg0);
- throw new UnsupportedOperationException("Not supported yet.");
+ public String getSourceContents(URI uri) {
+ LOG.log(Level.INFO, "SourceContent {0}", uri);
+ URL url;
+ try {
+ url = uri.toURL();
+ } catch (MalformedURLException ex) {
+ return ex.getLocalizedMessage();
+ }
+ StringBuilder content = new StringBuilder();
+ char[] buffer = new char[8192];
+ try (Reader r = new InputStreamReader(url.openConnection().getInputStream())) {
+ int l;
+ while ((l = r.read(buffer)) > 0) {
+ content.append(buffer, 0, l);
+ }
+ } catch (IOException ex) {
+ return ex.getLocalizedMessage();
+ }
+ return content.toString();
}
public Source getSource(String sourceName, String debuggerURI) {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java
index 2cfef05..0a36344 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java
@@ -19,6 +19,7 @@
package org.netbeans.modules.java.lsp.server.debugging.breakpoints;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@@ -35,6 +36,7 @@
import org.netbeans.api.debugger.jpda.LineBreakpoint;
import org.netbeans.modules.debugger.jpda.truffle.breakpoints.TruffleLineBreakpoint;
import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
+import org.openide.filesystems.URLMapper;
/**
*
@@ -56,6 +58,15 @@
public NbBreakpoint(Source source, String sourceURL, int line, int hitCount, String condition, String logMessage, DebugAdapterContext context) {
this.source = source;
+ Integer ref = source.getSourceReference();
+ if (ref != null && ref != 0) {
+ URI uri = context.getSourceUri(ref);
+ if (uri != null) {
+ try {
+ sourceURL = uri.toURL().toString();
+ } catch (MalformedURLException ex) {}
+ }
+ }
this.sourceURL = sourceURL;
this.line = line;
this.hitCount = hitCount;
@@ -116,9 +127,9 @@
breakpoint.addPropertyChangeListener(Breakpoint.PROP_VALIDITY, evt -> {
updateValid(breakpoint, true);
});
- updateValid(breakpoint, false);
DebuggerManager d = DebuggerManager.getDebuggerManager();
d.addBreakpoint(breakpoint);
+ updateValid(breakpoint, false);
this.breakpoint = breakpoint;
return CompletableFuture.completedFuture(this);
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java
index 894ece4..c7436f7 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java
@@ -45,6 +45,8 @@
} else {
debugSession.detach();
}
+ } else {
+ context.requestProcessTermination();
}
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
index 14a0fa1..92ea02f 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
@@ -22,6 +22,7 @@
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -41,6 +42,8 @@
import org.netbeans.api.project.Project;
import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
import org.netbeans.modules.java.lsp.server.debugging.NbSourceProvider;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.progress.spi.InternalHandle;
import org.netbeans.spi.project.ActionProgress;
import org.netbeans.spi.project.ActionProvider;
import org.openide.filesystems.FileObject;
@@ -57,6 +60,10 @@
public abstract void preLaunch(Map<String, Object> launchArguments, DebugAdapterContext context);
public abstract void postLaunch(Map<String, Object> launchArguments, DebugAdapterContext context);
+
+ protected void notifyFinished(DebugAdapterContext ctx, boolean success) {
+ // no op.
+ }
public final CompletableFuture<Void> nbLaunch(FileObject toRun, DebugAdapterContext context, boolean debug, Consumer<NbProcessConsole.ConsoleMessage> consoleMessages) {
CompletableFuture<Void> launchFuture = new CompletableFuture<>();
@@ -100,6 +107,7 @@
@Override
public void finished(boolean success) {
ioContext.stop();
+ notifyFinished(context, success);
}
};
Lookup launchCtx = new ProxyLookup(
@@ -109,6 +117,12 @@
);
Lookups.executeWith(launchCtx, () -> {
providerAndCommand.first().invokeAction(providerAndCommand.second(), Lookups.fixed(toRun, ioContext, progress));
+
+ OperationContext ctx = OperationContext.find(Lookup.getDefault());
+ List<InternalHandle> created = ctx.getOperationHandles();
+ if (!created.isEmpty()) {
+ context.setProcessExecutorHandle(created.get(0));
+ }
});
}).exceptionally((t) -> {
launchFuture.completeExceptionally(t);
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java
index 5fcc559..86f42e2 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java
@@ -36,6 +36,12 @@
}
@Override
+ protected void notifyFinished(DebugAdapterContext ctx, boolean success) {
+ super.notifyFinished(ctx, success);
+ onFinishCallback.accept(ctx);
+ }
+
+ @Override
public void postLaunch(Map<String, Object> launchArguments, DebugAdapterContext context) {
// Do not send InitializedEvent, so that we do not get DAP requests.
return;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspInternalHandle.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspInternalHandle.java
new file mode 100644
index 0000000..62a5c80
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspInternalHandle.java
@@ -0,0 +1,219 @@
+/*
+ * 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.progress;
+
+import java.lang.reflect.Field;
+import java.text.MessageFormat;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.eclipse.lsp4j.ProgressParams;
+import org.eclipse.lsp4j.WorkDoneProgressBegin;
+import org.eclipse.lsp4j.WorkDoneProgressEnd;
+import org.eclipse.lsp4j.WorkDoneProgressNotification;
+import org.eclipse.lsp4j.WorkDoneProgressReport;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEvent;
+import org.openide.util.Cancellable;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author sdedic
+ */
+public class LspInternalHandle extends InternalHandle {
+ private static final Logger LOG = Logger.getLogger(LspInternalHandle.class.getName());
+
+ private final NbCodeLanguageClient lspClient;
+ private final OperationContext opContext;
+ private final Function<InternalHandle, Controller> controllerProvider;
+
+ private CompletableFuture<Either<String, Number>> tokenPromise;
+ private int reportedPercentage;
+
+ /**
+ * Set from the START event handler. Workaround for NETBEANS-5167 if start event is skipped.
+ */
+ private boolean started;
+
+ private boolean explicitCancelRequest;
+
+ private static Field controllerField;
+
+ public LspInternalHandle(OperationContext opContext,
+ NbCodeLanguageClient lspClient, Function<InternalHandle, Controller> controllerProvider,
+ String displayName, Cancellable cancel, boolean userInitiated) {
+ super(displayName, cancel, userInitiated);
+ this.lspClient = lspClient;
+ this.opContext = opContext;
+ this.controllerProvider = controllerProvider;
+ if (opContext != null) {
+ opContext.internalHandleCreated(this);
+ }
+ }
+
+ public void forceRequestCancel() {
+ explicitCancelRequest = true;
+ requestCancel();
+ }
+
+ @Override
+ public boolean isAllowCancel() {
+ return super.isAllowCancel() && (explicitCancelRequest || !opContext.isDisableCancels());
+ }
+
+ public OperationContext getContext() {
+ return opContext;
+ }
+
+ private synchronized Controller findController() {
+ if (controllerField == null) {
+ try {
+ controllerField = InternalHandle.class.getDeclaredField("controller");
+ controllerField.setAccessible(true);
+ } catch (NoSuchFieldException | SecurityException ex) {
+ throw new IllegalStateException();
+ }
+ }
+ try {
+ return (Controller)controllerField.get(this);
+ } catch (IllegalArgumentException | IllegalAccessException ex) {
+ Exceptions.printStackTrace(ex);
+ return null;
+ }
+ }
+
+ @Override
+ public synchronized void start(String message, int workunits, long estimate) {
+ Controller attached = findController();
+ if (attached == null) {
+ setController(controllerProvider.apply(this));
+ }
+ super.start(message, workunits, estimate);
+ }
+
+ @Override
+ public void requestView() {
+ // no op
+ }
+
+ @Override
+ public boolean isCustomPlaced() {
+ return false;
+ }
+
+ @Override
+ public boolean isAllowView() {
+ // by default not supported.
+ return false;
+ }
+
+ String id() {
+ return Integer.toHexString(System.identityHashCode(this));
+ }
+
+ void sendStartMessage(ProgressEvent e) {
+ WorkDoneProgressBegin start = new WorkDoneProgressBegin();
+ boolean determinate = getTotalUnits() > 0;
+ start.setCancellable(isAllowCancel());
+ start.setTitle(getDisplayName());
+ if (determinate) {
+ double percent = e.getPercentageDone();
+ if (percent != -1) {
+ start.setPercentage(cleverFloor(percent));
+ } else {
+ start.setPercentage(0);
+ }
+ }
+ LOG.log(Level.FINE, "Starting progress: {0}", this);
+ started = true;
+ notify(start);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("LspProgress@").append(id()).append("[");
+ sb.append("display: ").append(getDisplayName()).
+ append(", total: ").append(getTotalUnits()).
+ append(", percent: ").append(String.format("%3.2f", getPercentageDone())).
+ append(", state: ").append(getState());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ static int cleverFloor(double percent) {
+ return (int)(percent > 90.0 ? Math.floor(percent) : Math.round(percent));
+ }
+
+ void sendProgress(ProgressEvent e) {
+ if (!started) {
+ sendStartMessage(e);
+ return;
+ }
+ WorkDoneProgressReport report = new WorkDoneProgressReport();
+
+ double percent = e.getPercentageDone();
+ if (percent != -1) {
+ report.setPercentage(cleverFloor(percent));
+ }
+ report.setMessage(e.getMessage());
+ report.setCancellable(isAllowCancel());
+ notify(report);
+ }
+
+ Either<String, Number> token() {
+ if (tokenPromise == null || tokenPromise.isCompletedExceptionally()) {
+ return null;
+ } else {
+ return tokenPromise.getNow(null);
+ }
+ }
+
+ void sendFinish(ProgressEvent e) {
+ WorkDoneProgressEnd end = new WorkDoneProgressEnd();
+ end.setMessage(e.getMessage());
+ notify(end);
+ opContext.removeHandle(token(), this);
+ }
+
+ CompletableFuture<Either<String, Number>> findProgressToken() {
+ if (tokenPromise != null) {
+ return tokenPromise;
+ }
+ return tokenPromise = opContext.acquireOrObtainToken(this);
+ }
+
+ void notify(WorkDoneProgressNotification msg) {
+ findProgressToken().thenAccept(token -> {
+ LOG.log(Level.FINER, () ->
+ MessageFormat.format("Sending progress {0}, msg: {1}",
+ id(), msg
+ )
+ );
+ ProgressParams param = new ProgressParams(token, msg);
+ lspClient.notifyProgress(param);
+ });
+ }
+
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspProgressUIWorker.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspProgressUIWorker.java
new file mode 100644
index 0000000..9919a5a
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspProgressUIWorker.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.progress;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEvent;
+import org.netbeans.modules.progress.spi.ProgressUIWorkerWithModel;
+import org.netbeans.modules.progress.spi.TaskModel;
+
+/**
+ *
+ * @author sdedic
+ */
+public class LspProgressUIWorker implements ProgressUIWorkerWithModel{
+ private static final Logger LOG = Logger.getLogger(LspProgressUIWorker.class.getName());
+
+ private TaskModel taskModel;
+
+ public LspProgressUIWorker() {
+ }
+
+ @Override
+ public void setModel(TaskModel model) {
+ this.taskModel = model;
+ }
+
+ @Override
+ public void showPopup() {
+ // ??
+ }
+
+ @Override
+ public void processProgressEvent(ProgressEvent event) {
+ InternalHandle h = event.getSource();
+ if (!(h instanceof LspInternalHandle)) {
+ // sorry ...
+ return;
+ }
+ LspInternalHandle lsHandle = (LspInternalHandle)h;
+
+ switch (event.getType()) {
+ case ProgressEvent.TYPE_START:
+ lsHandle.sendStartMessage(event);
+ break;
+
+ case ProgressEvent.TYPE_PROGRESS:
+ lsHandle.sendProgress(event);
+ break;
+
+ case ProgressEvent.TYPE_SWITCH:
+ case ProgressEvent.TYPE_SILENT:
+ case ProgressEvent.TYPE_REQUEST_STOP:
+ // ignore
+ break;
+
+ case ProgressEvent.TYPE_FINISH:
+ lsHandle.sendFinish(event);
+ break;
+ default:
+ LOG.log(Level.INFO, "Unexpected progress event type for {0}: {1}", new Object[] {
+ lsHandle, event.getType()
+ });
+ break;
+ }
+ }
+
+ @Override
+ public void processSelectedProgressEvent(ProgressEvent event) {
+ // No support from LSP / VScode UI. Most probably can be handled
+ // on the client side, if it stacks progresses somehow.
+ }
+
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/OperationContext.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/OperationContext.java
new file mode 100644
index 0000000..38d6d83
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/OperationContext.java
@@ -0,0 +1,272 @@
+/*
+ * 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.progress;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.TaskModel;
+import org.openide.util.Lookup;
+
+/**
+ * Operation context for reporting progress. Initially the instance is
+ * request-scoped in the initial operation request. Since it becomes part of
+ * the default Lookup, it should be handed into any RequestProcessor-forked
+ * tasks initiated from the operation request.
+ *
+ * @author sdedic
+ */
+public final class OperationContext {
+ private static Reference<OperationContext> lastCtx = new WeakReference<>(null);
+
+ /**
+ * Initial context
+ */
+ private static Reference<OperationContext> initialCtx = new WeakReference<>(null);
+
+ /**
+ * LSP client that issues progress tokens.
+ */
+ private final NbCodeLanguageClient client;
+
+ /**
+ * Progress token for reporting progress to the client. The token is valid
+ * for a single ProgressHandle created.
+ */
+ private Either<String, Number> progressToken;
+
+ /**
+ * Token to report partial results to the client.
+ */
+ private String partialResultsToken;
+
+ /**
+ * The controller that collects tasks in progress.
+ */
+ private final Controller progressController;
+
+ /**
+ * Handles created during the operation.
+ */
+ private final List<InternalHandle> createdHandles = new ArrayList<>();
+
+ private boolean finished;
+
+ private final OperationContext top;
+
+ private boolean disableCancels;
+
+ OperationContext(OperationContext top, NbCodeLanguageClient client, Controller controller) {
+ this.client = client;
+ this.progressController = controller;
+ this.top = top == null ? this : top;
+ }
+
+ private TaskModel getTaskModel() {
+ return progressController.getModel();
+ }
+
+ /**
+ * Acquires a progress token. The token is either issued by the client,
+ * from its original operation request, or the server will initiate a
+ * progress on the client, asking it to issue a token.
+ * @return
+ */
+ public synchronized Either<String, Number> acquireProgressToken() {
+ Either<String, Number> t = progressToken;
+ progressToken = null;
+ return t;
+ }
+
+ public String getPartialResultsToken() {
+ return partialResultsToken;
+ }
+
+ /**
+ * INTERNAL. Do not call this method; it will be likely removed
+ * soon.
+ * @param s
+ */
+ public void setProgressToken(Either<String, Number> s) {
+ progressToken = s;
+ }
+
+ public OperationContext operationContext() {
+ OperationContext ctx = new OperationContext(this, client, progressController);
+ lastCtx = new WeakReference<>(ctx);
+ return ctx;
+ }
+
+ public static synchronized OperationContext create(NbCodeLanguageClient client) {
+ OperationContext ctx = new OperationContext(null, client,
+ new Controller(new LspProgressUIWorker()));
+ lastCtx = new WeakReference<>(ctx);
+ ctx.registerInitialContext();
+ return ctx;
+ }
+
+ void registerInitialContext() {
+ initialCtx = new WeakReference<>(this);
+ }
+
+ public NbCodeLanguageClient getClient() {
+ return client;
+ }
+
+ public static synchronized OperationContext find(Lookup lkp) {
+ OperationContext ctx;
+ Lookup def = Lookup.getDefault();
+ if (lkp == null) {
+ lkp = def;
+ }
+ ctx = lkp.lookup(OperationContext.class);
+ if (ctx != null) {
+ return ctx;
+ }
+ ctx = lastCtx.get();
+ if (ctx == null) {
+ ctx = initialCtx.get();
+ if (ctx == null) {
+ ctx = create(lkp.lookup(NbCodeLanguageClient.class));
+ }
+ }
+ lastCtx = new WeakReference<>(ctx);
+ return ctx;
+ }
+
+ public void stop() {
+ acquireProgressToken();
+ if (!isActive()) {
+ return;
+ }
+ if (this != top) {
+ finished = true;
+ }
+ }
+
+ public boolean isActive() {
+ return Lookup.getDefault().lookup(OperationContext.class) == this;
+ }
+
+ /**
+ * Finds an active handle identified by client's progress token.
+ * @param token token
+ * @return handle instance or {@code null}
+ */
+ public InternalHandle findActiveHandle(Either<String, Number> token) {
+ if (top != this) {
+ return top.findActiveHandle(token);
+ }
+ synchronized (this) {
+ return handles.get(token);
+ }
+ }
+
+ private Either<String, Number> addHandle(Either<String, Number> token, InternalHandle h) {
+ top.registerHandle(token, h);
+ return token;
+ }
+
+ void removeHandle(Either<String, Number> token, InternalHandle h) {
+ if (top != this) {
+ top.unregisterHandle(token, h);
+ } else {
+ unregisterHandle(token, h);
+ }
+ }
+
+ private Map<Either<String, Number>, InternalHandle> handles = new HashMap<>();
+
+ private synchronized void unregisterHandle(Either<String, Number> token, InternalHandle h) {
+ if (token == null) {
+ handles.values().remove(h);
+ } else {
+ handles.remove(token);
+ }
+ }
+
+ private synchronized void registerHandle(Either<String, Number> token, InternalHandle h) {
+ handles.put(token, h);
+ }
+
+ CompletableFuture<Either<String, Number>> acquireOrObtainToken(InternalHandle h) {
+ Either<String, Number> t = acquireProgressToken();
+ if (t != null) {
+ return CompletableFuture.completedFuture(addHandle(t, h));
+ } else {
+ WorkDoneProgressCreateParams params = new WorkDoneProgressCreateParams(
+ Either.forLeft(UUID.randomUUID().toString())
+ );
+ CompletableFuture<Either<String, Number>> tokenPromise = client.
+ createProgress(params).thenApply(v -> params.getToken());
+ return tokenPromise.thenApply((p) -> {
+ synchronized (this) {
+ return addHandle(p, h);
+ }
+ });
+ }
+ }
+
+ public void disableCancels() {
+ if (top != this) {
+ disableCancels = true;
+ }
+ }
+
+ public boolean isDisableCancels() {
+ return disableCancels;
+ }
+
+ public static OperationContext getHandleContext(InternalHandle h) {
+ if (!(h instanceof LspInternalHandle)) {
+ return null;
+ }
+ return ((LspInternalHandle)h).getContext();
+ }
+
+ public Collection<InternalHandle> getAllActiveHandles() {
+ if (top != this) {
+ return top.getAllActiveHandles();
+ }
+ synchronized (this) {
+ return new ArrayList<>(handles.values());
+ }
+ }
+
+ public synchronized List<InternalHandle> getOperationHandles() {
+ return new ArrayList<>(createdHandles);
+ }
+
+ void internalHandleCreated(InternalHandle h) {
+ synchronized (this) {
+ createdHandles.add(h);
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java
new file mode 100644
index 0000000..9c741f8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java
@@ -0,0 +1,215 @@
+/*
+ * 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.protocol;
+
+import com.sun.source.tree.LineMap;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.Command;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.modules.editor.java.Utilities;
+import org.netbeans.modules.java.source.ElementHandleAccessor;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class CodeGenerator {
+
+ public static final String CODE_GENERATOR_KIND = "source.generate";
+ protected static final String ERROR = "<error>"; //NOI18N
+
+ public abstract List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params);
+
+ public abstract Set<String> getCommands();
+
+ public abstract CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments);
+
+ protected static int getOffset(CompilationInfo info, Position pos) {
+ LineMap lm = info.getCompilationUnit().getLineMap();
+ return (int) lm.getPosition(pos.getLine() + 1, pos.getCharacter() + 1);
+ }
+
+ protected static CodeAction createCodeAction(String name, String kind, String command, Object... args) {
+ CodeAction action = new CodeAction(name);
+ action.setKind(kind);
+ action.setCommand(new Command(name, command, Arrays.asList(args)));
+ return action;
+ }
+
+ protected static String createLabel(CompilationInfo info, TypeElement e) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(e.getSimpleName());
+ List<? extends TypeParameterElement> typeParams = e.getTypeParameters();
+ if (typeParams != null && !typeParams.isEmpty()) {
+ sb.append("<"); // NOI18N
+ for(Iterator<? extends TypeParameterElement> it = typeParams.iterator(); it.hasNext();) {
+ TypeParameterElement tp = it.next();
+ sb.append(tp.getSimpleName());
+ List<? extends TypeMirror> bounds = tp.getBounds();
+ if (!bounds.isEmpty()) {
+ if (bounds.size() > 1 || !"java.lang.Object".equals(bounds.get(0).toString())) { // NOI18N
+ sb.append(" extends "); // NOI18N
+ for (Iterator<? extends TypeMirror> bIt = bounds.iterator(); bIt.hasNext();) {
+ sb.append(Utilities.getTypeName(info, bIt.next(), false));
+ if (bIt.hasNext()) {
+ sb.append(" & "); // NOI18N
+ }
+ }
+ }
+ }
+ if (it.hasNext()) {
+ sb.append(", "); // NOI18N
+ }
+ }
+ sb.append(">"); // NOI18N
+ }
+ return sb.toString();
+ }
+
+ protected static String createLabel(CompilationInfo info, VariableElement e) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(e.getSimpleName());
+ if (e.getKind() != ElementKind.ENUM_CONSTANT) {
+ sb.append(" : "); // NOI18N
+ sb.append(Utilities.getTypeName(info, e.asType(), false));
+ }
+ return sb.toString();
+ }
+
+ protected static String createLabel(CompilationInfo info, ExecutableElement e) {
+ StringBuilder sb = new StringBuilder();
+ if (e.getKind() == ElementKind.CONSTRUCTOR) {
+ sb.append(e.getEnclosingElement().getSimpleName());
+ } else {
+ sb.append(e.getSimpleName());
+ }
+ sb.append("("); // NOI18N
+ for (Iterator<? extends VariableElement> it = e.getParameters().iterator(); it.hasNext();) {
+ VariableElement param = it.next();
+ if (!it.hasNext() && e.isVarArgs() && param.asType().getKind() == TypeKind.ARRAY) {
+ sb.append(Utilities.getTypeName(info, ((ArrayType) param.asType()).getComponentType(), false));
+ sb.append("...");
+ } else {
+ sb.append(Utilities.getTypeName(info, param.asType(), false));
+ }
+ sb.append(" "); // NOI18N
+ sb.append(param.getSimpleName());
+ if (it.hasNext()) {
+ sb.append(", "); // NOI18N
+ }
+ }
+ sb.append(")"); // NOI18N
+ if (e.getKind() != ElementKind.CONSTRUCTOR) {
+ TypeMirror rt = e.getReturnType();
+ if (rt.getKind() != TypeKind.VOID) {
+ sb.append(" : "); // NOI18N
+ sb.append(Utilities.getTypeName(info, e.getReturnType(), false));
+ }
+ }
+ return sb.toString();
+ }
+
+ public static class ElementData {
+
+ private String kind;
+ private String[] signature;
+
+ public ElementData() {
+ }
+
+ public ElementData(Element element) {
+ ElementHandle<Element> handle = ElementHandle.create(element);
+ this.kind = handle.getKind().name();
+ this.signature = ElementHandleAccessor.getInstance().getJVMSignature(handle);
+ }
+
+ Element resolve(CompilationInfo info) {
+ ElementHandle handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(kind), signature);
+ return handle.resolve(info);
+ }
+
+ @Pure
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(final String kind) {
+ this.kind = kind;
+ }
+
+ @Pure
+ public String[] getSignature() {
+ return signature;
+ }
+
+ public void setSignature(final String[] signature) {
+ this.signature = signature;
+ }
+
+ @Override
+ @Pure
+ public int hashCode() {
+ int hash = 7;
+ hash = 97 * hash + Objects.hashCode(this.kind);
+ hash = 97 * hash + Arrays.deepHashCode(this.signature);
+ return hash;
+ }
+
+ @Override
+ @Pure
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final ElementData other = (ElementData) obj;
+ if (this.kind != other.kind) {
+ return false;
+ }
+ if (!Arrays.deepEquals(this.signature, other.signature)) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
new file mode 100644
index 0000000..27e3dd8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
@@ -0,0 +1,231 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 10)
+public final class ConstructorGenerator extends CodeGenerator {
+
+ public static final String GENERATE_CONSTRUCTOR = "java.generate.constructor";
+
+ private final Set<String> commands = Collections.singleton(GENERATE_CONSTRUCTOR);
+ private final Gson gson = new Gson();
+
+ public ConstructorGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateConstructor=Generate Constructor...",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Source)) {
+ return Collections.emptyList();
+ }
+ int offset = getOffset(info, params.getRange().getStart());
+ TreePath tp = info.getTreeUtilities().pathFor(offset);
+ tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp == null) {
+ return Collections.emptyList();
+ }
+ TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+ if (typeElement == null || !typeElement.getKind().isClass() || NestingKind.ANONYMOUS.equals(typeElement.getNestingKind())) {
+ return Collections.emptyList();
+ }
+ final Set<? extends VariableElement> uninitializedFields = info.getTreeUtilities().getUninitializedFields(tp);
+ final List<ExecutableElement> inheritedConstructors = new ArrayList<>();
+ TypeMirror superClassType = typeElement.getSuperclass();
+ if (superClassType.getKind() == TypeKind.DECLARED) {
+ TypeElement superClass = (TypeElement) ((DeclaredType) superClassType).asElement();
+ Elements elements = info.getElements();
+ for (ExecutableElement executableElement : ElementFilter.constructorsIn(superClass.getEnclosedElements())) {
+ PackageElement currentPackage = elements.getPackageOf(typeElement);
+ PackageElement ctorPackage = elements.getPackageOf(executableElement);
+ Set<Modifier> ctorMods = executableElement.getModifiers();
+ if ((currentPackage != ctorPackage && !(ctorMods.contains(Modifier.PUBLIC) || ctorMods.contains(Modifier.PROTECTED)))
+ || ctorMods.contains(Modifier.PRIVATE)) {
+ continue;
+ }
+ inheritedConstructors.add(executableElement);
+ }
+ }
+ List<QuickPickItem> constructors;
+ if (typeElement.getKind() != ElementKind.ENUM && inheritedConstructors.size() == 1) {
+ if (uninitializedFields.isEmpty() && inheritedConstructors.get(0).getParameters().isEmpty()
+ && ElementFilter.constructorsIn(typeElement.getEnclosedElements()).stream().filter(ctor -> ctor.getParameters().isEmpty() && !info.getElementUtilities().isSynthetic(ctor)).count() > 0) {
+ constructors = Collections.emptyList();
+ } else {
+ QuickPickItem item = new QuickPickItem(createLabel(info, inheritedConstructors.get(0)));
+ item.setUserData(new ElementData(inheritedConstructors.get(0)));
+ constructors = Collections.singletonList(item);
+ }
+ } else if (inheritedConstructors.size() > 1) {
+ constructors = new ArrayList<>(inheritedConstructors.size());
+ for (ExecutableElement constructorElement : inheritedConstructors) {
+ QuickPickItem item = new QuickPickItem(createLabel(info, constructorElement));
+ item.setUserData(new ElementData(constructorElement));
+ constructors.add(item);
+ }
+ } else {
+ constructors = Collections.emptyList();
+ }
+ List<QuickPickItem> fields;
+ if (uninitializedFields.isEmpty()) {
+ fields = Collections.emptyList();
+ } else {
+ fields = new ArrayList<>();
+ for (VariableElement variableElement : uninitializedFields) {
+ QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+ item.setUserData(new ElementData(variableElement));
+ fields.add(item);
+ }
+ }
+ if (constructors.isEmpty() && fields.isEmpty()) {
+ return Collections.emptyList();
+ }
+ String uri = Utils.toUri(info.getFileObject());
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateConstructor(), CODE_GENERATOR_KIND, GENERATE_CONSTRUCTOR, uri, offset, constructors, fields));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectSuperConstructor=Select super constructor",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 3) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ List<QuickPickItem> constructors = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+ List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+ if (constructors.size() < 2 && fields.isEmpty()) {
+ generate(client, uri, offset, constructors, fields);
+ } else {
+ if (constructors.size() > 1) {
+ client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectSuperConstructor(), true, constructors)).thenAccept(selected -> {
+ if (selected != null) {
+ selectFields(client, uri, offset, selected, fields);
+ }
+ });
+ } else {
+ selectFields(client, uri, offset, constructors, fields);
+ }
+ }
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ @NbBundle.Messages({
+ "DN_SelectConstructorFields=Select fields to be initialized by constructor",
+ })
+ private void selectFields(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+ if (!fields.isEmpty()) {
+ client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectConstructorFields(), true, fields)).thenAccept(selected -> {
+ if (selected != null) {
+ generate(client, uri, offset, constructors, selected);
+ }
+ });
+ } else {
+ generate(client, uri, offset, constructors, fields);
+ }
+ }
+
+ @NbBundle.Messages({
+ "DN_ConstructorAlreadyExists=Given constructor already exists",
+ })
+ private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp != null) {
+ List<ExecutableElement> selectedConstructors = constructors.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (ExecutableElement)data.resolve(wc);
+ }).collect(Collectors.toList());
+ List<VariableElement> selectedFields = fields.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (VariableElement)data.resolve(wc);
+ }).collect(Collectors.toList());
+ GeneratorUtils.generateConstructors(wc, tp, selectedFields, selectedConstructors, -1);
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (GeneratorUtils.DuplicateMemberException dme) {
+ client.showMessage(new MessageParams(MessageType.Info, Bundle.DN_ConstructorAlreadyExists()));
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
new file mode 100644
index 0000000..d59cec4
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
@@ -0,0 +1,223 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.Scope;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 60)
+public final class DelegateMethodGenerator extends CodeGenerator {
+
+ public static final String GENERATE_DELEGATE_METHOD = "java.generate.delegateMethod";
+
+ private final Set<String> commands = Collections.singleton(GENERATE_DELEGATE_METHOD);
+ private final Gson gson = new Gson();
+
+ public DelegateMethodGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateDelegateMethod=Generate Delegate Method...",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Source)) {
+ return Collections.emptyList();
+ }
+ int offset = getOffset(info, params.getRange().getStart());
+ TreePath tp = info.getTreeUtilities().pathFor(offset);
+ tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp == null) {
+ return Collections.emptyList();
+ }
+ TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+ if (typeElement == null || !typeElement.getKind().isClass()) {
+ return Collections.emptyList();
+ }
+ Elements elements = info.getElements();
+ Trees trees = info.getTrees();
+ Scope scope = trees.getScope(tp);
+ List<QuickPickItem> fields = new ArrayList<>();
+ TypeElement cls;
+ while (scope != null && (cls = scope.getEnclosingClass()) != null) {
+ DeclaredType type = (DeclaredType) cls.asType();
+ for (VariableElement field : ElementFilter.fieldsIn(elements.getAllMembers(cls))) {
+ TypeMirror fieldType = field.asType();
+ if (!ERROR.contentEquals(field.getSimpleName()) && !fieldType.getKind().isPrimitive() && fieldType.getKind() != TypeKind.ARRAY
+ && (fieldType.getKind() != TypeKind.DECLARED || ((DeclaredType)fieldType).asElement() != cls) && trees.isAccessible(scope, field, type)) {
+ QuickPickItem item = new QuickPickItem(createLabel(info, field));
+ item.setUserData(new ElementData(field));
+ fields.add(item);
+ }
+ }
+ scope = scope.getEnclosingScope();
+ }
+ if (fields.isEmpty()) {
+ return Collections.emptyList();
+ }
+ String uri = Utils.toUri(info.getFileObject());
+ QuickPickItem typeItem = new QuickPickItem(createLabel(info, typeElement));
+ typeItem.setUserData(new ElementData(typeElement));
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateDelegateMethod(), CODE_GENERATOR_KIND, GENERATE_DELEGATE_METHOD, uri, offset, typeItem, fields));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectDelegateMethodField=Select target field to generate delegates for",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 3) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ QuickPickItem type = gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem.class);
+ List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+ if (fields.size() == 1) {
+ selectMethods(client, uri, offset, type, fields.get(0));
+ } else {
+ client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethodField(), false, fields)).thenAccept(selected -> {
+ if (selected != null && !selected.isEmpty()) {
+ selectMethods(client, uri, offset, type, selected.get(0));
+ }
+ });
+ }
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ @NbBundle.Messages({
+ "DN_SelectDelegateMethods=Select methods to generate delegates for",
+ })
+ private void selectMethods(NbCodeLanguageClient client, String uri, int offset, QuickPickItem type, QuickPickItem selectedField) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ js.runUserActionTask(info -> {
+ info.toPhase(JavaSource.Phase.RESOLVED);
+ TypeElement origin = (TypeElement) gson.fromJson(gson.toJson(type.getUserData()), ElementData.class).resolve(info);
+ VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(info);
+ if (origin != null && field != null) {
+ final ElementUtilities eu = info.getElementUtilities();
+ final Trees trees = info.getTrees();
+ final Scope scope = info.getTreeUtilities().scopeFor(offset);
+ ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
+ @Override
+ public boolean accept(Element e, TypeMirror type) {
+ if (e.getKind() == ElementKind.METHOD && trees.isAccessible(scope, e, (DeclaredType)type)) {
+ Element impl = eu.getImplementationOf((ExecutableElement)e, origin);
+ return impl == null || (!impl.getModifiers().contains(Modifier.FINAL) && impl.getEnclosingElement() != origin);
+ }
+ return false;
+ }
+ };
+ List<QuickPickItem> methods = new ArrayList<>();
+ for (ExecutableElement method : ElementFilter.methodsIn(eu.getMembers(field.asType(), acceptor))) {
+ QuickPickItem item = new QuickPickItem(String.format("%s.%s", field.getSimpleName().toString(), createLabel(info, method)));
+ item.setUserData(new ElementData(method));
+ methods.add(item);
+ }
+ client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethods(), true, methods)).thenAccept(selected -> {
+ if (selected != null && !selected.isEmpty()) {
+ generate(client, uri, offset, selectedField, selected);
+ }
+ });
+ }
+ }, true);
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+
+ private void generate(NbCodeLanguageClient client, String uri, int offset, QuickPickItem selectedField, List<QuickPickItem> selectedMethods) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp != null) {
+ VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(wc);
+ List<ExecutableElement> methods = selectedMethods.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (ExecutableElement)data.resolve(wc);
+ }).collect(Collectors.toList());
+ org.netbeans.modules.java.editor.codegen.DelegateMethodGenerator.generateDelegatingMethods(wc, tp, field, methods, -1);
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
new file mode 100644
index 0000000..45fb6d3
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
@@ -0,0 +1,172 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementFilter;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 40)
+public final class EqualsHashCodeGenerator extends CodeGenerator {
+
+ public static final String GENERATE_EQUALS = "java.generate.equals";
+ public static final String GENERATE_HASH_CODE = "java.generate.hashCode";
+ public static final String GENERATE_EQUALS_HASH_CODE = "java.generate.equals.hashCode";
+
+ private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_EQUALS_HASH_CODE, GENERATE_EQUALS, GENERATE_HASH_CODE)));
+ private final Gson gson = new Gson();
+
+ public EqualsHashCodeGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateEquals=Generate equals()...",
+ "DN_GenerateHashCode=Generate hashCode()...",
+ "DN_GenerateEqualsHashCode=Generate equals() and hashCode()...",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Source)) {
+ return Collections.emptyList();
+ }
+ int offset = getOffset(info, params.getRange().getStart());
+ TreePath tp = info.getTreeUtilities().pathFor(offset);
+ tp = info.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
+ if (tp == null) {
+ return Collections.emptyList();
+ }
+ TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+ if (type == null || type.getKind() != ElementKind.CLASS) {
+ return Collections.emptyList();
+ }
+ ExecutableElement[] equalsHashCode = org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.overridesHashCodeAndEquals(info, type, null);
+ if (equalsHashCode[0] != null && equalsHashCode[1] != null) {
+ return Collections.emptyList();
+ }
+ List<QuickPickItem> fields = new ArrayList<>();
+ for (VariableElement variableElement : ElementFilter.fieldsIn(type.getEnclosedElements())) {
+ if (!ERROR.contentEquals(variableElement.getSimpleName()) && !variableElement.getModifiers().contains(Modifier.STATIC)) {
+ QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+ item.setUserData(new ElementData(variableElement));
+ fields.add(item);
+ }
+ }
+ if (fields.isEmpty()) {
+ return Collections.emptyList();
+ }
+ String uri = Utils.toUri(info.getFileObject());
+ if (equalsHashCode[0] == null) {
+ if (equalsHashCode[1] == null) {
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEqualsHashCode(), CODE_GENERATOR_KIND, GENERATE_EQUALS_HASH_CODE, uri, offset, fields));
+ }
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEquals(), CODE_GENERATOR_KIND, GENERATE_EQUALS, uri, offset, fields));
+ }
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateHashCode(), CODE_GENERATOR_KIND, GENERATE_HASH_CODE, uri, offset, fields));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectEquals=Select fields to be included in equals()",
+ "DN_SelectHashCode=Select fields to be included in hashCode()",
+ "DN_SelectEqualsHashCode=Select fields to be included in equals() and hashCode()",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 2) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+ String text;
+ boolean generateEquals = !GENERATE_HASH_CODE.equals(command);
+ boolean generateHashCode = !GENERATE_EQUALS.equals(command);
+ switch (command) {
+ case GENERATE_EQUALS: text = Bundle.DN_SelectEquals(); break;
+ case GENERATE_HASH_CODE: text = Bundle.DN_SelectHashCode(); break;
+ default: text = Bundle.DN_SelectEqualsHashCode(); break;
+ }
+ client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
+ if (selected != null) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
+ if (tp != null) {
+ List<VariableElement> selectedFields = selected.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (VariableElement)data.resolve(wc);
+ }).collect(Collectors.toList());
+ org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.generateEqualsAndHashCode(wc, tp, generateEquals ? selectedFields : null, generateHashCode ? selectedFields : null, -1);
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+ });
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
new file mode 100644
index 0000000..4b7df97
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
@@ -0,0 +1,250 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementFilter;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CodeStyle;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.Pair;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 30)
+public final class GetterSetterGenerator extends CodeGenerator {
+
+ public static final String GENERATE_GETTERS = "java.generate.getters";
+ public static final String GENERATE_SETTERS = "java.generate.setters";
+ public static final String GENERATE_GETTERS_SETTERS = "java.generate.getters.setters";
+
+ private final Set<String> commands = Collections.unmodifiableSet(new HashSet(Arrays.asList(GENERATE_GETTERS, GENERATE_SETTERS, GENERATE_GETTERS_SETTERS)));
+ private final Gson gson = new Gson();
+
+ public GetterSetterGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateGetters=Generate Getters...",
+ "DN_GenerateSetters=Generate Setters...",
+ "DN_GenerateGettersSetters=Generate Getters and Setters...",
+ "DN_GenerateGetterFor=Generate Getter for \"{0}\"",
+ "DN_GenerateSetterFor=Generate Setter for \"{0}\"",
+ "DN_GenerateGetterSetterFor=Generate Getter and Setter for \"{0}\"",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ boolean all = only != null && only.contains(CodeActionKind.Source);
+ Pair<Set<VariableElement>, Set<VariableElement>> pair = findMissingGettersSetters(info, params.getRange(), all);
+ boolean missingGetters = !pair.first().isEmpty();
+ boolean missingSetters = !pair.second().isEmpty();
+ String uri = Utils.toUri(info.getFileObject());
+ int offset = getOffset(info, params.getRange().getStart());
+ List<CodeAction> result = new ArrayList<>();
+ if (missingGetters) {
+ String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGetters();
+ result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+ QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+ item.setUserData(new ElementData(variableElement));
+ return item;
+ }).collect(Collectors.toList())));
+ }
+ if (missingSetters) {
+ String name = pair.second().size() == 1 ? Bundle.DN_GenerateSetterFor(pair.second().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateSetters();
+ result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_SETTERS, uri, offset, all, pair.second().stream().map(variableElement -> {
+ QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+ item.setUserData(new ElementData(variableElement));
+ return item;
+ }).collect(Collectors.toList())));
+ }
+ if (missingGetters && missingSetters) {
+ pair.first().retainAll(pair.second());
+ String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterSetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGettersSetters();
+ result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS_SETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+ QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+ item.setUserData(new ElementData(variableElement));
+ return item;
+ }).collect(Collectors.toList())));
+ }
+ return result;
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectGetters=Select fields to generate getters for",
+ "DN_SelectSetters=Select fields to generate setters for",
+ "DN_SelectGettersSetters=Select fields to generate getters and setters for",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 3) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ boolean all = gson.fromJson(gson.toJson(arguments.get(2)), boolean.class);
+ List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+ int kind;
+ String text;
+ switch (command) {
+ case GENERATE_GETTERS: kind = GeneratorUtils.GETTERS_ONLY; text = Bundle.DN_SelectGetters(); break;
+ case GENERATE_SETTERS: kind = GeneratorUtils.SETTERS_ONLY; text = Bundle.DN_SelectSetters(); break;
+ default: kind = 0; text = Bundle.DN_SelectGettersSetters(); break;
+ }
+ if (all && fields.size() > 1) {
+ client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
+ if (selected != null && !selected.isEmpty()) {
+ generate(client, kind, uri, offset, selected);
+ }
+ });
+ } else if (fields.size() == 1) {
+ generate(client, kind, uri, offset, fields);
+ }
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ private void generate(NbCodeLanguageClient client, int kind, String uri, int offset, List<QuickPickItem> fields) throws IllegalArgumentException {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp != null) {
+ List<VariableElement> variableElements = fields.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (VariableElement) data.resolve(wc);
+ }).collect(Collectors.toList());
+ GeneratorUtils.generateGettersAndSetters(wc, tp, variableElements, kind, -1);
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (IOException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+
+ private static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, Range range, boolean all) {
+ TreePath tp = info.getTreeUtilities().pathFor(getOffset(info, range.getStart()));
+ tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp == null) {
+ return Pair.of(Collections.emptySet(), Collections.emptySet());
+ }
+
+ TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+ if (type == null) {
+ return Pair.of(Collections.emptySet(), Collections.emptySet());
+ }
+
+ int selectionStart = getOffset(info, range.getStart());
+ int selectionEnd = getOffset(info, range.getEnd());
+
+ ClassTree clazz = (ClassTree) tp.getLeaf();
+ Set<VariableElement> selectedFields = new HashSet<>();
+
+ for (Tree m : clazz.getMembers()) {
+ if (m.getKind() != Tree.Kind.VARIABLE) continue;
+ int start = (int) info.getTrees().getSourcePositions().getStartPosition(tp.getCompilationUnit(), m);
+ int end = (int) info.getTrees().getSourcePositions().getEndPosition(tp.getCompilationUnit(), m);
+
+ if (all || intersects(start, end, selectionStart, selectionEnd)) {
+ selectedFields.add((VariableElement) info.getTrees().getElement(new TreePath(tp, m)));
+ }
+ }
+
+ Pair<Set<VariableElement>, Set<VariableElement>> pair = findMissingGettersSetters(info, type);
+
+ pair.first().retainAll(selectedFields);
+ pair.second().retainAll(selectedFields);
+
+ return pair;
+ }
+
+ private static boolean intersects(int fieldStart, int fieldEnd, int selectionStart, int selectionEnd) {
+ return selectionStart <= fieldEnd && selectionEnd >= fieldStart;
+ }
+
+ private static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, TypeElement type) {
+ Set<VariableElement> missingGetters = new LinkedHashSet<>();
+ Set<VariableElement> missingSetters = new LinkedHashSet<>();
+ ElementUtilities eu = info.getElementUtilities();
+ CodeStyle codeStyle = CodeStyle.getDefault(info.getFileObject());
+
+ for (VariableElement variableElement : ElementFilter.fieldsIn(info.getElements().getAllMembers(type))) {
+ if (ERROR.contentEquals(variableElement.getSimpleName())) {
+ continue;
+ }
+ boolean hasGetter = eu.hasGetter(type, variableElement, codeStyle);
+ boolean hasSetter = variableElement.getModifiers().contains(Modifier.FINAL) ||
+ eu.hasSetter(type, variableElement, codeStyle);
+ if (!hasGetter) {
+ missingGetters.add(variableElement);
+ }
+ if (!hasSetter) {
+ missingSetters.add(variableElement);
+ }
+ }
+
+ return Pair.of(missingGetters, missingSetters);
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
new file mode 100644
index 0000000..231287d
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
@@ -0,0 +1,183 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 70)
+public final class ImplementOverrideMethodGenerator extends CodeGenerator {
+
+ public static final String GENERATE_IMPLEMENT_METHOD = "java.generate.implementMethod";
+ public static final String GENERATE_OVERRIDE_METHOD = "java.generate.overrideMethod";
+
+ private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_IMPLEMENT_METHOD, GENERATE_OVERRIDE_METHOD)));
+ private final Gson gson = new Gson();
+
+ public ImplementOverrideMethodGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateImplementMethod=Generate Implement Method...",
+ "DN_GenerateOverrideMethod=Generate Override Method...",
+ "DN_From=(from {0})",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Source)) {
+ return Collections.emptyList();
+ }
+ int offset = getOffset(info, params.getRange().getStart());
+ TreePath tp = info.getTreeUtilities().pathFor(offset);
+ tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp == null) {
+ return Collections.emptyList();
+ }
+ TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+ if (typeElement == null || typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
+ return Collections.emptyList();
+ }
+ List<CodeAction> result = new ArrayList<>();
+ String uri = Utils.toUri(info.getFileObject());
+ ElementUtilities eu = info.getElementUtilities();
+ if (typeElement.getKind().isClass() || typeElement.getKind().isInterface() && SourceVersion.RELEASE_8.compareTo(info.getSourceVersion()) <= 0) {
+ List<QuickPickItem> implementMethods = new ArrayList<>();
+ for (ExecutableElement method : eu.findUnimplementedMethods(typeElement, true)) {
+ boolean mustImplement = !method.getModifiers().contains(Modifier.DEFAULT);
+ Element enclosingElement = method.getEnclosingElement();
+ String enclosingTypeName = enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface() ? Bundle.DN_From(((TypeElement)enclosingElement).getQualifiedName().toString()) : null;
+ implementMethods.add(new QuickPickItem(createLabel(info, method), enclosingTypeName, null, mustImplement, new ElementData(method)));
+ }
+ if (!implementMethods.isEmpty()) {
+ result.add(createCodeAction(Bundle.DN_GenerateImplementMethod(), CODE_GENERATOR_KIND, GENERATE_IMPLEMENT_METHOD, uri, offset, implementMethods));
+ }
+ }
+ if (typeElement.getKind().isClass() || typeElement.getKind().isInterface()) {
+ List<QuickPickItem> overrideMethods = new ArrayList<>();
+ for (ExecutableElement method : eu.findOverridableMethods(typeElement)) {
+ Element enclosingElement = method.getEnclosingElement();
+ String enclosingTypeName = enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface() ? Bundle.DN_From(((TypeElement) enclosingElement).getQualifiedName().toString()) : null;
+ QuickPickItem item = new QuickPickItem(createLabel(info, method));
+ if (enclosingTypeName != null) {
+ item.setDescription(enclosingTypeName);
+ }
+ item.setUserData(new ElementData(method));
+ overrideMethods.add(item);
+ }
+ if (!overrideMethods.isEmpty()) {
+ result.add(createCodeAction(Bundle.DN_GenerateOverrideMethod(), CODE_GENERATOR_KIND, GENERATE_OVERRIDE_METHOD, uri, offset, overrideMethods));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectImplementMethod=Select methods to implement",
+ "DN_SelectOverrideMethod=Select methods to override",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 2) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ List<QuickPickItem> methods = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+ String text = command == GENERATE_IMPLEMENT_METHOD ? Bundle.DN_SelectImplementMethod() : Bundle.DN_SelectOverrideMethod();
+ boolean isImplement = command == GENERATE_IMPLEMENT_METHOD;
+ client.showQuickPick(new ShowQuickPickParams(text, true, methods)).thenAccept(selected -> {
+ if (selected != null && !selected.isEmpty()) {
+ generate(client, uri, offset, isImplement, selected);
+ }
+ });
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ private void generate(NbCodeLanguageClient client, String uri, int offset, boolean isImplement, List<QuickPickItem> methods) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp != null) {
+ List<ExecutableElement> selectedMethods = methods.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (ExecutableElement)data.resolve(wc);
+ }).collect(Collectors.toList());
+ if (isImplement) {
+ GeneratorUtils.generateAbstractMethodImplementations(wc, tp, selectedMethods, -1);
+ } else {
+ GeneratorUtils.generateMethodOverrides(wc, tp, selectedMethods, -1);
+ }
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
new file mode 100644
index 0000000..e0fcc30
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
@@ -0,0 +1,144 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.GeneratorUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.BaseUtilities;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 20)
+public final class LoggerGenerator extends CodeGenerator {
+
+ public static final String GENERATE_LOGGER = "java.generate.logger";
+
+ private final Set<String> commands = Collections.singleton(GENERATE_LOGGER);
+ private final Gson gson = new Gson();
+
+ public LoggerGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateLogger=Generate Logger...",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Source)) {
+ return Collections.emptyList();
+ }
+ int offset = getOffset(info, params.getRange().getStart());
+ TreePath tp = info.getTreeUtilities().pathFor(offset);
+ tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp == null) {
+ return Collections.emptyList();
+ }
+ TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+ if (typeElement == null || !typeElement.getKind().isClass()) {
+ return Collections.emptyList();
+ }
+ for (VariableElement ve : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
+ TypeMirror type = ve.asType();
+ if (type.getKind() == TypeKind.DECLARED && ((TypeElement)((DeclaredType)type).asElement()).getQualifiedName().contentEquals(Logger.class.getName())
+ || type.getKind() == TypeKind.ERROR && ((TypeElement)((DeclaredType)type).asElement()).getSimpleName().contentEquals(Logger.class.getSimpleName())) {
+ return Collections.emptyList();
+ }
+ }
+ String uri = Utils.toUri(info.getFileObject());
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateLogger(), CODE_GENERATOR_KIND, GENERATE_LOGGER, uri, offset));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectLoggerName=Logger field name",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 1) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ client.showInputBox(new ShowInputBoxParams(Bundle.DN_SelectLoggerName(), "LOG")).thenAccept(value -> {
+ if (value != null && BaseUtilities.isJavaIdentifier(value)) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp != null) {
+ ClassTree cls = (ClassTree) tp.getLeaf();
+ VariableTree field = org.netbeans.modules.java.editor.codegen.LoggerGenerator.createLoggerField(wc.getTreeMaker(), cls, value, EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL));
+ wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, field));
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+ });
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
index 41f7e0a..33046d3 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
@@ -20,6 +20,7 @@
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
+import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.InitializeParams;
/**
@@ -35,12 +36,21 @@
*/
public final class NbCodeClientCapabilities {
/**
+ * The LSP client official capabilities.
+ */
+ private ClientCapabilities clientCaps;
+
+ /**
* Supports status bar messages:
* <ul>
* <li>window/showStatusBarMessage
* </ul>
*/
private Boolean statusBarMessageSupport;
+
+ public ClientCapabilities getClientCapabilities() {
+ return clientCaps;
+ }
public Boolean getStatusBarMessageSupport() {
return statusBarMessageSupport;
@@ -54,6 +64,14 @@
this.statusBarMessageSupport = statusBarMessageSupport;
}
+ private NbCodeClientCapabilities withCapabilities(ClientCapabilities caps) {
+ if (caps == null) {
+ caps = new ClientCapabilities();
+ }
+ this.clientCaps = caps;
+ return this;
+ }
+
public static NbCodeClientCapabilities get(InitializeParams initParams) {
if (initParams == null) {
return null;
@@ -69,7 +87,7 @@
*/
create().
fromJson((JsonElement)ext, InitializationExtendedCapabilities.class);
- return root == null ? null : root.getNbcodeCapabilities();
+ return root == null ? null : root.getNbcodeCapabilities().withCapabilities(initParams.getCapabilities());
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
index c4ba4b5..975f018 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
@@ -25,11 +25,13 @@
import org.eclipse.lsp4j.ConfigurationParams;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.ProgressParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.RegistrationParams;
import org.eclipse.lsp4j.SemanticHighlightingParams;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.UnregistrationParams;
+import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
import org.eclipse.lsp4j.WorkspaceFolder;
/**
@@ -52,7 +54,7 @@
this.clientCaps = clientCaps;
}
}
-
+
@Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
return clientCaps;
@@ -64,6 +66,16 @@
}
@Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return remote.showQuickPick(params);
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ return remote.showInputBox(params);
+ }
+
+ @Override
public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
return remote.applyEdit(params);
}
@@ -117,4 +129,14 @@
public void semanticHighlighting(SemanticHighlightingParams params) {
remote.semanticHighlighting(params);
}
+
+ @Override
+ public CompletableFuture<Void> createProgress(WorkDoneProgressCreateParams params) {
+ return remote.createProgress(params);
+ }
+
+ @Override
+ public void notifyProgress(ProgressParams params) {
+ remote.notifyProgress(params);
+ }
}
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 06177ae..50fe926 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
@@ -18,8 +18,11 @@
*/
package org.netbeans.modules.java.lsp.server.protocol;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.lsp4j.services.LanguageClient;
@@ -40,7 +43,25 @@
*/
@JsonNotification("window/showStatusBarMessage")
public void showStatusBarMessage(@NonNull ShowStatusMessageParams params);
-
+
+ /**
+ * Shows a selection list allowing multiple selections.
+ *
+ * @param params input parameters
+ * @return selected items
+ */
+ @JsonRequest("window/showQuickPick")
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(@NonNull ShowQuickPickParams params);
+
+ /**
+ * Shows an input box to ask the user for input.
+ *
+ * @param params input parameters
+ * @return input value
+ */
+ @JsonRequest("window/showInputBox")
+ public CompletableFuture<String> showInputBox(@NonNull ShowInputBoxParams params);
+
/**
* Returns extended code capabilities.
* @return code capabilities.
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
new file mode 100644
index 0000000..3b17385
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
@@ -0,0 +1,206 @@
+/*
+ * 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.protocol;
+
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * Represents an item that can be selected from a list of items.
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class QuickPickItem {
+
+ /**
+ * A human-readable string which is rendered prominent.
+ */
+ @NonNull
+ private String label;
+
+ /**
+ * A human-readable string which is rendered less prominent in the same line.
+ */
+ private String description;
+
+ /**
+ * A human-readable string which is rendered less prominent in a separate line.
+ */
+ private String detail;
+
+ /**
+ * Optional flag indicating if this item is picked initially.
+ */
+ private boolean picked;
+
+ /**
+ * Optional user data.
+ */
+ private Object userData;
+
+ public QuickPickItem() {
+ }
+
+ public QuickPickItem(@NonNull final String label) {
+ this.label = Preconditions.checkNotNull(label, "label");
+ }
+
+ public QuickPickItem(@NonNull final String label, final String description, final String detail, final boolean picked, final Object userData) {
+ this(label);
+ this.description = description;
+ this.detail = detail;
+ this.picked = picked;
+ this.userData = userData;
+ }
+
+ /**
+ * A human-readable string which is rendered prominent.
+ */
+ @Pure
+ @NonNull
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * A human-readable string which is rendered prominent.
+ */
+ public void setLabel(@NonNull final String label) {
+ this.label = label;
+ }
+
+ /**
+ * A human-readable string which is rendered less prominent in the same line.
+ */
+ @Pure
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * A human-readable string which is rendered less prominent in the same line.
+ */
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * A human-readable string which is rendered less prominent in a separate line.
+ */
+ @Pure
+ public String getDetail() {
+ return detail;
+ }
+
+ /**
+ * A human-readable string which is rendered less prominent in a separate line.
+ */
+ public void setDetail(final String detail) {
+ this.detail = detail;
+ }
+
+ /**
+ * Optional flag indicating if this item is picked initially.
+ */
+ @Pure
+ public boolean isPicked() {
+ return picked;
+ }
+
+ /**
+ * Optional flag indicating if this item is picked initially.
+ */
+ public void setPicked(boolean picked) {
+ this.picked = picked;
+ }
+
+ /**
+ * Optional user data.
+ */
+ @Pure
+ public Object getUserData() {
+ return userData;
+ }
+
+ /**
+ * Optional user data.
+ */
+ public void setUserData(final Object userData) {
+ this.userData = userData;
+ }
+
+ @Override
+ @Pure
+ public String toString() {
+ ToStringBuilder b = new ToStringBuilder(this);
+ b.add("label", label);
+ b.add("description", description);
+ b.add("detail", detail);
+ b.add("picked", picked);
+ b.add("userData", userData);
+ return b.toString();
+ }
+
+ @Override
+ @Pure
+ public int hashCode() {
+ int hash = 7;
+ hash = 83 * hash + Objects.hashCode(this.label);
+ hash = 83 * hash + Objects.hashCode(this.description);
+ hash = 83 * hash + Objects.hashCode(this.detail);
+ hash = 83 * hash + (this.picked ? 1 : 0);
+ hash = 83 * hash + Objects.hashCode(this.userData);
+ return hash;
+ }
+
+ @Override
+ @Pure
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final QuickPickItem other = (QuickPickItem) obj;
+ if (this.picked != other.picked) {
+ return false;
+ }
+ if (!Objects.equals(this.label, other.label)) {
+ return false;
+ }
+ if (!Objects.equals(this.description, other.description)) {
+ return false;
+ }
+ if (!Objects.equals(this.detail, other.detail)) {
+ return false;
+ }
+ if (!Objects.equals(this.userData, other.userData)) {
+ return false;
+ }
+ return true;
+ }
+}
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 af46181..1e5ff19 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
@@ -32,6 +32,8 @@
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionOptions;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.InitializeParams;
@@ -40,15 +42,20 @@
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
+import org.eclipse.lsp4j.RenameOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.TextDocumentSyncKind;
+import org.eclipse.lsp4j.WorkDoneProgressCancelParams;
+import org.eclipse.lsp4j.WorkDoneProgressParams;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
+import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
+import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage;
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
@@ -63,6 +70,9 @@
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.Sources;
import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.progress.spi.InternalHandle;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
@@ -115,6 +125,7 @@
.setInput(in)
.setOutput(out)
.wrapMessages(processor)
+// .traceMessages(new java.io.PrintWriter(System.err))
.create();
}
@@ -127,6 +138,7 @@
private static class ConsumeWithLookup {
private final Lookup sessionLookup;
private NbCodeLanguageClient client;
+ private OperationContext initialContext;
public ConsumeWithLookup(Lookup sessionLookup) {
this.sessionLookup = sessionLookup;
@@ -137,13 +149,67 @@
}
public MessageConsumer attachLookup(MessageConsumer delegate) {
+ // PENDING: allow for message consumer wrappers to be registered to add pre/post processing for
+ // the request plus build the request's default Lookup contents.
return new MessageConsumer() {
@Override
public void consume(Message msg) throws MessageIssueException, JsonRpcException {
+ InstanceContent ic = new InstanceContent();
+ ProxyLookup ll = new ProxyLookup(new AbstractLookup(ic), sessionLookup);
+ final OperationContext ctx;
+
+ // Intercept client REQUESTS; take the progress token from them, if it is
+ // attached.
+ Runnable r;
+ InternalHandle toCancel = null;
+ if (msg instanceof RequestMessage) {
+ RequestMessage rq = (RequestMessage)msg;
+ Object p = rq.getParams();
+ if (initialContext == null) {
+ initialContext = OperationContext.create(client);
+ ctx = initialContext;
+ } else {
+ ctx = initialContext.operationContext();
+ }
+ // PENDING: this ought to be somehow registered, so different services
+ // may enrich lookup/pre/postprocess the processing, not just the progress support.
+ if (p instanceof WorkDoneProgressParams) {
+ ctx.setProgressToken(((WorkDoneProgressParams)p).getWorkDoneToken());
+ }
+ } else if (msg instanceof NotificationMessage) {
+ NotificationMessage not = (NotificationMessage)msg;
+ Object p = not.getParams();
+ OperationContext selected = null;
+ if (p instanceof WorkDoneProgressCancelParams && initialContext != null) {
+ WorkDoneProgressCancelParams wdc = (WorkDoneProgressCancelParams)p;
+ toCancel = initialContext.findActiveHandle(wdc.getToken());
+ selected = OperationContext.getHandleContext(toCancel);
+ }
+ ctx = selected;
+ } else {
+ ctx = null;
+ }
+ if (ctx != null) {
+ ic.add(ctx);
+ }
+ final InternalHandle ftoCancel = toCancel;
try {
DISPATCHERS.set(client);
- Lookups.executeWith(sessionLookup, () -> {
- delegate.consume(msg);
+ Lookups.executeWith(ll, () -> {
+ try {
+ delegate.consume(msg);
+ } finally {
+ // cancel while the OperationContext is still active.
+ if (ftoCancel != null) {
+ ftoCancel.requestCancel();
+ }
+ if (ctx != null) {
+ // if initialized (for requests only), discards the token,
+ // as it becomes invalid at the end of this message. Further progresses
+ // must do their own processing.
+ ctx.stop();
+ }
+ }
});
} finally {
DISPATCHERS.remove();
@@ -247,12 +313,21 @@
completionOptions.setResolveProvider(true);
completionOptions.setTriggerCharacters(Collections.singletonList("."));
capabilities.setCompletionProvider(completionOptions);
- capabilities.setCodeActionProvider(true);
+ capabilities.setHoverProvider(true);
+ capabilities.setCodeActionProvider(new CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix, CodeActionKind.Source)));
capabilities.setDocumentSymbolProvider(true);
capabilities.setDefinitionProvider(true);
capabilities.setDocumentHighlightProvider(true);
capabilities.setReferencesProvider(true);
- capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT)));
+ List<String> commands = new ArrayList<>(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT));
+ for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+ commands.addAll(codeGenerator.getCommands());
+ }
+ capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(commands));
+ capabilities.setWorkspaceSymbolProvider(true);
+ RenameOptions renOpt = new RenameOptions();
+ renOpt.setPrepareProvider(true);
+ capabilities.setRenameProvider(renOpt);
}
return new InitializeResult(capabilities);
}
@@ -266,7 +341,7 @@
if (folders != null) {
for (WorkspaceFolder w : folders) {
try {
- projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri()));
+ projectCandidates.add(Utils.fromUri(w.getUri()));
} catch (MalformedURLException ex) {
LOG.log(Level.FINE, null, ex);
}
@@ -276,7 +351,7 @@
if (root != null) {
try {
- projectCandidates.add(TextDocumentServiceImpl.fromUri(root));
+ projectCandidates.add(Utils.fromUri(root));
} catch (MalformedURLException ex) {
LOG.log(Level.FINE, null, ex);
}
@@ -289,9 +364,18 @@
return fProjects.
thenApply(this::showIndexingCompleted).
- thenApply(this::constructInitResponse);
+ thenApply(this::constructInitResponse).
+ thenApply(this::finishInitialization);
}
-
+
+ public InitializeResult finishInitialization(InitializeResult res) {
+ OperationContext c = OperationContext.find(sessionLookup);
+ // discard the progress token as it is going to be invalid anyway. Further pending
+ // initializations need to create its own tokens.
+ c.acquireProgressToken();
+ return res;
+ }
+
@Override
public CompletableFuture<Object> shutdown() {
return CompletableFuture.completedFuture(null);
@@ -322,7 +406,6 @@
}
});
sessionServices.add(new WorkspaceUIContext(client));
-
((LanguageClientAware) getTextDocumentService()).connect(aClient);
((LanguageClientAware) getWorkspaceService()).connect(aClient);
}
@@ -332,7 +415,7 @@
public static final String GRAALVM_PAUSE_SCRIPT = "graalvm.pause.script";
static final String INDEXING_COMPLETED = "Indexing completed.";
static final String NO_JAVA_SUPPORT = "Cannot initialize Java support on JDK ";
-
+
static final NbCodeLanguageClient STUB_CLIENT = new NbCodeLanguageClient() {
private final NbCodeClientCapabilities caps = new NbCodeClientCapabilities();
@@ -347,6 +430,18 @@
}
@Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ logWarning(params);
+ return CompletableFuture.completedFuture(params.getCanPickMany() || params.getItems().isEmpty() ? params.getItems() : Collections.singletonList(params.getItems().get(0)));
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ logWarning(params);
+ return CompletableFuture.completedFuture(params.getValue());
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
logWarning();
return caps;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
new file mode 100644
index 0000000..a262919
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
@@ -0,0 +1,124 @@
+/*
+ * 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.protocol;
+
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class ShowInputBoxParams {
+
+ /**
+ * The text to display underneath the input box.
+ */
+ @NonNull
+ private String prompt;
+
+ /**
+ * The value to prefill in the input box.
+ */
+ @NonNull
+ private String value;
+
+ public ShowInputBoxParams() {
+ this("", "");
+ }
+
+ public ShowInputBoxParams(@NonNull final String prompt, @NonNull final String value) {
+ this.prompt = Preconditions.checkNotNull(prompt, "prompt");
+ this.value = Preconditions.checkNotNull(value, "value");
+ }
+
+ /**
+ * The text to display underneath the input box.
+ */
+ @Pure
+ @NonNull
+ public String getPrompt() {
+ return prompt;
+ }
+
+ /**
+ * The text to display underneath the input box.
+ */
+ public void setPrompt(@NonNull final String prompt) {
+ this.prompt = prompt;
+ }
+
+ /**
+ * The value to prefill in the input box.
+ */
+ @Pure
+ @NonNull
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * The value to prefill in the input box.
+ */
+ public void setValue(@NonNull final String value) {
+ this.value = value;
+ }
+
+ @Override
+ @Pure
+ public String toString() {
+ ToStringBuilder b = new ToStringBuilder(this);
+ b.add("prompt", prompt);
+ b.add("value", value);
+ return b.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 59 * hash + Objects.hashCode(this.prompt);
+ hash = 59 * hash + Objects.hashCode(this.value);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final ShowInputBoxParams other = (ShowInputBoxParams) obj;
+ if (!Objects.equals(this.prompt, other.prompt)) {
+ return false;
+ }
+ if (!Objects.equals(this.value, other.value)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
new file mode 100644
index 0000000..107084f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
@@ -0,0 +1,159 @@
+/*
+ * 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.protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * A selection list parameters.
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class ShowQuickPickParams {
+
+ /**
+ * A string to show as placeholder in the input box to guide the user what to pick on.
+ */
+ @NonNull
+ private String placeHolder;
+
+ /**
+ * An optional flag to make the picker accept multiple selections.
+ */
+ private boolean canPickMany;
+
+ /**
+ * A list of items.
+ */
+ @NonNull
+ private List<QuickPickItem> items;
+
+ public ShowQuickPickParams() {
+ this("", new ArrayList<>());
+ }
+
+ public ShowQuickPickParams(@NonNull final String placeHolder, @NonNull final List<QuickPickItem> items) {
+ this.placeHolder = Preconditions.checkNotNull(placeHolder, "placeHolder");
+ this.items = Preconditions.checkNotNull(items, "items");
+ }
+
+ public ShowQuickPickParams(final String placeHolder, final boolean canPickMany, @NonNull final List<QuickPickItem> items) {
+ this(placeHolder, items);
+ this.canPickMany = canPickMany;
+ }
+
+ /**
+ * A string to show as placeholder in the input box to guide the user what to pick on.
+ */
+ @Pure
+ @NonNull
+ public String getPlaceHolder() {
+ return placeHolder;
+ }
+
+ /**
+ * A string to show as placeholder in the input box to guide the user what to pick on.
+ */
+ public void setPlaceHolder(@NonNull final String placeHolder) {
+ this.placeHolder = placeHolder;
+ }
+
+ /**
+ * An optional flag to make the picker accept multiple selections.
+ */
+ @Pure
+ public boolean getCanPickMany() {
+ return canPickMany;
+ }
+
+ /**
+ * An optional flag to make the picker accept multiple selections.
+ */
+ public void setCanPickMany(final boolean canPickMany) {
+ this.canPickMany = canPickMany;
+ }
+
+ /**
+ * A list of items.
+ */
+ @Pure
+ @NonNull
+ public List<QuickPickItem> getItems() {
+ return items;
+ }
+
+ /**
+ * A list of items.
+ */
+ public void setItems(@NonNull final List<QuickPickItem> items) {
+ this.items = items;
+ }
+
+ @Override
+ @Pure
+ public String toString() {
+ ToStringBuilder b = new ToStringBuilder(this);
+ b.add("placeHolder", placeHolder);
+ b.add("canPickMany", canPickMany);
+ b.add("items", items);
+ return b.toString();
+ }
+
+ @Override
+ @Pure
+ public int hashCode() {
+ int hash = 7;
+ hash = 29 * hash + Objects.hashCode(this.placeHolder);
+ hash = 29 * hash + (this.canPickMany ? 1 : 0);
+ hash = 29 * hash + Objects.hashCode(this.items);
+ return hash;
+ }
+
+ @Override
+ @Pure
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final ShowQuickPickParams other = (ShowQuickPickParams) obj;
+ if (this.canPickMany != other.canPickMany) {
+ return false;
+ }
+ if (!Objects.equals(this.placeHolder, other.placeHolder)) {
+ return false;
+ }
+ if (!Objects.equals(this.items, other.items)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 33d95eb..4122491 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -24,33 +24,28 @@
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.LineMap;
import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Scope;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UncheckedIOException;
import java.net.MalformedURLException;
-import java.net.URI;
-import java.nio.file.Files;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
@@ -58,7 +53,9 @@
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
+import java.util.function.IntFunction;
import java.util.prefs.Preferences;
+import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@@ -83,6 +80,8 @@
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
+import org.eclipse.lsp4j.CompletionTriggerKind;
+import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
@@ -106,25 +105,33 @@
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PrepareRenameParams;
+import org.eclipse.lsp4j.PrepareRenameResult;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameFile;
import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.SignatureHelp;
import org.eclipse.lsp4j.SignatureHelpParams;
import org.eclipse.lsp4j.SymbolInformation;
-import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.netbeans.api.editor.document.LineDocument;
import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.lexer.JavaTokenId;
+import org.netbeans.api.java.queries.SourceJavadocAttacher;
+import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.CompilationInfo.CacheClearPolicy;
@@ -139,6 +146,7 @@
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.java.source.support.ReferencesCount;
import org.netbeans.api.java.source.ui.ElementJavadoc;
+import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.editor.java.GoToSupport;
import org.netbeans.modules.editor.java.GoToSupport.Context;
import org.netbeans.modules.editor.java.GoToSupport.GoToTarget;
@@ -147,41 +155,59 @@
import org.netbeans.modules.java.completion.JavaCompletionTask.Options;
import org.netbeans.modules.java.completion.JavaDocumentationTask;
import org.netbeans.modules.java.editor.base.semantic.MarkOccurrencesHighlighterBase;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
+import org.netbeans.modules.java.hints.errors.CreateFixBase;
import org.netbeans.modules.java.hints.errors.ImportClass;
import org.netbeans.modules.java.hints.infrastructure.CreatorBasedLazyFixList;
import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
+import org.netbeans.modules.java.hints.introduce.IntroduceFixBase;
+import org.netbeans.modules.java.hints.introduce.IntroduceHint;
+import org.netbeans.modules.java.hints.introduce.IntroduceKind;
import org.netbeans.modules.java.hints.project.IncompleteClassPath;
import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl;
import org.netbeans.modules.java.hints.spiimpl.hints.HintsInvoker;
import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
import org.netbeans.modules.java.source.ElementHandleAccessor;
import org.netbeans.modules.java.source.ui.ElementOpenAccessor;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
+import org.netbeans.modules.parsing.impl.indexing.implspi.ActiveDocumentProvider.IndexingAware;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.api.RefactoringElement;
import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
import org.netbeans.modules.refactoring.api.WhereUsedQuery;
+import org.netbeans.modules.refactoring.api.impl.APIAccessor;
+import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
+import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
+import org.netbeans.modules.refactoring.plugins.FileRenamePlugin;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
+import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
import org.netbeans.spi.editor.hints.EnhancedFix;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.editor.hints.LazyFixList;
+import org.netbeans.spi.editor.hints.Severity;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.java.hints.JavaFix;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
-import org.openide.filesystems.FileUtil;
-import org.openide.filesystems.URLMapper;
-import org.openide.modules.Places;
import org.openide.text.NbDocument;
import org.openide.text.PositionBounds;
import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
+import org.openide.util.WeakSet;
import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ServiceProvider;
/**
*
@@ -197,41 +223,94 @@
private NbCodeLanguageClient client;
public TextDocumentServiceImpl() {
+ Lookup.getDefault().lookup(RefreshDocument.class).register(this);
+ }
+
+ private void reRunDiagnostics() {
+ Set<String> documents = new HashSet<>(openedDocuments.keySet());
+
+ for (String doc : documents) {
+ runDiagnoticTasks(doc);
+ }
+ }
+
+ @ServiceProvider(service=IndexingAware.class, position=0)
+ public static final class RefreshDocument implements IndexingAware {
+
+ private final Set<TextDocumentServiceImpl> delegates = new WeakSet<>();
+
+ public synchronized void register(TextDocumentServiceImpl delegate) {
+ delegates.add(delegate);
+ }
+
+ @Override
+ public void indexingComplete(Set<URL> indexedRoots) {
+ TextDocumentServiceImpl[] delegates;
+ synchronized (this) {
+ delegates = this.delegates.toArray(new TextDocumentServiceImpl[this.delegates.size()]);
+ }
+ for (TextDocumentServiceImpl delegate : delegates) {
+ delegate.reRunDiagnostics();
+ }
+ }
}
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
try {
String uri = params.getTextDocument().getUri();
- FileObject file = fromUri(uri);
+ FileObject file = Utils.fromUri(uri);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
- int caret = getOffset(doc, params.getPosition());
- JavaCompletionTask<CompletionItem> task = JavaCompletionTask.create(caret, new ItemFactoryImpl(client, uri), EnumSet.noneOf(Options.class), () -> false);
- ParserManager.parse(Collections.singletonList(Source.create(doc)), task);
- List<CompletionItem> result = task.getResults();
- for (Iterator<CompletionItem> it = result.iterator(); it.hasNext();) {
- CompletionItem item = it.next();
- if (item == null) {
- it.remove();
+ final int caret = Utils.getOffset(doc, params.getPosition());
+ final CompletionList completionList = new CompletionList();
+ ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
+ @Override
+ public void run(ResultIterator resultIterator) throws Exception {
+ TokenSequence<JavaTokenId> ts = resultIterator.getSnapshot().getTokenHierarchy().tokenSequence(JavaTokenId.language());
+ if (ts.move(caret) == 0 || !ts.moveNext()) {
+ if (!ts.movePrevious()) {
+ ts.moveNext();
+ }
+ }
+ int len = caret - ts.offset();
+ boolean allCompletion = params.getContext() != null && params.getContext().getTriggerKind() == CompletionTriggerKind.TriggerForIncompleteCompletions
+ || len > 0 && ts.token().length() >= len && ts.token().id() == JavaTokenId.IDENTIFIER;
+ CompilationController controller = CompilationController.get(resultIterator.getParserResult(ts.offset()));
+ controller.toPhase(JavaSource.Phase.RESOLVED);
+ JavaCompletionTask<CompletionItem> task = JavaCompletionTask.create(caret, new ItemFactoryImpl(client, controller, uri, ts.offset()), allCompletion ? EnumSet.of(Options.ALL_COMPLETION) : EnumSet.noneOf(Options.class), () -> false);
+ task.run(resultIterator);
+ List<CompletionItem> results = task.getResults();
+ if (results != null) {
+ for (Iterator<CompletionItem> it = results.iterator(); it.hasNext();) {
+ CompletionItem item = it.next();
+ if (item == null) {
+ it.remove();
+ }
+ }
+ completionList.setItems(results);
+ }
+ completionList.setIsIncomplete(task.hasAdditionalClasses());
}
- }
- return CompletableFuture.completedFuture(Either.<List<CompletionItem>, CompletionList>forRight(new CompletionList(result)));
+ });
+ return CompletableFuture.completedFuture(Either.<List<CompletionItem>, CompletionList>forRight(completionList));
} catch (IOException | ParseException ex) {
throw new IllegalStateException(ex);
- }
}
+ }
public static final class CompletionData {
public String uri;
+ public int offset;
public String kind;
public String[] elementHandle;
public CompletionData() {
}
- public CompletionData(String uri, String kind, String[] elementHandle) {
+ public CompletionData(String uri, int offset, String kind, String[] elementHandle) {
this.uri = uri;
+ this.offset = offset;
this.kind = kind;
this.elementHandle = elementHandle;
}
@@ -252,30 +331,44 @@
private final LanguageClient client;
private final String uri;
+ private final int offset;
+ private final CompilationInfo info;
+ private final Scope scope;
- public ItemFactoryImpl(LanguageClient client, String uri) {
+ public ItemFactoryImpl(LanguageClient client, CompilationInfo info, String uri, int offset) {
this.client = client;
this.uri = uri;
+ this.offset = offset;
+ this.info = info;
+ this.scope = info.getTrees().getScope(info.getTreeUtilities().pathFor(offset));
}
private static final Set<String> SUPPORTED_ELEMENT_KINDS = new HashSet<>(Arrays.asList("PACKAGE", "CLASS", "INTERFACE", "ENUM", "ANNOTATION_TYPE", "METHOD", "CONSTRUCTOR", "INSTANCE_INIT", "STATIC_INIT", "FIELD", "ENUM_CONSTANT", "TYPE_PARAMETER", "MODULE"));
+
private void setCompletionData(CompletionItem ci, Element el) {
if (SUPPORTED_ELEMENT_KINDS.contains(el.getKind().name())) {
- ci.setData(new CompletionData(uri, el.getKind().name(), SourceUtils.getJVMSignature(ElementHandle.create(el))));
+ setCompletionData(ci, ElementHandle.create(el));
}
}
+ private void setCompletionData(CompletionItem ci, ElementHandle handle) {
+ ci.setData(new CompletionData(uri, offset, handle.getKind().name(), SourceUtils.getJVMSignature(handle)));
+ }
+
@Override
public CompletionItem createKeywordItem(String kwd, String postfix, int substitutionOffset, boolean smartType) {
CompletionItem item = new CompletionItem(kwd);
item.setKind(CompletionItemKind.Keyword);
+ item.setSortText(String.format("%4d%s", smartType ? 670 : 1670, kwd)); //NOI18N
return item;
}
@Override
public CompletionItem createPackageItem(String pkgFQN, int substitutionOffset, boolean inPackageStatement) {
- CompletionItem item = new CompletionItem(pkgFQN.substring(pkgFQN.lastIndexOf('.') + 1));
+ final String simpleName = pkgFQN.substring(pkgFQN.lastIndexOf('.') + 1);
+ CompletionItem item = new CompletionItem(simpleName);
item.setKind(CompletionItemKind.Folder);
+ item.setSortText(String.format("%4d%s#%s", 1900, simpleName, pkgFQN)); //NOI18N
return item;
}
@@ -283,13 +376,34 @@
public CompletionItem createTypeItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addTypeVars, boolean addSimpleName, boolean smartType, boolean autoImportEnclosingType) {
CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+ String name = elem.getQualifiedName().toString();
+ int idx = name.lastIndexOf('.');
+ String pkgName = idx < 0 ? "" : name.substring(0, idx);
+ if (!pkgName.isEmpty()) {
+ item.setDetail(name.substring(0, idx));
+ }
+ item.setSortText(String.format("%4d%s#%2d#%s", smartType ? 800 : 1800, elem.getSimpleName().toString(), Utilities.getImportanceLevel(name), pkgName)); //NOI18N
setCompletionData(item, elem);
return item;
}
@Override
public CompletionItem createTypeItem(ElementHandle<TypeElement> handle, EnumSet<ElementKind> kinds, int substitutionOffset, ReferencesCount referencesCount, Source source, boolean insideNew, boolean addTypeVars, boolean afterExtends) {
- return null; //TODO: fill
+ TypeElement te = handle.resolve(info);
+ if (te != null && info.getTrees().isAccessible(scope, te)) {
+ CompletionItem item = new CompletionItem(te.getSimpleName().toString());
+ String name = handle.getQualifiedName();
+ int idx = name.lastIndexOf('.');
+ String pkgName = idx < 0 ? "" : name.substring(0, idx);
+ if (!pkgName.isEmpty()) {
+ item.setDetail(pkgName);
+ }
+ item.setKind(elementKind2CompletionItemKind(handle.getKind()));
+ item.setSortText(String.format("%4d%s#%2d#%s", 1800, te.getSimpleName().toString(), Utilities.getImportanceLevel(name), pkgName)); //NOI18N
+ setCompletionData(item, handle);
+ return item;
+ }
+ return null;
}
@Override
@@ -301,6 +415,7 @@
public CompletionItem createTypeParameterItem(TypeParameterElement elem, int substitutionOffset) {
CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+ item.setSortText(String.format("%4d%s", 1700, elem.getSimpleName().toString())); //NOI18N
return item;
}
@@ -308,6 +423,8 @@
public CompletionItem createVariableItem(CompilationInfo info, VariableElement elem, TypeMirror type, int substitutionOffset, ReferencesCount referencesCount, boolean isInherited, boolean isDeprecated, boolean smartType, int assignToVarOffset) {
CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+ int priority = elem.getKind() == ElementKind.ENUM_CONSTANT || elem.getKind() == ElementKind.FIELD ? smartType ? 300 : 1300 : smartType ? 200 : 1200;
+ item.setSortText(String.format("%4d%s", priority, elem.getSimpleName().toString())); //NOI18N
setCompletionData(item, elem);
return item;
}
@@ -316,6 +433,7 @@
public CompletionItem createVariableItem(CompilationInfo info, String varName, int substitutionOffset, boolean newVarName, boolean smartType) {
CompletionItem item = new CompletionItem(varName);
item.setKind(CompletionItemKind.Variable);
+ item.setSortText(String.format("%4d%s", smartType ? 200 : 1200, varName)); //NOI18N
return item;
}
@@ -327,18 +445,28 @@
String sep = "";
label.append(elem.getSimpleName().toString());
label.append("(");
+ StringBuilder sortParams = new StringBuilder();
+ sortParams.append('(');
+ int cnt = 0;
while(it.hasNext() && tIt.hasNext()) {
TypeMirror tm = tIt.next();
if (tm == null) {
break;
}
label.append(sep);
- label.append(Utilities.getTypeName(info, tm, false, elem.isVarArgs() && !tIt.hasNext()).toString());
+ String paramTypeName = Utilities.getTypeName(info, tm, false, elem.isVarArgs() && !tIt.hasNext()).toString();
+ label.append(paramTypeName);
label.append(' ');
label.append(it.next().getSimpleName().toString());
sep = ", ";
+ sortParams.append(paramTypeName);
+ if (tIt.hasNext()) {
+ sortParams.append(',');
+ }
+ cnt++;
}
label.append(") : ");
+ sortParams.append(')');
TypeMirror retType = type.getReturnType();
label.append(Utilities.getTypeName(info, retType, false).toString());
CompletionItem item = new CompletionItem(label.toString());
@@ -351,22 +479,52 @@
}
item.setInsertText(insertText.toString());
item.setInsertTextFormat(InsertTextFormat.PlainText);
+ int priority = elem.getKind() == ElementKind.METHOD ? smartType ? 500 : 1500 : smartType ? 650 : 1650;
+ item.setSortText(String.format("%4d%s#%2d%s", priority, elem.getSimpleName().toString(), cnt, sortParams)); //NOI18N
setCompletionData(item, elem);
return item;
}
@Override
public CompletionItem createThisOrSuperConstructorItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated, String name) {
- CompletionItem item = new CompletionItem(name);
- item.setKind(CompletionItemKind.Field);
+ CompletionItem item = createExecutableItem(info, elem, type, substitutionOffset, null, false, isDeprecated, false, false, false, -1, false);
+ item.setLabel(name != null ? name : elem.getEnclosingElement().getSimpleName().toString());
+ StringBuilder insertText = new StringBuilder();
+ insertText.append(item.getLabel());
+ insertText.append("(");
+ if (elem.getParameters().isEmpty()) {
+ insertText.append(")");
+ }
+ item.setInsertText(insertText.toString());
+ item.setKind(CompletionItemKind.Constructor);
+ item.setSortText(String.format("%4d%s", name != null ? 1550 : 1650, item.getSortText().substring(4))); //NOI18N
setCompletionData(item, elem);
return item;
}
@Override
public CompletionItem createOverrideMethodItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean implement) {
- CompletionItem item = new CompletionItem(elem.getSimpleName().toString() + " - override");
+ CompletionItem item = createExecutableItem(info, elem, type, substitutionOffset, null, false, false, false, false, false, -1, false);
+ item.setLabel(String.format("%s - %s", item.getLabel(), implement ? "implement" : "override"));
+ item.setInsertText(null);
item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+ try {
+ List<TextEdit> textEdits = modify2TextEdits(JavaSource.forFileObject(info.getFileObject()), wc -> {
+ wc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ if (implement) {
+ GeneratorUtils.generateAbstractMethodImplementation(wc, tp, elem, offset);
+ } else {
+ GeneratorUtils.generateMethodOverride(wc, tp, elem, offset);
+ }
+ });
+ if (!textEdits.isEmpty()) {
+ item.setTextEdit(textEdits.get(0));
+ }
+ } catch (IOException ex) {
+ //TODO: include stack trace:
+ client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+ }
setCompletionData(item, elem);
return item;
}
@@ -429,6 +587,29 @@
item.setInsertText(label);
item.setInsertTextFormat(InsertTextFormat.PlainText);
item.setAdditionalTextEdits(currentClassImport);
+ String sortText = memberElem.getSimpleName().toString();
+ if (memberElem.getKind().isField()) {
+ sortText += String.format("#%s", Utilities.getTypeName(info, type, false)); //NOI18N
+ } else {
+ StringBuilder sortParams = new StringBuilder();
+ sortParams.append('(');
+ int cnt = 0;
+ Iterator<? extends TypeMirror> tIt = ((ExecutableType)memberType).getParameterTypes().iterator();
+ while(tIt.hasNext()) {
+ TypeMirror tm = tIt.next();
+ if (tm == null) {
+ break;
+ }
+ sortParams.append(Utilities.getTypeName(info, tm, false, ((ExecutableElement)memberElem).isVarArgs() && !tIt.hasNext()).toString());
+ if (tIt.hasNext()) {
+ sortParams.append(',');
+ }
+ cnt++;
+ }
+ sortParams.append(')');
+ sortText += String.format("#%2d#%s#s", cnt, sortParams.toString(), Utilities.getTypeName(info, type, false)); //NOI18N
+ }
+ item.setSortText(String.format("%4d%s", memberElem.getKind().isField() ? 720 : 750, sortText)); //NOI18N
setCompletionData(item, memberElem);
return item;
}
@@ -499,17 +680,41 @@
}
CompletionData data = new Gson().fromJson(rawData, CompletionData.class);
try {
- FileObject file = fromUri(data.uri);
+ FileObject file = Utils.fromUri(data.uri);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
- ElementHandle<Element> handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(data.kind), data.elementHandle);
- JavaDocumentationTask<Future<String>> task = JavaDocumentationTask.create(-1, handle, new JavaDocumentationTask.DocumentationFactory<Future<String>>() {
+ final ElementHandle<Element> handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(data.kind), data.elementHandle);
+ final JavaDocumentationTask<Future<String>> task = JavaDocumentationTask.create(-1, handle, new JavaDocumentationTask.DocumentationFactory<Future<String>>() {
@Override
public Future<String> create(CompilationInfo compilationInfo, Element element, Callable<Boolean> cancel) {
return ElementJavadoc.create(compilationInfo, element, cancel).getTextAsync();
}
}, () -> false);
- ParserManager.parse(Collections.singletonList(Source.create(doc)), task);
+ LineMap[] lm = new LineMap[1];
+ ModificationResult mr = ModificationResult.runModificationTask(Collections.singletonList(Source.create(doc)), new UserTask() {
+ @Override
+ public void run(ResultIterator resultIterator) throws Exception {
+ task.run(resultIterator);
+ if (ci.getDetail() != null) {
+ final WorkingCopy copy = WorkingCopy.get(resultIterator.getParserResult(data.offset));
+ copy.toPhase(JavaSource.Phase.RESOLVED);
+ Element e = handle.resolve(copy);
+ if (e != null) {
+ copy.rewrite(copy.getCompilationUnit(), GeneratorUtilities.get(copy).addImports(copy.getCompilationUnit(), Collections.singleton(e)));
+ }
+ lm[0] = copy.getCompilationUnit().getLineMap();
+ }
+ }
+ });
+ List<? extends ModificationResult.Difference> diffs = mr.getDifferences(file);
+ if (diffs != null && !diffs.isEmpty()) {
+ List<TextEdit> edits = new ArrayList<>();
+ for (ModificationResult.Difference diff : diffs) {
+ edits.add(new TextEdit(new Range(Utils.createPosition(lm[0], diff.getStartPosition().getOffset()),
+ Utils.createPosition(lm[0], diff.getEndPosition().getOffset())), diff.getNewText()));
+ }
+ ci.setAdditionalTextEdits(edits);
+ }
Future<String> futureJavadoc = task.getDocumentation();
CompletableFuture<CompletionItem> result = new CompletableFuture<CompletionItem>() {
@Override
@@ -539,12 +744,55 @@
}
public static String html2MD(String html) {
- return FlexmarkHtmlConverter.builder().build().convert(html).replaceAll("<br />[ \n]*$", "");
+ int idx = html.indexOf("<p id=\"not-found\">"); // strip "No Javadoc found" message
+ return FlexmarkHtmlConverter.builder().build().convert(idx >= 0 ? html.substring(0, idx) : html).replaceAll("<br />[ \n]*$", "");
}
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
- throw new UnsupportedOperationException("Not supported yet.");
+ try {
+ String uri = params.getTextDocument().getUri();
+ FileObject file = Utils.fromUri(uri);
+ EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+ Document doc = ec.openDocument();
+ final JavaDocumentationTask<Future<String>> task = JavaDocumentationTask.create(Utils.getOffset(doc, params.getPosition()), null, new JavaDocumentationTask.DocumentationFactory<Future<String>>() {
+ @Override
+ public Future<String> create(CompilationInfo compilationInfo, Element element, Callable<Boolean> cancel) {
+ return ElementJavadoc.create(compilationInfo, element, cancel).getTextAsync();
+ }
+ }, () -> false);
+ ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
+ @Override
+ public void run(ResultIterator resultIterator) throws Exception {
+ task.run(resultIterator);
+ }
+ });
+ Future<String> futureJavadoc = task.getDocumentation();
+ CompletableFuture<Hover> result = new CompletableFuture<Hover>() {
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return futureJavadoc != null && futureJavadoc.cancel(mayInterruptIfRunning) && super.cancel(mayInterruptIfRunning);
+ }
+ };
+ JAVADOC_WORKER.post(() -> {
+ try {
+ String javadoc = futureJavadoc != null ? futureJavadoc.get() : null;
+ if (javadoc != null) {
+ MarkupContent markup = new MarkupContent();
+ markup.setKind("markdown");
+ markup.setValue(html2MD(javadoc));
+ result.complete(new Hover(markup));
+ } else {
+ result.complete(null);
+ }
+ } catch (ExecutionException | InterruptedException ex) {
+ result.completeExceptionally(ex);
+ }
+ });
+ return result;
+ } catch (IOException | ParseException ex) {
+ throw new IllegalStateException(ex);
+ }
}
@Override
@@ -561,7 +809,7 @@
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
- int offset = getOffset(doc, params.getPosition());
+ int offset = Utils.getOffset(doc, params.getPosition());
Context context = GoToSupport.resolveContext(cc, doc, offset, false, false);
if (context == null) {
return ;
@@ -579,21 +827,79 @@
if (target[0] != null && target[0].success) {
if (target[0].offsetToOpen < 0) {
Object[] openInfo = ElementOpenAccessor.getInstance().getOpenInfo(target[0].cpInfo, target[0].elementToOpen, new AtomicBoolean());
+ if (openInfo == null && target[0].resourceName != null) {
+ // try to attach sources
+ final ClassPath cp = ClassPathSupport.createProxyClassPath(
+ target[0].cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT),
+ target[0].cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE),
+ target[0].cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE));
+ final FileObject resource = cp.findResource(target[0].resourceName);
+ if (resource != null) {
+ final FileObject root = cp.findOwnerRoot(resource);
+ if (root != null) {
+ final CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> future = new CompletableFuture<>();
+ SourceJavadocAttacher.attachSources(root.toURL(), new SourceJavadocAttacher.AttachmentListener() {
+ @Override
+ public void attachmentSucceeded() {
+ Object[] openInfo = ElementOpenAccessor.getInstance().getOpenInfo(target[0].cpInfo, target[0].elementToOpen, new AtomicBoolean());
+ if (openInfo != null && (int) openInfo[1] != (-1) && (int) openInfo[2] != (-1) && openInfo[3] != null) {
+ FileObject file = (FileObject) openInfo[0];
+ int start = (int) openInfo[1];
+ int end = (int) openInfo[2];
+ LineMap lm = (LineMap) openInfo[3];
+ future.complete(Either.forLeft(Collections.singletonList(new Location(Utils.toUri(file),
+ new Range(Utils.createPosition(lm, start), Utils.createPosition(lm, end))))));
+ }
+ }
+ @Override
+ public void attachmentFailed() {
+ try {
+ FileObject generated = org.netbeans.modules.java.classfile.CodeGenerator.generateCode(target[0].cpInfo, target[0].elementToOpen);
+ if (generated != null) {
+ final int[] pos = new int[] {-1};
+ try {
+ JavaSource.create(target[0].cpInfo, generated).runUserActionTask(new Task<CompilationController>() {
+ @Override public void run(CompilationController parameter) throws Exception {
+ parameter.toPhase(JavaSource.Phase.RESOLVED);
+ Element el = target[0].elementToOpen.resolve(parameter);
+ if (el != null) {
+ TreePath p = parameter.getTrees().getPath(el);
+ if (p != null) {
+ pos[0] = (int) parameter.getTrees().getSourcePositions().getStartPosition(p.getCompilationUnit(), p.getLeaf());
+ }
+ }
+ }
+ }, true);
+ } catch (IOException ex) {
+ }
+ int offset = pos[0] != -1 ? pos[0] : 0;
+ future.complete(Either.forLeft(Collections.singletonList(new Location(Utils.toUri(generated), new Range(Utils.createPosition(generated, offset), Utils.createPosition(generated, offset))))));
+ return;
+ }
+ } catch (Exception e) {
+ }
+ future.complete(Either.forLeft((Collections.emptyList())));
+ }
+ });
+ return future;
+ }
+ }
+ }
if (openInfo != null && (int) openInfo[1] != (-1) && (int) openInfo[2] != (-1) && openInfo[3] != null) {
FileObject file = (FileObject) openInfo[0];
int start = (int) openInfo[1];
int end = (int) openInfo[2];
LineMap lm = (LineMap) openInfo[3];
- result.add(new Location(toUri(file),
- new Range(createPosition(lm, start),
- createPosition(lm, end))));
+ result.add(new Location(Utils.toUri(file),
+ new Range(Utils.createPosition(lm, start),
+ Utils.createPosition(lm, end))));
}
} else {
int start = target[0].offsetToOpen;
int end = target[0].endPos;
result.add(new Location(params.getTextDocument().getUri(),
- new Range(createPosition(thisFileLineMap[0], start),
- createPosition(thisFileLineMap[0], end))));
+ new Range(Utils.createPosition(thisFileLineMap[0], start),
+ Utils.createPosition(thisFileLineMap[0], end))));
}
}
return CompletableFuture.completedFuture(Either.forLeft(result));
@@ -622,7 +928,7 @@
cc.toPhase(JavaSource.Phase.RESOLVED);
if (cancel.get()) return ;
Document doc = cc.getSnapshot().getSource().getDocument(true);
- TreePath path = cc.getTreeUtilities().pathFor(getOffset(doc, params.getPosition()));
+ TreePath path = cc.getTreeUtilities().pathFor(Utils.getOffset(doc, params.getPosition()));
if (params.getContext().isIncludeDeclaration()) {
Element decl = cc.getTrees().getElement(path);
if (decl != null) {
@@ -630,7 +936,7 @@
if (declPath != null && cc.getCompilationUnit() == declPath.getCompilationUnit()) {
Range range = declarationRange(cc, declPath);
if (range != null) {
- locations.add(new Location(toUri(cc.getFileObject()),
+ locations.add(new Location(Utils.toUri(cc.getFileObject()),
range));
}
} else {
@@ -645,7 +951,7 @@
if (declPath2 != null) {
Range range = declarationRange(nestedCC, declPath2);
if (range != null) {
- locations.add(new Location(toUri(nestedCC.getFileObject()),
+ locations.add(new Location(Utils.toUri(nestedCC.getFileObject()),
range));
}
}
@@ -663,23 +969,23 @@
p = query[0].checkParameters();
if (cancel.get()) return ;
if (p != null && p.isFatal()) {
- result.completeExceptionally(new IllegalStateException(p.getMessage()));
+ ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
return ;
}
p = query[0].preCheck();
if (p != null && p.isFatal()) {
- result.completeExceptionally(new IllegalStateException(p.getMessage()));
+ ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
return ;
}
if (cancel.get()) return ;
p = query[0].prepare(refactoring);
if (p != null && p.isFatal()) {
- result.completeExceptionally(new IllegalStateException(p.getMessage()));
+ ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
return ;
}
for (RefactoringElement re : refactoring.getRefactoringElements()) {
if (cancel.get()) return ;
- locations.add(new Location(toUri(re.getParentFile()), toRange(re.getPosition())));
+ locations.add(new Location(Utils.toUri(re.getParentFile()), toRange(re.getPosition())));
}
refactoring.finished();
@@ -710,8 +1016,8 @@
if (span == null) {
return null;
}
- return new Range(createPosition(info.getCompilationUnit().getLineMap(), span[0]),
- createPosition(info.getCompilationUnit().getLineMap(), span[1]));
+ return new Range(Utils.createPosition(info.getCompilationUnit().getLineMap(), span[0]),
+ Utils.createPosition(info.getCompilationUnit().getLineMap(), span[1]));
}
private static Range toRange(PositionBounds bounds) throws IOException {
@@ -742,12 +1048,12 @@
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
- int offset = getOffset(doc, params.getPosition());
+ int offset = Utils.getOffset(doc, params.getPosition());
List<int[]> spans = new MOHighligther().processImpl(cc, node, doc, offset);
if (spans != null) {
for (int[] span : spans) {
- result.add(new DocumentHighlight(new Range(createPosition(cc.getCompilationUnit(), span[0]),
- createPosition(cc.getCompilationUnit(), span[1]))));
+ result.add(new DocumentHighlight(new Range(Utils.createPosition(cc.getCompilationUnit(), span[0]),
+ Utils.createPosition(cc.getCompilationUnit(), span[1]))));
}
}
}, true);
@@ -783,12 +1089,9 @@
TreePath path = info.getTrees().getPath(el);
if (path == null)
return null;
- long start = info.getTrees().getSourcePositions().getStartPosition(path.getCompilationUnit(), path.getLeaf());
- long end = info.getTrees().getSourcePositions().getEndPosition(path.getCompilationUnit(), path.getLeaf());
- if (end == (-1))
+ Range range = Utils.treeRange(info, path.getLeaf());
+ if (range == null)
return null;
- Range range = new Range(createPosition(info.getCompilationUnit(), (int) start),
- createPosition(info.getCompilationUnit(), (int) end));
List<DocumentSymbol> children = new ArrayList<>();
for (Element c : el.getEnclosedElements()) {
DocumentSymbol ds = element2DocumentSymbol(info, c);
@@ -796,61 +1099,28 @@
children.add(ds);
}
}
- return new DocumentSymbol(el.getSimpleName().toString(), elementKind2SymbolKind(el.getKind()), range, range, null, children);
- }
- private static SymbolKind elementKind2SymbolKind(ElementKind kind) {
- switch (kind) {
- case PACKAGE:
- return SymbolKind.Package;
- case ENUM:
- return SymbolKind.Enum;
- case CLASS:
- return SymbolKind.Class;
- case ANNOTATION_TYPE:
- return SymbolKind.Interface;
- case INTERFACE:
- return SymbolKind.Interface;
- case ENUM_CONSTANT:
- return SymbolKind.EnumMember;
- case FIELD:
- return SymbolKind.Field; //TODO: constant
- case PARAMETER:
- return SymbolKind.Variable;
- case LOCAL_VARIABLE:
- return SymbolKind.Variable;
- case EXCEPTION_PARAMETER:
- return SymbolKind.Variable;
- case METHOD:
- return SymbolKind.Method;
- case CONSTRUCTOR:
- return SymbolKind.Constructor;
- case TYPE_PARAMETER:
- return SymbolKind.TypeParameter;
- case RESOURCE_VARIABLE:
- return SymbolKind.Variable;
- case MODULE:
- return SymbolKind.Module;
- case STATIC_INIT:
- case INSTANCE_INIT:
- case OTHER:
- default:
- return SymbolKind.File; //XXX: what here?
+ String simpleName;
+
+ if (el.getKind() == ElementKind.CONSTRUCTOR) {
+ simpleName = el.getEnclosingElement().getSimpleName().toString();
+ } else {
+ simpleName = el.getSimpleName().toString();
}
+
+ return new DocumentSymbol(simpleName, Utils.elementKind2SymbolKind(el.getKind()), range, range, null, children);
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
Document doc = openedDocuments.get(params.getTextDocument().getUri());
- if (doc == null) {
+ JavaSource js = JavaSource.forDocument(doc);
+ if (doc == null || js == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
Map<String, ErrorDescription> id2Errors = (Map<String, ErrorDescription>) doc.getProperty("lsp-errors");
- if (id2Errors == null) {
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
- JavaSource js = JavaSource.forDocument(doc);
List<Either<Command, CodeAction>> result = new ArrayList<>();
+ if (id2Errors != null) {
for (Diagnostic diag : params.getContext().getDiagnostics()) {
ErrorDescription err = id2Errors.get(diag.getCode().getLeft());
@@ -932,8 +1202,108 @@
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
}
+ if (f instanceof CreateFixBase) {
+ try {
+ CreateFixBase cf = (CreateFixBase) f;
+ ModificationResult changes = cf.getModificationResult();
+ List<Either<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
+ Set<File> newFiles = changes.getNewFiles();
+ if (newFiles.size() > 1) {
+ throw new IllegalStateException();
+ }
+ String newFilePath = null;
+ for (File newFile : newFiles) {
+ newFilePath = newFile.getPath();
+ documentChanges.add(Either.forRight(new CreateFile(newFilePath)));
+ }
+ outer: for (FileObject fileObject : changes.getModifiedFileObjects()) {
+ List<? extends ModificationResult.Difference> diffs = changes.getDifferences(fileObject);
+ if (diffs != null) {
+ List<TextEdit> edits = new ArrayList<>();
+ for (ModificationResult.Difference diff : diffs) {
+ String newText = diff.getNewText();
+ if (diff.getKind() == ModificationResult.Difference.Kind.CREATE) {
+ if (newFilePath != null) {
+ documentChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(newFilePath, -1),
+ Collections.singletonList(new TextEdit(new Range(Utils.createPosition(fileObject, 0), Utils.createPosition(fileObject, 0)),
+ newText != null ? newText : "")))));
+ }
+ continue outer;
+ } else {
+ edits.add(new TextEdit(new Range(Utils.createPosition(fileObject, diff.getStartPosition().getOffset()),
+ Utils.createPosition(fileObject, diff.getEndPosition().getOffset())),
+ newText != null ? newText : ""));
+ }
+ }
+ documentChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toUri(fileObject), -1), edits)));
+ }
+ }
+ CodeAction codeAction = new CodeAction(f.getText());
+ codeAction.setKind(CodeActionKind.QuickFix);
+ codeAction.setEdit(new WorkspaceEdit(documentChanges));
+ result.add(Either.forRight(codeAction));
+ } catch (IOException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+ }
+ }
}
}
+ }
+
+ try {
+ js.runUserActionTask(cc -> {
+ cc.toPhase(JavaSource.Phase.RESOLVED);
+ //code generators:
+ for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+ for (CodeAction codeAction : codeGenerator.getCodeActions(cc, params)) {
+ result.add(Either.forRight(codeAction));
+ }
+ }
+ //introduce hints
+ Range range = params.getRange();
+ if (!range.getStart().equals(range.getEnd())) {
+ for (ErrorDescription err : IntroduceHint.computeError(cc, Utils.getOffset(doc, range.getStart()), Utils.getOffset(doc, range.getEnd()), new EnumMap<IntroduceKind, Fix>(IntroduceKind.class), new EnumMap<IntroduceKind, String>(IntroduceKind.class), new AtomicBoolean())) {
+ for (Fix fix : err.getFixes().getFixes()) {
+ if (fix instanceof IntroduceFixBase) {
+ try {
+ ModificationResult changes = ((IntroduceFixBase) fix).getModificationResult();
+ if (changes != null) {
+ List<Either<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
+ Set<? extends FileObject> fos = changes.getModifiedFileObjects();
+ if (fos.size() == 1) {
+ FileObject fileObject = fos.iterator().next();
+ List<? extends ModificationResult.Difference> diffs = changes.getDifferences(fileObject);
+ if (diffs != null) {
+ List<TextEdit> edits = new ArrayList<>();
+ for (ModificationResult.Difference diff : diffs) {
+ String newText = diff.getNewText();
+ edits.add(new TextEdit(new Range(Utils.createPosition(fileObject, diff.getStartPosition().getOffset()),
+ Utils.createPosition(fileObject, diff.getEndPosition().getOffset())),
+ newText != null ? newText : ""));
+ }
+ documentChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toUri(fileObject), -1), edits)));
+ }
+ CodeAction codeAction = new CodeAction(fix.getText());
+ codeAction.setKind(CodeActionKind.RefactorExtract);
+ codeAction.setEdit(new WorkspaceEdit(documentChanges));
+ int renameOffset = ((IntroduceFixBase) fix).getNameOffset(changes);
+ if (renameOffset >= 0) {
+ codeAction.setCommand(new Command("Rename", "java.rename.element.at", Collections.singletonList(renameOffset)));
+ }
+ result.add(Either.forRight(codeAction));
+ }
+ }
+ } catch (GeneratorUtils.DuplicateMemberException dme) {
+ }
+ }
+ }
+ }
+ }
+ }, true);
+ } catch (IOException ex) {
+ //TODO: include stack trace:
+ client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+ }
return CompletableFuture.completedFuture(result);
}
@@ -1001,14 +1371,166 @@
}
@Override
- public CompletableFuture<WorkspaceEdit> rename(RenameParams arg0) {
- throw new UnsupportedOperationException("Not supported yet.");
+ public CompletableFuture<Either<Range, PrepareRenameResult>> prepareRename(PrepareRenameParams params) {
+ JavaSource source = getSource(params.getTextDocument().getUri());
+ if (source == null) {
+ return CompletableFuture.completedFuture(Either.forLeft(null));
+ }
+ CompletableFuture<Either<Range, PrepareRenameResult>> result = new CompletableFuture<>();
+ try {
+ source.runUserActionTask(cc -> {
+ cc.toPhase(JavaSource.Phase.RESOLVED);
+ Document doc = cc.getSnapshot().getSource().getDocument(true);
+ int pos = Utils.getOffset(doc, params.getPosition());
+ TreePath path = cc.getTreeUtilities().pathFor(pos);
+ RenameRefactoring ref = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(path, cc)));
+ ref.setNewName("any");
+ Problem p = ref.fastCheckParameters();
+ boolean hasFatalProblem = false;
+ while (p != null) {
+ hasFatalProblem |= p.isFatal();
+ p = p.getNext();
+ }
+ if (hasFatalProblem) {
+ result.complete(null);
+ } else {
+ //XXX: better range computation
+ TokenSequence<JavaTokenId> ts = cc.getTokenHierarchy().tokenSequence(JavaTokenId.language());
+ int d = ts.move(pos);
+ if (ts.moveNext()) {
+ if (d == 0 && ts.token().id() != JavaTokenId.IDENTIFIER) {
+ ts.movePrevious();
+ }
+ Range r = new Range(Utils.createPosition(cc.getCompilationUnit(), ts.offset()),
+ Utils.createPosition(cc.getCompilationUnit(), ts.offset() + ts.token().length()));
+ result.complete(Either.forRight(new PrepareRenameResult(r, ts.token().text().toString())));
+ } else {
+ result.complete(null);
+ }
+ }
+ }, true);
+ } catch (IOException ex) {
+ result.completeExceptionally(ex);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
+ AtomicBoolean cancel = new AtomicBoolean();
+ Runnable[] cancelCallback = new Runnable[1];
+ CompletableFuture<WorkspaceEdit> result = new CompletableFuture<WorkspaceEdit>() {
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ cancel.set(mayInterruptIfRunning);
+ if (cancelCallback[0] != null) {
+ cancelCallback[0].run();
+ }
+ return super.cancel(mayInterruptIfRunning);
+ }
+ };
+ WORKER.post(() -> {
+ JavaSource js = getSource(params.getTextDocument().getUri());
+ try {
+ RenameRefactoring[] refactoring = new RenameRefactoring[1];
+ js.runUserActionTask(cc -> {
+ cc.toPhase(JavaSource.Phase.RESOLVED);
+ if (cancel.get()) return ;
+ Document doc = cc.getSnapshot().getSource().getDocument(true);
+ TreePath path = cc.getTreeUtilities().pathFor(Utils.getOffset(doc, params.getPosition()));
+ List<Object> lookupContent = new ArrayList<>();
+
+ lookupContent.add(TreePathHandle.create(path, cc));
+
+ //from RenameRefactoringUI:
+ Element selected = cc.getTrees().getElement(path);
+ if (selected instanceof TypeElement && !((TypeElement) selected).getNestingKind().isNested()) {
+ ElementHandle<TypeElement> handle = ElementHandle.create((TypeElement) selected);
+ FileObject f = SourceUtils.getFile(handle, cc.getClasspathInfo());
+ if (f != null && selected.getSimpleName().toString().equals(f.getName())) {
+ lookupContent.add(f);
+ }
+ }
+
+ refactoring[0] = new RenameRefactoring(Lookups.fixed(lookupContent.toArray(new Object[0])));
+ refactoring[0].setNewName(params.getNewName());
+ refactoring[0].setSearchInComments(true); //TODO?
+ }, true);
+ if (cancel.get()) return ;
+ cancelCallback[0] = () -> refactoring[0].cancelRequest();
+ RefactoringSession session = RefactoringSession.create("Rename");
+ Problem p;
+ p = refactoring[0].checkParameters();
+ if (cancel.get()) return ;
+ if (p != null && p.isFatal()) {
+ ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
+ return ;
+ }
+ p = refactoring[0].preCheck();
+ if (p != null && p.isFatal()) {
+ ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
+ return ;
+ }
+ if (cancel.get()) return ;
+ p = refactoring[0].prepare(session);
+ if (p != null && p.isFatal()) {
+ ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
+ return ;
+ }
+ //TODO: check client capabilities!
+ List<Either<TextDocumentEdit, ResourceOperation>> resultChanges = new ArrayList<>();
+ List<Transaction> transactions = APIAccessor.DEFAULT.getCommits(session);
+ List<ModificationResult> results = new ArrayList<>();
+ for (Transaction t : transactions) {
+ if (t instanceof RefactoringCommit) {
+ RefactoringCommit c = (RefactoringCommit) t;
+ for (org.netbeans.modules.refactoring.spi.ModificationResult refResult : SPIAccessor.DEFAULT.getTransactions(c)) {
+ if (refResult instanceof JavaModificationResult) {
+ results.add(((JavaModificationResult) refResult).delegate);
+ } else {
+ throw new IllegalStateException(refResult.getClass().toString());
+ }
+ }
+ } else {
+ throw new IllegalStateException(t.getClass().toString());
+ }
+ }
+ for (ModificationResult mr : results) {
+ for (FileObject modified : mr.getModifiedFileObjects()) {
+ resultChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(params.getTextDocument().getUri(), /*XXX*/-1), fileModifications(mr, modified, null))));
+ }
+ }
+ List<RefactoringElementImplementation> fileChanges = APIAccessor.DEFAULT.getFileChanges(session);
+ for (RefactoringElementImplementation rei : fileChanges) {
+ if (rei instanceof FileRenamePlugin.RenameFile) {
+ String oldURI = params.getTextDocument().getUri();
+ int dot = oldURI.lastIndexOf('.');
+ int slash = oldURI.lastIndexOf('/');
+ String newURI = oldURI.substring(0, slash + 1) + params.getNewName() + oldURI.substring(dot);
+ ResourceOperation op = new RenameFile(oldURI, newURI);
+ resultChanges.add(Either.forRight(op));
+ } else {
+ throw new IllegalStateException(rei.getClass().toString());
+ }
+ }
+ for (RefactoringElement re : session.getRefactoringElements()) {
+ //TODO: verify no unknown elements!
+ }
+
+ session.finished();
+
+ result.complete(new WorkspaceEdit(resultChanges));
+ } catch (Throwable ex) {
+ result.completeExceptionally(ex);
+ }
+ });
+ return result;
}
@Override
public void didOpen(DidOpenTextDocumentParams params) {
try {
- FileObject file = fromUri(params.getTextDocument().getUri());
+ FileObject file = Utils.fromUri(params.getTextDocument().getUri());
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.getDocument();
// the document may be not opened yet. Clash with in-memory content can happen only if
@@ -1042,8 +1564,8 @@
NbDocument.runAtomic((StyledDocument) doc, () -> {
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
try {
- int start = getOffset(doc, change.getRange().getStart());
- int end = getOffset(doc, change.getRange().getEnd());
+ int start = Utils.getOffset(doc, change.getRange().getStart());
+ int end = Utils.getOffset(doc, change.getRange().getEnd());
doc.remove(start, end - start);
doc.insertString(start, change.getText(), null);
} catch (BadLocationException ex) {
@@ -1057,8 +1579,16 @@
@Override
public void didClose(DidCloseTextDocumentParams params) {
- openedDocuments.remove(params.getTextDocument().getUri());
- reportNotificationDone("didClose", params);
+ try {
+ FileObject file = Utils.fromUri(params.getTextDocument().getUri());
+ EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+ ec.close();
+ openedDocuments.remove(params.getTextDocument().getUri());
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ } finally {
+ reportNotificationDone("didClose", params);
+ }
}
@Override
@@ -1076,7 +1606,14 @@
}, "errors", false);
BACKGROUND_TASKS.create(() -> {
computeDiags(u, (info, doc) -> {
- return new HintsInvoker(HintsSettings.getGlobalSettings(), new AtomicBoolean()).computeHints(info);
+ Set<Severity> disabled = org.netbeans.modules.java.hints.spiimpl.Utilities.disableErrors(info.getFileObject());
+ if (disabled.size() == Severity.values().length) {
+ return Collections.emptyList();
+ }
+ return new HintsInvoker(HintsSettings.getGlobalSettings(), new AtomicBoolean()).computeHints(info)
+ .stream()
+ .filter(ed -> !disabled.contains(ed.getSeverity()))
+ .collect(Collectors.toList());
}, "hints", true);
}).schedule(DELAY);
});
@@ -1087,7 +1624,7 @@
private void computeDiags(String uri, ProduceErrors produceErrors, String keyPrefix, boolean update) {
try {
- FileObject file = fromUri(uri);
+ FileObject file = Utils.fromUri(uri);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
@@ -1103,8 +1640,8 @@
errors = Collections.emptyList();
}
for (ErrorDescription err : errors) {
- Diagnostic diag = new Diagnostic(new Range(createPosition(cc.getCompilationUnit(), err.getRange().getBegin().getOffset()),
- createPosition(cc.getCompilationUnit(), err.getRange().getEnd().getOffset())),
+ Diagnostic diag = new Diagnostic(new Range(Utils.createPosition(cc.getCompilationUnit(), err.getRange().getBegin().getOffset()),
+ Utils.createPosition(cc.getCompilationUnit(), err.getRange().getEnd().getOffset())),
err.getDescription());
switch (err.getSeverity()) {
case ERROR: diag.setSeverity(DiagnosticSeverity.Error); break;
@@ -1113,7 +1650,7 @@
case HINT: diag.setSeverity(DiagnosticSeverity.Hint); break;
default: diag.setSeverity(DiagnosticSeverity.Information); break;
}
- String id = keyPrefix + ":" + idx + "-" + err.getId();
+ String id = keyPrefix + ":" + idx++ + "-" + err.getId();
diag.setCode(id);
id2Errors.put(id, err);
diags.add(diag);
@@ -1152,7 +1689,7 @@
Document doc = openedDocuments.get(fileUri);
if (doc == null) {
try {
- FileObject file = fromUri(fileUri);
+ FileObject file = Utils.fromUri(fileUri);
return JavaSource.forFileObject(file);
} catch (MalformedURLException ex) {
return null;
@@ -1162,115 +1699,7 @@
}
}
- public static Position createPosition(CompilationUnitTree cut, int offset) {
- return createPosition(cut.getLineMap(), offset);
- }
-
- public static Position createPosition(LineMap lm, int offset) {
- return new Position((int) lm.getLineNumber(offset) - 1,
- (int) lm.getColumnNumber(offset) - 1);
- }
-
- public static Position createPosition(FileObject file, int offset) {
- try {
- EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
- StyledDocument doc = ec.openDocument();
- int line = NbDocument.findLineNumber(doc, offset);
- int column = NbDocument.findLineColumn(doc, offset);
-
- return new Position(line, column);
- } catch (IOException ex) {
- throw new IllegalStateException(ex);
- }
- }
-
- public static int getOffset(Document doc, Position pos) {
- return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter();
- }
-
- private static String toUri(FileObject file) {
- if (FileUtil.isArchiveArtifact(file)) {
- //VS code cannot open jar:file: URLs, workaround:
- //another workaround, should be:
- //File cacheDir = Places.getCacheSubfile("java-server");
- //but that locks up VS Code, using a temp directory:
- File cacheDir;
- try {
- cacheDir = Files.createTempDirectory("nbcode").toFile();
- } catch (IOException ex) {
- throw new UncheckedIOException(ex);
- }
- File segments = new File(cacheDir, "segments");
- Properties props = new Properties();
-
- try (InputStream in = new FileInputStream(segments)) {
- props.load(in);
- } catch (IOException ex) {
- //OK, may not exist yet
- }
- FileObject archive = FileUtil.getArchiveFile(file);
- String archiveString = archive.toURL().toString();
- File foundSegment = null;
- for (String segment : props.stringPropertyNames()) {
- if (archiveString.equals(props.getProperty(segment))) {
- foundSegment = new File(cacheDir, segment);
- break;
- }
- }
- if (foundSegment == null) {
- int i = 0;
- while (props.getProperty("s" + i) != null)
- i++;
- foundSegment = new File(cacheDir, "s" + i);
- props.put("s" + i, archiveString);
- try (OutputStream in = new FileOutputStream(segments)) {
- props.store(in, "");
- } catch (IOException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
- File cache = new File(foundSegment, FileUtil.getRelativePath(FileUtil.getArchiveRoot(archive), file));
- cache.getParentFile().mkdirs();
- try (OutputStream out = new FileOutputStream(cache)) {
- out.write(file.asBytes());
- return cache.toURI().toString();
- } catch (IOException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
- return file.toURI().toString();
- }
-
- //TODO: move to a separate Utils class:
- public static FileObject fromUri(String uri) throws MalformedURLException {
- File cacheDir = Places.getCacheSubfile("java-server");
- URI uriUri = URI.create(uri);
- URI relative = cacheDir.toURI().relativize(uriUri);
- if (relative != null && new File(cacheDir, relative.toString()).canRead()) {
- String segmentAndPath = relative.toString();
- int slash = segmentAndPath.indexOf('/');
- String segment = segmentAndPath.substring(0, slash);
- String path = segmentAndPath.substring(slash + 1);
- File segments = new File(cacheDir, "segments");
- Properties props = new Properties();
-
- try (InputStream in = new FileInputStream(segments)) {
- props.load(in);
- String archiveUri = props.getProperty(segment);
- FileObject archive = URLMapper.findFileObject(URI.create(archiveUri).toURL());
- archive = archive != null ? FileUtil.getArchiveRoot(archive) : null;
- FileObject file = archive != null ? archive.getFileObject(path) : null;
- if (file != null) {
- return file;
- }
- } catch (IOException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
- return URLMapper.findFileObject(URI.create(uri).toURL());
- }
-
- private static List<TextEdit> modify2TextEdits(JavaSource js, Task<WorkingCopy> task) throws IOException {
+ public static List<TextEdit> modify2TextEdits(JavaSource js, Task<WorkingCopy> task) throws IOException {
FileObject[] file = new FileObject[1];
LineMap[] lm = new LineMap[1];
ModificationResult changes = js.runModificationTask(wc -> {
@@ -1278,27 +1707,34 @@
file[0] = wc.getFileObject();
lm[0] = wc.getCompilationUnit().getLineMap();
});
+ return fileModifications(changes, file[0], lm[0]);
+ }
+
+ private static List<TextEdit> fileModifications(ModificationResult changes, FileObject file, LineMap lm) {
//TODO: full, correct and safe edit production:
- List<? extends ModificationResult.Difference> diffs = changes.getDifferences(file[0]);
+ List<? extends ModificationResult.Difference> diffs = changes.getDifferences(file);
if (diffs == null) {
return Collections.emptyList();
}
List<TextEdit> edits = new ArrayList<>();
+ IntFunction<Position> offset2Position = lm != null ? pos -> Utils.createPosition(lm, pos)
+ : pos -> Utils.createPosition(file, pos);
+
for (ModificationResult.Difference diff : diffs) {
String newText = diff.getNewText();
- edits.add(new TextEdit(new Range(createPosition(lm[0], diff.getStartPosition().getOffset()),
- createPosition(lm[0], diff.getEndPosition().getOffset())),
+ edits.add(new TextEdit(new Range(offset2Position.apply(diff.getStartPosition().getOffset()),
+ offset2Position.apply(diff.getEndPosition().getOffset())),
newText != null ? newText : ""));
}
return edits;
}
-
+
private static void reportNotificationDone(String s, Object parameter) {
if (HOOK_NOTIFICATION != null) {
HOOK_NOTIFICATION.accept(s, parameter);
}
}
-
+
/**
* For testing only; calls that do not return a result should call
* this hook, if defined, with the method name and parameter.
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
new file mode 100644
index 0000000..846bd3f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.GeneratorUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 50)
+public final class ToStringGenerator extends CodeGenerator {
+
+ public static final String GENERATE_TO_STRING = "java.generate.toString";
+
+ private final Set<String> commands = Collections.singleton(GENERATE_TO_STRING);
+ private final Gson gson = new Gson();
+
+ public ToStringGenerator() {
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_GenerateToString=Generate toString()...",
+ })
+ public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Source)) {
+ return Collections.emptyList();
+ }
+ int offset = getOffset(info, params.getRange().getStart());
+ TreePath tp = info.getTreeUtilities().pathFor(offset);
+ tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp == null) {
+ return Collections.emptyList();
+ }
+ TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+ if (type == null || !type.getKind().isClass()) {
+ return Collections.emptyList();
+ }
+ List<QuickPickItem> fields = new ArrayList<>();
+ for (Element element : type.getEnclosedElements()) {
+ switch (element.getKind()) {
+ case METHOD:
+ if (element.getSimpleName().contentEquals("toString") && ((ExecutableElement) element).getParameters().isEmpty()) { //NOI18N
+ return Collections.emptyList();
+ }
+ break;
+ case FIELD:
+ if (!ERROR.contentEquals(element.getSimpleName()) && !element.getModifiers().contains(Modifier.STATIC)) {
+ QuickPickItem item = new QuickPickItem(createLabel(info, (VariableElement)element));
+ item.setUserData(new ElementData(element));
+ fields.add(item);
+ }
+ break;
+ }
+ }
+ String uri = Utils.toUri(info.getFileObject());
+ return Collections.singletonList(createCodeAction(Bundle.DN_GenerateToString(), CODE_GENERATOR_KIND, GENERATE_TO_STRING, uri, offset, fields));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectToString=Select fields to be included in toString()",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+ if (arguments.size() > 2) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+ List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+ if (fields.isEmpty()) {
+ generate(client, uri, offset, fields);
+ } else {
+ client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectToString(), true, fields)).thenAccept(selected -> {
+ if (selected != null) {
+ generate(client, uri, offset, selected);
+ }
+ });
+ }
+ } else {
+ client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> fields) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+ wc.toPhase(JavaSource.Phase.RESOLVED);
+ TreePath tp = wc.getTreeUtilities().pathFor(offset);
+ tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+ if (tp != null) {
+ ClassTree cls = (ClassTree) tp.getLeaf();
+ List<VariableElement> selectedFields = fields.stream().map(item -> {
+ ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+ return (VariableElement)data.resolve(wc);
+ }).collect(Collectors.toList());
+ MethodTree method = org.netbeans.modules.java.editor.codegen.ToStringGenerator.createToStringMethod(wc, selectedFields, cls.getSimpleName().toString(), true);
+ wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, method));
+ }
+ });
+ client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+ } catch (IOException | IllegalArgumentException ex) {
+ client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+ }
+ }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index 52c395b..e542b39 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -18,21 +18,53 @@
*/
package org.netbeans.modules.java.lsp.server.protocol;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
+import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.WorkspaceService;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.debugger.ActionsManager;
import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.JavaSource.Phase;
+import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
+import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler;
+import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler.Exec;
+import org.netbeans.modules.java.source.usages.ClassIndexImpl;
+import org.netbeans.modules.parsing.lucene.support.Queries;
+import org.netbeans.spi.jumpto.type.SearchType;
+import org.netbeans.spi.project.ActionProgress;
import org.netbeans.spi.project.ActionProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
import org.openide.util.lookup.Lookups;
/**
@@ -41,6 +73,8 @@
*/
public final class WorkspaceServiceImpl implements WorkspaceService, LanguageClientAware {
+ private static final RequestProcessor WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false);
+
private NbCodeLanguageClient client;
public WorkspaceServiceImpl() {
@@ -48,27 +82,232 @@
@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
- switch (params.getCommand()) {
+ String command = params.getCommand();
+ switch (command) {
case Server.GRAALVM_PAUSE_SCRIPT:
ActionsManager am = DebuggerManager.getDebuggerManager().getCurrentEngine().getActionsManager();
am.doAction("pauseInGraalScript");
return CompletableFuture.completedFuture(true);
- case Server.JAVA_BUILD_WORKSPACE:
- for (Project prj : OpenProjects.getDefault().getOpenProjects()) {
- ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
- if (ap != null && ap.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookups.fixed())) {
- ap.invokeAction(ActionProvider.COMMAND_REBUILD, Lookups.fixed());
+ case Server.JAVA_BUILD_WORKSPACE: {
+ CompletableFuture<Object> compileFinished = new CompletableFuture<>();
+ class CompileAllProjects extends ActionProgress {
+ private int running;
+ private int success;
+ private int failure;
+
+ @Override
+ protected synchronized void started() {
+ running++;
+ }
+
+ @Override
+ public synchronized void finished(boolean ok) {
+ if (ok) {
+ success++;
+ } else {
+ failure++;
+ }
+ checkStatus();
+ }
+
+ synchronized final void checkStatus() {
+ if (running <= success + failure) {
+ compileFinished.complete(failure == 0);
+ }
}
}
- return CompletableFuture.completedFuture(true);
+ final CompileAllProjects progressOfCompilation = new CompileAllProjects();
+ final Lookup ctx = Lookups.singleton(progressOfCompilation);
+ for (Project prj : OpenProjects.getDefault().getOpenProjects()) {
+ ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
+ if (ap != null && ap.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookup.EMPTY)) {
+ ap.invokeAction(ActionProvider.COMMAND_REBUILD, ctx);
+ }
+ }
+ progressOfCompilation.checkStatus();
+ return compileFinished;
+ }
default:
- throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
+ for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+ if (codeGenerator.getCommands().contains(command)) {
+ return codeGenerator.processCommand(client, command, params.getArguments());
+ }
+ }
}
+ throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
}
@Override
- public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams arg0) {
- throw new UnsupportedOperationException("Not supported yet.");
+ public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams params) {
+ String query = params.getQuery();
+ if (query.isEmpty()) {
+ //cannot query "all":
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+ System.err.println("query=" + query);
+ boolean exact = false;
+ if (query.endsWith(" ")) {
+ query = query.substring(0, query.length() - 1);
+ exact = true;
+ }
+ String queryFin = query;
+ boolean exactFin = exact;
+ AtomicBoolean cancel = new AtomicBoolean();
+ CompletableFuture<List<? extends SymbolInformation>> result = new CompletableFuture<List<? extends SymbolInformation>>() {
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ cancel.set(mayInterruptIfRunning);
+ return super.cancel(mayInterruptIfRunning);
+ }
+ };
+ WORKER.post(() -> {
+ try {
+ List<SymbolInformation> symbols = new ArrayList<>();
+ ResultHandler handler = new ResultHandler() {
+ @Override
+ public void setHighlightText(String text) {
+ }
+
+ private Map<ElementHandle<TypeElement>, List<String>> type2Idents;
+
+ @Override
+ public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException {
+ ClasspathInfo cpInfo = ClasspathInfo.create(root);
+ try {
+ type2Idents = new HashMap<>();
+ exec.run();
+ Map<FileObject, Map<ElementHandle<TypeElement>, List<String>>> sources = new HashMap<>();
+ for (Entry<ElementHandle<TypeElement>, List<String>> e : type2Idents.entrySet()) {
+ FileObject sourceFile = SourceUtils.getFile(e.getKey(), cpInfo);
+ sources.computeIfAbsent(sourceFile, s -> new HashMap<>())
+ .put(e.getKey(), e.getValue());
+ }
+ if (!sources.isEmpty()) {
+ JavaSource.create(cpInfo, sources.keySet())
+ .runUserActionTask(cc -> {
+ if (Phase.ELEMENTS_RESOLVED.compareTo(cc.toPhase(Phase.ELEMENTS_RESOLVED))> 0) {
+ return ;
+ }
+ for (Entry<ElementHandle<TypeElement>, List<String>> e : sources.get(cc.getFileObject()).entrySet()) {
+ TypeElement te = e.getKey().resolve(cc);
+
+ if (te == null) {
+ //cannot resolve
+ continue;
+ }
+
+ for (String ident : e.getValue()) {
+ if (ident.equals(getSimpleName(te, null, false))) {
+ TreePath path = cc.getTrees().getPath(te);
+
+ if (path != null) {
+ final String symbolName = te.getSimpleName().toString();
+ final ElementKind kind = te.getKind();
+ SymbolInformation symbol = new SymbolInformation(symbolName, Utils.elementKind2SymbolKind(kind), tree2Location(cc, path), te.getQualifiedName().toString());
+
+ symbol.setDeprecated(false);
+ symbols.add(symbol);
+ }
+ }
+ for (Element ne : te.getEnclosedElements()) {
+ if (ident.equals(getSimpleName(ne, te, false))) {
+ TreePath path = cc.getTrees().getPath(ne);
+
+ if (path != null) {
+ final Pair<String,String> name = JavaSymbolProvider.getDisplayName(ne, te);
+ final String symbolName = name.first() + (name.second() != null ? name.second() : "");
+ final ElementKind kind = ne.getKind();
+ SymbolInformation symbol = new SymbolInformation(symbolName, Utils.elementKind2SymbolKind(kind), tree2Location(cc, path), te.getQualifiedName().toString());
+
+ symbol.setDeprecated(false);
+ symbols.add(symbol);
+ }
+ }
+ }
+ }
+ }
+ }, true);
+ }
+ //TODO: handle exceptions
+ } finally {
+ type2Idents = null;
+ }
+ }
+
+ @Override
+ public void handleResult(ElementHandle<TypeElement> owner, String ident, boolean caseSensitive) {
+ type2Idents.computeIfAbsent(owner, s -> new ArrayList<>()).add(ident);
+ }
+ };
+ JavaSymbolProvider.doComputeSymbols(getSearchType(queryFin, exactFin, false, null, null), queryFin, handler, true, cancel);
+ Collections.sort(symbols, (i1, i2) -> i1.getName().compareToIgnoreCase(i2.getName()));
+ result.complete(symbols);
+ } catch (Throwable t) {
+ result.completeExceptionally(t);
+ }
+ });
+ return result;
+ }
+
+ private Location tree2Location(CompilationInfo info, TreePath path) {
+ return new Location(Utils.toUri(info.getFileObject()),
+ Utils.treeRange(info, path.getLeaf()));
+ }
+
+ //from jumpto.Utils:
+ public static int containsWildCard( String text ) {
+ for( int i = 0; i < text.length(); i++ ) {
+ if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static boolean isAllUpper( String text ) {
+ for( int i = 0; i < text.length(); i++ ) {
+ if ( !Character.isUpperCase( text.charAt( i ) ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static SearchType getSearchType(
+ @NonNull final String text,
+ final boolean exact,
+ final boolean isCaseSensitive,
+ @NullAllowed final String camelCaseSeparator,
+ @NullAllowed final String camelCasePart) {
+ int wildcard = containsWildCard(text);
+ if (exact) {
+ //nameKind = isCaseSensitive ? SearchType.EXACT_NAME : SearchType.CASE_INSENSITIVE_EXACT_NAME;
+ return SearchType.EXACT_NAME;
+ } else if (wildcard != -1) {
+ return isCaseSensitive ? SearchType.REGEXP : SearchType.CASE_INSENSITIVE_REGEXP;
+ } else if ((isAllUpper(text) && text.length() > 1) || Queries.isCamelCase(text, camelCaseSeparator, camelCasePart)) {
+ return isCaseSensitive ? SearchType.CAMEL_CASE : SearchType.CASE_INSENSITIVE_CAMEL_CASE;
+ } else {
+ return isCaseSensitive ? SearchType.PREFIX : SearchType.CASE_INSENSITIVE_PREFIX;
+ }
+ }
+
+ //TODO: from AsyncJavaSymbolDescriptor:
+ private static final String INIT = "<init>"; //NOI18N
+ @NonNull
+ private static String getSimpleName (
+ @NonNull final Element element,
+ @NullAllowed final Element enclosingElement,
+ final boolean caseSensitive) {
+ String result = element.getSimpleName().toString();
+ if (enclosingElement != null && INIT.equals(result)) {
+ result = enclosingElement.getSimpleName().toString();
+ }
+ if (!caseSensitive) {
+ result = result.toLowerCase();
+ }
+ return result;
}
@Override
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
index 35e05a0..a35ba88 100644
--- 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
@@ -46,6 +46,22 @@
@Override
public Object notify(NotifyDescriptor descriptor) {
LspServerUtils.avoidClientMessageThread(context);
+
+ // XXX: Subject to better fix
+ // XXX: Possibly use 3.16 protocol's willRename/didRename
+ // XXX: when available
+ StackTraceElement[] stack = new Exception().getStackTrace();
+ if (stack.length > 2 && stack[1] != null) {
+ if (
+ "org.openide.text.DataEditorSupport".equals(stack[1].getClassName()) &&
+ "canClose".equals(stack[1].getMethodName())
+ ) {
+ // when VSCode renames modified file, don't try to save the
+ // original
+ return NotifyDescriptor.CLOSED_OPTION;
+ }
+ }
+
UIContext ctx = UIContext.find(context);
NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(descriptor, ctx);
return adapter.clientNotify();
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractProgressEnvironment.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractProgressEnvironment.java
new file mode 100644
index 0000000..13923ab
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractProgressEnvironment.java
@@ -0,0 +1,135 @@
+/*
+ * 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.regex.Pattern;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.modules.java.lsp.server.progress.LspInternalHandle;
+import org.netbeans.modules.java.lsp.server.progress.LspProgressUIWorker;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
+import org.openide.util.Cancellable;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author sdedic
+ */
+public abstract class AbstractProgressEnvironment implements ProgressEnvironment {
+ /**
+ * Some cancellables should not be accepted: they actually do weird things like
+ * RepositoryUpdater's one: it will do further UI operations when cancelled. Add
+ * impl class patterns here to make such progresses not cancellable in LSP clients.
+ */
+ private static final String[] MASK_CANCELLABLES = {
+ "org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater$"
+ };
+
+ private static final Controller NO_CONTEXT_CONTROLLER = new Controller(null);
+ private final Lookup env;
+
+ private Pattern patternMaskingCancellables;
+
+ protected AbstractProgressEnvironment() {
+ this(Lookups.proxy(() -> Lookup.getDefault()));
+ }
+
+ protected AbstractProgressEnvironment(Lookup env) {
+ this.env = env;
+ }
+
+ Pattern maskCancellablesPattern() {
+ if (patternMaskingCancellables == null) {
+ StringBuilder re = new StringBuilder();
+ for (String pref : MASK_CANCELLABLES) {
+ if (re.length() > 0) {
+ re.append("|");
+ }
+ re.append(Pattern.quote(pref) + ".*");
+ }
+ patternMaskingCancellables = Pattern.compile(re.toString());
+ }
+ return patternMaskingCancellables;
+ }
+
+ @Override
+ public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
+ NbCodeLanguageClient client = env.lookup(NbCodeLanguageClient.class);
+ OperationContext ctx = OperationContext.find(env);
+ if (client == null) {
+ if (ctx == null) {
+ return new NoContextHandle(displayname, c, userInit).createProgressHandle();
+ }
+ client = ctx.getClient();
+ }
+ if (c != null && maskCancellablesPattern().matcher(c.getClass().getName()).matches()) {
+ c = null;
+ }
+ return new LspInternalHandle(
+ ctx, client, this::findController, displayname, c, userInit).createProgressHandle();
+ }
+
+ public Controller findController(InternalHandle h) {
+ if (!(h instanceof LspInternalHandle)) {
+ return getController();
+ }
+ OperationContext ctx = ((LspInternalHandle)h).getContext();
+ NbCodeLanguageClient client = ctx.getClient();
+ if (client == null) {
+ return getController();
+ }
+ return new Controller(
+ new LspProgressUIWorker()
+ );
+ }
+
+ @Override
+ public Controller getController() {
+ NbCodeLanguageClient client = Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+ if (client == null) {
+ return NO_CONTEXT_CONTROLLER;
+ }
+ return new Controller(
+ new LspProgressUIWorker()
+ );
+ }
+
+ static class NoContextHandle extends InternalHandle {
+
+ public NoContextHandle(String displayName, Cancellable cancel, boolean userInitiated) {
+ super(displayName, cancel, userInitiated);
+ }
+
+
+ @Override
+ public synchronized void start(String message, int workunits, long estimate) {
+ try {
+ setController(NO_CONTEXT_CONTROLLER);
+ } catch (IllegalStateException ex) {
+ // controller already set
+ }
+ super.start(message, workunits, estimate);
+ }
+ }
+
+}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 38bccfe..bbb84d6 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -18,7 +18,6 @@
*/
package org.netbeans.modules.java.lsp.server.protocol;
-import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.File;
import java.io.FileWriter;
@@ -30,25 +29,33 @@
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
+import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
+import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItem;
@@ -65,10 +72,14 @@
import org.eclipse.lsp4j.DocumentHighlightParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
+import org.eclipse.lsp4j.ExecuteCommandParams;
+import org.eclipse.lsp4j.Hover;
+import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.Position;
@@ -76,14 +87,22 @@
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ReferenceContext;
import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
+import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WorkspaceClientCapabilities;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.WorkspaceEditCapabilities;
import org.eclipse.lsp4j.WorkspaceFolder;
+import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.launch.LSPLauncher;
@@ -93,6 +112,7 @@
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.api.sendopts.CommandLine;
import org.netbeans.junit.NbTestCase;
import org.netbeans.modules.java.source.BootClassPathUtil;
@@ -106,6 +126,7 @@
import org.openide.cookies.LineCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
import org.openide.modules.ModuleInfo;
import org.openide.modules.Places;
import org.openide.text.Line;
@@ -150,7 +171,7 @@
Class jsClass = JavaSource.class;
File javaCluster = Utilities.toFile(jsClass.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getParentFile();
System.setProperty("netbeans.dirs", javaCluster.getAbsolutePath());
- CacheFolderProvider.getCacheFolderForRoot(Places.getUserDirectory().toURI().toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT);
+ CacheFolderProvider.getCacheFolderForRoot(Utilities.toURI(Places.getUserDirectory()).toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT);
Lookup.getDefault().lookup(ModuleInfo.class); //start the module system
@@ -168,6 +189,7 @@
super.tearDown();
TextDocumentServiceImpl.HOOK_NOTIFICATION = null;
serverThread.stop();
+ OpenProjects.getDefault().close(OpenProjects.getDefault().getOpenProjects());
}
List<Diagnostic>[] diags = new List[1];
@@ -214,26 +236,26 @@
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
assertDiags(diags);//errors
assertDiags(diags);//hints
int hashCodeStart = code.indexOf("hashCode");
- Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get();
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, hashCodeStart + 2))).get();
assertTrue(completion.isRight());
List<String> actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
assertEquals(Arrays.asList("Method:hashCode() : int"), actualItems);
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
- id.setUri(src.toURI().toString());
+ id.setUri(toURI(src));
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "hashCode".length())), "hashCode".length(), "equ"))));
assertDiags(diags, "Error:0:31-0:34");//errors
assertDiags(diags, "Error:0:31-0:34");//hints
- completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get();
+ completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, hashCodeStart + 2))).get();
actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
if (jdk9Plus()) {
assertEquals(Arrays.asList("Method:equals(Object anObject) : boolean", "Method:equalsIgnoreCase(String anotherString) : boolean"), actualItems);
}
int testStart = code.indexOf("test") + "equ".length() - "hashCode".length();
- completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, testStart + 3))).get();
+ completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, testStart + 3))).get();
List<CompletionItem> actualCompletionItem = completion.getRight().getItems();
actualItems = actualCompletionItem.stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
assertEquals(Arrays.asList("Method:test() : void"), actualItems);
@@ -248,7 +270,7 @@
"Test.\n" +
"\n",
resolvedItem.getDocumentation().getRight().getValue());
- completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, 0))).get();
+ completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, 0))).get();
actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
assertTrue(actualItems.contains("Keyword:interface"));
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "equ".length())), "equ".length(), "hashCode"))));
@@ -258,7 +280,7 @@
assertDiags(diags, "Error:1:0-1:9");//hints
List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 0), new Position(1, 9)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
String log = codeActions.toString();
- assertEquals(log, 2, codeActions.size());
+ assertTrue(log, codeActions.size() >= 2);
assertTrue(log, codeActions.get(0).isRight());
CodeAction action = codeActions.get(0).getRight();
assertEquals("Cast ...o to String", action.getTitle());
@@ -503,14 +525,14 @@
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
assertDiags(diags); //errors
List<Diagnostic> diagnostics = assertDiags(diags, "Warning:1:7-1:19");//hints
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
- id.setUri(src.toURI().toString());
+ id.setUri(toURI(src));
List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 7), new Position(1, 19)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
String log = codeActions.toString();
- assertEquals(log, 1, codeActions.size());
+ assertTrue(log, codeActions.size() >= 1);
assertTrue(log, codeActions.get(0).isRight());
CodeAction action = codeActions.get(0).getRight();
assertEquals("Remove .toString()", action.getTitle());
@@ -597,8 +619,8 @@
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
- List<Either<SymbolInformation, DocumentSymbol>> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(src.toURI().toString()))).get();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+ List<Either<SymbolInformation, DocumentSymbol>> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(toURI(src)))).get();
String textualSymbols = "";
String sep = "";
for (Either<SymbolInformation, DocumentSymbol> sym : symbols) {
@@ -625,7 +647,16 @@
" line = 7\n" +
" character = 5\n" +
" ]\n" +
- "]:(Method:innerMethod:Range [\n" +
+ "]:(Constructor:Inner:Range [\n" +
+ " start = Position [\n" +
+ " line = 4\n" +
+ " character = 4\n" +
+ " ]\n" +
+ " end = Position [\n" +
+ " line = 4\n" +
+ " character = 4\n" +
+ " ]\n" +
+ "]:(), Method:innerMethod:Range [\n" +
" start = Position [\n" +
" line = 5\n" +
" character = 8\n" +
@@ -634,7 +665,16 @@
" line = 6\n" +
" character = 9\n" +
" ]\n" +
- "]:()), Field:field:Range [\n" +
+ "]:()), Constructor:Test:Range [\n" +
+ " start = Position [\n" +
+ " line = 0\n" +
+ " character = 7\n" +
+ " ]\n" +
+ " end = Position [\n" +
+ " line = 0\n" +
+ " character = 7\n" +
+ " ]\n" +
+ "]:(), Field:field:Range [\n" +
" start = Position [\n" +
" line = 1\n" +
" character = 4\n" +
@@ -715,27 +755,27 @@
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
Position pos = new Position(3, 30);
- List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
+ List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
assertEquals(1, definition.size());
- assertEquals(src.toURI().toString(), definition.get(0).getUri());
+ assertEquals(toURI(src), definition.get(0).getUri());
assertEquals(1, definition.get(0).getRange().getStart().getLine());
assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
assertEquals(1, definition.get(0).getRange().getEnd().getLine());
assertEquals(22, definition.get(0).getRange().getEnd().getCharacter());
pos = new Position(4, 30);
- definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
+ definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
assertEquals(1, definition.size());
- assertEquals(src.toURI().toString(), definition.get(0).getUri());
+ assertEquals(toURI(src), definition.get(0).getUri());
assertEquals(2, definition.get(0).getRange().getStart().getLine());
assertEquals(23, definition.get(0).getRange().getStart().getCharacter());
assertEquals(2, definition.get(0).getRange().getEnd().getLine());
assertEquals(30, definition.get(0).getRange().getEnd().getCharacter());
pos = new Position(5, 22);
- definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
+ definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
assertEquals(1, definition.size());
- assertEquals(otherSrc.toURI().toString(), definition.get(0).getUri());
+ assertEquals(toURI(otherSrc), definition.get(0).getUri());
assertEquals(2, definition.get(0).getRange().getStart().getLine());
assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
assertEquals(2, definition.get(0).getRange().getEnd().getLine());
@@ -856,13 +896,73 @@
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeResult result = server.initialize(new InitializeParams()).get();
assertTrue(result.getCapabilities().getDocumentHighlightProvider());
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
- assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 13))).get(),
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+ assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(toURI(src)), new Position(1, 13))).get(),
"<none>:2:21-2:31", "<none>:3:26-3:35", "<none>:4:13-4:22");
- assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 27))).get(),
+ assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(toURI(src)), new Position(1, 27))).get(),
"<none>:1:26-1:29", "<none>:2:12-2:15", "<none>:3:17-3:20");
}
+ public void testHover() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "/**\n" +
+ " * This is a test class with Javadoc.\n" +
+ " */\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " Test t = new Test();\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ FileUtil.refreshFor(getWorkDir());
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeResult result = server.initialize(new InitializeParams()).get();
+ assertTrue(result.getCapabilities().getHoverProvider());
+ Hover hover = server.getTextDocumentService().hover(new HoverParams(new TextDocumentIdentifier(toURI(src)), new Position(5, 10))).get();
+ assertNotNull(hover);
+ assertTrue(hover.getContents().isRight());
+ MarkupContent content = hover.getContents().getRight();
+ assertNotNull(content);
+ assertEquals(content.getKind(), "markdown");
+ assertEquals(content.getValue(), "**[](*0)**\n" +
+ "\n" +
+ "```\n" +
+ "public class Test\n" +
+ "extends Object\n" +
+ "```\n" +
+ "\n" +
+ "This is a test class with Javadoc.\n" +
+ "\n");
+ }
+
public void testAdvancedCompletion1() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
@@ -913,16 +1013,16 @@
serverLauncher.startListening();
LanguageServer server = serverLauncher.getRemoteProxy();
InitializeParams initParams = new InitializeParams();
- initParams.setRootUri(getWorkDir().toURI().toString());
+ initParams.setRootUri(toURI(getWorkDir()));
InitializeResult result = server.initialize(initParams).get();
indexingComplete.await();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
{
- VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(toURI(src), 1);
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id1, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(2, 8), new Position(2, 8)), 0, "s."))));
- Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(2, 8 + "s.".length()))).get();
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(2, 8 + "s.".length()))).get();
assertTrue(completion.isRight());
Optional<CompletionItem> lengthItem = completion.getRight().getItems().stream().filter(ci -> "length() : int".equals(ci.getLabel())).findAny();
assertTrue(lengthItem.isPresent());
@@ -935,13 +1035,13 @@
}
{
- VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(toURI(src), 1);
server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(1, 1), new Position(1, 1)), 0, "@java.lang."))));
Position afterJavaLang = new Position(1, 1 + "@java.lang.".length());
{
- Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLang)).get();
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterJavaLang)).get();
assertTrue(completion.isRight());
Optional<CompletionItem> annotationItem = completion.getRight().getItems().stream().filter(ci -> "annotation".equals(ci.getLabel())).findAny();
assertTrue(annotationItem.isPresent());
@@ -954,7 +1054,7 @@
Position afterJavaLangAnnotation = new Position(1, afterJavaLang.getCharacter() + "annotation.".length());
{
- Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLangAnnotation)).get();
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterJavaLangAnnotation)).get();
assertTrue(completion.isRight());
completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
Optional<CompletionItem> targetItem = completion.getRight().getItems().stream().filter(ci -> "Target".equals(ci.getLabel())).findAny();
@@ -968,7 +1068,7 @@
Position afterTarget = new Position(1, afterJavaLangAnnotation.getCharacter() + "Target(".length());
{
- Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get();
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterTarget)).get();
assertTrue(completion.isRight());
completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
Optional<CompletionItem> methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny();
@@ -987,7 +1087,7 @@
{
//import already exists:
- Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get();
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterTarget)).get();
assertTrue(completion.isRight());
completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
Optional<CompletionItem> methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny();
@@ -999,6 +1099,94 @@
}
}
+ public void testAutoImportOnCompletion() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
+ String code = "public class Test {\n" +
+ " private void t(String s) {\n" +
+ " \n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ List<Diagnostic>[] diags = new List[1];
+ CountDownLatch indexingComplete = new CountDownLatch(1);
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams params) {
+ if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+ indexingComplete.countDown();
+ } else {
+ throw new UnsupportedOperationException("Unexpected message.");
+ }
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeParams initParams = new InitializeParams();
+ initParams.setRootUri(toURI(getWorkDir()));
+ server.initialize(initParams).get();
+ indexingComplete.await();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+
+ {
+ VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(toURI(src), 1);
+ server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id1, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(2, 8), new Position(2, 8)), 0, "ArrayL"))));
+
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(2, 8 + "ArrayL".length()))).get();
+ assertTrue(completion.isRight());
+ Optional<CompletionItem> arrayListItem = completion.getRight().getItems().stream().filter(ci -> "ArrayList".equals(ci.getLabel())).findAny();
+ assertTrue(arrayListItem.isPresent());
+ assertNull(arrayListItem.get().getAdditionalTextEdits());
+ CompletableFuture<CompletionItem> resolvedItem = server.getTextDocumentService().resolveCompletionItem(arrayListItem.get());
+ assertEquals(1, resolvedItem.get().getAdditionalTextEdits().size());
+ assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getLine());
+ assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getCharacter());
+ assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getLine());
+ assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getCharacter());
+ assertEquals("\nimport java.util.ArrayList;\n\n", resolvedItem.get().getAdditionalTextEdits().get(0).getNewText());
+ }
+
+ {
+ VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(toURI(src), 1);
+ server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, 0), new Position(0, 0)), 0, "import java.util.ArrayList;\n"))));
+ server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(3, 8), new Position(3, 8)), 0, "ArrayL"))));
+
+ Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(3, 8 + "ArrayL".length()))).get();
+ assertTrue(completion.isRight());
+ Optional<CompletionItem> arrayListItem = completion.getRight().getItems().stream().filter(ci -> "ArrayList".equals(ci.getLabel())).findAny();
+ assertTrue(arrayListItem.isPresent());
+ assertNull(arrayListItem.get().getAdditionalTextEdits());
+ CompletableFuture<CompletionItem> resolvedItem = server.getTextDocumentService().resolveCompletionItem(arrayListItem.get());
+ assertNull(resolvedItem.get().getAdditionalTextEdits());
+ }
+ }
+
public void testFixImports() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
@@ -1022,10 +1210,20 @@
}
@Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
-
+
@Override
public void telemetryEvent(Object arg0) {
throw new UnsupportedOperationException("Not supported yet.");
@@ -1059,13 +1257,13 @@
InitializeParams initParams = new InitializeParams();
initParams.setInitializationOptions(new JsonParser().parse(
"{ nbcodeCapabilities: { statusBarMessageSupport : true } }").getAsJsonObject());
- initParams.setRootUri(getWorkDir().toURI().toString());
+ initParams.setRootUri(toURI(getWorkDir()));
InitializeResult result = server.initialize(initParams).get();
indexingComplete.await();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
Diagnostic unresolvable = assertDiags(diags, "Error:2:8-2:12").get(0);
- List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(src.toURI().toString()), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get();
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(toURI(src)), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get();
if (jdk9Plus()) {
assertEquals(2, codeActions.size());
}
@@ -1130,10 +1328,10 @@
initParams.setRootUri(getWorkDir().toURI().toString());
InitializeResult result = server.initialize(initParams).get();
indexingComplete.await();
- server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
{
- ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()),
+ ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(toURI(src)),
new Position(0, 15),
new ReferenceContext(false));
@@ -1144,7 +1342,7 @@
}
{
- ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()),
+ ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(toURI(src)),
new Position(0, 15),
new ReferenceContext(true));
@@ -1188,11 +1386,1841 @@
}
}
+ public void testWorkspaceSymbols() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
+ String code = "public class Test {\n" +
+ " public static class TestNested {}\n" +
+ " public static void testMethod() {}\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ CountDownLatch indexingComplete = new CountDownLatch(1);
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void showMessage(MessageParams params) {
+ if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+ indexingComplete.countDown();
+ } else {
+ throw new UnsupportedOperationException("Unexpected message.");
+ }
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeParams initParams = new InitializeParams();
+ initParams.setRootUri(toURI(getWorkDir()));
+ InitializeResult result = server.initialize(initParams).get();
+ indexingComplete.await();
+ List<? extends SymbolInformation> symbols = server.getWorkspaceService().symbol(new WorkspaceSymbolParams("Tes")).get();
+ List<String> actual = symbols.stream().map(si -> si.getKind() + ":" + si.getName() + ":" + si.getContainerName() + ":" + si.getDeprecated() + ":" + toString(si.getLocation())).collect(Collectors.toList());
+ assertEquals(Arrays.asList("Class:Test:Test:false:Test.java:0:0-3:1",
+ "Constructor:Test():Test:false:Test.java:0:7-0:7",
+ "Method:testMethod():Test:false:Test.java:2:4-2:38",
+ "Class:TestNested:Test.TestNested:false:Test.java:1:4-1:37",
+ "Constructor:TestNested():Test.TestNested:false:Test.java:1:18-1:18"),
+ actual);
+ }
+
+ public void testCodeActionGenerateVarFieldOrParam() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public String getName() {\n" +
+ " return name;\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 17), new Position(2, 17)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 3);
+ Optional<CodeAction> generateVariable =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Create local variable \"name\"".equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateVariable.isPresent());
+ assertEquals(generateVariable.get().getKind(), CodeActionKind.QuickFix);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = generateVariable.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(2, 0),
+ new Position(2, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals(" String name;\n",
+ fileChanges.get(0).getNewText());
+ Optional<CodeAction> generateField =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Create field \"name\" in Test".equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateField.isPresent());
+ assertEquals(generateField.get().getKind(), CodeActionKind.QuickFix);
+ changes = generateField.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(1, 0),
+ new Position(1, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " private String name;\n",
+ fileChanges.get(0).getNewText());
+ Optional<CodeAction> generateParameter =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Create parameter \"name\"".equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateParameter.isPresent());
+ assertEquals(generateParameter.get().getKind(), CodeActionKind.QuickFix);
+ changes = generateParameter.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(1, 26),
+ new Position(1, 26)),
+ fileChanges.get(0).getRange());
+ assertEquals("String name",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testCodeActionGenerateMethod() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public String get(int value) {\n" +
+ " return convertToString(value);\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 17), new Position(2, 17)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 1);
+ Optional<CodeAction> generateMehtod =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Create method \"convertToString(int)\" in Test".equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateMehtod.isPresent());
+ assertEquals(generateMehtod.get().getKind(), CodeActionKind.QuickFix);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = generateMehtod.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(4, 0),
+ new Position(4, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " private String convertToString(int value) {\n" +
+ " throw new UnsupportedOperationException(\"Not supported yet.\"); //To change body of generated methods, choose Tools | Templates.\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testCodeActionGenerateClass() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public Hello hello;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 14), new Position(2, 14)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 2);
+ Optional<CodeAction> generateClass =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Create class \"Hello\" in Test".equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateClass.isPresent());
+ assertEquals(generateClass.get().getKind(), CodeActionKind.QuickFix);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = generateClass.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(2, 0),
+ new Position(2, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " private static class Hello {\n" +
+ "\n" +
+ " public Hello() {\n" +
+ " }\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testCodeActionIntroduceVariable() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " System.out.println(\"Hello World\");\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 4);
+ Optional<CodeAction> introduceVariable =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Introduce Variable...".equals(a.getTitle()))
+ .findAny();
+ assertTrue(introduceVariable.isPresent());
+ assertEquals(introduceVariable.get().getKind(), CodeActionKind.RefactorExtract);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceVariable.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(4, fileChanges.size());
+ assertEquals(new Range(new Position(0, 0),
+ new Position(0, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ "import java.io.PrintStream;\n" +
+ "\n",
+ fileChanges.get(0).getNewText());
+ assertEquals(new Range(new Position(2, 8),
+ new Position(2, 8)),
+ fileChanges.get(1).getRange());
+ assertEquals("PrintStream out = ",
+ fileChanges.get(1).getNewText());
+ assertEquals(new Range(new Position(2, 18),
+ new Position(2, 41)),
+ fileChanges.get(2).getRange());
+ assertEquals("",
+ fileChanges.get(2).getNewText());
+ assertEquals(new Range(new Position(3, 0),
+ new Position(3, 0)),
+ fileChanges.get(3).getRange());
+ assertEquals(" out.println(\"Hello World\");\n",
+ fileChanges.get(3).getNewText());
+ Command command = introduceVariable.get().getCommand();
+ assertNotNull(command);
+ assertEquals("java.rename.element.at", command.getCommand());
+ List<Object> arguments = command.getArguments();
+ assertNotNull(arguments);
+ assertEquals(1, arguments.size());
+ assertEquals("115", arguments.get(0).toString());
+ }
+
+ public void testCodeActionIntroduceConstant() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " System.out.println(\"Hello World\");\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 4);
+ Optional<CodeAction> introduceConstant =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Introduce Constant...".equals(a.getTitle()))
+ .findAny();
+ assertTrue(introduceConstant.isPresent());
+ assertEquals(introduceConstant.get().getKind(), CodeActionKind.RefactorExtract);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceConstant.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(3, fileChanges.size());
+ assertEquals(new Range(new Position(0, 0),
+ new Position(0, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ "import java.io.PrintStream;\n" +
+ "\n",
+ fileChanges.get(0).getNewText());
+ assertEquals(new Range(new Position(2, 8),
+ new Position(2, 18)),
+ fileChanges.get(1).getRange());
+ assertEquals("OUT",
+ fileChanges.get(1).getNewText());
+ assertEquals(new Range(new Position(4, 0),
+ new Position(4, 0)),
+ fileChanges.get(2).getRange());
+ assertEquals(" private static PrintStream OUT = System.out;\n",
+ fileChanges.get(2).getNewText());
+ Command command = introduceConstant.get().getCommand();
+ assertNotNull(command);
+ assertEquals("java.rename.element.at", command.getCommand());
+ List<Object> arguments = command.getArguments();
+ assertNotNull(arguments);
+ assertEquals(1, arguments.size());
+ assertEquals("168", arguments.get(0).toString());
+ }
+
+ public void testCodeActionIntroduceField() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " System.out.println(\"Hello World\");\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 4);
+ Optional<CodeAction> introduceField =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Introduce Field...".equals(a.getTitle()))
+ .findAny();
+ assertTrue(introduceField.isPresent());
+ assertEquals(introduceField.get().getKind(), CodeActionKind.RefactorExtract);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceField.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(3, fileChanges.size());
+ assertEquals(new Range(new Position(0, 0),
+ new Position(0, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ "import java.io.PrintStream;\n" +
+ "\n",
+ fileChanges.get(0).getNewText());
+ assertEquals(new Range(new Position(2, 8),
+ new Position(2, 15)),
+ fileChanges.get(1).getRange());
+ assertEquals("",
+ fileChanges.get(1).getNewText());
+ assertEquals(new Range(new Position(4, 0),
+ new Position(4, 0)),
+ fileChanges.get(2).getRange());
+ assertEquals(" private static PrintStream out = System.out;\n",
+ fileChanges.get(2).getNewText());
+ Command command = introduceField.get().getCommand();
+ assertNotNull(command);
+ assertEquals("java.rename.element.at", command.getCommand());
+ List<Object> arguments = command.getArguments();
+ assertNotNull(arguments);
+ assertEquals(1, arguments.size());
+ assertEquals("168", arguments.get(0).toString());
+ }
+
+ public void testCodeActionIntroduceMethod() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " System.out.println(\"Hello World\");\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+
+ List<Diagnostic>[] diags = new List[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ synchronized (diags) {
+ while (diags[0] == null) {
+ try {
+ diags.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+ assertTrue(codeActions.size() >= 4);
+ Optional<CodeAction> introduceMethod =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> "Introduce Method...".equals(a.getTitle()))
+ .findAny();
+ assertTrue(introduceMethod.isPresent());
+ assertEquals(introduceMethod.get().getKind(), CodeActionKind.RefactorExtract);
+ List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceMethod.get().getEdit().getDocumentChanges();
+ assertEquals(1, changes.size());
+ assertTrue(changes.get(0).isLeft());
+ TextDocumentEdit edit = changes.get(0).getLeft();
+ assertEquals(edit.getTextDocument().getUri(), uri);
+ List<TextEdit> fileChanges = edit.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(3, fileChanges.size());
+ assertEquals(new Range(new Position(0, 0),
+ new Position(0, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ "import java.io.PrintStream;\n" +
+ "\n",
+ fileChanges.get(0).getNewText());
+ assertEquals(new Range(new Position(2, 8),
+ new Position(2, 18)),
+ fileChanges.get(1).getRange());
+ assertEquals("method()",
+ fileChanges.get(1).getNewText());
+ assertEquals(new Range(new Position(3, 0),
+ new Position(3, 0)),
+ fileChanges.get(2).getRange());
+ assertEquals(" }\n" +
+ "\n" +
+ " private static PrintStream method() {\n" +
+ " return System.out;\n",
+ fileChanges.get(2).getNewText());
+ Command command = introduceMethod.get().getCommand();
+ assertNotNull(command);
+ assertEquals("java.rename.element.at", command.getCommand());
+ List<Object> arguments = command.getArguments();
+ assertNotNull(arguments);
+ assertEquals(1, arguments.size());
+ assertEquals("174", arguments.get(0).toString());
+ }
+
+ public void testCodeActionGetterSetter() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private final String f1;\n" +
+ " private String f2;\n" +
+ " private final String f3;\n" +
+ " private final String f4;\n" +
+ " public String getF4() { return f4; }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 6), new Position(2, 6)), new CodeActionContext(Arrays.asList()))).get();
+ assertEquals(3, codeActions.size());
+ Optional<CodeAction> generateGetterSetter =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateGetterSetter.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(6, 0),
+ new Position(6, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " public String getF2() {\n" +
+ " return f2;\n" +
+ " }\n" +
+ "\n" +
+ " public void setF2(String f2) {\n" +
+ " this.f2 = f2;\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testSourceActionGetterSetter() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private final String f1;\n" +
+ " private String f2;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(9, codeActions.size());
+ Optional<CodeAction> generateGetterSetter =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateGetterSetter.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(3, 0),
+ new Position(3, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " public String getF2() {\n" +
+ " return f2;\n" +
+ " }\n" +
+ "\n" +
+ " public void setF2(String f2) {\n" +
+ " this.f2 = f2;\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(fileChanges.get(0).getRange(), 0, fileChanges.get(0).getNewText()))));
+ codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(7, codeActions.size());
+ Optional<CodeAction> generateGetter =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateGetterFor("f1").equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateGetter.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetter.get().getCommand().getCommand(), generateGetter.get().getCommand().getArguments())).get();
+ assertEquals(1, edit[0].getChanges().size());
+ fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(11, 0),
+ new Position(11, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " public String getF1() {\n" +
+ " return f1;\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testSourceActionConstructor() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test extends Exception {\n" +
+ " private final String f1;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(7, codeActions.size());
+ Optional<CodeAction> generateConstructor =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateConstructor().equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateConstructor.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateConstructor.get().getCommand().getCommand(), generateConstructor.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(2, 0),
+ new Position(2, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " public Test(String f1) {\n" +
+ " this.f1 = f1;\n" +
+ " }\n" +
+ "\n" +
+ " public Test(String f1, String string) {\n" +
+ " super(string);\n" +
+ " this.f1 = f1;\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testSourceActionEqualsHashCode() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "import java.util.Objects;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " private final String f1;\n" +
+ " private java.util.List<String> f2;\n" +
+ "\n" +
+ " @Override\n" +
+ " public int hashCode() {\n" +
+ " int hash = 3;\n" +
+ " hash = 71 * hash + Objects.hashCode(this.f1);\n" +
+ " hash = 71 * hash + Objects.hashCode(this.f2);\n" +
+ " return hash;\n" +
+ " }\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(5, 0), new Position(5, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(9, codeActions.size());
+ Optional<CodeAction> generateEquals =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateEquals().equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateEquals.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateEquals.get().getCommand().getCommand(), generateEquals.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(13, 0),
+ new Position(13, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " @Override\n" +
+ " public boolean equals(Object obj) {\n" +
+ " if (this == obj) {\n" +
+ " return true;\n" +
+ " }\n" +
+ " if (obj == null) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " if (getClass() != obj.getClass()) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " final Test other = (Test) obj;\n" +
+ " if (!Objects.equals(this.f1, other.f1)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " if (!Objects.equals(this.f2, other.f2)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " return true;\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testSourceActionToString() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private final String f1;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(7, codeActions.size());
+ Optional<CodeAction> generateToString =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateToString().equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateToString.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateToString.get().getCommand().getCommand(), generateToString.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(2, 0),
+ new Position(2, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " @Override\n" +
+ " public String toString() {\n" +
+ " StringBuilder sb = new StringBuilder();\n" +
+ " sb.append(\"Test{f1=\").append(f1);\n" +
+ " sb.append('}');\n" +
+ " return sb.toString();\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testSourceActionDelegateMethod() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private final String f1;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(7, codeActions.size());
+ Optional<CodeAction> generateDelegateMethod =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateDelegateMethod().equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateDelegateMethod.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateDelegateMethod.get().getCommand().getCommand(), generateDelegateMethod.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(2, fileChanges.size());
+ assertEquals(new Range(new Position(0, 0),
+ new Position(0, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\nimport java.util.stream.IntStream;\n\n",
+ fileChanges.get(0).getNewText());
+ assertEquals(new Range(new Position(2, 0),
+ new Position(2, 0)),
+ fileChanges.get(1).getRange());
+ assertEquals("\n" +
+ " public IntStream chars() {\n" +
+ " return f1.chars();\n" +
+ " }\n" +
+ "\n" +
+ " public IntStream codePoints() {\n" +
+ " return f1.codePoints();\n" +
+ " }\n",
+ fileChanges.get(1).getNewText());
+ }
+
+ public void testSourceActionOverrideMethod() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private final String f1;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(7, codeActions.size());
+ Optional<CodeAction> generateOverrideMethod =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateOverrideMethod().equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateOverrideMethod.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateOverrideMethod.get().getCommand().getCommand(), generateOverrideMethod.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(2, 0),
+ new Position(2, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n" +
+ " @Override\n" +
+ " protected void finalize() throws Throwable {\n" +
+ " super.finalize(); //To change body of generated methods, choose Tools | Templates.\n" +
+ " }\n" +
+ "\n" +
+ " @Override\n" +
+ " public String toString() {\n" +
+ " return super.toString(); //To change body of generated methods, choose Tools | Templates.\n" +
+ " }\n",
+ fileChanges.get(0).getNewText());
+ }
+
+ public void testSourceActionLogger() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private final String f1;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+ return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+ return CompletableFuture.completedFuture("LOGGER");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ server.initialize(new InitializeParams()).get();
+ String uri = src.toURI().toString();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+ assertEquals(7, codeActions.size());
+ Optional<CodeAction> generateLogger =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a -> Bundle.DN_GenerateLogger().equals(a.getTitle()))
+ .findAny();
+ assertTrue(generateLogger.isPresent());
+ server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateLogger.get().getCommand().getCommand(), generateLogger.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ assertEquals(1, edit[0].getChanges().size());
+ List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+ assertNotNull(fileChanges);
+ assertEquals(2, fileChanges.size());
+ assertEquals(new Range(new Position(0, 0),
+ new Position(0, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\nimport java.util.logging.Logger;\n\n",
+ fileChanges.get(0).getNewText());
+ assertEquals(new Range(new Position(1, 0),
+ new Position(1, 0)),
+ fileChanges.get(1).getRange());
+ assertEquals("\n" +
+ " private static final Logger LOGGER = Logger.getLogger(Test.class.getName());\n",
+ fileChanges.get(1).getNewText());
+ }
+
+ public void testNoErrorAndHintsFor() throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ String code = "public class Test {\n" +
+ " private String field;\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ File otherSrc = new File(getWorkDir(), "Other.java");
+ try (Writer w = new FileWriter(otherSrc)) {
+ w.write("/**Some source*/\n" +
+ "public class Other {\n" +
+ " public void test() { }\n" +
+ "}");
+ }
+ Map<String, List<Integer>> publishedDiagnostics = new HashMap<>();
+ FileUtil.refreshFor(getWorkDir());
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (publishedDiagnostics) {
+ publishedDiagnostics.computeIfAbsent(params.getUri(), uri -> new ArrayList<>())
+ .add(params.getDiagnostics().size());
+ publishedDiagnostics.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams arg0) {
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeResult result = server.initialize(new InitializeParams()).get();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+ Position pos = new Position(1, 14);
+ List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
+ assertEquals(1, definition.size());
+ String jlStringURI = definition.get(0).getUri();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(jlStringURI, "java", 0, URLMapper.findFileObject(new URL(jlStringURI)).asText())));
+ String otherSrcURI = toURI(otherSrc);
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(otherSrcURI, "java", 0, FileUtil.toFileObject(otherSrc).asText())));
+ synchronized (publishedDiagnostics) {
+ while (publishedDiagnostics.get(otherSrcURI) == null || publishedDiagnostics.get(otherSrcURI).size() != 2) {
+ publishedDiagnostics.wait();
+ }
+ }
+ assertEquals(Arrays.asList(0, 0), publishedDiagnostics.get(jlStringURI));
+ }
+
+ public void testRenameDocumentChangesCapabilitiesRenameOp() throws Exception {
+ doTestRename(init -> {
+ WorkspaceEditCapabilities wec = new WorkspaceEditCapabilities();
+ wec.setDocumentChanges(true);
+ wec.setResourceOperations(Arrays.asList("rename"));
+ WorkspaceClientCapabilities wcc = new WorkspaceClientCapabilities();
+ wcc.setWorkspaceEdit(wec);
+ init.setCapabilities(new ClientCapabilities(wcc, null, null));
+ },
+ cf -> {
+ WorkspaceEdit edit = cf.get();
+ assertTrue(edit.getChanges().isEmpty());
+ Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+ Set<String> expected = new HashSet<>(Arrays.asList("Test2.java:[3:25-3:28=>nue]", "Test2.java:[1:8-1:11=>nue]"));
+ assertEquals(expected, actual);
+ },
+ cf -> {
+ WorkspaceEdit edit = cf.get();
+ assertTrue(edit.getChanges().isEmpty());
+ Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+ Set<String> expected = new HashSet<>(Arrays.asList("Test.java:[0:27-0:31=>TestNew, 1:4-1:8=>TestNew, 2:11-2:15=>TestNew]", "Test.java:[0:13-0:17=>TestNew]", "Test.java=>TestNew.java"));
+ assertEquals(expected, actual);
+ });
+ }
+
+ public void testRenameDocumentChangesCapabilitiesNoRenameOp() throws Exception {
+ doTestRename(init -> {
+ WorkspaceEditCapabilities wec = new WorkspaceEditCapabilities();
+ wec.setDocumentChanges(true);
+ WorkspaceClientCapabilities wcc = new WorkspaceClientCapabilities();
+ wcc.setWorkspaceEdit(wec);
+ init.setCapabilities(new ClientCapabilities(wcc, null, null));
+ },
+ cf -> {
+ WorkspaceEdit edit = cf.get();
+ assertTrue(edit.getChanges().isEmpty());
+ Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+ Set<String> expected = new HashSet<>(Arrays.asList("Test2.java:[3:25-3:28=>nue]", "Test2.java:[1:8-1:11=>nue]"));
+ assertEquals(expected, actual);
+ },
+ cf -> {
+ WorkspaceEdit edit = cf.get();
+ assertTrue(edit.getChanges().isEmpty());
+ Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+ Set<String> expected = new HashSet<>(Arrays.asList("Test.java:[0:27-0:31=>TestNew, 1:4-1:8=>TestNew, 2:11-2:15=>TestNew]", "Test.java:[0:13-0:17=>TestNew]", "Test.java=>TestNew.java"));
+ assertEquals(expected, actual);
+ });
+ }
+
+ private void doTestRename(Consumer<InitializeParams> settings,
+ Validator<CompletableFuture<WorkspaceEdit>> validateFieldRename,
+ Validator<CompletableFuture<WorkspaceEdit>> validateClassRename) throws Exception {
+ File src = new File(getWorkDir(), "Test.java");
+ src.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
+ String code = "public class Test {\n" +
+ " int val = new Test2().get();\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ File src2 = new File(getWorkDir(), "Test2.java");
+ try (Writer w = new FileWriter(src2)) {
+ w.write("public class Test2 extends Test {\n" +
+ " Test t;\n" +
+ " void m(Test p) {};\n" +
+ " int get() { return t.val; };\n" +
+ "}\n");
+ }
+ List<Diagnostic>[] diags = new List[1];
+ CountDownLatch indexingComplete = new CountDownLatch(1);
+ Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams params) {
+ if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+ indexingComplete.countDown();
+ } else {
+ throw new UnsupportedOperationException("Unexpected message.");
+ }
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeParams initParams = new InitializeParams();
+ initParams.setRootUri(getWorkDir().toURI().toString());
+ settings.accept(initParams);
+ InitializeResult result = server.initialize(initParams).get();
+ indexingComplete.await();
+ server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+
+ {
+ RenameParams params = new RenameParams(new TextDocumentIdentifier(src2.toURI().toString()),
+ new Position(3, 27),
+ "nue");
+
+ validateFieldRename.validate(server.getTextDocumentService().rename(params));
+ }
+
+ {
+ RenameParams params = new RenameParams(new TextDocumentIdentifier(src.toURI().toString()),
+ new Position(0, 15),
+ "TestNew");
+
+ validateClassRename.validate(server.getTextDocumentService().rename(params));
+ }
+
+ }
+
+ interface Validator<T> {
+ public void validate(T t) throws Exception;
+ }
+
+ private String toString(Either<TextDocumentEdit, ResourceOperation> e) {
+ if (e.isLeft()) {
+ TextDocumentEdit ted = e.getLeft();
+ VersionedTextDocumentIdentifier td = ted.getTextDocument();
+
+ return toString(td) + ":" + ted.getEdits().stream().map(this::toString).collect(Collectors.joining(", ", "[", "]"));
+ } else {
+ switch (e.getRight().getKind()) {
+ case "rename":
+ RenameFile rf = (RenameFile) e.getRight();
+ return uriToString(rf.getOldUri()) + "=>" + uriToString(rf.getNewUri());
+ default:
+ throw new IllegalStateException(e.getRight().getKind());
+ }
+ }
+ }
+
+ private String toString(VersionedTextDocumentIdentifier td) {
+ return uriToString(td.getUri())/* + "(" + td.getVersion() + ")"*/;
+ }
+
+ private String toString(TextEdit edit) {
+ return toString(edit.getRange()) + "=>" + edit.getNewText();
+ }
+
private String toString(Location location) {
String path = location.getUri();
String simpleName = path.substring(path.lastIndexOf('/') + 1);
- return simpleName + ":" + location.getRange().getStart().getLine() + ":" + location.getRange().getStart().getCharacter() +
- "-" + location.getRange().getEnd().getLine() + ":" + location.getRange().getEnd().getCharacter();
+ return simpleName + ":" + toString(location.getRange());
+ }
+
+ private String uriToString(String uri) {
+ return uri.substring(uri.lastIndexOf('/') + 1);
+ }
+
+ private String toString(Range range) {
+ return range.getStart().getLine() + ":" + range.getStart().getCharacter() +
+ "-" + range.getEnd().getLine() + ":" + range.getEnd().getCharacter();
}
private void assertHighlights(List<? extends DocumentHighlight> highlights, String... expected) {
@@ -1207,6 +3235,10 @@
stringHighlights);
}
+ private String toURI(File f) {
+ return Utilities.toURI(f).toString();
+ }
+
private static boolean jdk9Plus() {
String version = System.getProperty("java.version");
if (version == null || version.startsWith("1.")) {
diff --git a/java/java.lsp.server/vscode/.vscode/launch.json b/java/java.lsp.server/vscode/.vscode/launch.json
index b1fbaf5..9f7bc80 100644
--- a/java/java.lsp.server/vscode/.vscode/launch.json
+++ b/java/java.lsp.server/vscode/.vscode/launch.json
@@ -25,7 +25,8 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
- "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
+ "--extensionTestsPath=${workspaceFolder}/out/test/suite/index",
+ "${workspaceFolder}/out/test/ws"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
diff --git a/java/java.lsp.server/vscode/BUILD.md b/java/java.lsp.server/vscode/BUILD.md
index 46d66ec..fd6157b 100644
--- a/java/java.lsp.server/vscode/BUILD.md
+++ b/java/java.lsp.server/vscode/BUILD.md
@@ -62,11 +62,33 @@
Often it is also important to properly clean everything. Use:
```bash
-java.lsp.server$ ant clean-vscode-server
+java.lsp.server$ ant clean-vscode-ext
java.lsp.server$ cd ../..
netbeans$ ant clean
```
+### Testing
+
+The `java.lsp.server` module has classical (as other NetBeans modules) tests.
+The most important one is [ServerTest](https://github.com/apache/netbeans/blob/master/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java)
+which simulates LSP communication and checks expected replies. In addition to
+that there are VS Code integration tests - those launch VS Code with the
+VSNetBeans extension and check behavior of the TypeScript integration code:
+
+```bash
+java.lsp.server$ ant build-vscode-ext # first and then
+java.lsp.server$ ant test-vscode-ext
+```
+
+In case you are behind a proxy, you may want to run the tests with
+
+```bash
+java.lsp.server$ npm_config_https_proxy=http://your.proxy.com:port ant test-vscode-ext
+```
+
+when executing the tests for the first time. That shall overcome the proxy
+and download an instance of `code` execute the tests on.
+
## Running and Debugging
Have a sample Maven project, open it in NetBeans first and select the main file for both
diff --git a/java/java.lsp.server/vscode/package-lock.json b/java/java.lsp.server/vscode/package-lock.json
index 0e3253f..3ed2b2e 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -810,6 +810,11 @@
"glob": "^7.1.3"
}
},
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -884,18 +889,18 @@
"dev": true
},
"vscode-debugadapter": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.33.0.tgz",
- "integrity": "sha512-GsSD6PyVokjyW+7LU3FRXbu8SINv9muQtMQEDvwB6SIGQfrQzld8kECpQRr71j3mgXW+sTY+44/XMzueeaU6/Q==",
+ "version": "1.42.1",
+ "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.42.1.tgz",
+ "integrity": "sha512-bICDB8mxReU2kGL13ftjNqeInYtVN3zpRdZKjRZHCpXC/mofrdaj9KRlJsALwcUNivMA9S/LwTZ2n+ros3X0jg==",
"requires": {
- "mkdirp": "^0.5.1",
- "vscode-debugprotocol": "1.33.0"
+ "mkdirp": "^0.5.5",
+ "vscode-debugprotocol": "1.42.0"
}
},
"vscode-debugprotocol": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.33.0.tgz",
- "integrity": "sha512-d+l4lrEz6OP2kmGpweqe37x9H7icAMV8S4m8azTWGAIlNJxBP4rlSTnZa7NMLcbgqWkWG9lTGY7fJ+rSPaW7yg=="
+ "version": "1.42.0",
+ "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.42.0.tgz",
+ "integrity": "sha512-nVsfVCat9FZlOso5SYB1LQQiFGifTyOALpkpJdudDlRXGTpI3mSFiDYXWaoFm7UcfqTOzn1SC7Hqw4d89btT0w=="
},
"vscode-jsonrpc": {
"version": "5.0.1",
@@ -903,11 +908,12 @@
"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
},
"vscode-languageclient": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.4.2.tgz",
- "integrity": "sha512-9TUzsg1UM6n1UEyPlWbDf7tK1wJAK7UGFRmGDN8sz4KmbbDiVRh6YicaB/5oRSVTpuV47PdJpYlOl3SJ0RiK1Q==",
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz",
+ "integrity": "sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==",
"requires": {
- "vscode-languageserver-protocol": "^3.10.3"
+ "semver": "^6.3.0",
+ "vscode-languageserver-protocol": "^3.15.3"
}
},
"vscode-languageserver-protocol": {
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index cc68979..71de400 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -25,12 +25,22 @@
},
"activationEvents": [
"onLanguage:java",
+ "workspaceContains:**/*.java",
"workspaceContains:pom.xml",
"workspaceContains:build.gradle",
"onDebug"
],
"main": "./out/extension.js",
"contributes": {
+ "languages": [
+ {
+ "id": "javascript",
+ "mimetypes": [
+ "text/javascript",
+ "application/javascript"
+ ]
+ }
+ ],
"configuration": {
"title": "Java",
"properties": {
@@ -159,11 +169,10 @@
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
- "lint": "eslint src --ext ts",
"watch": "tsc -watch -p ./",
- "pretest": "npm run compile && npm run lint",
"test": "node ./out/test/runTest.js",
- "nbcode": "node ./out/nbcode.js"
+ "nbcode": "node ./out/nbcode.js",
+ "nbjavac": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install .*nbjavac.*"
},
"devDependencies": {
"@types/vscode": "^1.47.0",
@@ -176,7 +185,7 @@
"vscode-test": "^1.3.0"
},
"dependencies": {
- "vscode-languageclient": "4.4.2",
- "vscode-debugadapter": "1.33.0"
+ "vscode-languageclient": "6.1.3",
+ "vscode-debugadapter": "1.42.1"
}
}
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 1c85249..c3a78f3 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -28,6 +28,8 @@
StreamInfo,
Message,
MessageType,
+ LogMessageNotification,
+ RevealOutputChannelOn
} from 'vscode-languageclient';
import * as net from 'net';
@@ -36,13 +38,34 @@
import { ChildProcess } from 'child_process';
import * as vscode from 'vscode';
import * as launcher from './nbcode';
-import { StatusMessageRequest, ShowStatusMessageParams } from './protocol';
+import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest } from './protocol';
-let client: LanguageClient;
+const API_VERSION : string = "1.0";
+let client: Promise<LanguageClient>;
let nbProcess : ChildProcess | null = null;
let debugPort: number = -1;
+let consoleLog: boolean = !!process.env['ENABLE_CONSOLE_LOG'];
-function findClusters(myPath : string): string[] {
+function handleLog(log: vscode.OutputChannel, msg: string): void {
+ log.appendLine(msg);
+ if (consoleLog) {
+ console.log(msg);
+ }
+}
+
+function handleLogNoNL(log: vscode.OutputChannel, msg: string): void {
+ log.append(msg);
+ if (consoleLog) {
+ process.stdout.write(msg);
+ }
+}
+
+export function enableConsoleLog() {
+ consoleLog = true;
+ console.log("enableConsoleLog");
+}
+
+export function findClusters(myPath : string): string[] {
let clusters = [];
for (let e of vscode.extensions.all) {
if (e.extensionPath === myPath) {
@@ -108,14 +131,18 @@
onChange(currentJdk);
}
-export function activate(context: ExtensionContext) {
+interface VSNetBeansAPI {
+ version : string;
+}
+
+export function activate(context: ExtensionContext): VSNetBeansAPI {
let log = vscode.window.createOutputChannel("Apache NetBeans Language Server");
let conf = workspace.getConfiguration();
if (conf.get("netbeans.conflict.check")) {
let e = vscode.extensions.getExtension('redhat.java');
function disablingFailed(reason: any) {
- log.appendLine('Disabling some services failed ' + reason);
+ handleLog(log, 'Disabling some services failed ' + reason);
}
if (e && workspace.name) {
vscode.window.showInformationMessage(`redhat.java found at ${e.extensionPath} - Suppressing some services to not clash with Apache NetBeans Language Server.`);
@@ -143,29 +170,42 @@
context.subscriptions.push(commands.registerCommand('java.workspace.compile', () => {
return window.withProgress({ location: ProgressLocation.Window }, p => {
return new Promise(async (resolve, reject) => {
+ let c : LanguageClient = await client;
const commands = await vscode.commands.getCommands();
if (commands.includes('java.build.workspace')) {
p.report({ message: 'Compiling workspace...' });
- if (client != null) {
- client.outputChannel.show(true);
- }
+ c.outputChannel.show(true);
const start = new Date().getTime();
+ handleLog(log, `starting java.build.workspace`);
const res = await vscode.commands.executeCommand('java.build.workspace');
const elapsed = new Date().getTime() - start;
+ handleLog(log, `finished java.build.workspace in ${elapsed} ms with result ${res}`);
const humanVisibleDelay = elapsed < 1000 ? 1000 : 0;
setTimeout(() => { // set a timeout so user would still see the message when build time is short
if (res) {
- resolve();
+ resolve(res);
} else {
- reject();
+ reject(res);
}
}, humanVisibleDelay);
} else {
- reject();
+ reject(`cannot compile workspace; client is ${client}`);
}
});
});
}));
+ context.subscriptions.push(commands.registerCommand('java.rename.element.at', async (offset) => {
+ const editor = window.activeTextEditor;
+ if (editor) {
+ await commands.executeCommand('editor.action.rename', [
+ editor.document.uri,
+ editor.document.positionAt(offset),
+ ]);
+ }
+ }));
+ return Object.freeze({
+ version : API_VERSION
+ });
}
/**
@@ -179,29 +219,33 @@
let activationPending : boolean = false;
function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean): void {
+ let setClient : [(c : LanguageClient) => void, (err : any) => void];
+ client = new Promise<LanguageClient>((clientOK, clientErr) => {
+ setClient = [ clientOK, clientErr ];
+ });
const a : Promise<void> | null = maintenance;
if (activationPending) {
// do not activate more than once in parallel.
- console.log("Server activation requested repeatedly, ignoring...");
+ handleLog(log, "Server activation requested repeatedly, ignoring...");
return;
}
activationPending = true;
// chain the restart after termination of the former process.
if (a != null) {
- console.log("Server activation initiated while in maintenance mode, scheduling after maintenance");
+ handleLog(log, "Server activation initiated while in maintenance mode, scheduling after maintenance");
a.then(() => killNbProcess(notifyKill, log)).
- then(() => doActivateWithJDK(specifiedJDK, context, log, notifyKill));
+ then(() => doActivateWithJDK(specifiedJDK, context, log, notifyKill, setClient));
} else {
- console.log("Initiating server activation");
+ handleLog(log, "Initiating server activation");
killNbProcess(notifyKill, log).then(
- () => doActivateWithJDK(specifiedJDK, context, log, notifyKill)
+ () => doActivateWithJDK(specifiedJDK, context, log, notifyKill, setClient)
);
}
}
function killNbProcess(notifyKill : boolean, log : vscode.OutputChannel, specProcess?: ChildProcess) : Promise<void> {
const p = nbProcess;
- console.log("Request to kill LSP server.");
+ handleLog(log, "Request to kill LSP server.");
if (p && (!specProcess || specProcess == p)) {
if (notifyKill) {
vscode.window.setStatusBarMessage("Restarting Apache NetBeans Language Server.", 2000);
@@ -209,10 +253,10 @@
return new Promise((resolve, reject) => {
nbProcess = null;
p.on('close', function(code: number) {
- console.log("LSP server closed: " + p.pid)
+ handleLog(log, "LSP server closed: " + p.pid)
resolve();
});
- console.log("Killing LSP server " + p.pid);
+ handleLog(log, "Killing LSP server " + p.pid);
if (!p.kill()) {
reject("Cannot kill");
}
@@ -222,15 +266,17 @@
if (specProcess) {
msg += "Requested kill on " + specProcess.pid + ", ";
}
- console.log(msg + "current process is " + (p ? p.pid : "None"));
+ handleLog(log, msg + "current process is " + (p ? p.pid : "None"));
return new Promise((res, rej) => { res(); });
}
}
-function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean): void {
+function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean,
+ setClient : [(c : LanguageClient) => void, (err : any) => void]
+): void {
maintenance = null;
let restartWithJDKLater : ((time: number, n: boolean) => void) = function restartLater(time: number, n : boolean) {
- log.appendLine(`Restart of Apache Language Server requested in ${(time / 1000)} s.`);
+ handleLog(log, `Restart of Apache Language Server requested in ${(time / 1000)} s.`);
setTimeout(() => {
activateWithJDK(specifiedJDK, context, log, n);
}, time);
@@ -245,51 +291,53 @@
verbose: beVerbose
};
let launchMsg = `Launching Apache NetBeans Language Server with ${specifiedJDK ? specifiedJDK : 'default system JDK'}`;
- log.appendLine(launchMsg);
+ handleLog(log, launchMsg);
vscode.window.setStatusBarMessage(launchMsg, 2000);
let ideRunning = new Promise((resolve, reject) => {
- let collectedText : string | null = '';
- function logAndWaitForEnabled(text: string) {
+ let stdOut : string | null = '';
+ function logAndWaitForEnabled(text: string, isOut: boolean) {
if (p == nbProcess) {
activationPending = false;
}
- log.append(text);
- if (collectedText == null) {
+ handleLogNoNL(log, text);
+ if (stdOut == null) {
return;
}
- collectedText += text;
- if (collectedText.match(/org.netbeans.modules.java.lsp.server.*Enabled/)) {
+ if (isOut) {
+ stdOut += text;
+ }
+ if (stdOut.match(/org.netbeans.modules.java.lsp.server.*Enabled/)) {
resolve(text);
- collectedText = null;
+ stdOut = null;
}
}
let p = launcher.launch(info, "--modules", "--list");
- console.log("LSP server launching: " + p.pid);
+ handleLog(log, "LSP server launching: " + p.pid);
p.stdout.on('data', function(d: any) {
- logAndWaitForEnabled(d.toString());
+ logAndWaitForEnabled(d.toString(), true);
});
p.stderr.on('data', function(d: any) {
- logAndWaitForEnabled(d.toString());
+ logAndWaitForEnabled(d.toString(), false);
});
nbProcess = p;
p.on('close', function(code: number) {
if (p == nbProcess && code != 0 && code) {
vscode.window.showWarningMessage("Apache NetBeans Language Server exited with " + code);
}
- if (collectedText != null) {
- let match = collectedText.match(/org.netbeans.modules.java.lsp.server[^\n]*/)
+ if (stdOut != null) {
+ let match = stdOut.match(/org.netbeans.modules.java.lsp.server[^\n]*/)
if (match?.length == 1) {
- log.appendLine(match[0]);
+ handleLog(log, match[0]);
} else {
- log.appendLine("Cannot find org.netbeans.modules.java.lsp.server in the log!");
+ handleLog(log, "Cannot find org.netbeans.modules.java.lsp.server in the log!");
}
log.show(false);
killNbProcess(false, log, p);
reject("Apache NetBeans Language Server not enabled!");
} else {
- console.log("LSP server " + p.pid + " terminated with " + code);
- log.appendLine("Exit code " + code);
+ handleLog(log, "LSP server " + p.pid + " terminated with " + code);
+ handleLog(log, "Exit code " + code);
}
});
});
@@ -347,7 +395,8 @@
]
},
outputChannel: log,
- revealOutputChannelOn: 3, // error
+ revealOutputChannelOn: RevealOutputChannelOn.Never,
+ progressOnInitialization: true,
initializationOptions : {
'nbcodeCapabilities' : {
'statusBarMessageSupport' : true
@@ -358,7 +407,7 @@
return ErrorAction.Continue;
},
closed : function(): CloseAction {
- log.appendLine("Connection to Apache NetBeans Language Server closed.");
+ handleLog(log, "Connection to Apache NetBeans Language Server closed.");
if (!activationPending) {
restartWithJDKLater(10000, false);
}
@@ -367,23 +416,32 @@
}
}
- if (client) {
- client.stop();
- }
- client = new LanguageClient(
+
+ let c = new LanguageClient(
'java',
'NetBeans Java',
connection,
clientOptions
);
- client.start();
- client.onReady().then(() => {
+ handleLog(log, 'Language Client: Starting');
+ c.start();
+ c.onReady().then(() => {
commands.executeCommand('setContext', 'nbJavaLSReady', true);
- client.onNotification(StatusMessageRequest.type, showStatusBarMessage);
- });
+ c.onNotification(StatusMessageRequest.type, showStatusBarMessage);
+ c.onNotification(LogMessageNotification.type, (param) => handleLog(log, param.message));
+ c.onRequest(QuickPickRequest.type, async param => {
+ const selected = await window.showQuickPick(param.items, { placeHolder: param.placeHolder, canPickMany: param.canPickMany });
+ return selected ? Array.isArray(selected) ? selected : [selected] : undefined;
+ });
+ c.onRequest(InputBoxRequest.type, async param => {
+ return await window.showInputBox({ prompt: param.prompt, value: param.value });
+ });
+ handleLog(log, 'Language Client: Ready');
+ setClient[0](c);
+ }).catch(setClient[1]);
}).catch((reason) => {
activationPending = false;
- log.append(reason);
+ handleLog(log, reason);
window.showErrorMessage('Error initializing ' + reason);
});
@@ -422,15 +480,15 @@
if (yes === reply) {
vscode.window.setStatusBarMessage("Preparing Apache NetBeans Language Server for additional installation", 2000);
restartWithJDKLater = function() {
- console.log("Ignoring request for restart of Apache NetBeans Language Server");
+ handleLog(log, "Ignoring request for restart of Apache NetBeans Language Server");
};
maintenance = new Promise((resolve, reject) => {
const kill : Promise<void> = killNbProcess(false, log);
kill.then(() => {
let installProcess = launcher.launch(info, "-J-Dnetbeans.close=true", "--modules", "--install", ".*nbjavac.*");
- console.log("Launching installation process: " + installProcess.pid);
+ handleLog(log, "Launching installation process: " + installProcess.pid);
let logData = function(d: any) {
- log.append(d.toString());
+ handleLogNoNL(log, d.toString());
};
installProcess.stdout.on('data', logData);
installProcess.stderr.on('data', logData);
@@ -438,8 +496,8 @@
// MUST wait on 'close', since stdout is inherited by children. The installProcess dies but
// the inherited stream will be closed by the last child dying.
installProcess.on('close', function(code: number) {
- console.log("Installation completed: " + installProcess.pid);
- log.appendLine("Additional Java Support installed with exit code " + code);
+ handleLog(log, "Installation completed: " + installProcess.pid);
+ handleLog(log, "Additional Java Support installed with exit code " + code);
// will be actually run after maintenance is resolve()d.
activateWithJDK(specifiedJDK, context, log, notifyKill)
resolve();
@@ -457,10 +515,7 @@
if (nbProcess != null) {
nbProcess.kill();
}
- if (!client) {
- return Promise.resolve();
- }
- return client.stop();
+ return client.then(c => c.stop());
}
class NetBeansDebugAdapterDescriptionFactory implements vscode.DebugAdapterDescriptorFactory {
diff --git a/java/java.lsp.server/vscode/src/protocol.ts b/java/java.lsp.server/vscode/src/protocol.ts
index 4b485ba..6f56c6d 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -18,8 +18,10 @@
*/
'use strict';
+import {QuickPickItem} from 'vscode';
import {
NotificationType,
+ RequestType,
ShowMessageParams
} from 'vscode-languageclient';
@@ -33,3 +35,37 @@
export namespace StatusMessageRequest {
export const type = new NotificationType<ShowStatusMessageParams, void>('window/showStatusBarMessage');
};
+
+export interface ShowQuickPickParams {
+ /**
+ * A string to show as placeholder in the input box to guide the user what to pick on.
+ */
+ placeHolder: string;
+ /**
+ * An optional flag to make the picker accept multiple selections.
+ */
+ canPickMany?: boolean;
+ /**
+ * A list of items.
+ */
+ items: QuickPickItem[];
+}
+
+export namespace QuickPickRequest {
+ export const type = new RequestType<ShowQuickPickParams, QuickPickItem[], void, void>('window/showQuickPick');
+}
+
+export interface ShowInputBoxParams {
+ /**
+ * The text to display underneath the input box.
+ */
+ prompt: string;
+ /**
+ * The value to prefill in the input box.
+ */
+ value: string;
+}
+
+export namespace InputBoxRequest {
+ export const type = new RequestType<ShowInputBoxParams, string | undefined, void, void>('window/showInputBox');
+}
diff --git a/java/java.lsp.server/vscode/src/test/runTest.ts b/java/java.lsp.server/vscode/src/test/runTest.ts
index 1eabfa3..5ed5f50 100644
--- a/java/java.lsp.server/vscode/src/test/runTest.ts
+++ b/java/java.lsp.server/vscode/src/test/runTest.ts
@@ -2,22 +2,39 @@
import { runTests } from 'vscode-test';
+import * as os from 'os';
+import * as fs from 'fs';
+
async function main() {
- try {
- // The folder containing the Extension Manifest package.json
- // Passed to `--extensionDevelopmentPath`
- const extensionDevelopmentPath = path.resolve(__dirname, '../../');
+ try {
+ // The folder containing the Extension Manifest package.json
+ // Passed to `--extensionDevelopmentPath`
+ const extensionDevelopmentPath = path.resolve(__dirname, '../../');
- // The path to test runner
- // Passed to --extensionTestsPath
- const extensionTestsPath = path.resolve(__dirname, './suite/index');
+ // The path to test runner
+ // Passed to --extensionTestsPath
+ const extensionTestsPath = path.resolve(__dirname, './suite/index');
- // Download VS Code, unzip it and run the integration test
- await runTests({ extensionDevelopmentPath, extensionTestsPath });
- } catch (err) {
- console.error('Failed to run tests');
- process.exit(1);
- }
+ const workspaceDir = path.join(extensionDevelopmentPath, 'out', 'test', 'ws');
+
+ if (!fs.statSync(workspaceDir).isDirectory()) {
+ throw `Expecting ${workspaceDir} to be a directory!`;
+ }
+
+ // Download VS Code, unzip it and run the integration test
+ await runTests({
+ version: "1.50.0",
+ extensionDevelopmentPath,
+ extensionTestsPath,
+ extensionTestsEnv: {
+ 'ENABLE_CONSOLE_LOG' : 'true'
+ },
+ launchArgs: [workspaceDir, '--async-stack-traces']
+ });
+ } catch (err) {
+ console.error('Failed to run tests');
+ process.exit(1);
+ }
}
main();
diff --git a/java/java.lsp.server/vscode/src/test/suite/extension.test.ts b/java/java.lsp.server/vscode/src/test/suite/extension.test.ts
index 08a6f78..0ab9c32 100644
--- a/java/java.lsp.server/vscode/src/test/suite/extension.test.ts
+++ b/java/java.lsp.server/vscode/src/test/suite/extension.test.ts
@@ -1,15 +1,130 @@
import * as assert from 'assert';
+import * as fs from 'fs';
+import * as path from 'path';
+import { spawn, ChildProcessByStdio, spawnSync, SpawnSyncReturns } from 'child_process';
+import { Readable } from 'stream';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
-// import * as myExtension from '../../extension';
+import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
- vscode.window.showInformationMessage('Start all tests.');
+ vscode.window.showInformationMessage('Cleaning up workspace.');
+ let folder: string = assertWorkspace();
+ fs.rmdirSync(folder, { recursive: true });
+ fs.mkdirSync(folder, { recursive: true });
+ vscode.window.showInformationMessage('Start all tests.');
+ myExtension.enableConsoleLog();
- test('Sample test', () => {
- assert.equal(-1, [1, 2, 3].indexOf(5));
- assert.equal(-1, [1, 2, 3].indexOf(0));
- });
+ test('VSNetBeans is present', async () => {
+ let nbcode = vscode.extensions.getExtension('asf.apache-netbeans-java');
+ assert.ok(nbcode, "Apache NetBeans Extension is present");
+ let api = await nbcode.activate();
+ assert.ok(api.version, "Some version is specified");
+
+ let cannotReassignVersion = false;
+ try {
+ api.version = "different";
+ } catch (e) {
+ cannotReassignVersion = true;
+ }
+ assert.ok(cannotReassignVersion, "Cannot reassign value of version");
+ });
+
+ test('Find clusters', async () => {
+ let clusters = myExtension.findClusters('non-existent');
+ assert.strictEqual(clusters.length, 6, 'six clusters found: ' + clusters);
+ let nbcode = vscode.extensions.getExtension('asf.apache-netbeans-java');
+ assert.ok(nbcode);
+ for (let c of clusters) {
+ assert.ok(c.startsWith(nbcode.extensionPath), `All extensions are below ${nbcode.extensionPath}, but: ${c}`);
+ }
+ });
+
+ async function demo(where: number) {
+ let folder: string = assertWorkspace();
+
+ await fs.promises.writeFile(path.join(folder, 'pom.xml'), `
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.netbeans.demo.vscode.t1</groupId>
+ <artifactId>basicapp</artifactId>
+ <version>1.0</version>
+ <properties>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ </properties>
+</project>
+ `);
+
+ let pkg = path.join(folder, 'src', 'main', 'java', 'pkg');
+ let mainJava = path.join(pkg, 'Main.java');
+
+ await fs.promises.mkdir(pkg, { recursive: true });
+
+ await fs.promises.writeFile(mainJava, `
+package pkg;
+class Main {
+ public static void main(String... args) {
+ System.out.println("Hello World!");
+ }
+}
+ `);
+
+ vscode.workspace.saveAll();
+
+ if (where === 6) return;
+
+ try {
+ console.log("Test: invoking compile");
+ let res = await vscode.commands.executeCommand("java.workspace.compile");
+ console.log(`Test: compile finished with ${res}`);
+ } catch (error) {
+ dumpJava();
+ throw error;
+ }
+
+ if (where === 7) return;
+
+ let mainClass = path.join(folder, 'target', 'classes', 'pkg', 'Main.class');
+
+ if (where === 8) return;
+
+ assert.ok(fs.statSync(mainClass).isFile(), "Class created by compilation: " + mainClass);
+ }
+
+ test("Compile workspace6", async() => demo(6));
+// test("Compile workspace7", async() => demo(7));
+// test("Compile workspace8", async() => demo(8));
});
+
+function assertWorkspace(): string {
+ assert.ok(vscode.workspace, "workspace is defined");
+ const dirs = vscode.workspace.workspaceFolders;
+ assert.ok(dirs?.length, "There are some workspace folders: " + dirs);
+ assert.strictEqual(dirs.length, 1, "One folder provided");
+ let folder: string = dirs[0].uri.fsPath;
+ return folder;
+}
+
+async function dumpJava() {
+ const cmd = 'jps';
+ const args = [ '-v' ];
+ console.log(`Running: ${cmd} ${args.join(' ')}`);
+ let p : ChildProcessByStdio<null, Readable, Readable> = spawn(cmd, args, {
+ stdio : ["ignore", "pipe", "pipe"],
+ });
+ let n = await new Promise<number>((r, e) => {
+ p.stdout.on('data', function(d: any) {
+ console.log(d.toString());
+ });
+ p.stderr.on('data', function(d: any) {
+ console.log(d.toString());
+ });
+ p.on('close', function(code: number) {
+ r(code);
+ });
+ });
+ console.log(`${cmd} ${args.join(' ')} finished with code ${n}`);
+}
diff --git a/java/java.lsp.server/vscode/src/test/suite/index.ts b/java/java.lsp.server/vscode/src/test/suite/index.ts
index 7029e38..84c0fbd 100644
--- a/java/java.lsp.server/vscode/src/test/suite/index.ts
+++ b/java/java.lsp.server/vscode/src/test/suite/index.ts
@@ -6,33 +6,36 @@
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
- color: true
+ color: true,
+ timeout: 60000
});
const testsRoot = path.resolve(__dirname, '..');
return new Promise((c, e) => {
- glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
- if (err) {
- return e(err);
- }
+ setTimeout(function() {
+ glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
+ if (err) {
+ return e(err);
+ }
- // Add files to the test suite
- files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
+ // Add files to the test suite
+ files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
- try {
- // Run the mocha test
- mocha.run(failures => {
- if (failures > 0) {
- e(new Error(`${failures} tests failed.`));
- } else {
- c();
- }
- });
- } catch (err) {
- console.error(err);
- e(err);
- }
- });
+ try {
+ // Run the mocha test
+ mocha.run(failures => {
+ if (failures > 0) {
+ e(new Error(`${failures} tests failed.`));
+ } else {
+ c();
+ }
+ });
+ } catch (err) {
+ console.error(err);
+ e(err);
+ }
+ });
+ }, 3000);
});
}
diff --git a/java/java.lsp.server/vscode/src/test/ws/empty.ts b/java/java.lsp.server/vscode/src/test/ws/empty.ts
new file mode 100644
index 0000000..952376c
--- /dev/null
+++ b/java/java.lsp.server/vscode/src/test/ws/empty.ts
@@ -0,0 +1,2 @@
+export function empty(): any {
+}
diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
index 7e2baa0..baef5da 100644
--- a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
+++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
@@ -3356,6 +3356,10 @@
}
private void mapComments(BlockTree block, String inputText, WorkingCopy copy, CommentHandler comments, SourcePositions positions) {
+ if (copy.getFileObject() == null) {
+ // prevent IllegalStateException thrown form AssignComments constructor below
+ return;
+ }
TokenSequence<JavaTokenId> seq = TokenHierarchy.create(inputText, JavaTokenId.language()).tokenSequence(JavaTokenId.language());
AssignComments ti = new AssignComments(copy, block, seq, positions);
ti.scan(block, null);
diff --git a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java
index 2498cc5..8a56856 100644
--- a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java
+++ b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java
@@ -29,6 +29,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.Element;
@@ -82,7 +83,7 @@
private static final String CAPTURED_WILDCARD = "<captured wildcard>"; //NOI18N
private static final String UNKNOWN = "<unknown>"; //NOI18N
- private volatile boolean canceled;
+ private final AtomicBoolean canceled = new AtomicBoolean();
@Override
public String name() {
@@ -117,110 +118,155 @@
final Cache cache = scanInProgress ?
Cache.create(textToSearch, st) :
null;
- String prefix = null;
- final int dotIndex = textToSearch.lastIndexOf('.'); //NOI18N
- if (dotIndex > 0 && dotIndex != textToSearch.length()-1) {
- prefix = textToSearch.substring(0, dotIndex);
- textToSearch = textToSearch.substring(dotIndex+1);
- }
- final String textToHighLight = textToSearch;
- ClassIndex.NameKind _kind;
- boolean _caseSensitive;
- switch (st) {
- case PREFIX:
- _kind = ClassIndex.NameKind.PREFIX;
- _caseSensitive = true;
- break;
- case REGEXP:
- _kind = ClassIndex.NameKind.REGEXP;
- textToSearch = NameMatcherFactory.wildcardsToRegexp(
- removeNonJavaChars(textToSearch),
- true);
- _caseSensitive = true;
- break;
- case CAMEL_CASE:
- _kind = ClassIndex.NameKind.CAMEL_CASE;
- _caseSensitive = true;
- break;
- case CASE_INSENSITIVE_CAMEL_CASE:
- _kind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE;
- _caseSensitive = false;
- break;
- case EXACT_NAME:
- _kind = ClassIndex.NameKind.SIMPLE_NAME;
- _caseSensitive = true;
- break;
- case CASE_INSENSITIVE_PREFIX:
- _kind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX;
- _caseSensitive = false;
- break;
- case CASE_INSENSITIVE_EXACT_NAME:
- _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
- _caseSensitive = false;
- break;
- case CASE_INSENSITIVE_REGEXP:
- _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
- textToSearch = NameMatcherFactory.wildcardsToRegexp(
- removeNonJavaChars(textToSearch),
- true);
- _caseSensitive = false;
- break;
- default:
- throw new IllegalArgumentException();
- }
- final String ident = textToSearch;
- final ClassIndex.NameKind kind = _kind;
- final boolean caseSensitive = _caseSensitive;
- final Pair<NameMatcher,Boolean> restriction;
- if (prefix != null) {
- restriction = compileName(prefix,caseSensitive);
- result.setHighlightText(textToHighLight);
- } else {
- restriction = null;
- }
- try {
- final ClassIndexManager manager = ClassIndexManager.getDefault();
-
- Collection<FileObject> roots = QuerySupport.findRoots(
- (Project)null,
- Collections.singleton(ClassPath.SOURCE),
- Collections.<String>emptySet(),
- Collections.<String>emptySet());
-
- final Set<URL> rootUrls = new HashSet<>();
- for(FileObject root : roots) {
- if (canceled) {
- return;
- }
- rootUrls.add(root.toURL());
+ doComputeSymbols(st, textToSearch, new ResultHandler() {
+ private FileObject root;
+ private ProjectInformation projectInfo;
+ private ClassIndexImpl ci;
+ @Override
+ public void setHighlightText(String text) {
+ result.setHighlightText(text);
}
- if (LOGGER.isLoggable(Level.FINE)) {
- LOGGER.log(Level.FINE, "Querying following roots:"); //NOI18N
+ @Override
+ public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException {
+ try {
+ Project project = FileOwnerQuery.getOwner(root);
+
+ this.root = root;
+ this.projectInfo = project == null ?
+ null :
+ project.getLookup().lookup(ProjectInformation.class); //Intentionally does not use ProjectUtils.getInformation() it does project icon annotation which is expensive
+ this.ci = ci;
+ exec.run();
+ } finally {
+ this.root = null;
+ this.projectInfo = null;
+ this.ci = null;
+ }
+ }
+
+ @Override
+ public void handleResult(ElementHandle<TypeElement> owner, String ident, boolean caseSensitive) {
+ final AsyncJavaSymbolDescriptor d = new AsyncJavaSymbolDescriptor(
+ projectInfo,
+ root,
+ ci,
+ owner,
+ ident,
+ caseSensitive);
+ result.addResult(d);
+ if (cache != null) {
+ cache.offer(d);
+ }
+ }
+ }, true, canceled);
+ } finally {
+ clearCancel();
+ }
+ }
+
+ public static void doComputeSymbols(SearchType st, String textToSearch, ResultHandler handler, boolean async, AtomicBoolean canceled) {
+ String prefix = null;
+ final int dotIndex = textToSearch.lastIndexOf('.'); //NOI18N
+ if (dotIndex > 0 && dotIndex != textToSearch.length()-1) {
+ prefix = textToSearch.substring(0, dotIndex);
+ textToSearch = textToSearch.substring(dotIndex+1);
+ }
+ final String textToHighLight = textToSearch;
+ ClassIndex.NameKind _kind;
+ boolean _caseSensitive;
+ switch (st) {
+ case PREFIX:
+ _kind = ClassIndex.NameKind.PREFIX;
+ _caseSensitive = true;
+ break;
+ case REGEXP:
+ _kind = ClassIndex.NameKind.REGEXP;
+ textToSearch = NameMatcherFactory.wildcardsToRegexp(
+ removeNonJavaChars(textToSearch),
+ true);
+ _caseSensitive = true;
+ break;
+ case CAMEL_CASE:
+ _kind = ClassIndex.NameKind.CAMEL_CASE;
+ _caseSensitive = true;
+ break;
+ case CASE_INSENSITIVE_CAMEL_CASE:
+ _kind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE;
+ _caseSensitive = false;
+ break;
+ case EXACT_NAME:
+ _kind = ClassIndex.NameKind.SIMPLE_NAME;
+ _caseSensitive = true;
+ break;
+ case CASE_INSENSITIVE_PREFIX:
+ _kind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX;
+ _caseSensitive = false;
+ break;
+ case CASE_INSENSITIVE_EXACT_NAME:
+ _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
+ _caseSensitive = false;
+ break;
+ case CASE_INSENSITIVE_REGEXP:
+ _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
+ textToSearch = NameMatcherFactory.wildcardsToRegexp(
+ removeNonJavaChars(textToSearch),
+ true);
+ _caseSensitive = false;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ final String ident = textToSearch;
+ final ClassIndex.NameKind kind = _kind;
+ final boolean caseSensitive = _caseSensitive;
+ final Pair<NameMatcher,Boolean> restriction;
+ if (prefix != null) {
+ restriction = compileName(prefix,caseSensitive);
+ handler.setHighlightText(textToHighLight);
+ } else {
+ restriction = null;
+ }
+ try {
+ final ClassIndexManager manager = ClassIndexManager.getDefault();
+
+ Collection<FileObject> roots = QuerySupport.findRoots(
+ (Project)null,
+ Collections.singleton(ClassPath.SOURCE),
+ Collections.<String>emptySet(),
+ Collections.<String>emptySet());
+
+ final Set<URL> rootUrls = new HashSet<>();
+ for(FileObject root : roots) {
+ if (canceled.get()) {
+ return;
+ }
+ rootUrls.add(root.toURL());
+ }
+
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, "Querying following roots:"); //NOI18N
+ for (URL url : rootUrls) {
+ LOGGER.log(Level.FINE, " {0}", url); //NOI18N
+ }
+ LOGGER.log(Level.FINE, "-------------------------"); //NOI18N
+ }
+ //Perform all queries in single op
+ IndexManager.priorityAccess(new IndexManager.Action<Void>() {
+ @Override
+ public Void run() throws IOException, InterruptedException {
for (URL url : rootUrls) {
- LOGGER.log(Level.FINE, " {0}", url); //NOI18N
- }
- LOGGER.log(Level.FINE, "-------------------------"); //NOI18N
- }
- //Perform all queries in single op
- IndexManager.priorityAccess(new IndexManager.Action<Void>() {
- @Override
- public Void run() throws IOException, InterruptedException {
- for (URL url : rootUrls) {
- if (canceled) {
- return null;
- }
- final FileObject root = URLMapper.findFileObject(url);
- if (root == null) {
- continue;
- }
+ if (canceled.get()) {
+ return null;
+ }
+ final FileObject root = URLMapper.findFileObject(url);
+ if (root == null) {
+ continue;
+ }
- final Project project = FileOwnerQuery.getOwner(root);
- final ProjectInformation projectInfo = project == null ?
- null :
- project.getLookup().lookup(ProjectInformation.class); //Intentionally does not use ProjectUtils.getInformation() it does project icon annotation which is expensive
- final ClassIndexImpl impl = manager.getUsagesQuery(root.toURL(), true);
- if (impl != null) {
+ final ClassIndexImpl impl = manager.getUsagesQuery(root.toURL(), true);
+ if (impl != null) {
+ handler.runRoot(root, impl, () -> {
final Map<ElementHandle<TypeElement>,Set<String>> r = new HashMap<>();
impl.getDeclaredElements(ident, kind, DocumentUtil.typeElementConvertor(),r);
if (!r.isEmpty()) {
@@ -228,34 +274,30 @@
final ElementHandle<TypeElement> owner = p.getKey();
for (String symbol : p.getValue()) {
if (matchesRestrictions(owner.getQualifiedName(), symbol, restriction, caseSensitive)) {
- final AsyncJavaSymbolDescriptor d = new AsyncJavaSymbolDescriptor(
- projectInfo,
- root,
- impl,
- owner,
- symbol,
- caseSensitive);
- result.addResult(d);
- if (cache != null) {
- cache.offer(d);
- }
+ handler.handleResult(owner, symbol, caseSensitive);
}
}
}
}
- }
+ });
}
- return null;
}
- });
- } catch (IOException ioe) {
- Exceptions.printStackTrace(ioe);
- }
- catch (InterruptedException ie) {
- return;
- }
- } finally {
- clearCancel();
+ return null;
+ }
+ });
+ } catch (IOException ioe) {
+ Exceptions.printStackTrace(ioe);
+ } catch (InterruptedException ie) {
+ //ignore
+ }
+ }
+
+ public interface ResultHandler {
+ public void setHighlightText(String text);
+ public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException;
+ public void handleResult(@NonNull ElementHandle<TypeElement> owner, @NonNull String ident, boolean caseSensitive);
+ public interface Exec {
+ public void run() throws IOException, InterruptedException;
}
}
@@ -317,7 +359,7 @@
}
@NonNull
- static Pair<String,String> getDisplayName (
+ public static Pair<String,String> getDisplayName (
@NonNull final Element e,
@NonNull final Element enclosingElement) {
assert e != null;
@@ -483,7 +525,7 @@
@Override
public void cancel() {
- canceled = true;
+ canceled.set(true);
}
@Override
@@ -493,7 +535,7 @@
}
private void clearCancel() {
- canceled = false;
+ canceled.set(false);
}
private static final class Cache {
@@ -519,7 +561,7 @@
}
}
- void offer(@NonNull final AsyncJavaSymbolDescriptor d) {
+ void offer(@NonNull final SymbolDescriptor d) {
descriptors.add(d);
}
diff --git a/java/maven/nbproject/project.xml b/java/maven/nbproject/project.xml
index f0a6100..231a621 100644
--- a/java/maven/nbproject/project.xml
+++ b/java/maven/nbproject/project.xml
@@ -80,15 +80,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java b/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java
index 3daa166..19f5ae6 100644
--- a/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java
+++ b/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java
@@ -44,7 +44,6 @@
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.libraries.LibrariesCustomizer;
import org.netbeans.api.project.libraries.Library;
import org.netbeans.api.project.libraries.LibraryManager;
@@ -145,7 +144,7 @@
})
@SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") // baseFolder.mkdirs; will throw IOE later from getJarUri
private static @CheckForNull Library createLibrary(LibraryManager libraryManager, String libraryName, List<Artifact> includeArtifacts, boolean allSourceAndJavadoc, MavenProject project, String copyTo) {
- ProgressHandle handle = ProgressHandleFactory.createHandle(MSG_Create_Library(),
+ ProgressHandle handle = ProgressHandle.createHandle(MSG_Create_Library(),
ProgressTransferListener.cancellable());
int count = includeArtifacts.size() * (allSourceAndJavadoc ? 3 : 1) + 5;
handle.start(count);
diff --git a/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java b/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java
index 8fdf544..95235be 100644
--- a/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java
+++ b/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java
@@ -40,8 +40,8 @@
import org.apache.maven.model.building.ModelBuildingException;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.annotations.common.NonNull;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
import org.netbeans.api.progress.aggregate.ProgressContributor;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
@@ -63,7 +63,6 @@
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.RequestProcessor;
-import org.openide.util.Task;
import org.openide.util.Utilities;
/**
@@ -206,9 +205,9 @@
return;
}
MavenEmbedder online = EmbedderFactory.getOnlineEmbedder();
- AggregateProgressHandle hndl = AggregateProgressFactory.createHandle(Progress_Download(),
+ AggregateProgressHandle hndl = BasicAggregateProgressFactory.createHandle(Progress_Download(),
new ProgressContributor[] {
- AggregateProgressFactory.createProgressContributor("zaloha") }, //NOI18N
+ BasicAggregateProgressFactory.createProgressContributor("zaloha") }, //NOI18N
ProgressTransferListener.cancellable(), null);
boolean ok = true;
@@ -440,10 +439,10 @@
Set<Artifact> arts = project.getOriginalMavenProject().getArtifacts();
ProgressContributor[] contribs = new ProgressContributor[arts.size()];
for (int i = 0; i < arts.size(); i++) {
- contribs[i] = AggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
+ contribs[i] = BasicAggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
}
String label = javadoc ? Progress_Javadoc() : Progress_Source();
- AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label,
+ AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle(label,
contribs, ProgressTransferListener.cancellable(), null);
handle.start();
try {
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java b/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java
index 79fbdc8..1115a40 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java
@@ -37,7 +37,6 @@
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.options.OptionsDisplayer;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.api.FileUtilities;
import org.netbeans.modules.maven.api.NbMavenProject;
@@ -294,7 +293,7 @@
return;
}
final AtomicReference<Thread> t = new AtomicReference<Thread>();
- final ProgressHandle handle = ProgressHandleFactory.createHandle(ResumeAction_scanning(), new Cancellable() {
+ final ProgressHandle handle = ProgressHandle.createHandle(ResumeAction_scanning(), new Cancellable() {
@Override public boolean cancel() {
Thread _t = t.get();
if (_t != null) {
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java b/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java
index e920702..b3aad93 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java
@@ -57,7 +57,6 @@
import org.netbeans.api.extexecution.base.Processes;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.Constants;
@@ -203,8 +202,7 @@
int executionresult = -10;
final InputOutput ioput = getInputOutput();
- final ProgressHandle handle = ProgressHandleFactory.createHandle(clonedConfig.getTaskDisplayName(), this, new AbstractAction() {
-
+ final ProgressHandle handle = ProgressHandle.createHandle(clonedConfig.getTaskDisplayName(), this, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
ioput.select();
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java b/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java
index 02a81b4..2332f66 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java
@@ -64,7 +64,7 @@
@NbBundle.Messages(value = "TIT_DEBUG_Plugin=Debugging Plugin Mojo")
public void actionPerformed(ActionEvent e) {
final AtomicBoolean cancel = new AtomicBoolean();
- org.netbeans.api.progress.ProgressUtils.runOffEventDispatchThread(new Runnable() {
+ org.netbeans.api.progress.BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
@Override
public void run() {
doLoad(cancel);
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java b/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java
index 04b53ac..1a8fb04 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java
@@ -25,8 +25,8 @@
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
import org.netbeans.api.progress.aggregate.ProgressContributor;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.api.NbMavenProject;
@@ -63,7 +63,7 @@
@NbBundle.Messages(value = "TIT_GOTO_Plugin=Opening Plugin Mojo Sources")
public void actionPerformed(ActionEvent e) {
final AtomicBoolean cancel = new AtomicBoolean();
- org.netbeans.api.progress.ProgressUtils.runOffEventDispatchThread(new Runnable() {
+ org.netbeans.api.progress.BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
@Override
public void run() {
doLoad(cancel);
@@ -77,8 +77,8 @@
Project prj = config.getProject();
if (prj != null) {
//todo what about build without project.. it's just create archetype one though..
- ProgressContributor contributor = AggregateProgressFactory.createProgressContributor("multi-1");
- AggregateProgressHandle handle = AggregateProgressFactory.createHandle("Downloading plugin sources", new ProgressContributor[]{contributor}, ProgressTransferListener.cancellable(), null);
+ ProgressContributor contributor = BasicAggregateProgressFactory.createProgressContributor("multi-1");
+ AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle("Downloading plugin sources", new ProgressContributor[]{contributor}, ProgressTransferListener.cancellable(), null);
handle.start();
try {
ProgressTransferListener.setAggregateHandle(handle);
diff --git a/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java b/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java
index 2f5b873..d06c3f5 100644
--- a/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java
+++ b/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java
@@ -43,8 +43,8 @@
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.netbeans.api.options.OptionsDisplayer;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
import org.netbeans.api.progress.aggregate.ProgressContributor;
import org.netbeans.modules.maven.api.MavenValidators;
import org.netbeans.modules.maven.api.archetype.Archetype;
@@ -713,9 +713,9 @@
@Messages("Handle_Download=Downloading Archetype")
private Artifact downloadArchetype(Archetype arch) throws ArtifactResolutionException, ArtifactNotFoundException {
- AggregateProgressHandle hndl = AggregateProgressFactory.createHandle(Handle_Download(),
+ AggregateProgressHandle hndl = BasicAggregateProgressFactory.createHandle(Handle_Download(),
new ProgressContributor[] {
- AggregateProgressFactory.createProgressContributor("zaloha") }, //NOI18N
+ BasicAggregateProgressFactory.createProgressContributor("zaloha") }, //NOI18N
ProgressTransferListener.cancellable(), null);
synchronized (HANDLE_LOCK) {
handle = hndl;
diff --git a/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java b/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java
index 62cdb40..f30cce6 100644
--- a/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java
+++ b/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java
@@ -50,8 +50,8 @@
import org.apache.maven.project.MavenProject;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.java.source.SourceUtils;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
import org.netbeans.api.progress.aggregate.ProgressContributor;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
@@ -406,10 +406,10 @@
Node[] nds = getChildren().getNodes(true);
ProgressContributor[] contribs = new ProgressContributor[nds.length];
for (int i = 0; i < nds.length; i++) {
- contribs[i] = AggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
+ contribs[i] = BasicAggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
}
String label = javadoc ? Progress_Javadoc() : Progress_Source();
- AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label,
+ AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle(label,
contribs, ProgressTransferListener.cancellable(), null);
handle.start();
try {
diff --git a/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java b/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java
index 9e19210..80fcd2d 100644
--- a/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java
+++ b/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java
@@ -75,8 +75,8 @@
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.queries.JavadocForBinaryQuery;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
import org.netbeans.api.progress.aggregate.ProgressContributor;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
@@ -1055,16 +1055,16 @@
}
RP.post(new Runnable() {
public @Override void run() {
- ProgressContributor contributor =AggregateProgressFactory.createProgressContributor("multi-1");
+ ProgressContributor contributor = BasicAggregateProgressFactory.createProgressContributor("multi-1");
String label = javadoc ? Progress_Javadoc() : Progress_Source();
- AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label,
+ AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle(label,
new ProgressContributor [] {contributor}, ProgressTransferListener.cancellable(), null);
handle.start();
try {
ProgressTransferListener.setAggregateHandle(handle);
for (Data data : actionContext.lookupAll(Data.class)) {
- ProgressContributor contributor2 = AggregateProgressFactory.createProgressContributor("multi-1");
+ ProgressContributor contributor2 = BasicAggregateProgressFactory.createProgressContributor("multi-1");
handle.addContributor(contributor2);
if (javadoc && !data.hasJavadocInRepository()) {
data.getNode().downloadJavadocSources(contributor2, javadoc);
diff --git a/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java b/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java
index 534fa22..febe583 100644
--- a/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java
+++ b/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java
@@ -27,9 +27,7 @@
import javax.swing.JCheckBox;
import javax.swing.SwingUtilities;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.project.ProjectBuildingException;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ui.OpenProjects;
@@ -265,7 +263,7 @@
FileObject pomFO = project.getProjectDirectory().getFileObject("pom.xml"); //NOI18N
Utilities.performPOMModelOperations(pomFO, opers);
if (folder) {
- final ProgressHandle handle = ProgressHandleFactory.createHandle(RenameProject());
+ final ProgressHandle handle = ProgressHandle.createHandle(RenameProject());
//#76559
handle.start(MAX_WORK);
try {
diff --git a/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java b/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java
index 2baa661..1fa51cc 100644
--- a/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java
+++ b/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java
@@ -32,8 +32,8 @@
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.netbeans.api.annotations.common.NonNull;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
import org.netbeans.api.progress.aggregate.ProgressContributor;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
@@ -112,8 +112,8 @@
return Collections.emptyList();
}
- AggregateProgressHandle hndl = AggregateProgressFactory.createHandle(Bundle.attaching(art.getId()),
- new ProgressContributor[]{AggregateProgressFactory.createProgressContributor("attach")},
+ AggregateProgressHandle hndl = BasicAggregateProgressFactory.createHandle(Bundle.attaching(art.getId()),
+ new ProgressContributor[]{ BasicAggregateProgressFactory.createProgressContributor("attach")},
ProgressTransferListener.cancellable(), null);
ProgressTransferListener.setAggregateHandle(hndl);
try {
diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java
index fb9d3b6..ff0be12 100644
--- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java
+++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java
@@ -37,6 +37,7 @@
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.plugins.FindVisitor;
import org.netbeans.modules.refactoring.java.plugins.JavaPluginUtils;
+import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
import org.netbeans.modules.refactoring.spi.ProgressProviderAdapter;
import org.netbeans.modules.refactoring.spi.RefactoringCommit;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaModificationResult.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/hooks/JavaModificationResult.java
similarity index 84%
rename from java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaModificationResult.java
rename to java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/hooks/JavaModificationResult.java
index e4d0618..f4b0b4a 100644
--- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaModificationResult.java
+++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/hooks/JavaModificationResult.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.netbeans.modules.refactoring.java.spi;
+package org.netbeans.modules.refactoring.java.spi.hooks;
import java.io.File;
import java.io.IOException;
@@ -28,11 +28,11 @@
*
* @author Jan Becicka
*/
-class JavaModificationResult implements ModificationResult {
+public class JavaModificationResult implements ModificationResult {
- private org.netbeans.api.java.source.ModificationResult delegate;
+ public final org.netbeans.api.java.source.ModificationResult delegate;
- JavaModificationResult(org.netbeans.api.java.source.ModificationResult r) {
+ public JavaModificationResult(org.netbeans.api.java.source.ModificationResult r) {
this.delegate = r;
}
diff --git a/platform/api.progress.nb/apichanges.xml b/platform/api.progress.nb/apichanges.xml
index abaf699..37f5b3a 100644
--- a/platform/api.progress.nb/apichanges.xml
+++ b/platform/api.progress.nb/apichanges.xml
@@ -83,6 +83,20 @@
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="progressAction">
+ <api name="ProgressSwing"/>
+ <summary>
+ Added support for non-visual default actions for ProgressHandles in headless mode.
+ </summary>
+ <date day="5" month="1" year="2021"/>
+ <author login="sdedic"/>
+ <compatibility binary="compatible" source="compatible" addition="yes" deprecation="yes"/>
+ <description>
+ The default action on <a href="@TOP@/org/netbeans/modules/progress/api/ProgressHandle.html">ProgressHandle</a> is sometimes non-visual: open a file, focus a result,
+ display an output window associated with the task, etc. Abstract action can be used now with the basic Progress API without
+ dependency on NB- or Swing- specific APIs.
+ </description>
+ </change>
<change id="swingSplit">
<api name="ProgressSwing"/>
<summary>
diff --git a/platform/api.progress.nb/manifest.mf b/platform/api.progress.nb/manifest.mf
index f47dc12..a41f749 100644
--- a/platform/api.progress.nb/manifest.mf
+++ b/platform/api.progress.nb/manifest.mf
@@ -2,5 +2,5 @@
AutoUpdate-Show-In-Client: false
OpenIDE-Module: org.netbeans.api.progress.nb
OpenIDE-Module-Localizing-Bundle: org/netbeans/api/progress/nb/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.57
+OpenIDE-Module-Specification-Version: 1.57.1
diff --git a/platform/api.progress.nb/nbproject/project.properties b/platform/api.progress.nb/nbproject/project.properties
index 7098a75..100ba03 100644
--- a/platform/api.progress.nb/nbproject/project.properties
+++ b/platform/api.progress.nb/nbproject/project.properties
@@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-javac.source=1.6
+javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
javadoc.apichanges=${basedir}/apichanges.xml
javadoc.arch=${basedir}/arch.xml
diff --git a/platform/api.progress.nb/nbproject/project.xml b/platform/api.progress.nb/nbproject/project.xml
index ea18036..959adab 100644
--- a/platform/api.progress.nb/nbproject/project.xml
+++ b/platform/api.progress.nb/nbproject/project.xml
@@ -26,14 +26,6 @@
<code-name-base>org.netbeans.api.progress.nb</code-name-base>
<module-dependencies>
<dependency>
- <code-name-base>org.openide.util.ui</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>9.3</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
<code-name-base>org.openide.util</code-name-base>
<build-prerequisite/>
<compile-dependency/>
@@ -63,7 +55,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.45</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
</module-dependencies>
@@ -75,6 +67,11 @@
<compile-dependency/>
</test-dependency>
<test-dependency>
+ <code-name-base>org.openide.util.lookup</code-name-base>
+ <compile-dependency/>
+ <test/>
+ </test-dependency>
+ <test-dependency>
<code-name-base>org.netbeans.modules.nbjunit</code-name-base>
<recursive/>
<compile-dependency/>
diff --git a/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java b/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java
index b8d4a2f..ea7d9f4 100644
--- a/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java
+++ b/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java
@@ -22,8 +22,16 @@
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
+import org.netbeans.modules.progress.spi.ExtractedProgressUIWorker;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEvent;
+import org.netbeans.modules.progress.spi.ProgressUIWorkerProvider;
+import org.netbeans.modules.progress.spi.SwingController;
import org.netbeans.modules.progress.spi.UIInternalHandle;
+import org.netbeans.progress.module.TrivialProgressUIWorkerProvider;
+import org.netbeans.progress.module.UIInternalHandleAccessor;
import org.openide.util.Cancellable;
+import org.openide.util.Lookup;
/**
* Factory to create various ProgressHandle instances that allow long lasting
@@ -65,41 +73,62 @@
}
/**
- * Create a progress ui handle for a long lasting task.
+ * Create a progress ui handle for a long lasting task. Since {@code 1.59}, this method was
+ * migrated to {@link ProgressHandle basic Progress API}.
* @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
* to an output of the task. The action is assumed to open the apropriate component with the task's output.
* @param displayName to be shown in the progress UI
* @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @deprecated Please use {@link ProgressHandle#createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
*/
+ @Deprecated
public static ProgressHandle createHandle(String displayName, Action linkOutput) {
return createHandle(displayName, null, linkOutput);
}
/**
- * Create a progress ui handle for a long lasting task.
+ * Create a progress UI handle for a long lasting task. Since {@code 1.59}, this method was
+ * migrated to {@link ProgressHandle basic Progress API}. This implementation delegates to
+ * {@link ProgressHandle#createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
+ * to enable smooth transition of older Progress API clients.
* @param allowToCancel either null, if the task cannot be cancelled or
* an instance of {@link org.openide.util.Cancellable} that will be called when user
* triggers cancel of the task.
* @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
- * to an output of the task. The action is assumed to open the apropriate component with the task's output.
+ * to an output of the task. The action is assumed to open the appropriate component with the task's output.
* @param displayName to be shown in the progress UI
* @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @deprecated Please use {@link ProgressHandle#createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
*/
+ @Deprecated
public static ProgressHandle createHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
+ return ProgressHandle.createHandle(displayName, allowToCancel, linkOutput);
+ }
+
+ /**
+ * Create a progress UI handle for a long lasting task.
+ * This call creates a Swing-based implementation. Use only when the Handle is directly used in
+ * Swing UIs. These handles will never work in other presenters.
+ * @param allowToCancel either null, if the task cannot be cancelled or
+ * an instance of {@link org.openide.util.Cancellable} that will be called when user
+ * triggers cancel of the task.
+ * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+ * to an output of the task. The action is assumed to open the appropriate component with the task's output.
+ * @param displayName to be shown in the progress UI
+ * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @since 1.59
+ */
+ public static ProgressHandle createUIHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
return new UIInternalHandle(displayName, allowToCancel, true, linkOutput).createProgressHandle();
}
- private static UIInternalHandle ih(ProgressHandle h) {
- return (UIInternalHandle)h.getInternalHandle();
- }
-
/**
* Get the progress bar component for use in custom dialogs, the task won't
* show in the progress bar anymore.
* @return the component to use in custom UI.
*/
public static JComponent createProgressComponent(ProgressHandle handle) {
- return ih(handle).extractComponent();
+ return ihextract(handle).getProgressComponent();
}
/**
@@ -109,7 +138,7 @@
* @since org.netbeans.api.progress 1.8
*/
public static JLabel createMainLabelComponent(ProgressHandle handle) {
- return ih(handle).extractMainLabel();
+ return ihextract(handle).getMainLabelComponent();
}
/**
@@ -119,15 +148,18 @@
* @since org.netbeans.api.progress 1.8
*/
public static JLabel createDetailLabelComponent(ProgressHandle handle) {
- return ih(handle).extractDetailLabel();
+ return ihextract(handle).getDetailLabelComponent();
}
/**
* Create a handle for a long lasting task that is not triggered by explicit user action.
* Such tasks have lower priority in the UI.
+ * Since {@code 1.59}, the functionality moves to basic {@link ProgressHandle Progress API}; this method is retained for smooth transition of older API clients.
* @param displayName to be shown in the progress UI
* @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @deprecated Use {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable)}.
*/
+ @Deprecated
public static ProgressHandle createSystemHandle(String displayName) {
return createSystemHandle(displayName, null, null);
}
@@ -135,18 +167,22 @@
/**
* Create a cancelable handle for a task that is not triggered by explicit user action.
* Such tasks have lower priority in the UI.
+ * Since {@code 1.59}, the functionality moves to basic {@link ProgressHandle Progress API}; this method is retained for smooth transition of older API clients.
* @param displayName to be shown in the progress UI
* @param allowToCancel either null, if the task cannot be cancelled or
* an instance of {@link org.openide.util.Cancellable} that will be called when user
* triggers cancel of the task.
* @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @deprecated Use {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable)}.
*/
public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel) {
return createSystemHandle(displayName, allowToCancel, null);
}
/**
- * Create a progress ui handle for a task that is not triggered by explicit user action.
+ * Create a progress UI handle for a task that is not triggered by explicit user action.
+ * Starting from {@code 1.59}, this method is fully superseded by {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}.
+ * Since {@code 1.59}, the functionality moves to basic {@link ProgressHandle Progress API}; this method is retained for smooth transition of older API clients.
* @param allowToCancel either null, if the task cannot be cancelled or
* an instance of {@link org.openide.util.Cancellable} that will be called when user
* triggers cancel of the task.
@@ -154,9 +190,138 @@
* to an output of the task. The action is assumed to open the apropriate component with the task's output.
* @param displayName to be shown in the progress UI
* @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
- *
+ * @deprecated Please use {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
+ * @Deprecated
*/
public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
+ return ProgressHandle.createSystemHandle(displayName, allowToCancel, linkOutput);
+ }
+
+ /**
+ * Create a progress UI handle for a task that is not triggered by explicit user action.
+ * This call creates a Swing-based implementation. Use only when the Handle is directly used in
+ * Swing UIs. These handles will never work in other presenters.
+ * @param allowToCancel either null, if the task cannot be cancelled or
+ * an instance of {@link org.openide.util.Cancellable} that will be called when user
+ * triggers cancel of the task.
+ * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+ * to an output of the task. The action is assumed to open the apropriate component with the task's output.
+ * @param displayName to be shown in the progress UI
+ * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @since 1.59
+ */
+ public static ProgressHandle createSystemUIHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
return new UIInternalHandle(displayName, allowToCancel, false, linkOutput).createProgressHandle();
}
+
+ private static ProgressUIWorkerProvider TRIVIAL_PROVIDER = new TrivialProgressUIWorkerProvider();
+
+ private static ExtractedProgressUIWorker ihextract(ProgressHandle h) {
+ InternalHandle ih = h.getInternalHandle();
+ if (ih instanceof UIInternalHandle) {
+ return new UIHandleExtractor((UIInternalHandle)ih);
+ } else {
+ // fallback for non-UIInternalHandles. Let the environment to create
+ // a suitable component representation.
+ ProgressUIWorkerProvider prov = Lookup.getDefault().lookup(ProgressUIWorkerProvider.class);
+ if (prov == null) {
+ prov = TRIVIAL_PROVIDER;
+ }
+ ExtractedProgressUIWorker worker = prov.extractProgressWorker(ih);
+ if (worker != null) {
+ return new ForeignExtractor(ih, worker);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Creates UI components for InternalHandles that are not UI ones. Even though the
+ * handle is not implemented in the standard way, SWing JComponents can be created for it;
+ * the handle can be also marked as {@link InternalHandle#isCustomPlaced()}, so potential displayed
+ * can check and remove it from the originally intended display.
+ * <p>
+ * This path is mainly used if an alternate ProgressHandle display (@link ProgressEnvironment} implementation) is
+ * in place, but GUI code still wants to embed Swing components for the handle in the UI.
+ */
+ private static class ForeignExtractor implements ExtractedProgressUIWorker {
+ private final InternalHandle ih;
+ private final ExtractedProgressUIWorker del;
+
+ public ForeignExtractor(InternalHandle ih, ExtractedProgressUIWorker del) {
+ this.del = del;
+ this.ih = ih;
+ }
+
+ void customPlaced() {
+ boolean wasCustomPlaced = ih.isCustomPlaced();
+ UIInternalHandleAccessor acc = UIInternalHandleAccessor.instance();
+ acc.markCustomPlaced(ih);
+ if (!wasCustomPlaced) {
+ acc.setController(ih, new SwingController(del));
+ }
+ }
+
+ @Override
+ public JComponent getProgressComponent() {
+ customPlaced();
+ return del.getProgressComponent();
+ }
+
+ @Override
+ public JLabel getMainLabelComponent() {
+ customPlaced();
+ return del.getMainLabelComponent();
+ }
+
+ @Override
+ public JLabel getDetailLabelComponent() {
+ customPlaced();
+ return del.getDetailLabelComponent();
+ }
+
+ @Override
+ public void processProgressEvent(ProgressEvent event) {
+ del.processProgressEvent(event);
+ }
+
+ @Override
+ public void processSelectedProgressEvent(ProgressEvent event) {
+ del.processSelectedProgressEvent(event);
+ }
+ }
+
+ private static class UIHandleExtractor implements ExtractedProgressUIWorker {
+ private final UIInternalHandle uiih;
+
+ public UIHandleExtractor(UIInternalHandle uiih) {
+ this.uiih = uiih;
+ }
+
+ @Override
+ public JComponent getProgressComponent() {
+ return uiih.extractComponent();
+ }
+
+ @Override
+ public JLabel getMainLabelComponent() {
+ return uiih.extractMainLabel();
+ }
+
+ @Override
+ public JLabel getDetailLabelComponent() {
+ return uiih.extractDetailLabel();
+ }
+
+ @Override
+ public void processProgressEvent(ProgressEvent event) {
+ throw new UnsupportedOperationException("Never called.");
+ }
+
+ @Override
+ public void processSelectedProgressEvent(ProgressEvent event) {
+ throw new UnsupportedOperationException("Never called.");
+ }
+ }
}
diff --git a/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java b/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java
index fcf59ab..7da3e37 100644
--- a/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java
+++ b/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java
@@ -22,6 +22,7 @@
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
+import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
@@ -47,12 +48,12 @@
* to an output of the task. The action is assumed to open the apropriate component with the task's output.
* @param displayName to be shown in the progress UI
* @return an instance of <code>ProgressHandle</code>, initialized but not started.
- *
+ * @deprecated use {@link BasicAggregateProgressFactory#createHandle(java.lang.String, org.netbeans.api.progress.aggregate.ProgressContributor[], org.openide.util.Cancellable, javax.swing.Action)}.
*/
+ @Deprecated
public static AggregateProgressHandle createHandle(String displayName, ProgressContributor[] contributors,
Cancellable allowToCancel, Action linkOutput) {
- return doCreateHandle(displayName, contributors, allowToCancel, false,
- ProgressHandleFactory.createHandle(displayName, allowToCancel, linkOutput));
+ return BasicAggregateProgressFactory.createHandle(displayName, contributors, allowToCancel, linkOutput);
}
/**
@@ -65,12 +66,12 @@
* to an output of the task. The action is assumed to open the apropriate component with the task's output.
* @param displayName to be shown in the progress UI
* @return an instance of <code>ProgressHandle</code>, initialized but not started.
- *
+ * @deprecated use {@link BasicAggregateProgressFactory#createSystemHandle(java.lang.String, org.netbeans.api.progress.aggregate.ProgressContributor[], org.openide.util.Cancellable, javax.swing.Action)}.
*/
+ @Deprecated
public static AggregateProgressHandle createSystemHandle(String displayName, ProgressContributor[] contributors,
Cancellable allowToCancel, Action linkOutput) {
- return doCreateHandle(displayName, contributors, allowToCancel, true,
- ProgressHandleFactory.createSystemHandle(displayName, allowToCancel, linkOutput));
+ return BasicAggregateProgressFactory.createSystemHandle(displayName, contributors, allowToCancel, linkOutput);
}
/**
diff --git a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java
index e21df17..8d3341e 100644
--- a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java
+++ b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java
@@ -29,4 +29,14 @@
public ProgressUIWorkerWithModel getDefaultWorker();
public ExtractedProgressUIWorker getExtractedComponentWorker();
+
+ /**
+ * Provides an extracted worker instance for the given internal handle.
+ * @param handle internal handle for the worker.
+ * @return the worker instance, possibly {@code null}.
+ * @since 1.59
+ */
+ public default ExtractedProgressUIWorker extractProgressWorker(InternalHandle handle) {
+ return getExtractedComponentWorker();
+ }
}
diff --git a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java
index edb7dd9..81201a2 100644
--- a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java
+++ b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java
@@ -19,13 +19,16 @@
package org.netbeans.modules.progress.spi;
import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
+import org.netbeans.api.progress.ProgressHandle;
import static org.netbeans.modules.progress.spi.InternalHandle.STATE_INITIALIZED;
import org.netbeans.progress.module.TrivialProgressUIWorkerProvider;
+import org.netbeans.progress.module.UIInternalHandleAccessor;
import org.openide.util.Cancellable;
import org.openide.util.Lookup;
@@ -37,12 +40,13 @@
public final class UIInternalHandle extends InternalHandle {
private static final Logger LOG = Logger.getLogger(UIInternalHandle.class.getName());
- private final Action viewAction;
+ private ActionListener viewAction;
private ExtractedProgressUIWorker component;
private boolean customPlaced1 = false;
private boolean customPlaced2 = false;
private boolean customPlaced3 = false;
-
+ private ProgressHandle handle;
+
public UIInternalHandle(String displayName,
Cancellable cancel,
boolean userInitiated,
@@ -70,6 +74,16 @@
viewAction.actionPerformed(new ActionEvent(viewAction, ActionEvent.ACTION_PERFORMED, "performView"));
}
+ @Override
+ public boolean requestAction(String actionCommand, Action al) {
+ if (actionCommand != ProgressHandle.ACTION_VIEW) {
+ // no UI atm
+ return false;
+ }
+ viewAction = (Action)al;
+ return true;
+ }
+
private void createExtractedWorker() {
if (component == null) {
ProgressUIWorkerProvider prov = Lookup.getDefault().lookup(ProgressUIWorkerProvider.class);
@@ -77,7 +91,7 @@
LOG.log(Level.CONFIG, "Using fallback trivial progress implementation");
prov = new TrivialProgressUIWorkerProvider();
}
- component = prov.getExtractedComponentWorker();
+ component = prov.extractProgressWorker(this);
setController(new SwingController(component));
}
}
@@ -121,4 +135,18 @@
return component.getMainLabelComponent();
}
+
+ static {
+ UIInternalHandleAccessor.setInstance(new UIInternalHandleAccessor() {
+ @Override
+ public void setController(InternalHandle h, Controller c) {
+ h.setController(c);
+ }
+
+ @Override
+ public void markCustomPlaced(InternalHandle h) {
+ h.markCustomPlaced();
+ }
+ });
+ }
}
diff --git a/platform/api.progress.nb/src/org/netbeans/progress/module/UIInternalHandleAccessor.java b/platform/api.progress.nb/src/org/netbeans/progress/module/UIInternalHandleAccessor.java
new file mode 100644
index 0000000..a593278
--- /dev/null
+++ b/platform/api.progress.nb/src/org/netbeans/progress/module/UIInternalHandleAccessor.java
@@ -0,0 +1,51 @@
+/*
+ * 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.progress.module;
+
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.UIInternalHandle;
+
+/**
+ *
+ * @author sdedic
+ */
+public abstract class UIInternalHandleAccessor {
+ private static UIInternalHandleAccessor INSTANCE;
+
+ public static void setInstance(UIInternalHandleAccessor acc) {
+ if (INSTANCE != null) {
+ throw new IllegalStateException();
+ }
+ INSTANCE = acc;
+ }
+
+ public static UIInternalHandleAccessor instance() {
+ return INSTANCE;
+ }
+
+ public abstract void setController(InternalHandle h, Controller c);
+
+ public abstract void markCustomPlaced(InternalHandle h);
+
+ static {
+ // force creation -> accessor is registered.
+ new UIInternalHandle("", null, false, null);
+ }
+}
diff --git a/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java
index b8b22e4..c37eafb 100644
--- a/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java
+++ b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java
@@ -20,6 +20,7 @@
package org.netbeans.api.progress;
import javax.swing.JComponent;
+import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
@@ -28,11 +29,14 @@
import org.netbeans.junit.NbTestCase;
import org.netbeans.junit.RandomlyFails;
import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
import org.netbeans.modules.progress.spi.UIInternalHandle;
import org.netbeans.modules.progress.spi.ProgressEvent;
import org.netbeans.modules.progress.spi.ProgressUIWorker;
import org.netbeans.modules.progress.spi.SwingController;
import org.openide.util.Cancellable;
+import org.openide.util.test.MockLookup;
/**
*
@@ -179,5 +183,60 @@
}
+
+ /**
+ * Checks that handles produced by other env than org.netbeans.modules.progress.ui module
+ * can still get some +- suitable Progress components.
+ */
+ public void testUnexpectedInternalHandleExtraction() throws Exception {
+ MockLookup.setLayersAndInstances(new StrangeEnvironment());
+
+ ProgressHandle handle = ProgressHandle.createHandle("task 1");
+ assertFalse(handle.getInternalHandle() instanceof UIInternalHandle);
+
+ InternalHandle ih = handle.getInternalHandle();
+ assertFalse(ih.isCustomPlaced());
+
+ // now attempt to extract a component from it:
+ JLabel l = ProgressHandleFactory.createMainLabelComponent(handle);
+ assertNotNull(l);
+ // the handle changed its placement:
+ assertTrue(ih.isCustomPlaced());
+ }
+
+ /**
+ * Checks semantics of the InternalHandle + extraction after the progress starts.
+ */
+ public void testUnexpectedInternalHandleExtractFailsAfterStart() throws Exception {
+ MockLookup.setLayersAndInstances(new StrangeEnvironment());
+ ProgressHandle handle = ProgressHandle.createHandle("task 1");
+ handle.start(100);
+
+ // should throw an exception:
+ try {
+ JLabel l = ProgressHandleFactory.createMainLabelComponent(handle);
+ fail("Exppected ISE.");
+ } catch (IllegalStateException ex) {
+ // OK
+ }
+ }
+ public class StrangeHandle extends InternalHandle {
+ public StrangeHandle(String displayName, Cancellable cancel, boolean userInitiated) {
+ super(displayName, cancel, userInitiated);
+ }
+ }
+
+ public class StrangeEnvironment implements ProgressEnvironment {
+
+ @Override
+ public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
+ return new StrangeHandle(displayname, c, userInit).createProgressHandle();
+ }
+
+ @Override
+ public Controller getController() {
+ return SwingController.getDefault();
+ }
+ }
}
diff --git a/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/TestProgressEnvironment.java b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/TestProgressEnvironment.java
new file mode 100644
index 0000000..fa547d4
--- /dev/null
+++ b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/TestProgressEnvironment.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.api.progress;
+
+import java.util.concurrent.Callable;
+import junit.framework.AssertionFailedError;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
+import org.openide.util.Cancellable;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+@ServiceProvider(service = ProgressEnvironment.class, position = 0)
+public class TestProgressEnvironment implements ProgressEnvironment {
+ public volatile ProgressEnvironment delegate;
+
+ public TestProgressEnvironment() {
+ }
+
+ public ProgressEnvironment delegate() {
+ if (delegate != null) {
+ return delegate;
+ }
+ return Lookup.getDefault().lookupAll(ProgressEnvironment.class).stream().
+ filter(i -> i != this).findFirst().orElseThrow(() -> new AssertionFailedError());
+ }
+
+ @Override
+ public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
+ return delegate().createHandle(displayname, c, userInit);
+ }
+
+ @Override
+ public Controller getController() {
+ return delegate().getController();
+ }
+
+ public static void withEnvironment(ProgressEnvironment instance, Callable<Void> c) throws Exception {
+ // will fail on CCE if this is not the 1st.
+ TestProgressEnvironment e = (TestProgressEnvironment)Lookup.getDefault().lookup(TestProgressEnvironment.class);
+ try {
+ e.delegate = instance;
+ c.call();
+ } finally {
+ e.delegate = null;
+ }
+ }
+}
diff --git a/platform/api.progress/apichanges.xml b/platform/api.progress/apichanges.xml
index 6dd10e6..436c7be 100644
--- a/platform/api.progress/apichanges.xml
+++ b/platform/api.progress/apichanges.xml
@@ -83,6 +83,22 @@
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="progressAction">
+ <api name="progress_api"/>
+ <summary>
+ Added support for non-visual default actions for ProgressHandles in headless mode.
+ </summary>
+ <date day="5" month="1" year="2021"/>
+ <author login="sdedic"/>
+ <compatibility binary="compatible" source="compatible" addition="yes"/>
+ <description>
+ The default action on <a href="@TOP@/org/netbeans/modules/progress/api/ProgressHandle.html">ProgressHandle</a> is sometimes non-visual: open a file, focus a result,
+ display an output window associated with the task, etc. Abstract action can be used now with the basic Progress API without
+ dependency on NB- or Swing- specific APIs.
+ </description>
+ <class package="org.netbeans.modules.progress.api" name="ProgressHandle"/>
+ <class package="org.netbeans.modules.progress.spi" name="InternalHandle"/>
+ </change>
<change id="progresshandle.time">
<api name="progress_api"/>
<summary>
diff --git a/platform/api.progress/manifest.mf b/platform/api.progress/manifest.mf
index 8654ce7..70bc401 100644
--- a/platform/api.progress/manifest.mf
+++ b/platform/api.progress/manifest.mf
@@ -3,5 +3,5 @@
OpenIDE-Module-Localizing-Bundle: org/netbeans/progress/module/resources/Bundle.properties
OpenIDE-Module-Recommends: org.netbeans.modules.progress.spi.ProgressUIWorkerProvider, org.netbeans.modules.progress.spi.RunOffEDTProvider
AutoUpdate-Essential-Module: true
-OpenIDE-Module-Specification-Version: 1.57
+OpenIDE-Module-Specification-Version: 1.57.1
diff --git a/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java b/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java
index 11f9ae3..d748246 100644
--- a/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java
+++ b/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java
@@ -19,8 +19,12 @@
package org.netbeans.api.progress;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.swing.Action;
import org.netbeans.modules.progress.spi.InternalHandle;
import org.netbeans.progress.module.DefaultHandleFactory;
import org.netbeans.progress.module.ProgressApiAccessor;
@@ -39,7 +43,12 @@
* @author Milos Kleint (mkleint@netbeans.org)
*/
public final class ProgressHandle implements AutoCloseable {
-
+ /**
+ * The action command generated by user "activation" of a {@link ProgressHandle}'s
+ * representation.
+ */
+ public static final String ACTION_VIEW = "performView"; // NOI18N
+
private static final Logger LOG = Logger.getLogger(ProgressHandle.class.getName());
private InternalHandle internal;
@@ -77,6 +86,48 @@
public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel) {
return DefaultHandleFactory.get().createHandle(displayName, allowToCancel, false);
}
+
+ /**
+ * Creates a potentially cancellable handle, which can deliver an event if the user 'triggers' the handle.
+ * How the handle will be presented, and what the "trigger" gesture is depends on the {@link Controller}
+ * implementation. The user may make an action on the handle (other than cancel), which will
+ * be reflected back. Typically used to focus or display some part of the UI.
+ * If {@code triggerCallback} is provided, it should handle at least {@link #ACTION_VIEW} action command
+ * {@link ActionEvent#getActionCommand}.
+ *
+ * @param viewAction callback on user's trigger.
+ * @param displayName to be shown in the progress UI
+ * @param allowToCancel either null, if the task cannot be cancelled or
+ * an instance of {@link org.openide.util.Cancellable} that will be called when user
+ * triggers cancel of the task.
+ * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+ * @since 1.59
+ */
+ public static ProgressHandle createHandle(String displayName, Cancellable allowToCancel, Action viewAction) {
+ ProgressHandle h = DefaultHandleFactory.get().createHandle(displayName, allowToCancel, true);
+ if (viewAction != null) {
+ h.addDefaultAction(viewAction);
+ }
+ return h;
+ }
+
+ /**
+ * Creates a possibly cancellable handle, <b>not initiated</b> by the user, e.g. for indexing,
+ * repository or other system-launched tasks. See documentation for {@link #createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action) }
+ * for more details.
+ * @param displayName to be shown in the progress UI
+ * @param allowToCancel either {@code null}, if the task cannot be cancelled or an {@link Cancellable} instance
+ * that will be called when the user requests cancel.
+ * @param viewAction listener that will be triggered when the progress is 'activated'.
+ * @return created ProgressHandle instance
+ */
+ public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel, Action viewAction) {
+ ProgressHandle h = DefaultHandleFactory.get().createHandle(displayName, allowToCancel, false);
+ if (viewAction != null) {
+ h.addDefaultAction(viewAction);
+ }
+ return h;
+ }
/** Creates a new instance of ProgressHandle */
ProgressHandle(InternalHandle internal) {
@@ -241,6 +292,16 @@
}
/**
+ * Adds an action to the progress indicator.
+ * @param action describes the action
+ * @return true, if actions are supported by the progress implementation.
+ * @since 1.59
+ */
+ public final boolean addDefaultAction(Action action ) {
+ return internal.requestAction(ACTION_VIEW, action );
+ }
+
+ /**
* for unit testing only..
*/
@PatchedPublic
diff --git a/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java b/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java
index 5a12910..16b233a 100644
--- a/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java
+++ b/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java
@@ -19,6 +19,7 @@
package org.netbeans.api.progress.aggregate;
+import javax.swing.Action;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.Cancellable;
@@ -53,6 +54,42 @@
return new ProgressContributor(trackingId);
}
+ /**
+ * Create an aggregating progress ui handle for a long lasting task.
+ * @param contributors the initial set of progress indication contributors that are aggregated in the UI.
+ * @param allowToCancel either null, if the task cannot be cancelled or
+ * an instance of {@link org.openide.util.Cancellable} that will be called when user
+ * triggers cancel of the task.
+ * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+ * to an output of the task. The action is assumed to open the apropriate component with the task's output.
+ * @param displayName to be shown in the progress UI
+ * @return an instance of <code>ProgressHandle</code>, initialized but not started.
+ * @since 1.59
+ */
+ public static AggregateProgressHandle createSystemHandle(String displayName, ProgressContributor[] contributors,
+ Cancellable allowToCancel, Action linkOutput) {
+ return doCreateHandle(displayName, contributors, allowToCancel, true,
+ ProgressHandle.createSystemHandle(displayName, allowToCancel, linkOutput));
+ }
+
+ /**
+ * Create an aggregating progress ui handle for a long lasting task.
+ * @param contributors the initial set of progress indication contributors that are aggregated in the UI.
+ * @param allowToCancel either null, if the task cannot be cancelled or
+ * an instance of {@link org.openide.util.Cancellable} that will be called when user
+ * triggers cancel of the task.
+ * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+ * to an output of the task. The action is assumed to open the apropriate component with the task's output.
+ * @param displayName to be shown in the progress UI
+ * @return an instance of <code>ProgressHandle</code>, initialized but not started.
+ * @since 1.59
+ */
+ public static AggregateProgressHandle createHandle(String displayName, ProgressContributor[] contributors,
+ Cancellable allowToCancel, Action linkOutput) {
+ return doCreateHandle(displayName, contributors, allowToCancel, false,
+ ProgressHandle.createHandle(displayName, allowToCancel, linkOutput));
+ }
+
protected static AggregateProgressHandle doCreateHandle(String displayName, ProgressContributor[] contributors,
Cancellable allowToCancel, boolean systemHandle, ProgressHandle h) {
return new AggregateProgressHandle(displayName, contributors, allowToCancel, systemHandle,
diff --git a/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java b/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java
index 1ed5831..b465e55 100644
--- a/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java
+++ b/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java
@@ -34,6 +34,7 @@
* @since org.netbeans.api.progress/1 1.18
*/
public class Controller {
+ private static final Logger LOG = Logger.getLogger(Controller.class.getName());
// non-private so that it can be accessed from the tests
public static Controller defaultInstance;
@@ -310,14 +311,17 @@
boolean isShort = (stamp - event.getSource().getTimeStampStarted()) < event.getSource().getInitialDelay();
if (event.getType() == ProgressEvent.TYPE_START) {
if (event.getSource().isCustomPlaced() || !isShort) {
+ LOG.log(Level.FINER, "Adding to model {0}", event);
model.addHandle(event.getSource());
} else {
+ LOG.log(Level.FINER, "Short-start: {0}", event);
justStarted.add(event.getSource());
}
}
else if (event.getType() == ProgressEvent.TYPE_FINISH &&
(! justStarted.contains(event.getSource())))
{
+ LOG.log(Level.FINER, "Removed from model: {0}", event);
model.removeHandle(event.getSource());
}
ProgressEvent lastEvent = map.get(event.getSource());
@@ -326,16 +330,22 @@
{
// if task quits really fast, ignore..
// defined 'really fast' as being shorter than initial delay
+ LOG.log(Level.FINER, "Short task ended: {0}", event);
map.remove(event.getSource());
justStarted.remove(event.getSource());
} else {
if (lastEvent != null) {
// preserve last message
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.log(Level.FINE, "Merging event " + event.toString());
+ }
event.copyMessageFromEarlier(lastEvent);
// preserve the switched state
if (lastEvent.isSwitched()) {
event.markAsSwitched();
}
+ LOG.log(Level.FINER, "Event merged with {0} to {1}",
+ new Object [] { lastEvent, event} );
}
map.put(event.getSource(), event);
}
@@ -351,9 +361,12 @@
if (diff >= hndl.getInitialDelay()) {
model.addHandle(hndl);
} else {
- eventQueue.add(new ProgressEvent(hndl, ProgressEvent.TYPE_START, isWatched(hndl)));
+ ProgressEvent stE;
+ eventQueue.add(stE = new ProgressEvent(hndl, ProgressEvent.TYPE_START, isWatched(hndl)));
+ LOG.log(Level.FINER, "Repost start event: {0}", stE);
ProgressEvent evnt = map.remove(hndl);
if (evnt.getType() != ProgressEvent.TYPE_START) {
+ LOG.log(Level.FINER, "Repost queued event: {0}", evnt);
eventQueue.add(evnt);
}
hasShortOne = true;
@@ -369,6 +382,7 @@
}
while (it.hasNext()) {
ProgressEvent event = it.next();
+ LOG.log(Level.FINER, "Dispatching: {0}", event);
if (selected == event.getSource()) {
component.processSelectedProgressEvent(event);
}
diff --git a/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java b/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java
index b7e73fd..f09f993 100644
--- a/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java
+++ b/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java
@@ -24,6 +24,7 @@
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.swing.Action;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.progress.module.*;
import org.openide.modules.PatchedPublic;
@@ -53,6 +54,8 @@
private final boolean userInitiated;
private int initialDelay = Controller.INITIAL_DELAY;
private Controller controller;
+ private ProgressHandle handle;
+ private boolean customPlaced;
public static final int STATE_INITIALIZED = 0;
public static final int STATE_RUNNING = 1;
@@ -85,10 +88,23 @@
* @since 1.40
*/
public final ProgressHandle createProgressHandle() {
- if (del != null) {
- return ProgressApiAccessor.getInstance().create(del);
+ synchronized (this) {
+ if (handle != null) {
+ return handle;
+ }
}
- return ProgressApiAccessor.getInstance().create(this);
+ ProgressHandle h;
+ if (del != null) {
+ h = ProgressApiAccessor.getInstance().create(del);
+ } else {
+ h = ProgressApiAccessor.getInstance().create(this);
+ }
+ synchronized (this) {
+ if (handle == null) {
+ handle = h;
+ }
+ return handle;
+ }
}
public String getDisplayName() {
@@ -126,7 +142,7 @@
if (del != null) {
return del.isCustomPlaced();
}
- return false;
+ return customPlaced;
}
public final boolean isUserInitialized() {
@@ -182,7 +198,7 @@
public boolean isInSleepMode() {
if (del != null) {
- return isInSleepMode();
+ return del.isInSleepMode();
}
return timeSleepy == timeLastProgress;
}
@@ -369,6 +385,21 @@
}
controller.explicitSelection(this);
}
+
+ /**
+ * Request a interaction callback to be attached to the Handle. The
+ * implementation decides if the callback is permitted and desirable. One command,
+ * {@link ProgressHandle#ACTION_VIEW} is defined as a default command (action) for
+ * the progress handle presentation. Implementations are free to ignore request
+ * for adding actions.
+ * @param actionCommand command to bind the action for.
+ * @param action action instance
+ * @return true, if the handle agrees to support the action.
+ * @since 1.59
+ */
+ public boolean requestAction(String actionCommand, Action action) {
+ return false;
+ }
public synchronized void requestDisplayNameChange(String newDisplayName) {
if (del != null) {
@@ -479,4 +510,22 @@
}
compatInit = m;
}
+
+ /**
+ * Marks this handle as custom-placed. Handle should be marked as custom-placed
+ * if some controller overtakes (part of) handle's presentation.
+ * @since 1.59
+ */
+ protected final void markCustomPlaced() {
+ if (getState() != STATE_INITIALIZED) {
+ throw new IllegalStateException();
+ }
+ customPlaced = true;
+ }
+
+ @Override
+ public String toString() {
+ return "H@" + Integer.toHexString(System.identityHashCode(this)) +
+ "[\"" + getDisplayName() + "\", state: " + state + "]";
+ }
}
diff --git a/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java b/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java
index b01ea88..ab18c5b 100644
--- a/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java
+++ b/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java
@@ -134,4 +134,27 @@
return displayName;
}
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("EV[").append(getSource());
+ sb.append(", disp: ").append(displayName);
+
+ String tName;
+ switch (type) {
+ case TYPE_FINISH: tName = "finish"; break;
+ case TYPE_PROGRESS: tName = "progress"; break;
+ case TYPE_REQUEST_STOP: tName = "stop"; break;
+ case TYPE_SILENT: tName = "silent"; break;
+ case TYPE_START: tName = "start"; break;
+ case TYPE_SWITCH: tName = "switch"; break;
+ default: tName = "" + type;
+ }
+ sb.append(", type: ").append(tName);
+ sb.append(", pctDone: ").append(String.format("%3.2f", percentageDone));
+ sb.append(", message: ").append(message);
+ sb.append(", disp: ").append(displayName);
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/platform/autoupdate.services/nbproject/project.xml b/platform/autoupdate.services/nbproject/project.xml
index 344a9d0..69602df 100644
--- a/platform/autoupdate.services/nbproject/project.xml
+++ b/platform/autoupdate.services/nbproject/project.xml
@@ -31,15 +31,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java
index f9d433b..f0225e2 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java
@@ -37,7 +37,6 @@
import org.netbeans.api.autoupdate.OperationContainer.OperationInfo;
import org.netbeans.api.autoupdate.OperationSupport.Restarter;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.spi.autoupdate.CustomInstaller;
import org.netbeans.spi.autoupdate.CustomUninstaller;
import org.openide.LifecycleManager;
@@ -531,10 +530,10 @@
}
assert containerForUpdate.listInvalid().isEmpty();
- Validator v = containerForUpdate.getSupport().doDownload(ProgressHandleFactory.createHandle(OperationSupportImpl.class.getName()), null, false);
- Installer i = containerForUpdate.getSupport().doValidate(v, ProgressHandleFactory.createHandle(OperationSupportImpl.class.getName()));
+ Validator v = containerForUpdate.getSupport().doDownload(ProgressHandle.createHandle(OperationSupportImpl.class.getName()), null, false);
+ Installer i = containerForUpdate.getSupport().doValidate(v, ProgressHandle.createHandle(OperationSupportImpl.class.getName()));
InstallSupportImpl installSupportImpl = Trampoline.API.impl(containerForUpdate.getSupport());
- Boolean needRestart = installSupportImpl.doInstall(i, ProgressHandleFactory.createHandle(OperationSupportImpl.class.getName()), true);
+ Boolean needRestart = installSupportImpl.doInstall(i, ProgressHandle.createHandle(OperationSupportImpl.class.getName()), true);
return needRestart;
}
@Override
@@ -583,7 +582,7 @@
}
CustomInstaller installer = impl.getInstallInfo ().getCustomInstaller ();
assert installer != null : "CustomInstaller must found for " + impl.getUpdateElement ();
- ProgressHandle handle = ProgressHandleFactory.createHandle("Installing " + impl.getDisplayName());
+ ProgressHandle handle = ProgressHandle.createHandle("Installing " + impl.getDisplayName());
//handle.start();
success = installer.install (impl.getCodeName (),
impl.getSpecificationVersion () == null ? null : impl.getSpecificationVersion ().toString (),
@@ -660,7 +659,7 @@
progress.progress(NbBundle.getMessage(OperationSupportImpl.class, "OperationSupportImpl_Custom_Uninstall", impl.getDisplayName()), ++index);
CustomUninstaller uninstaller = impl.getNativeItem ().getUpdateItemDeploymentImpl ().getCustomUninstaller ();
assert uninstaller != null : "CustomInstaller must found for " + impl.getUpdateElement ();
- ProgressHandle handle = ProgressHandleFactory.createHandle("Installing " + impl.getDisplayName());
+ ProgressHandle handle = ProgressHandle.createHandle("Installing " + impl.getDisplayName());
success = uninstaller.uninstall (impl.getCodeName (),
impl.getSpecificationVersion () == null ? null : impl.getSpecificationVersion ().toString (),
handle);
diff --git a/platform/openide.loaders/nbproject/project.xml b/platform/openide.loaders/nbproject/project.xml
index 0a5bd43..61ee28b 100644
--- a/platform/openide.loaders/nbproject/project.xml
+++ b/platform/openide.loaders/nbproject/project.xml
@@ -40,7 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/platform/openide.loaders/src/org/openide/loaders/DataObject.java b/platform/openide.loaders/src/org/openide/loaders/DataObject.java
index a01591e..98dd639 100644
--- a/platform/openide.loaders/src/org/openide/loaders/DataObject.java
+++ b/platform/openide.loaders/src/org/openide/loaders/DataObject.java
@@ -1722,7 +1722,7 @@
} else {
can = null;
}
- ProgressHandle ph = ProgressHandleFactory.createHandle(name, can);
+ ProgressHandle ph = ProgressHandle.createHandle(name, can);
ph.setInitialDelay(500);
ph.start();
this.progressHandle = ph;
diff --git a/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java b/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java
index bf470ec..ad1982b 100644
--- a/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java
+++ b/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java
@@ -127,7 +127,7 @@
@Override
public void run() {
java.lang.String n = org.openide.awt.Actions.cutAmpersand(getName());
- org.netbeans.api.progress.ProgressHandle h = org.netbeans.api.progress.ProgressHandleFactory.createHandle(n);
+ org.netbeans.api.progress.ProgressHandle h = org.netbeans.api.progress.ProgressHandle.createHandle(n);
h.start();
h.switchToIndeterminate();
diff --git a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java
index fd93608..32efb81 100644
--- a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java
+++ b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java
@@ -36,9 +36,9 @@
@Override
public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
if (userInit) {
- return ProgressHandleFactory.createHandle(displayname, c, null);
+ return ProgressHandleFactory.createUIHandle(displayname, c, null);
} else {
- return ProgressHandleFactory.createSystemHandle(displayname, c, null);
+ return ProgressHandleFactory.createSystemUIHandle(displayname, c, null);
}
}
diff --git a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java
index 5dffcd8..8423b1b 100644
--- a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java
+++ b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java
@@ -19,7 +19,10 @@
package org.netbeans.modules.progress.ui;
+import java.util.Map;
+import java.util.WeakHashMap;
import org.netbeans.modules.progress.spi.ExtractedProgressUIWorker;
+import org.netbeans.modules.progress.spi.InternalHandle;
import org.netbeans.modules.progress.spi.ProgressUIWorkerProvider;
import org.netbeans.modules.progress.spi.ProgressUIWorkerWithModel;
@@ -30,16 +33,26 @@
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.modules.progress.spi.ProgressUIWorkerProvider.class)
public class ProviderImpl implements ProgressUIWorkerProvider {
+ private final Map<InternalHandle, NbProgressBar> progresses = new WeakHashMap<>();
+
/** Creates a new instance of ProviderImpl */
public ProviderImpl() {
}
+ @Override
public ProgressUIWorkerWithModel getDefaultWorker() {
return new StatusLineComponent();
}
+ @Override
public ExtractedProgressUIWorker getExtractedComponentWorker() {
return new NbProgressBar();
}
-
+
+ @Override
+ public ExtractedProgressUIWorker extractProgressWorker(InternalHandle handle) {
+ synchronized (this) {
+ return progresses.computeIfAbsent(handle, (h) -> new NbProgressBar());
+ }
+ }
}
diff --git a/webcommon/html.angular/nbproject/project.xml b/webcommon/html.angular/nbproject/project.xml
index c52923f..d74de4d 100644
--- a/webcommon/html.angular/nbproject/project.xml
+++ b/webcommon/html.angular/nbproject/project.xml
@@ -40,15 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java b/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java
index 918cea1..7503955 100644
--- a/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java
+++ b/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java
@@ -32,7 +32,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.html.angular.model.Directive;
import org.openide.modules.Places;
import org.openide.util.Enumerations;
@@ -83,7 +82,7 @@
LOG.fine("start loading doc"); //NOI18N
Directive[] dirs = Directive.values();
directives = Enumerations.array(dirs);
- progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+ progress = ProgressHandle.createHandle(Bundle.doc_building());
progress.start(dirs.length);
buildDoc();
diff --git a/webcommon/html.knockout/nbproject/project.xml b/webcommon/html.knockout/nbproject/project.xml
index 19459eb..0b25b87 100644
--- a/webcommon/html.knockout/nbproject/project.xml
+++ b/webcommon/html.knockout/nbproject/project.xml
@@ -48,15 +48,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java b/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java
index 3383715..17dc997 100644
--- a/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java
+++ b/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java
@@ -36,7 +36,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.html.knockout.model.Binding;
import org.openide.modules.Places;
import org.openide.util.NbBundle;
@@ -86,7 +85,7 @@
items.addAll(Arrays.asList(Binding.values()));
bindings = items.iterator();
- progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+ progress = ProgressHandle.createHandle(Bundle.doc_building());
progress.start(items.size());
buildDoc();
diff --git a/webcommon/javascript2.nodejs/nbproject/project.xml b/webcommon/javascript2.nodejs/nbproject/project.xml
index a0e4b2d..cd7a7d3 100644
--- a/webcommon/javascript2.nodejs/nbproject/project.xml
+++ b/webcommon/javascript2.nodejs/nbproject/project.xml
@@ -40,15 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java b/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java
index db1a150..d8c9b6a 100644
--- a/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java
+++ b/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java
@@ -50,7 +50,6 @@
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.csl.api.Documentation;
@@ -634,7 +633,7 @@
private void startLoading() {
LOG.fine("start loading doc"); //NOI18N
loadingStarted = true;
- progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+ progress = ProgressHandle.createHandle(Bundle.doc_building());
progress.start(1);
}
diff --git a/webcommon/javascript2.requirejs/nbproject/project.xml b/webcommon/javascript2.requirejs/nbproject/project.xml
index 1bdf5a9..b8b6495 100644
--- a/webcommon/javascript2.requirejs/nbproject/project.xml
+++ b/webcommon/javascript2.requirejs/nbproject/project.xml
@@ -40,15 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.40</specification-version>
- </run-dependency>
- </dependency>
- <dependency>
- <code-name-base>org.netbeans.api.progress.nb</code-name-base>
- <build-prerequisite/>
- <compile-dependency/>
- <run-dependency>
- <specification-version>1.40</specification-version>
+ <specification-version>1.57.1</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git a/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java b/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java
index d041e6e..2a64b22 100644
--- a/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java
+++ b/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java
@@ -41,7 +41,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.modules.Places;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
@@ -151,7 +150,7 @@
private void startLoading() {
LOG.fine("start loading doc"); //NOI18N
- progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+ progress = ProgressHandle.createHandle(Bundle.doc_building());
progress.start(1);
}
diff --git a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java
index 1c2de60..25a6c0b 100644
--- a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java
+++ b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java
@@ -100,8 +100,13 @@
@ActionReference(
path = "Editors/application/x-typescript/Popup",
id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
- position = 1400,
- separatorAfter = 1450
+ position = 1400
+ ),
+ @ActionReference(
+ path = "Editors/application/x-typescript/Popup",
+ id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+ position = 1500,
+ separatorAfter = 1550
)
})
@GrammarRegistration(mimeType="application/x-typescript", grammar="TypeScript.tmLanguage.json")