This is an automated email from the ASF dual-hosted git repository.
dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new 8aa2c91 LSP: Groovy diagnostic handling. (#3009)
8aa2c91 is described below
commit 8aa2c913074415b49cc1df4407e818a27c8a2ade
Author: Dusan Balek <[email protected]>
AuthorDate: Tue Jun 22 19:57:02 2021 +0200
LSP: Groovy diagnostic handling. (#3009)
* LSP: Hint suggestions for offset computed via new generic ErrorProvider
API.
* ErrorProvider implementation for GSF languages.
* Fixing Groovy hints to work with LSP.
---
.../editor/hints/AddImportStatementHint.java | 16 +-
.../hints/ImplementAllAbstractMethodsHint.java | 73 +++---
.../groovy/editor/hints/MakeClassAbstractHint.java | 32 ++-
.../editor/hints/RemoveUnusedImportHint.java | 33 ++-
.../groovy/editor/imports/ImportHelper.java | 127 +++++------
ide/api.lsp/apichanges.xml | 13 ++
ide/api.lsp/manifest.mf | 2 +-
.../src/org/netbeans/spi/lsp/ErrorProvider.java | 26 +++
ide/csl.api/nbproject/project.properties | 2 +-
ide/csl.api/nbproject/project.xml | 9 +
.../src/org/netbeans/modules/csl/api/EditList.java | 14 +-
.../org/netbeans/modules/csl/core/ApiAccessor.java | 52 +++++
.../csl/core/LanguageRegistrationProcessor.java | 10 +-
.../modules/csl/hints/GsfErrorProvider.java | 186 +++++++++++++++
java/java.hints/nbproject/project.xml | 2 +-
.../hints/infrastructure/JavaErrorProvider.java | 4 +-
...eans.modules.java.hints.StaticImport.properties | 13 +-
java/java.lsp.server/nbproject/project.xml | 2 +-
.../server/protocol/TextDocumentServiceImpl.java | 253 ++++++++++-----------
java/java.lsp.server/vscode/package-lock.json | 12 +-
java/java.lsp.server/vscode/package.json | 5 +-
21 files changed, 598 insertions(+), 288 deletions(-)
diff --git
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/AddImportStatementHint.java
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/AddImportStatementHint.java
index 144cadc..ec029fb 100644
---
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/AddImportStatementHint.java
+++
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/AddImportStatementHint.java
@@ -89,7 +89,7 @@ public class AddImportStatementHint extends GroovyErrorRule {
HintFix fixToApply = new AddImportFix(fo, fqn);
fixList.add(fixToApply);
- Hint descriptor = new Hint(this, fixToApply.getDescription(),
fo, range,
+ Hint descriptor = new Hint(this, desc, fo, range,
fixList, DEFAULT_PRIORITY);
result.add(descriptor);
@@ -126,7 +126,7 @@ public class AddImportStatementHint extends GroovyErrorRule
{
return HintSeverity.ERROR;
}
- private static class AddImportFix implements HintFix {
+ private static class AddImportFix implements PreviewableFix {
private final FileObject fo;
private final String fqn;
@@ -147,7 +147,12 @@ public class AddImportStatementHint extends
GroovyErrorRule {
@Override
public void implement() throws Exception {
- ImportHelper.addImportStatement(fo, fqn);
+ getEditList().apply();
+ }
+
+ @Override
+ public EditList getEditList() throws Exception {
+ return ImportHelper.addImportStatementEdits(fo, fqn);
}
@Override
@@ -159,5 +164,10 @@ public class AddImportStatementHint extends
GroovyErrorRule {
public boolean isInteractive() {
return false;
}
+
+ @Override
+ public boolean canPreview() {
+ return true;
+ }
}
}
diff --git
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/ImplementAllAbstractMethodsHint.java
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/ImplementAllAbstractMethodsHint.java
index efa5bb2..ba30377 100644
---
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/ImplementAllAbstractMethodsHint.java
+++
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/ImplementAllAbstractMethodsHint.java
@@ -35,6 +35,7 @@ import org.netbeans.modules.csl.api.Hint;
import org.netbeans.modules.csl.api.HintFix;
import org.netbeans.modules.csl.api.HintSeverity;
import org.netbeans.modules.csl.api.OffsetRange;
+import org.netbeans.modules.csl.api.PreviewableFix;
import org.netbeans.modules.csl.api.RuleContext;
import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.modules.groovy.editor.api.lexer.GroovyTokenId;
@@ -70,7 +71,7 @@ public final class ImplementAllAbstractMethodsHint extends
GroovyErrorRule {
OffsetRange range = HintUtils.getLineOffset(context, error);
if (range != null) {
- result.add(new Hint(this, fix.getDescription(), fo, range,
Collections.<HintFix>singletonList(fix), 100));
+ result.add(new Hint(this, error.getDescription(), fo, range,
Collections.<HintFix>singletonList(fix), 100));
}
}
@@ -134,7 +135,7 @@ public final class ImplementAllAbstractMethodsHint extends
GroovyErrorRule {
* Fix representing possibility to add empty method stubs directly to the
source
* code.
*/
- private static class AddMethodStubsFix implements HintFix {
+ private static class AddMethodStubsFix implements PreviewableFix {
private final GroovyError error;
private final List<String> missingMethods;
@@ -151,41 +152,44 @@ public final class ImplementAllAbstractMethodsHint
extends GroovyErrorRule {
@Override
public void implement() throws Exception {
- BaseDocument baseDoc = LexUtilities.getDocument(error.getFile(),
true);
- if (baseDoc == null) {
- return;
- }
+ getEditList().apply();
+ }
+ @Override
+ public EditList getEditList() throws Exception {
+ BaseDocument baseDoc = LexUtilities.getDocument(error.getFile(),
true);
EditList edits = new EditList(baseDoc);
- for (int i = 0; i < missingMethods.size(); i++) {
- StringBuilder sb = new StringBuilder();
-
- // Add one additional new line before the first method stub
- if (i == 0) {
- sb.append("\n"); // NOI18N
- }
- sb.append("public " ); // NOI18N
- sb.append(missingMethods.get(i));
- sb.append(" {\n"); // NOI18N
-
- for (int space = 0; space <
IndentUtils.indentLevelSize(baseDoc); space++) {
- sb.append(" "); // NOI18N
- }
- sb.append("throw new UnsupportedOperationException(\"Not
supported yet.\");\n"); // NOI18N
- sb.append("}"); // NOI18N
-
- // In the last inserted stub, add only one additional new line
- if (i == missingMethods.size() - 1) {
- sb.append("\n"); // NOI18N
- } else {
- sb.append("\n\n"); // NOI18N
+ if (baseDoc != null) {
+ for (int i = 0; i < missingMethods.size(); i++) {
+ StringBuilder sb = new StringBuilder();
+
+ // Add one additional new line before the first method stub
+ if (i == 0) {
+ sb.append("\n"); // NOI18N
+ }
+ sb.append("public " ); // NOI18N
+ sb.append(missingMethods.get(i));
+ sb.append(" {\n"); // NOI18N
+
+ for (int space = 0; space <
IndentUtils.indentLevelSize(baseDoc); space++) {
+ sb.append(" "); // NOI18N
+ }
+ sb.append("throw new UnsupportedOperationException(\"Not
supported yet.\");\n"); // NOI18N
+ sb.append("}"); // NOI18N
+
+ // In the last inserted stub, add only one additional new
line
+ if (i == missingMethods.size() - 1) {
+ sb.append("\n"); // NOI18N
+ } else {
+ sb.append("\n\n"); // NOI18N
+ }
+
+ edits.replace(getInsertPosition(baseDoc), 0,
sb.toString(), true, 0);
}
-
- edits.replace(getInsertPosition(baseDoc), 0, sb.toString(),
true, 0);
}
- edits.apply();
+ return edits;
}
-
+
private int getInsertPosition(BaseDocument doc) {
TokenSequence<GroovyTokenId> ts =
LexUtilities.getGroovyTokenSequence(doc, 1);
@@ -270,5 +274,10 @@ public final class ImplementAllAbstractMethodsHint extends
GroovyErrorRule {
public boolean isInteractive() {
return false;
}
+
+ @Override
+ public boolean canPreview() {
+ return true;
+ }
}
}
diff --git
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/MakeClassAbstractHint.java
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/MakeClassAbstractHint.java
index 87aa9da..7671de9 100644
---
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/MakeClassAbstractHint.java
+++
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/MakeClassAbstractHint.java
@@ -34,6 +34,7 @@ import org.netbeans.modules.csl.api.Hint;
import org.netbeans.modules.csl.api.HintFix;
import org.netbeans.modules.csl.api.HintSeverity;
import org.netbeans.modules.csl.api.OffsetRange;
+import org.netbeans.modules.csl.api.PreviewableFix;
import org.netbeans.modules.csl.api.RuleContext;
import org.netbeans.modules.groovy.editor.api.lexer.GroovyTokenId;
import org.netbeans.modules.groovy.editor.api.lexer.LexUtilities;
@@ -69,7 +70,7 @@ public final class MakeClassAbstractHint extends
GroovyErrorRule {
if (range != null) {
HintFix fix = new MakeClassAbstractFix(error);
- result.add(new Hint(this, fix.getDescription(), error.getFile(),
range, Collections.singletonList(fix), 200));
+ result.add(new Hint(this, error.getDescription(), error.getFile(),
range, Collections.singletonList(fix), 200));
}
}
@@ -94,7 +95,7 @@ public final class MakeClassAbstractHint extends
GroovyErrorRule {
return HintSeverity.ERROR;
}
- private static class MakeClassAbstractFix implements HintFix {
+ private static class MakeClassAbstractFix implements PreviewableFix {
private final GroovyError error;
@@ -105,20 +106,22 @@ public final class MakeClassAbstractHint extends
GroovyErrorRule {
@Override
public void implement() throws Exception {
+ getEditList().apply();
+ }
+
+ @Override
+ public EditList getEditList() throws Exception {
BaseDocument baseDoc = LexUtilities.getDocument(error.getFile(),
true);
- if (baseDoc == null) {
- return;
- }
EditList edits = new EditList(baseDoc);
-
- int classPosition = getInsertPosition(baseDoc);
- if (classPosition != 0) {
- edits.replace(classPosition, 0, "abstract ", false, 0);
+ if (baseDoc != null) {
+ int classPosition = getInsertPosition(baseDoc);
+ if (classPosition != 0) {
+ edits.replace(classPosition, 0, "abstract ", false, 0);
+ }
}
-
- edits.apply();
+ return edits;
}
-
+
private int getInsertPosition(BaseDocument doc) {
TokenSequence<GroovyTokenId> ts =
LexUtilities.getGroovyTokenSequence(doc, 1);
@@ -184,5 +187,10 @@ public final class MakeClassAbstractHint extends
GroovyErrorRule {
public boolean isInteractive() {
return false;
}
+
+ @Override
+ public boolean canPreview() {
+ return true;
+ }
}
}
diff --git
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/RemoveUnusedImportHint.java
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/RemoveUnusedImportHint.java
index 1d16876..03e41ed 100644
---
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/RemoveUnusedImportHint.java
+++
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/hints/RemoveUnusedImportHint.java
@@ -35,6 +35,7 @@ import org.netbeans.modules.csl.api.EditList;
import org.netbeans.modules.csl.api.Hint;
import org.netbeans.modules.csl.api.HintFix;
import org.netbeans.modules.csl.api.HintSeverity;
+import org.netbeans.modules.csl.api.PreviewableFix;
import org.netbeans.modules.csl.api.RuleContext;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.groovy.editor.api.ASTUtils;
@@ -43,6 +44,7 @@ import
org.netbeans.modules.groovy.editor.api.lexer.LexUtilities;
import org.netbeans.modules.groovy.editor.hints.infrastructure.GroovyAstRule;
import
org.netbeans.modules.groovy.editor.hints.infrastructure.GroovyHintsProvider;
import org.openide.util.Exceptions;
+import org.openide.util.NbBundle;
/**
*
@@ -51,6 +53,7 @@ import org.openide.util.Exceptions;
public class RemoveUnusedImportHint extends GroovyAstRule {
@Override
+ @NbBundle.Messages("UnusedImport=Unused import")
public void computeHints(GroovyHintsProvider.GroovyRuleContext context,
List<Hint> result) {
final ModuleNode moduleNode =
context.getGroovyParserResult().getRootElement().getModuleNode();
if (null == moduleNode) {
@@ -68,10 +71,10 @@ public class RemoveUnusedImportHint extends GroovyAstRule {
while(-1 != (find = context.doc.find(stringFwdFinder, find+1,
-1)) && skipUsage(find, context.doc));
if (-1 == find) {
- result.add(new Hint(this, "Unused Import",
+ result.add(new Hint(this, Bundle.UnusedImport(),
NbEditorUtilities.getFileObject(context.doc),
ASTUtils.getRangeFull(importNode, context.doc),
- Collections.<HintFix>singletonList(new
RemoveUnusedImportFix("Remove unused import", context.doc, importNode)), 1));
+ Collections.<HintFix>singletonList(new
RemoveUnusedImportFix(Bundle.RemoveUnusedImportHintDescription(), context.doc,
importNode)), 1));
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
@@ -104,8 +107,9 @@ public class RemoveUnusedImportHint extends GroovyAstRule {
}
@Override
+ @NbBundle.Messages("RemoveUnusedImportHintDescription=Remove unused
import")
public String getDescription() {
- return "Remove unused Import";
+ return Bundle.RemoveUnusedImportHintDescription();
}
@Override
@@ -125,7 +129,7 @@ public class RemoveUnusedImportHint extends GroovyAstRule {
@Override
public String getDisplayName() {
- return "Remove unused import";
+ return Bundle.RemoveUnusedImportHintDescription();
}
@Override
@@ -137,14 +141,14 @@ public class RemoveUnusedImportHint extends GroovyAstRule
{
public HintSeverity getDefaultSeverity() {
return HintSeverity.INFO;
}
-
- private static class RemoveUnusedImportFix implements HintFix {
+
+ private static class RemoveUnusedImportFix implements PreviewableFix {
final BaseDocument baseDoc;
final String desc;
final ImportNode importNode;
- public RemoveUnusedImportFix(String desc, BaseDocument baseDoc,
ImportNode importNode) {
+ private RemoveUnusedImportFix(String desc, BaseDocument baseDoc,
ImportNode importNode) {
this.desc = desc;
this.baseDoc = baseDoc;
this.importNode = importNode;
@@ -157,11 +161,16 @@ public class RemoveUnusedImportHint extends GroovyAstRule
{
@Override
public void implement() throws Exception {
+ getEditList().apply();
+ }
+
+ @Override
+ public EditList getEditList() throws Exception {
EditList edits = new EditList(baseDoc);
int offset =
ASTUtils.getOffset(baseDoc,importNode.getLineNumber(), 1);
int removeLen =
ASTUtils.getOffset(baseDoc,importNode.getLineNumber()+1, 1)-offset;
edits.replace(offset, removeLen, "", true, 0);
- edits.apply();
+ return edits;
}
@Override
@@ -173,6 +182,10 @@ public class RemoveUnusedImportHint extends GroovyAstRule {
public boolean isInteractive() {
return false;
}
- }
-
+
+ @Override
+ public boolean canPreview() {
+ return true;
+ }
+ }
}
diff --git
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/imports/ImportHelper.java
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/imports/ImportHelper.java
index dd4901c..38865e1 100644
---
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/imports/ImportHelper.java
+++
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/imports/ImportHelper.java
@@ -129,7 +129,7 @@ public final class ImportHelper {
@Override
public void run() {
- addImportStatements(fo, singleCandidates);
+ addImportStatements(fo, singleCandidates).apply();
}
}, "Adding imports", cancel, false);
}
@@ -192,7 +192,7 @@ public final class ImportHelper {
private static Set<ImportCandidate> findJavaImportCandidates(FileObject
fo, String packageName, String missingClass) {
final Set<ImportCandidate> candidates = new HashSet<>();
- final ClasspathInfo pathInfo = createClasspathInfo(fo);
+ final ClasspathInfo pathInfo = ClasspathInfo.create(fo);
Set<ElementHandle<TypeElement>> typeNames =
pathInfo.getClassIndex().getDeclaredTypes(
missingClass, NameKind.SIMPLE_NAME,
EnumSet.allOf(ClassIndex.SearchScope.class));
@@ -218,24 +218,6 @@ public final class ImportHelper {
return candidates;
}
- @NonNull
- private static ClasspathInfo createClasspathInfo(FileObject fo) {
- ClassPath bootPath = ClassPath.getClassPath(fo, ClassPath.BOOT);
- ClassPath compilePath = ClassPath.getClassPath(fo, ClassPath.COMPILE);
- ClassPath srcPath = ClassPath.getClassPath(fo, ClassPath.SOURCE);
-
- if (bootPath == null) {
- bootPath = ClassPath.EMPTY;
- }
- if (compilePath == null) {
- compilePath = ClassPath.EMPTY;
- }
- if (srcPath == null) {
- srcPath = ClassPath.EMPTY;
- }
- return ClasspathInfo.create(bootPath, compilePath, srcPath);
- }
-
private static ImportCandidate createImportCandidate(String missingClass,
String fqnName, ElementKind kind) {
int level = getImportanceLevel(fqnName);
Icon icon = ElementIcons.getElementIcon(kind, null);
@@ -266,19 +248,17 @@ public final class ImportHelper {
*/
public static String getMissingClassName(String errorMessage) {
String errorPrefix = "unable to resolve class "; // NOI18N
- String missingClass = null;
-
- if (errorMessage.startsWith(errorPrefix)) {
-
- missingClass = errorMessage.substring(errorPrefix.length());
- int idx = missingClass.indexOf(" ");
+ if (!errorMessage.startsWith(errorPrefix)) {
+ return null;
+ }
- if (idx != -1) {
- return missingClass.substring(0, idx);
- }
+ String missingClass = errorMessage.substring(errorPrefix.length());
+ int idx = missingClass.indexOf(" ");
+ if (idx != -1) {
+ missingClass = missingClass.substring(0, idx);
}
- return missingClass;
+ return missingClass.trim();
}
private static List<String> showFixImportChooser(Map<String,
Set<ImportCandidate>> multipleCandidates) {
@@ -309,48 +289,60 @@ public final class ImportHelper {
* @param fqName fully qualified name of the import
*/
public static void addImportStatement(FileObject fo, String fqName) {
- addImportStatements(fo, Collections.singletonList(fqName));
+ addImportStatements(fo, Collections.singletonList(fqName)).apply();
}
- private static void addImportStatements(FileObject fo, List<String>
fqNames) {
- BaseDocument doc = LexUtilities.getDocument(fo, true);
- if (doc == null) {
- return;
- }
+ /**
+ * Returns edits for adding import to the source code (does not run any
checks if the import
+ * has more candidates from different packages etc.). Typically used by
"Add import
+ * hint" where we already know what to add.
+ *
+ * @param fo file where we want to put import statement
+ * @param fqName fully qualified name of the import
+ * @return list of edits to be made
+ */
+ public static EditList addImportStatementEdits(FileObject fo, String
fqName) {
+ return addImportStatements(fo, Collections.singletonList(fqName));
+ }
- for (String fqName : fqNames) {
- EditList edits = new EditList(doc);
- try {
- int packageLine = getPackageLineIndex(doc);
- int afterPackageLine = packageLine + 1;
- int afterPackageOffset =
Utilities.getRowStartFromLineOffset(doc, afterPackageLine);
- int importLine = getAppropriateLine(doc, fqName);
-
- // If the line after the package statement isn't empty, put
one empty line there
- if (!Utilities.isRowWhite(doc, afterPackageOffset)) {
- edits.replace(afterPackageOffset, 0, "\n", false, 0);
- } else {
- if (collectImports(doc).isEmpty()) {
- importLine++;
+ private static EditList addImportStatements(FileObject fo, List<String>
fqNames) {
+ BaseDocument doc = LexUtilities.getDocument(fo, true);
+ EditList edits = new EditList(doc);
+ if (doc != null) {
+ for (String fqName : fqNames) {
+ try {
+ int packageLine = getPackageLineIndex(doc);
+ int afterPackageLine = packageLine + 1;
+ int afterPackageOffset =
Utilities.getRowStartFromLineOffset(doc, afterPackageLine);
+ int importLine = getAppropriateLine(doc, fqName);
+ if (importLine >= 0) {
+ // If the line after the package statement isn't
empty, put one empty line there
+ if (!Utilities.isRowWhite(doc, afterPackageOffset)) {
+ edits.replace(afterPackageOffset, 0, "\n", false,
0);
+ } else {
+ if (collectImports(doc).isEmpty()) {
+ importLine++;
+ }
+ }
+
+ // Find appropriate place to import and put it there
+ int importOffset =
Utilities.getRowStartFromLineOffset(doc, importLine);
+ edits.replace(importOffset, 0, "import " + fqName +
"\n", false, 0);
+
+ // If it's the last import and if the line after the
last import
+ // statement isn't empty, put one empty line there
+ int afterImportsOffset =
Utilities.getRowStartFromLineOffset(doc, importLine);
+
+ if (!Utilities.isRowWhite(doc, afterImportsOffset) &&
isLastImport(doc, fqName)) {
+ edits.replace(afterImportsOffset, 0, "\n", false,
0);
+ }
}
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
}
-
- // Find appropriate place to import and put it there
- int importOffset = Utilities.getRowStartFromLineOffset(doc,
importLine);
- edits.replace(importOffset, 0, "import " + fqName + "\n",
false, 0);
-
- // If it's the last import and if the line after the last
import
- // statement isn't empty, put one empty line there
- int afterImportsOffset =
Utilities.getRowStartFromLineOffset(doc, importLine);
-
- if (!Utilities.isRowWhite(doc, afterImportsOffset) &&
isLastImport(doc, fqName)) {
- edits.replace(afterImportsOffset, 0, "\n", false, 0);
- }
- } catch (BadLocationException ex) {
- Exceptions.printStackTrace(ex);
}
- edits.apply();
}
+ return edits;
}
private static int getAppropriateLine(BaseDocument doc, String fqName)
throws BadLocationException {
@@ -360,6 +352,11 @@ public final class ImportHelper {
return getPackageLineIndex(doc) + 1;
}
+ if (imports.containsKey(fqName)) {
+ // Already imported
+ return -1;
+ }
+
imports.put(fqName, -1);
String lastImportName = null;
diff --git a/ide/api.lsp/apichanges.xml b/ide/api.lsp/apichanges.xml
index ba858ab..bfbd330 100644
--- a/ide/api.lsp/apichanges.xml
+++ b/ide/api.lsp/apichanges.xml
@@ -51,6 +51,19 @@
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="ErrorProvider.Context.getOffset">
+ <api name="LSP_API"/>
+ <summary>Adding ErrorProvider.Context.getOffset() method</summary>
+ <version major="1" minor="4"/>
+ <date day="21" month="6" year="2021"/>
+ <author login="balek"/>
+ <compatibility binary="compatible" source="compatible" addition="yes"
deletion="no"/>
+ <description>
+ An <code>ErrorProvider.Context.getOffset()</code> method
introduced that allows to
+ compute hint for a given file offset.
+ </description>
+ <class package="org.netbeans.api.lsp" name="ErrorProvider"/>
+ </change>
<change id="ErrorProvider">
<api name="LSP_API"/>
<summary>Adding ErrorProvider</summary>
diff --git a/ide/api.lsp/manifest.mf b/ide/api.lsp/manifest.mf
index 3681b69..8a3d75f 100644
--- a/ide/api.lsp/manifest.mf
+++ b/ide/api.lsp/manifest.mf
@@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.api.lsp/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/api/lsp/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.3
+OpenIDE-Module-Specification-Version: 1.4
AutoUpdate-Show-In-Client: false
diff --git a/ide/api.lsp/src/org/netbeans/spi/lsp/ErrorProvider.java
b/ide/api.lsp/src/org/netbeans/spi/lsp/ErrorProvider.java
index 8b18ea7..773df25 100644
--- a/ide/api.lsp/src/org/netbeans/spi/lsp/ErrorProvider.java
+++ b/ide/api.lsp/src/org/netbeans/spi/lsp/ErrorProvider.java
@@ -46,6 +46,7 @@ public interface ErrorProvider {
*/
public static final class Context {
private final FileObject file;
+ private final int offset;
private final Kind errorKind;
private final AtomicBoolean cancel = new AtomicBoolean();
private final List<Runnable> cancelCallbacks = new ArrayList<>();
@@ -57,7 +58,21 @@ public interface ErrorProvider {
* @param errorKind the type of errors/warnings that should be computed
*/
public Context(FileObject file, Kind errorKind) {
+ this(file, -1, errorKind);
+ }
+
+ /**
+ * Construct a new {@code Context}.
+ *
+ * @param file file for which the errors/warnings should be computed
+ * @param offset offset for which the errors/warnings should be
computed
+ * @param errorKind the type of errors/warnings that should be computed
+ *
+ * @since 1.4
+ */
+ public Context(FileObject file, int offset, Kind errorKind) {
this.file = file;
+ this.offset = offset;
this.errorKind = errorKind;
}
@@ -71,6 +86,17 @@ public interface ErrorProvider {
}
/**
+ * The offset for which the errors/warnings should be computed.
+ *
+ * @return the offset for which the errors/warnings should be computed
+ *
+ * @since 1.4
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
* The type of errors/warnings should be computed.
*
* @return the type of errors/warnings should be computed
diff --git a/ide/csl.api/nbproject/project.properties
b/ide/csl.api/nbproject/project.properties
index 3ba590e..b3967dc 100644
--- a/ide/csl.api/nbproject/project.properties
+++ b/ide/csl.api/nbproject/project.properties
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
-spec.version.base=2.67.0
+spec.version.base=2.68.0
is.autoload=true
javac.source=1.8
diff --git a/ide/csl.api/nbproject/project.xml
b/ide/csl.api/nbproject/project.xml
index 772b861..910c99b 100644
--- a/ide/csl.api/nbproject/project.xml
+++ b/ide/csl.api/nbproject/project.xml
@@ -44,6 +44,15 @@
</run-dependency>
</dependency>
<dependency>
+ <code-name-base>org.netbeans.api.lsp</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.4</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.api.progress</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git a/ide/csl.api/src/org/netbeans/modules/csl/api/EditList.java
b/ide/csl.api/src/org/netbeans/modules/csl/api/EditList.java
index c83db1d..b0ebec7 100644
--- a/ide/csl.api/src/org/netbeans/modules/csl/api/EditList.java
+++ b/ide/csl.api/src/org/netbeans/modules/csl/api/EditList.java
@@ -28,10 +28,12 @@ import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;
import javax.swing.text.StyledDocument;
+import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.editor.document.AtomicLockDocument;
import org.netbeans.api.editor.document.LineDocument;
import org.netbeans.api.editor.document.LineDocumentUtils;
import org.netbeans.editor.BaseDocument;
+import org.netbeans.modules.csl.core.ApiAccessor;
import org.netbeans.modules.editor.indent.api.Reformat;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
@@ -49,6 +51,14 @@ import org.openide.util.Exceptions;
*/
public class EditList {
private static final Logger LOG =
Logger.getLogger(EditList.class.getName());
+
+ static {
+ ApiAccessor.setInstance(new ApiAccessor() {
+ public List<EditList.Edit> getEdits(@NonNull EditList editList) {
+ return Collections.unmodifiableList(editList.edits);
+ }
+ });
+ }
private Document doc;
private List<Edit> edits;
@@ -267,7 +277,7 @@ public class EditList {
return 0;
}
}
-
+
/**
* A class which records a set of edits to a document, and then can apply
these edits.
* The edit regions are sorted in reverse order and applied from back to
front such that
@@ -275,7 +285,7 @@ public class EditList {
*
* @author Tor Norbye
*/
- private static class Edit implements Comparable<Edit> {
+ public static final class Edit implements Comparable<Edit> {
int offset;
int removeLen;
diff --git a/ide/csl.api/src/org/netbeans/modules/csl/core/ApiAccessor.java
b/ide/csl.api/src/org/netbeans/modules/csl/core/ApiAccessor.java
new file mode 100644
index 0000000..38107a1
--- /dev/null
+++ b/ide/csl.api/src/org/netbeans/modules/csl/core/ApiAccessor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.csl.core;
+
+import java.util.List;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.modules.csl.api.EditList;
+import org.openide.util.Parameters;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class ApiAccessor {
+ private static volatile ApiAccessor instance;
+
+ @NonNull
+ public static synchronized ApiAccessor getInstance() {
+ if (instance == null) {
+ try {
+ Class.forName(EditList.class.getName(), true,
ApiAccessor.class.getClassLoader());
+ assert instance != null;
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return instance;
+ }
+
+ public static void setInstance(@NonNull final ApiAccessor inst) {
+ Parameters.notNull("inst", inst); //NOI18N
+ instance = inst;
+ }
+
+ public abstract List<EditList.Edit> getEdits(@NonNull EditList editList);
+}
diff --git
a/ide/csl.api/src/org/netbeans/modules/csl/core/LanguageRegistrationProcessor.java
b/ide/csl.api/src/org/netbeans/modules/csl/core/LanguageRegistrationProcessor.java
index 1ef86b3..fad7989 100644
---
a/ide/csl.api/src/org/netbeans/modules/csl/core/LanguageRegistrationProcessor.java
+++
b/ide/csl.api/src/org/netbeans/modules/csl/core/LanguageRegistrationProcessor.java
@@ -44,6 +44,7 @@ import
org.netbeans.modules.csl.editor.fold.GsfFoldManagerFactory;
import org.netbeans.modules.csl.editor.hyperlink.GsfHyperlinkProvider;
import org.netbeans.modules.csl.editor.semantic.HighlightsLayerFactoryImpl;
import org.netbeans.modules.csl.editor.semantic.OccurrencesMarkProviderCreator;
+import org.netbeans.modules.csl.hints.GsfErrorProvider;
import org.netbeans.modules.csl.hints.GsfUpToDateStateProviderFactory;
import org.netbeans.modules.csl.navigation.ClassMemberPanel;
import org.netbeans.modules.csl.spi.LanguageRegistration;
@@ -124,7 +125,7 @@ public class LanguageRegistrationProcessor extends
LayerGeneratingProcessor {
if (methods.containsKey("getParser")) { //NOI18N
registerParser(lb, mimeType);
}
- if (methods.containsKey("getIndexerFactory")) {
//NOI18N
+ if (methods.containsKey("getIndexerFactory")) { //NOI18N
registerIndexer(lb, mimeType);
if (!isAnnotatedByPathRecognizerRegistration) {
registerPathRecognizer(lb, mimeType);
@@ -136,6 +137,9 @@ public class LanguageRegistrationProcessor extends
LayerGeneratingProcessor {
if (methods.containsKey("getDeclarationFinder")) { //NOI18N
registerHyperlinks(lb, mimeType);
}
+ if (methods.containsKey("getHintsProvider")) { //NOI18N
+ registerErrorProvider(lb, mimeType);
+ }
registerSemanticHighlighting(lb, mimeType);
registerUpToDateStatus(lb, mimeType);
registerContextMenu(lb, mimeType, methods);
@@ -308,6 +312,10 @@ public class LanguageRegistrationProcessor extends
LayerGeneratingProcessor {
// item = createFile(doc, mimeFolder,
"org-netbeans-modules-csl-editor-semantic-HighlightsLayerFactoryImpl.instance");
// NOI18N
}
+ private static void registerErrorProvider(LayerBuilder b, String mimeType)
{
+ instanceFile(b, "Editors/" + mimeType, null, GsfErrorProvider.class,
null).write(); //NOI18N
+ }
+
private void registerStructureScanner(LayerBuilder b, String mimeType) {
instanceFile(b, "Navigator/Panels/" + mimeType, null,
ClassMemberPanel.class, null).intvalue("position", 1000).write(); //NOI18N
File sideBar = instanceFile(b, "Editors/" + mimeType + "/SideBar",
null, "org.netbeans.modules.editor.breadcrumbs.spi.BreadcrumbsController",
"createSideBarFactory");
diff --git
a/ide/csl.api/src/org/netbeans/modules/csl/hints/GsfErrorProvider.java
b/ide/csl.api/src/org/netbeans/modules/csl/hints/GsfErrorProvider.java
new file mode 100644
index 0000000..ca0eab0
--- /dev/null
+++ b/ide/csl.api/src/org/netbeans/modules/csl/hints/GsfErrorProvider.java
@@ -0,0 +1,186 @@
+/*
+ * 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.csl.hints;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.StyledDocument;
+import org.netbeans.api.editor.document.LineDocument;
+import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.lsp.CodeAction;
+import org.netbeans.api.lsp.Diagnostic;
+import org.netbeans.api.lsp.TextDocumentEdit;
+import org.netbeans.api.lsp.TextEdit;
+import org.netbeans.api.lsp.WorkspaceEdit;
+import org.netbeans.modules.csl.api.DataLoadersBridge;
+import org.netbeans.modules.csl.api.EditList;
+import org.netbeans.modules.csl.api.Error;
+import org.netbeans.modules.csl.api.Hint;
+import org.netbeans.modules.csl.api.HintFix;
+import org.netbeans.modules.csl.api.HintsProvider;
+import org.netbeans.modules.csl.api.OffsetRange;
+import org.netbeans.modules.csl.api.PreviewableFix;
+import org.netbeans.modules.csl.api.RuleContext;
+import org.netbeans.modules.csl.core.ApiAccessor;
+import org.netbeans.modules.csl.core.Language;
+import org.netbeans.modules.csl.core.LanguageRegistry;
+import org.netbeans.modules.csl.hints.infrastructure.GsfHintsManager;
+import org.netbeans.modules.csl.spi.ParserResult;
+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.spi.ParseException;
+import org.netbeans.modules.parsing.spi.Parser;
+import org.netbeans.spi.lsp.ErrorProvider;
+import org.openide.util.Exceptions;
+import org.openide.util.Union2;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public class GsfErrorProvider implements ErrorProvider {
+
+ @Override
+ public List<? extends Diagnostic> computeErrors(Context context) {
+ final List<Hint> hints = new ArrayList<>();
+ final List<Error> errors = new ArrayList<>();
+ try {
+
ParserManager.parse(Collections.singletonList(Source.create(context.file())),
new UserTask() {
+ @Override
+ public void run(ResultIterator resultIterator) throws
Exception {
+ Parser.Result result =
resultIterator.getParserResult(context.getOffset());
+ if(result instanceof ParserResult) {
+ ParserResult parserResult = (ParserResult) result;
+ Language language =
LanguageRegistry.getInstance().getLanguageByMimeType(resultIterator.getSnapshot().getMimeType());
+ if (language != null) {
+ HintsProvider hintsProvider =
language.getHintsProvider();
+ if (hintsProvider != null) {
+ GsfHintsManager hintsManager =
language.getHintsManager();
+ RuleContext ruleContext =
hintsManager.createRuleContext(parserResult, language, context.getOffset(), -1,
-1);
+ if (ruleContext != null) {
+ switch (context.errorKind()) {
+ case ERRORS:
+
hintsProvider.computeErrors(hintsManager, ruleContext, hints, errors);
+ break;
+ case HINTS:
+
hintsProvider.computeHints(hintsManager, ruleContext, hints);
+
hintsProvider.computeSuggestions(hintsManager, ruleContext, hints,
context.getOffset());
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ });
+ } catch (ParseException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ StyledDocument doc =
DataLoadersBridge.getDefault().getDocument(context.file());
+ LineDocument lineDocument = doc != null ? LineDocumentUtils.as(doc,
LineDocument.class) : null;
+ List<Diagnostic> diagnostics = new ArrayList<>(hints.size() +
errors.size());
+ int idx = 0;
+ for (Hint hint : hints) {
+ diagnostics.add(hint2Diagnostic(hint, ++idx));
+ }
+ for (Error error : errors) {
+ diagnostics.add(error2Diagnostic(error, lineDocument, ++idx));
+ }
+ return diagnostics;
+ }
+
+ private Diagnostic error2Diagnostic(Error error, LineDocument
lineDocument, int idx) {
+ Diagnostic.Builder diagBuilder = Diagnostic.Builder.create(() -> {
+ if (lineDocument != null) {
+ try {
+ return
LineDocumentUtils.getLineFirstNonWhitespace(lineDocument,
error.getStartPosition());
+ } catch (BadLocationException ex) {}
+ }
+ return error.getStartPosition();
+ }, () -> {
+ if (lineDocument != null) {
+ try {
+ return
LineDocumentUtils.getLineLastNonWhitespace(lineDocument,
error.getEndPosition());
+ } catch (BadLocationException ex) {}
+ }
+ return error.getEndPosition();
+ }, error.getDescription());
+ switch (error.getSeverity()) {
+ case FATAL:
+ case ERROR:
+ diagBuilder.setSeverity(Diagnostic.Severity.Error);
+ break;
+ case WARNING:
+ diagBuilder.setSeverity(Diagnostic.Severity.Warning);
+ break;
+ case INFO:
+ diagBuilder.setSeverity(Diagnostic.Severity.Information);
+ break;
+ }
+ String id = "errors:" + idx + "-" + error.getKey();
+ diagBuilder.setCode(id);
+ return diagBuilder.build();
+ }
+
+ private Diagnostic hint2Diagnostic(Hint hint, int idx) {
+ final OffsetRange range = hint.getRange();
+ Diagnostic.Builder diagBuilder = Diagnostic.Builder.create(() ->
range.getStart(), () -> range.getEnd(), hint.getDescription());
+ switch (hint.getRule().getDefaultSeverity()) {
+ case ERROR:
+ diagBuilder.setSeverity(Diagnostic.Severity.Error);
+ break;
+ case CURRENT_LINE_WARNING:
+ case WARNING:
+ diagBuilder.setSeverity(Diagnostic.Severity.Warning);
+ break;
+ case INFO:
+ diagBuilder.setSeverity(Diagnostic.Severity.Information);
+ break;
+ }
+ String id = "hints:" + idx + "-" + hint.getRule().getDisplayName();
+ diagBuilder.setCode(id);
+ diagBuilder.addActions(errorReporter -> convertFixes(hint,
errorReporter));
+ return diagBuilder.build();
+ }
+
+ private static List<CodeAction> convertFixes(Hint hint,
Consumer<Exception> errorReporter) {
+ List<CodeAction> result = new ArrayList<>();
+ for (HintFix fix : hint.getFixes()) {
+ if (fix instanceof PreviewableFix) {
+ try {
+ List<TextEdit> edits = new ArrayList<>();
+ for (EditList.Edit edit :
ApiAccessor.getInstance().getEdits(((PreviewableFix) fix).getEditList())) {
+ String newText = edit.getInsertText();
+ edits.add(new TextEdit(edit.getOffset(),
edit.getOffset() + edit.getRemoveLen(), newText != null ? newText : ""));
+ }
+ TextDocumentEdit te = new
TextDocumentEdit(hint.getFile().toURI().toString(), edits);
+ result.add(new CodeAction(fix.getDescription(), new
WorkspaceEdit(Collections.singletonList(Union2.createFirst(te)))));
+ } catch (Exception ex) {
+ errorReporter.accept(ex);
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/java/java.hints/nbproject/project.xml
b/java/java.hints/nbproject/project.xml
index 9bc2f12..71477ae 100644
--- a/java/java.hints/nbproject/project.xml
+++ b/java/java.hints/nbproject/project.xml
@@ -58,7 +58,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.0</specification-version>
+ <specification-version>1.4</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git
a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
index e438009..2b6c246 100644
---
a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
+++
b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
@@ -111,7 +111,7 @@ public class JavaErrorProvider implements ErrorProvider {
if (disabled.size() !=
Severity.values().length) {
AtomicBoolean cancel = new AtomicBoolean();
context.registerCancelCallback(() ->
cancel.set(true));
-
result.addAll(convert2Diagnostic(context.errorKind(), new
HintsInvoker(HintsSettings.getGlobalSettings(), cancel).computeHints(cc), ed ->
!disabled.contains(ed.getSeverity())));
+
result.addAll(convert2Diagnostic(context.errorKind(), new
HintsInvoker(HintsSettings.getGlobalSettings(), context.getOffset(),
cancel).computeHints(cc), ed -> !disabled.contains(ed.getSeverity())));
}
break;
}
@@ -241,7 +241,7 @@ public class JavaErrorProvider implements ErrorProvider {
}
String newFilePath = null;
for (File newFile : newFiles) {
- newFilePath = newFile.getPath();
+ newFilePath = newFile.toURI().toString();
documentChanges.add(Union2.createSecond(new
CreateFile(newFilePath)));
}
outer: for (FileObject fileObject :
changes.getModifiedFileObjects()) {
diff --git a/ide/csl.api/nbproject/project.properties
b/java/java.lsp.server/nbcode/integration/release/config/Preferences/org/netbeans/modules/java/hints/default/org.netbeans.modules.java.hints.StaticImport.properties
similarity index 70%
copy from ide/csl.api/nbproject/project.properties
copy to
java/java.lsp.server/nbcode/integration/release/config/Preferences/org/netbeans/modules/java/hints/default/org.netbeans.modules.java.hints.StaticImport.properties
index 3ba590e..d11f28c 100644
--- a/ide/csl.api/nbproject/project.properties
+++
b/java/java.lsp.server/nbcode/integration/release/config/Preferences/org/netbeans/modules/java/hints/default/org.netbeans.modules.java.hints.StaticImport.properties
@@ -15,15 +15,4 @@
# specific language governing permissions and limitations
# under the License.
-spec.version.base=2.67.0
-is.autoload=true
-javac.source=1.8
-
-javadoc.overview=${basedir}/doc/overview.html
-javadoc.arch=${basedir}/arch.xml
-javadoc.apichanges=${basedir}/apichanges.xml
-javadoc.docfiles=${basedir}/doc
-
-test.config.stableBTD.includes=**/*Test.class
-test.config.stableBTD.excludes=\
- **/LanguageRegistryTest.class
+enabled=true
diff --git a/java/java.lsp.server/nbproject/project.xml
b/java/java.lsp.server/nbproject/project.xml
index 6c2e143..2f5e894 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -92,7 +92,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
- <specification-version>1.1</specification-version>
+ <specification-version>1.4</specification-version>
</run-dependency>
</dependency>
<dependency>
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 da75ed4..1a9d211 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
@@ -166,11 +166,6 @@ import
org.netbeans.modules.java.editor.overridden.ElementDescription;
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.spiimpl.JavaFixImpl;
-import org.netbeans.modules.java.hints.spiimpl.MessageImpl;
-import org.netbeans.modules.java.hints.spiimpl.RulesManager;
-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.LspServerState;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
@@ -193,7 +188,6 @@ import
org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
import org.netbeans.modules.refactoring.spi.Transaction;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.Fix;
-import org.netbeans.spi.java.hints.JavaFix;
import org.netbeans.spi.lsp.ErrorProvider;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
@@ -764,139 +758,122 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
if (doc == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
- JavaSource js = JavaSource.forDocument(doc);
- if (js == null) {
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
+
+ Range range = params.getRange();
+ int startOffset = Utils.getOffset(doc, range.getStart());
+ int endOffset = Utils.getOffset(doc, range.getEnd());
+
+ ArrayList<Diagnostic> diagnostics = new
ArrayList<>(params.getContext().getDiagnostics());
+ diagnostics.addAll(computeDiags(params.getTextDocument().getUri(),
startOffset, ErrorProvider.Kind.HINTS, documentVersion(doc)));
+
Map<String, org.netbeans.api.lsp.Diagnostic> id2Errors = (Map<String,
org.netbeans.api.lsp.Diagnostic>) doc.getProperty("lsp-errors");
List<Either<Command, CodeAction>> result = new ArrayList<>();
if (id2Errors != null) {
- for (Diagnostic diag : params.getContext().getDiagnostics()) {
- org.netbeans.api.lsp.Diagnostic err =
id2Errors.get(diag.getCode().getLeft());
+ for (Diagnostic diag : diagnostics) {
+ org.netbeans.api.lsp.Diagnostic err =
id2Errors.get(diag.getCode().getLeft());
- if (err == null) {
- client.logMessage(new MessageParams(MessageType.Log, "Cannot
resolve error, code: " + diag.getCode().getLeft()));
- continue;
- }
-
- for (org.netbeans.api.lsp.CodeAction inputAction :
err.getActions().computeCodeActions(ex -> client.logMessage(new
MessageParams(MessageType.Error, ex.getMessage())))) {
- CodeAction action = new CodeAction(inputAction.getTitle());
- action.setDiagnostics(Collections.singletonList(diag));
- action.setKind(CodeActionKind.QuickFix);
- if (inputAction.getCommand() != null) {
- action.setCommand(new
Command(inputAction.getCommand().getTitle(),
inputAction.getCommand().getCommand()));
+ if (err == null) {
+ client.logMessage(new MessageParams(MessageType.Log,
"Cannot resolve error, code: " + diag.getCode().getLeft()));
+ continue;
}
- if (inputAction.getEdit() != null) {
- org.netbeans.api.lsp.WorkspaceEdit edit =
inputAction.getEdit();
- List<Either<TextDocumentEdit, ResourceOperation>>
documentChanges = new ArrayList<>();
- FileObject file = js.getFileObjects().iterator().next();
-
- for (Union2<org.netbeans.api.lsp.TextDocumentEdit,
org.netbeans.api.lsp.ResourceOperation> parts : edit.getDocumentChanges()) {
- if (parts.hasFirst()) {
- List<TextEdit> edits =
parts.first().getEdits().stream().map(te -> new TextEdit(new
Range(Utils.createPosition(file, te.getStartOffset()),
Utils.createPosition(file, te.getEndOffset())),
te.getNewText())).collect(Collectors.toList());
- TextDocumentEdit tde = new TextDocumentEdit(new
VersionedTextDocumentIdentifier(parts.first().getDocument(), -1), edits);
- documentChanges.add(Either.forLeft(tde));
- } else {
- if (parts.second() instanceof
org.netbeans.api.lsp.ResourceOperation.CreateFile) {
- documentChanges.add(Either.forRight(new
CreateFile(((org.netbeans.api.lsp.ResourceOperation.CreateFile)
parts.second()).getNewFile())));
- } else {
- throw new
IllegalStateException(String.valueOf(parts.second()));
+ org.netbeans.api.lsp.Diagnostic.LazyCodeActions actions =
err.getActions();
+ if (actions != null) {
+ for (org.netbeans.api.lsp.CodeAction inputAction :
actions.computeCodeActions(ex -> client.logMessage(new
MessageParams(MessageType.Error, ex.getMessage())))) {
+ CodeAction action = new
CodeAction(inputAction.getTitle());
+ action.setDiagnostics(Collections.singletonList(diag));
+ action.setKind(kind(err.getSeverity()));
+ if (inputAction.getCommand() != null) {
+ action.setCommand(new
Command(inputAction.getCommand().getTitle(),
inputAction.getCommand().getCommand()));
+ }
+ if (inputAction.getEdit() != null) {
+ org.netbeans.api.lsp.WorkspaceEdit edit =
inputAction.getEdit();
+ List<Either<TextDocumentEdit, ResourceOperation>>
documentChanges = new ArrayList<>();
+ for (Union2<org.netbeans.api.lsp.TextDocumentEdit,
org.netbeans.api.lsp.ResourceOperation> parts : edit.getDocumentChanges()) {
+ if (parts.hasFirst()) {
+ String docUri =
parts.first().getDocument();
+ try {
+ FileObject file =
Utils.fromUri(docUri);
+ if (file == null) {
+ file =
Utils.fromUri(params.getTextDocument().getUri());
+ }
+ FileObject fo = file;
+ if (fo != null) {
+ List<TextEdit> edits =
parts.first().getEdits().stream().map(te -> new TextEdit(new
Range(Utils.createPosition(fo, te.getStartOffset()), Utils.createPosition(fo,
te.getEndOffset())), te.getNewText())).collect(Collectors.toList());
+ TextDocumentEdit tde = new
TextDocumentEdit(new VersionedTextDocumentIdentifier(docUri, -1), edits);
+
documentChanges.add(Either.forLeft(tde));
+ }
+ } catch (Exception ex) {
+ client.logMessage(new
MessageParams(MessageType.Error, ex.getMessage()));
+ }
+ } else {
+ if (parts.second() instanceof
org.netbeans.api.lsp.ResourceOperation.CreateFile) {
+
documentChanges.add(Either.forRight(new
CreateFile(((org.netbeans.api.lsp.ResourceOperation.CreateFile)
parts.second()).getNewFile())));
+ } else {
+ throw new
IllegalStateException(String.valueOf(parts.second()));
+ }
+ }
}
+
+ action.setEdit(new WorkspaceEdit(documentChanges));
}
+ result.add(Either.forRight(action));
}
-
- action.setEdit(new WorkspaceEdit(documentChanges));
}
- result.add(Either.forRight(action));
}
}
- }
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));
+ JavaSource js = JavaSource.forDocument(doc);
+ if (js != null) {
+ 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();
- int startOffset = Utils.getOffset(doc, range.getStart());
- int endOffset = Utils.getOffset(doc, range.getEnd());
- if (!range.getStart().equals(range.getEnd())) {
- for (ErrorDescription err : IntroduceHint.computeError(cc,
startOffset, endOffset, 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 : ""));
+ //introduce hints
+ if (!range.getStart().equals(range.getEnd())) {
+ for (ErrorDescription err :
IntroduceHint.computeError(cc, startOffset, endOffset, 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)));
}
-
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)));
+ 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));
}
-
result.add(Either.forRight(codeAction));
}
+ } catch
(GeneratorUtils.DuplicateMemberException dme) {
}
- } catch
(GeneratorUtils.DuplicateMemberException dme) {
}
}
}
}
- }
- HintsInvoker.join(new
HintsInvoker(HintsSettings.getGlobalSettings(), startOffset, endOffset, new
AtomicBoolean())
- // compute nonrecursive hints
- .computeHints(cc, new
TreePath(cc.getCompilationUnit()), false,
RulesManager.getInstance().readHints(cc, null, new
AtomicBoolean()).values().stream().collect(ArrayList::new, ArrayList::addAll,
ArrayList::addAll), new ArrayList<MessageImpl>()))
- .stream().forEach(err -> {
- // check if cursor/selection is in errors range
- if (err.getRange().getBegin().getOffset() <=
startOffset && err.getRange().getEnd().getOffset() >= endOffset) {
- for (Fix f : err.getFixes().getFixes()) {
- if (f instanceof JavaFixImpl) {
- try {
- JavaFix jf = ((JavaFixImpl) f).jf;
- List<TextEdit> edits =
modify2TextEdits(js, wc -> {
-
wc.toPhase(JavaSource.Phase.RESOLVED);
- Map<FileObject, byte[]>
resourceContentChanges = new HashMap<FileObject, byte[]>();
-
JavaFixImpl.Accessor.INSTANCE.process(jf, wc, true, resourceContentChanges,
/*Ignored in editor:*/ new ArrayList<>());
- });
- // check for edit presence
- if (edits.size() > 0 &&
!containsEdit(result, edits)) {
- TextDocumentEdit te = new
TextDocumentEdit(new
VersionedTextDocumentIdentifier(params.getTextDocument().getUri(), -1), edits);
- CodeAction action = new
CodeAction(f.getText());
-
action.setKind(CodeActionKind.RefactorRewrite);
- action.setEdit(new
WorkspaceEdit(Collections.singletonList(Either.forLeft(te))));
-
result.add(Either.forRight(action));
- // add only first relevant fix
- break;
- }
- } catch (IOException ex) {
- //TODO: include stack trace:
- client.logMessage(new
MessageParams(MessageType.Error, ex.getMessage()));
- }
- }
- }
- }
- });
- }, true);
+ }, true);
+ }
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error,
ex.getMessage()));
@@ -905,20 +882,6 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
return CompletableFuture.completedFuture(result);
}
- private boolean containsEdit(List<Either<Command, CodeAction>> results,
List<TextEdit> edit) {
- for (Either<Command, CodeAction> result : results) {
- List<Either<TextDocumentEdit, ResourceOperation>> documentChanges
= result.getRight().getEdit().getDocumentChanges();
- if (documentChanges != null) {
- for (Either<TextDocumentEdit, ResourceOperation> change :
documentChanges) {
- if (change.getLeft().getEdits().equals(edit)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
private ConcurrentHashMap<String, Boolean> upToDateTests = new
ConcurrentHashMap<>();
@Override
@@ -1433,15 +1396,15 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
return BACKGROUND_TASKS.create(() -> {
Document originalDoc = openedDocuments.get(uri);
long originalVersion = documentVersion(originalDoc);
- List<Diagnostic> errorDiags = computeDiags(u,
ErrorProvider.Kind.ERRORS, originalVersion);
+ List<Diagnostic> errorDiags = computeDiags(u, -1,
ErrorProvider.Kind.ERRORS, originalVersion);
if (documentVersion(originalDoc) == originalVersion) {
publishDiagnostics(uri, errorDiags);
BACKGROUND_TASKS.create(() -> {
- List<Diagnostic> hintDiags = computeDiags(u,
ErrorProvider.Kind.HINTS, originalVersion);
+ List<Diagnostic> hintDiags = computeDiags(u, -1,
ErrorProvider.Kind.HINTS, originalVersion);
Document doc = openedDocuments.get(uri);
if (documentVersion(doc) == originalVersion) {
publishDiagnostics(uri, hintDiags);
- }
+ }
}).schedule(DELAY);
}
});
@@ -1450,7 +1413,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
private static final int DELAY = 500;
- private List<Diagnostic> computeDiags(String uri, ErrorProvider.Kind
errorKind, long originalVersion) {
+ private List<Diagnostic> computeDiags(String uri, int offset,
ErrorProvider.Kind errorKind, long originalVersion) {
List<Diagnostic> result = new ArrayList<>();
FileObject file = fromURI(uri);
if (file == null) {
@@ -1466,7 +1429,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
.lookup(ErrorProvider.class);
List<? extends org.netbeans.api.lsp.Diagnostic> errors;
if (errorProvider != null) {
- ErrorProvider.Context context = new
ErrorProvider.Context(file, errorKind);
+ ErrorProvider.Context context = new
ErrorProvider.Context(file, offset, errorKind);
class CancelListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
@@ -1505,7 +1468,9 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
String id = err.getCode();
id2Errors.put(id, err);
}
- doc.putProperty("lsp-errors-" + keyPrefix, id2Errors);
+ if (offset < 0) {
+ doc.putProperty("lsp-errors-" + keyPrefix, id2Errors);
+ }
Map<String, org.netbeans.api.lsp.Diagnostic> mergedId2Errors = new
HashMap<>();
for (String k : ERROR_KEYS) {
Map<String, org.netbeans.api.lsp.Diagnostic> prevErrors =
(Map<String, org.netbeans.api.lsp.Diagnostic>) doc.getProperty("lsp-errors-" +
k);
@@ -1513,7 +1478,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
mergedId2Errors.putAll(prevErrors);
}
}
- for (Entry<String, org.netbeans.api.lsp.Diagnostic> id2Error :
mergedId2Errors.entrySet()) {
+ for (Entry<String, org.netbeans.api.lsp.Diagnostic> id2Error :
(offset < 0 ? mergedId2Errors : id2Errors).entrySet()) {
org.netbeans.api.lsp.Diagnostic err = id2Error.getValue();
Diagnostic diag = new Diagnostic(new
Range(Utils.createPosition(file, err.getStartPosition().getOffset()),
Utils.createPosition(file, err.getEndPosition().getOffset())),
@@ -1528,6 +1493,9 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
diag.setCode(id2Error.getKey());
result.add(diag);
}
+ if (offset >= 0) {
+ mergedId2Errors.putAll(id2Errors);
+ }
doc.putProperty("lsp-errors", mergedId2Errors);
} catch (IOException ex) {
throw new IllegalStateException(ex);
@@ -1539,6 +1507,15 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
return errorKind.name().toLowerCase(Locale.ROOT);
}
+ private String kind(org.netbeans.api.lsp.Diagnostic.Severity severity) {
+ switch (severity) {
+ case Hint:
+ return CodeActionKind.RefactorRewrite;
+ default:
+ return CodeActionKind.QuickFix;
+ }
+ }
+
private FileObject fromURI(String uri) {
return fromURI(uri, false);
}
diff --git a/java/java.lsp.server/vscode/package-lock.json
b/java/java.lsp.server/vscode/package-lock.json
index 3775aa2..df54329 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -365,9 +365,9 @@
}
},
"glob-parent": {
- "version": "5.1.1",
- "resolved":
"https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
- "integrity":
"sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "version": "5.1.2",
+ "resolved":
"https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity":
"sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
@@ -553,9 +553,9 @@
}
},
"lodash": {
- "version": "4.17.19",
- "resolved":
"https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
- "integrity":
"sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+ "version": "4.17.21",
+ "resolved":
"https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity":
"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"log-symbols": {
diff --git a/java/java.lsp.server/vscode/package.json
b/java/java.lsp.server/vscode/package.json
index 6a1b0fe..517dfb2 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -367,5 +367,8 @@
"vscode-languageclient": "6.1.3",
"vscode-test-adapter-api": "^1.9.0",
"vscode-test-adapter-util": "^0.7.1"
- }
+ },
+ "extensionDependencies": [
+ "hbenl.vscode-test-explorer"
+ ]
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists