diff --git a/ide/api.lsp/apichanges.xml b/ide/api.lsp/apichanges.xml index c65b589a2e2e..254fcfb91906 100644 --- a/ide/api.lsp/apichanges.xml +++ b/ide/api.lsp/apichanges.xml @@ -51,6 +51,19 @@ + + + Added URL to diagnostic code description + + + + + + Completion.getCommand to get an optional command + that is executed after inserting the completion. + + + Added URL to diagnostic code description diff --git a/ide/api.lsp/manifest.mf b/ide/api.lsp/manifest.mf index 065974d1c073..5bf8a9f99546 100644 --- a/ide/api.lsp/manifest.mf +++ b/ide/api.lsp/manifest.mf @@ -1,5 +1,5 @@ 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.16 +OpenIDE-Module-Specification-Version: 1.17 AutoUpdate-Show-In-Client: false diff --git a/ide/api.lsp/src/org/netbeans/api/lsp/Completion.java b/ide/api.lsp/src/org/netbeans/api/lsp/Completion.java index 29b4133d78bb..072f9832a96b 100644 --- a/ide/api.lsp/src/org/netbeans/api/lsp/Completion.java +++ b/ide/api.lsp/src/org/netbeans/api/lsp/Completion.java @@ -44,9 +44,9 @@ public final class Completion { CompletionAccessor.setDefault(new CompletionAccessor() { @Override public Completion createCompletion(String label, Kind kind, List tags, CompletableFuture detail, CompletableFuture documentation, - boolean preselect, String sortText, String filterText, String insertText, TextFormat insertTextFormat, TextEdit textEdit, CompletableFuture> additionalTextEdits, - List commitCharacters) { - return new Completion(label, kind, tags, detail, documentation, preselect, sortText, filterText, insertText, insertTextFormat, textEdit, additionalTextEdits, commitCharacters); + boolean preselect, String sortText, String filterText, String insertText, TextFormat insertTextFormat, TextEdit textEdit, Command command, + CompletableFuture> additionalTextEdits, List commitCharacters) { + return new Completion(label, kind, tags, detail, documentation, preselect, sortText, filterText, insertText, insertTextFormat, textEdit, command, additionalTextEdits, commitCharacters); } }); } @@ -62,12 +62,13 @@ public Completion createCompletion(String label, Kind kind, List tags, Comp private final String insertText; private final TextFormat insertTextFormat; private final TextEdit textEdit; + private final Command command; private final CompletableFuture> additionalTextEdits; private final List commitCharacters; private Completion(String label, Kind kind, List tags, CompletableFuture detail, CompletableFuture documentation, boolean preselect, String sortText, String filterText, String insertText, TextFormat insertTextFormat, - TextEdit textEdit, CompletableFuture> additionalTextEdits, List commitCharacters) { + TextEdit textEdit, Command command, CompletableFuture> additionalTextEdits, List commitCharacters) { this.label = label; this.kind = kind; this.tags = tags; @@ -79,6 +80,7 @@ private Completion(String label, Kind kind, List tags, CompletableFuture tags, CompletableFuture detail, CompletableFuture documentation, boolean preselect, String sortText, String filterText, String insertText, Completion.TextFormat insertTextFormat, - TextEdit textEdit, CompletableFuture> additionalTextEdits, List commitCharacters); + TextEdit textEdit, Command command, CompletableFuture> additionalTextEdits, List commitCharacters); } diff --git a/ide/api.lsp/src/org/netbeans/spi/lsp/CompletionCollector.java b/ide/api.lsp/src/org/netbeans/spi/lsp/CompletionCollector.java index e524d8619ac5..34b964ce0e79 100644 --- a/ide/api.lsp/src/org/netbeans/spi/lsp/CompletionCollector.java +++ b/ide/api.lsp/src/org/netbeans/spi/lsp/CompletionCollector.java @@ -29,6 +29,7 @@ import javax.swing.text.Document; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.lsp.Command; import org.netbeans.api.lsp.Completion; import org.netbeans.api.lsp.TextEdit; import org.netbeans.modules.lsp.CompletionAccessor; @@ -99,6 +100,7 @@ public static final class Builder { private String insertText; private Completion.TextFormat insertTextFormat; private TextEdit textEdit; + private Command command; private CompletableFuture> additionalTextEdits; private List commitCharacters; @@ -273,6 +275,16 @@ public Builder textEdit(@NonNull TextEdit textEdit) { return this; } + /** + * An optional command that is executed after inserting this completion. + * + * @since 1.17 + */ + @NonNull + public Builder command(@NonNull Command command) { + this.command = command; + return this; + } /** * A list of additional text edits that are applied when selecting this * completion. Edits must not overlap (including the same insert position) @@ -334,7 +346,7 @@ public Builder addCommitCharacter(char commitCharacter) { public Completion build() { return CompletionAccessor.getDefault().createCompletion(label, kind, tags, detail, documentation, preselect, sortText, filterText, - insertText, insertTextFormat, textEdit, additionalTextEdits, + insertText, insertTextFormat, textEdit, command, additionalTextEdits, commitCharacters); } diff --git a/java/java.editor/nbproject/project.xml b/java/java.editor/nbproject/project.xml index 4270767b7a54..132d3c9ffed9 100644 --- a/java/java.editor/nbproject/project.xml +++ b/java/java.editor/nbproject/project.xml @@ -58,7 +58,7 @@ 1 - 1.9 + 1.17 diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionCollector.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionCollector.java index 0b69d79d4e3f..679a3b84b3af 100644 --- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionCollector.java +++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionCollector.java @@ -88,6 +88,7 @@ import org.netbeans.api.java.source.ui.ElementHeaders; import org.netbeans.api.java.source.ui.ElementJavadoc; import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.lsp.Command; import org.netbeans.api.lsp.Completion; import org.netbeans.api.lsp.TextEdit; import org.netbeans.modules.java.completion.JavaCompletionTask; @@ -607,10 +608,14 @@ public Completion createGetterSetterMethodItem(CompilationInfo info, VariableEle @Override public Completion createDefaultConstructorItem(TypeElement elem, int substitutionOffset, boolean smartType) { + Builder builder = CompletionCollector.newBuilder(elem.getSimpleName().toString() + "()") + .kind(Completion.Kind.Constructor) + .sortText(String.format("%04d%s#0", smartType ? 650 : 1650, elem.getSimpleName().toString())); StringBuilder insertText = new StringBuilder(); - insertText.append(elem.getSimpleName()); + if (substitutionOffset < offset) { + insertText.append((elem.getSimpleName())); + } insertText.append(CodeStyle.getDefault(doc).spaceBeforeMethodCallParen() ? " ()" : "()"); - boolean asTemplate = false; if (elem.getModifiers().contains(Modifier.ABSTRACT)) { try { if (CodeStyle.getDefault(info.getDocument()).getClassDeclBracePlacement() == CodeStyle.BracePlacement.SAME_LINE) { @@ -618,16 +623,14 @@ public Completion createDefaultConstructorItem(TypeElement elem, int substitutio } else { insertText.append("\n{\n$0}"); } - asTemplate = true; + builder.command(new Command("Complete Abstract Methods", "java.complete.abstract.methods")); } catch (IOException ioe) { } + builder.insertTextFormat(Completion.TextFormat.Snippet); + } else { + builder.insertTextFormat(Completion.TextFormat.PlainText); } - return CompletionCollector.newBuilder(elem.getSimpleName().toString() + "()") - .kind(Completion.Kind.Constructor) - .insertText(insertText.toString()) - .insertTextFormat(asTemplate ? Completion.TextFormat.Snippet : Completion.TextFormat.PlainText) - .sortText(String.format("%04d%s#0", smartType ? 650 : 1650, elem.getSimpleName().toString())) - .build(); + return builder.insertText(insertText.toString()).build(); } @Override @@ -1016,10 +1019,13 @@ private Completion createTypeItem(CompilationInfo info, String prefix, ElementHa .sortText(String.format("%04d%s#%02d#%s", smartType ? 800 : 1800, elem.getSimpleName().toString(), Utilities.getImportanceLevel(name), pkgName)) .insertText(insertText.toString()); if (asTemplate) { - builder.insertTextFormat(Completion.TextFormat.Snippet); + builder.insertTextFormat(Completion.TextFormat.Snippet); } else { - builder.insertTextFormat(Completion.TextFormat.Snippet) - .addCommitCharacter('.'); + builder.insertTextFormat(Completion.TextFormat.Snippet) + .addCommitCharacter('.'); + } + if (insideNew) { + builder.command(new Command("Invoke Completion", "editor.action.triggerSuggest")); } if (handle != null) { builder.documentation(getDocumentation(doc, off, handle)); @@ -1048,6 +1054,7 @@ private Completion createExecutableItem(CompilationInfo info, ExecutableElement sortParams.append('('); int cnt = 0; boolean asTemplate = false; + Command command = null; while(it.hasNext() && tIt.hasNext()) { TypeMirror tm = tIt.next(); if (tm == null) { @@ -1094,6 +1101,7 @@ private Completion createExecutableItem(CompilationInfo info, ExecutableElement } else { insertText.append("\n{\n$0}"); } + command = new Command("Complete Abstract Methods", "java.complete.abstract.methods"); asTemplate = true; } catch (IOException ioe) { } @@ -1135,7 +1143,6 @@ private Completion createExecutableItem(CompilationInfo info, ExecutableElement } else { builder.insertText(insertText.toString()); } - ElementHandle handle = SUPPORTED_ELEMENT_KINDS.contains(elem.getKind().name()) ? ElementHandle.create(elem) : null; if (handle != null) { builder.documentation(getDocumentation(doc, offset, handle)); diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml index 377170d9b08c..d63e0c53580f 100644 --- a/java/java.lsp.server/nbproject/project.xml +++ b/java/java.lsp.server/nbproject/project.xml @@ -128,7 +128,7 @@ 1 - 1.11 + 1.17 diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementAllAbstractMethodsAction.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementAllAbstractMethodsAction.java new file mode 100644 index 000000000000..44a87db68e84 --- /dev/null +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementAllAbstractMethodsAction.java @@ -0,0 +1,93 @@ +/* + * 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.google.gson.JsonPrimitive; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import javax.swing.text.Document; +import org.eclipse.lsp4j.ApplyWorkspaceEditParams; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.netbeans.api.editor.document.LineDocument; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.modules.java.editor.codegen.GeneratorUtils; +import org.netbeans.modules.java.lsp.server.Utils; +import org.netbeans.modules.parsing.api.ResultIterator; +import org.openide.filesystems.FileObject; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Dusan Balek + */ +@ServiceProvider(service = CodeActionsProvider.class, position = 200) +public final class ImplementAllAbstractMethodsAction extends CodeActionsProvider { + + private static final String IMPLEMENT_ALL_ABSTRACT_METHODS = "java.implement.all.abstract.methods"; //NOI18N + private final Gson gson = new Gson(); + + @Override + public List getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception { + return Collections.emptyList(); + } + + @Override + public Set getCommands() { + return Collections.singleton(IMPLEMENT_ALL_ABSTRACT_METHODS); + } + + @Override + public CompletableFuture processCommand(NbCodeLanguageClient client, String command, List arguments) { + CompletableFuture future = new CompletableFuture<>(); + try { + if (arguments.size() >= 2) { + String uri = ((JsonPrimitive) arguments.get(0)).getAsString(); + FileObject file = Utils.fromUri(uri); + JavaSource js = JavaSource.forFileObject(file); + if (js == null) { + throw new IOException("Cannot get JavaSource for: " + uri); + } + Position position = gson.fromJson(gson.toJson(arguments.get(1)), Position.class); + List edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> { + wc.toPhase(JavaSource.Phase.RESOLVED); + Document doc = wc.getSnapshot().getSource().getDocument(true); + if (doc instanceof LineDocument) { + int offset = Utils.getOffset((LineDocument) doc, position); + GeneratorUtils.generateAllAbstractMethodImplementations(wc, wc.getTreeUtilities().pathFor(offset)); + } + }); + client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits)))); + future.complete(true); + } else { + throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command)); + } + } catch (IOException | IllegalArgumentException ex) { + future.completeExceptionally(ex); + } + return future; + } +} 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 8084aa341041..ed00816ca479 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 @@ -399,6 +399,10 @@ public CompletableFuture, CompletionList>> completio if (edit != null) { item.setTextEdit(Either.forLeft(new TextEdit(new Range(Utils.createPosition(file, edit.getStartOffset()), Utils.createPosition(file, edit.getEndOffset())), edit.getNewText()))); } + org.netbeans.api.lsp.Command command = completion.getCommand(); + if (command != null) { + item.setCommand(new Command(command.getTitle(), command.getCommand(), command.getArguments())); + } if (completion.getAdditionalTextEdits() != null && completion.getAdditionalTextEdits().isDone()) { List additionalTextEdits = completion.getAdditionalTextEdits().getNow(null); if (additionalTextEdits != null && !additionalTextEdits.isEmpty()) { diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index b4c1fa7f39bc..02297afb339a 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -610,6 +610,13 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { } } })); + context.subscriptions.push(commands.registerCommand('java.complete.abstract.methods', async () => { + const active = vscode.window.activeTextEditor; + if (active) { + const position = new vscode.Position(active.selection.start.line, active.selection.start.character); + await commands.executeCommand('java.implement.all.abstract.methods', active.document.uri.toString(), position); + } + })); context.subscriptions.push(commands.registerCommand('nbls.startup.condition', async () => { return client; }));