blob: 4567b040ac2ac97372777e5e61c7af8981eab3db [file] [log] [blame]
/*
* 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.JsonObject;
import com.sun.source.tree.ClassTree;
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.sun.source.util.TreePathScanner;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
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.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
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;
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.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.CodeLensParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItem;
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;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightParams;
import org.eclipse.lsp4j.DocumentOnTypeFormattingParams;
import org.eclipse.lsp4j.DocumentRangeFormattingParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeKind;
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.MarkupContent;
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.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.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;
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.JavaSource.Phase;
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.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
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;
import org.netbeans.modules.editor.java.Utilities;
import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController.TestMethod;
import org.netbeans.modules.java.completion.JavaCompletionTask;
import org.netbeans.modules.java.completion.JavaCompletionTask.Options;
import org.netbeans.modules.java.completion.JavaDocumentationTask;
import org.netbeans.modules.java.editor.base.fold.JavaElementFoldVisitor;
import org.netbeans.modules.java.editor.base.fold.JavaElementFoldVisitor.FoldCreator;
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.java.testrunner.ui.spi.ComputeTestMethods;
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.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;
/**
*
* @author lahvac
*/
public class TextDocumentServiceImpl implements TextDocumentService, LanguageClientAware {
private static final RequestProcessor BACKGROUND_TASKS = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false);
private static final RequestProcessor WORKER = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false);
private final Map<String, Document> openedDocuments = new HashMap<>();
private final Map<String, RequestProcessor.Task> diagnosticTasks = new HashMap<>();
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 = Utils.fromUri(uri);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
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(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, int offset, String kind, String[] elementHandle) {
this.uri = uri;
this.offset = offset;
this.kind = kind;
this.elementHandle = elementHandle;
}
@Override
public String toString() {
return "CompletionData{" + "uri=" + uri + ", kind=" + kind + ", elementHandle=" + elementHandle + '}';
}
}
@Override
public void connect(LanguageClient client) {
this.client = (NbCodeLanguageClient)client;
}
private static class ItemFactoryImpl implements JavaCompletionTask.ItemFactory<CompletionItem> {
private static final int DEPRECATED = 10;
private final LanguageClient client;
private final String uri;
private final int offset;
private final CompilationInfo info;
private final Scope scope;
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())) {
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) {
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;
}
@Override
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) {
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
public CompletionItem createArrayItem(CompilationInfo info, ArrayType type, int substitutionOffset, ReferencesCount referencesCount, Elements elements) {
return null; //TODO: fill
}
@Override
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;
}
@Override
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;
}
@Override
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;
}
@Override
public CompletionItem createExecutableItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, ReferencesCount referencesCount, boolean isInherited, boolean isDeprecated, boolean inImport, boolean addSemicolon, boolean smartType, int assignToVarOffset, boolean memberRef) {
Iterator<? extends VariableElement> it = elem.getParameters().iterator();
Iterator<? extends TypeMirror> tIt = type.getParameterTypes().iterator();
StringBuilder label = new StringBuilder();
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);
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());
item.setKind(elementKind2CompletionItemKind(elem.getKind()));
StringBuilder insertText = new StringBuilder();
insertText.append(elem.getSimpleName());
insertText.append("(");
if (elem.getParameters().isEmpty()) {
insertText.append(")");
}
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 = 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 = 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;
}
@Override
public CompletionItem createGetterSetterMethodItem(CompilationInfo info, VariableElement elem, TypeMirror type, int substitutionOffset, String name, boolean setter) {
return null; //TODO: fill
}
@Override
public CompletionItem createDefaultConstructorItem(TypeElement elem, int substitutionOffset, boolean smartType) {
return null; //TODO: fill
}
@Override
public CompletionItem createParametersItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated, int activeParamIndex, String name) {
return null; //TODO: fill
}
@Override
public CompletionItem createAnnotationItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated) {
return null; //TODO: fill
}
@Override
public CompletionItem createAttributeItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated) {
CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
item.setKind(CompletionItemKind.Property);
StringBuilder insertText = new StringBuilder();
insertText.append(elem.getSimpleName());
insertText.append("=");
item.setInsertText(insertText.toString());
item.setInsertTextFormat(InsertTextFormat.PlainText);
int priority = isDeprecated ? 100 + DEPRECATED : 100;
item.setSortText(String.format("%4d%s", priority, elem.getSimpleName().toString()));
setCompletionData(item, elem);
return item;
}
@Override
public CompletionItem createAttributeValueItem(CompilationInfo info, String value, String documentation, TypeElement element, int substitutionOffset, ReferencesCount referencesCount) {
CompletionItem item = new CompletionItem(value);
item.setKind(CompletionItemKind.Text);
item.setSortText(value);
item.setDocumentation(documentation);
return item;
}
private static final Object KEY_IMPORT_TEXT_EDITS = new Object();
@Override
public CompletionItem createStaticMemberItem(CompilationInfo info, DeclaredType type, Element memberElem, TypeMirror memberType, boolean multipleVersions, int substitutionOffset, boolean isDeprecated, boolean addSemicolon) {
//TODO: prefer static imports (but would be much slower?)
//TODO: should be resolveImport instead of addImports:
Map<Element, List<TextEdit>> imports = (Map<Element, List<TextEdit>>) info.getCachedValue(KEY_IMPORT_TEXT_EDITS);
if (imports == null) {
info.putCachedValue(KEY_IMPORT_TEXT_EDITS, imports = new HashMap<>(), CacheClearPolicy.ON_TASK_END);
}
List<TextEdit> currentClassImport = imports.computeIfAbsent(type.asElement(), toImport -> {
try {
return modify2TextEdits(JavaSource.forFileObject(info.getFileObject()), wc -> {
wc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
wc.rewrite(info.getCompilationUnit(), GeneratorUtilities.get(wc).addImports(wc.getCompilationUnit(), new HashSet<>(Arrays.asList(toImport))));
});
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
return Collections.emptyList();
}
});
String label = type.asElement().getSimpleName() + "." + memberElem.getSimpleName();
CompletionItem item = new CompletionItem(label);
item.setKind(elementKind2CompletionItemKind(memberElem.getKind()));
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;
}
@Override
public CompletionItem createStaticMemberItem(ElementHandle<TypeElement> handle, String name, int substitutionOffset, boolean addSemicolon, ReferencesCount referencesCount, Source source) {
return null; //TODO: fill
}
@Override
public CompletionItem createChainedMembersItem(CompilationInfo info, List<? extends Element> chainedElems, List<? extends TypeMirror> chainedTypes, int substitutionOffset, boolean isDeprecated, boolean addSemicolon) {
return null; //TODO: fill
}
@Override
public CompletionItem createInitializeAllConstructorItem(CompilationInfo info, boolean isDefault, Iterable<? extends VariableElement> fields, ExecutableElement superConstructor, TypeElement parent, int substitutionOffset) {
return null; //TODO: fill
}
private static CompletionItemKind elementKind2CompletionItemKind(ElementKind kind) {
switch (kind) {
case PACKAGE:
return CompletionItemKind.Folder;
case ENUM:
return CompletionItemKind.Enum;
case CLASS:
return CompletionItemKind.Class;
case ANNOTATION_TYPE:
return CompletionItemKind.Interface;
case INTERFACE:
return CompletionItemKind.Interface;
case ENUM_CONSTANT:
return CompletionItemKind.EnumMember;
case FIELD:
return CompletionItemKind.Field;
case PARAMETER:
return CompletionItemKind.Variable;
case LOCAL_VARIABLE:
return CompletionItemKind.Variable;
case EXCEPTION_PARAMETER:
return CompletionItemKind.Variable;
case METHOD:
return CompletionItemKind.Method;
case CONSTRUCTOR:
return CompletionItemKind.Constructor;
case TYPE_PARAMETER:
return CompletionItemKind.TypeParameter;
case RESOURCE_VARIABLE:
return CompletionItemKind.Variable;
case MODULE:
return CompletionItemKind.Module;
case STATIC_INIT:
case INSTANCE_INIT:
case OTHER:
default:
return CompletionItemKind.Text;
}
}
}
private static final RequestProcessor JAVADOC_WORKER = new RequestProcessor(TextDocumentServiceImpl.class.getName() + ".javadoc", 1);
@Override
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem ci) {
JsonObject rawData = (JsonObject) ci.getData();
if (rawData == null) {
return CompletableFuture.completedFuture(ci);
}
CompletionData data = new Gson().fromJson(rawData, CompletionData.class);
try {
FileObject file = Utils.fromUri(data.uri);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
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);
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
public boolean cancel(boolean mayInterruptIfRunning) {
return futureJavadoc.cancel(mayInterruptIfRunning) && super.cancel(mayInterruptIfRunning);
}
};
JAVADOC_WORKER.post(() -> {
try {
String javadoc = futureJavadoc.get();
MarkupContent markup = new MarkupContent();
markup.setKind("markdown");
markup.setValue(html2MD(javadoc));
ci.setDocumentation(markup);
result.complete(ci);
} catch (ExecutionException | InterruptedException ex) {
result.completeExceptionally(ex);
}
});
return result;
} catch (IOException | ParseException ex) {
CompletableFuture<CompletionItem> result = new CompletableFuture<CompletionItem>();
result.completeExceptionally(ex);
return result;
}
}
public static String html2MD(String html) {
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) {
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
public CompletableFuture<SignatureHelp> signatureHelp(SignatureHelpParams params) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams params) {
JavaSource js = getSource(params.getTextDocument().getUri());
GoToTarget[] target = new GoToTarget[1];
LineMap[] thisFileLineMap = new LineMap[1];
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
int offset = Utils.getOffset(doc, params.getPosition());
Context context = GoToSupport.resolveContext(cc, doc, offset, false, false);
if (context == null) {
return ;
}
target[0] = GoToSupport.computeGoToTarget(cc, context, offset);
thisFileLineMap[0] = cc.getCompilationUnit().getLineMap();
}, true);
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
List<Location> result = new ArrayList<>();
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(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(Utils.createPosition(thisFileLineMap[0], start),
Utils.createPosition(thisFileLineMap[0], end))));
}
}
return CompletableFuture.completedFuture(Either.forLeft(result));
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
AtomicBoolean cancel = new AtomicBoolean();
Runnable[] cancelCallback = new Runnable[1];
CompletableFuture<List<? extends Location>> result = new CompletableFuture<List<? extends Location>>() {
@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 {
WhereUsedQuery[] query = new WhereUsedQuery[1];
List<Location> locations = new ArrayList<>();
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()));
if (params.getContext().isIncludeDeclaration()) {
Element decl = cc.getTrees().getElement(path);
if (decl != null) {
TreePath declPath = cc.getTrees().getPath(decl);
if (declPath != null && cc.getCompilationUnit() == declPath.getCompilationUnit()) {
Range range = declarationRange(cc, declPath);
if (range != null) {
locations.add(new Location(Utils.toUri(cc.getFileObject()),
range));
}
} else {
ElementHandle<Element> declHandle = ElementHandle.create(decl);
FileObject sourceFile = SourceUtils.getFile(declHandle, cc.getClasspathInfo());
JavaSource source = sourceFile != null ? JavaSource.forFileObject(sourceFile) : null;
if (source != null) {
source.runUserActionTask(nestedCC -> {
nestedCC.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
Element declHandle2 = declHandle.resolve(nestedCC);
TreePath declPath2 = declHandle2 != null ? nestedCC.getTrees().getPath(declHandle2) : null;
if (declPath2 != null) {
Range range = declarationRange(nestedCC, declPath2);
if (range != null) {
locations.add(new Location(Utils.toUri(nestedCC.getFileObject()),
range));
}
}
}, true);
}
}
}
}
query[0] = new WhereUsedQuery(Lookups.singleton(TreePathHandle.create(path, cc)));
}, true);
if (cancel.get()) return ;
cancelCallback[0] = () -> query[0].cancelRequest();
RefactoringSession refactoring = RefactoringSession.create("FindUsages");
Problem p;
p = query[0].checkParameters();
if (cancel.get()) return ;
if (p != null && p.isFatal()) {
ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
return ;
}
p = query[0].preCheck();
if (p != null && p.isFatal()) {
ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
return ;
}
if (cancel.get()) return ;
p = query[0].prepare(refactoring);
if (p != null && p.isFatal()) {
ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
return ;
}
for (RefactoringElement re : refactoring.getRefactoringElements()) {
if (cancel.get()) return ;
locations.add(new Location(Utils.toUri(re.getParentFile()), toRange(re.getPosition())));
}
refactoring.finished();
result.complete(locations);
} catch (Throwable ex) {
result.completeExceptionally(ex);
}
});
return result;
}
private static Range declarationRange(CompilationInfo info, TreePath tp) {
Tree t = tp.getLeaf();
int[] span;
if (TreeUtilities.CLASS_TREE_KINDS.contains(t.getKind())) {
span = info.getTreeUtilities().findNameSpan((ClassTree) t);
} else if (t.getKind() == Kind.VARIABLE) {
span = info.getTreeUtilities().findNameSpan((VariableTree) t);
} else if (t.getKind() == Kind.METHOD) {
span = info.getTreeUtilities().findNameSpan((MethodTree) t);
if (span == null) {
span = info.getTreeUtilities().findNameSpan((ClassTree) tp.getParentPath().getLeaf());
}
} else {
return null;
}
if (span == null) {
return null;
}
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 {
return new Range(new Position(bounds.getBegin().getLine(),
bounds.getBegin().getColumn()),
new Position(bounds.getEnd().getLine(),
bounds.getEnd().getColumn()));
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(DocumentHighlightParams params) {
class MOHighligther extends MarkOccurrencesHighlighterBase {
@Override
protected void process(CompilationInfo arg0, Document arg1, SchedulerEvent arg2) {
throw new UnsupportedOperationException("Should not be called.");
}
@Override
public List<int[]> processImpl(CompilationInfo info, Preferences node, Document doc, int caretPosition) {
return super.processImpl(info, node, doc, caretPosition);
}
}
Preferences node = MarkOccurencesSettings.getCurrentNode();
JavaSource js = getSource(params.getTextDocument().getUri());
List<DocumentHighlight> result = new ArrayList<>();
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
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(Utils.createPosition(cc.getCompilationUnit(), span[0]),
Utils.createPosition(cc.getCompilationUnit(), span[1]))));
}
}
}, true);
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
return CompletableFuture.completedFuture(result);
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams params) {
JavaSource js = getSource(params.getTextDocument().getUri());
List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>();
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
for (Element tel : cc.getTopLevelElements()) {
DocumentSymbol ds = element2DocumentSymbol(cc, tel);
if (ds != null)
result.add(Either.forRight(ds));
}
}, true);
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
return CompletableFuture.completedFuture(result);
}
private DocumentSymbol element2DocumentSymbol(CompilationInfo info, Element el) throws BadLocationException {
TreePath path = info.getTrees().getPath(el);
if (path == null)
return null;
Range range = Utils.treeRange(info, path.getLeaf());
if (range == null)
return null;
List<DocumentSymbol> children = new ArrayList<>();
for (Element c : el.getEnclosedElements()) {
DocumentSymbol ds = element2DocumentSymbol(info, c);
if (ds != null) {
children.add(ds);
}
}
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());
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");
List<Either<Command, CodeAction>> result = new ArrayList<>();
if (id2Errors != null) {
for (Diagnostic diag : params.getContext().getDiagnostics()) {
ErrorDescription err = id2Errors.get(diag.getCode().getLeft());
if (err == null) {
client.logMessage(new MessageParams(MessageType.Log, "Cannot resolve error, code: " + diag.getCode().getLeft()));
continue;
}
TreePathHandle[] topLevelHandle = new TreePathHandle[1];
LazyFixList lfl = err.getFixes();
if (lfl instanceof CreatorBasedLazyFixList) {
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
((CreatorBasedLazyFixList) lfl).compute(cc, new AtomicBoolean());
topLevelHandle[0] = TreePathHandle.create(new TreePath(cc.getCompilationUnit()), cc);
}, true);
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
}
List<Fix> fixes = sortFixes(lfl.getFixes());
//TODO: ordering
for (Fix f : fixes) {
if (f instanceof IncompleteClassPath.ResolveFix) {
CodeAction action = new CodeAction(f.getText());
action.setDiagnostics(Collections.singletonList(diag));
action.setKind(CodeActionKind.QuickFix);
action.setCommand(new Command(f.getText(), Server.JAVA_BUILD_WORKSPACE));
result.add(Either.forRight(action));
}
if (f instanceof ImportClass.FixImport) {
//TODO: FixImport is not a JavaFix, create one. Is there a better solution?
String text = f.getText();
CharSequence sortText = ((ImportClass.FixImport) f).getSortText();
ElementHandle<Element> toImport = ((ImportClass.FixImport) f).getToImport();
f = new JavaFix(topLevelHandle[0], sortText != null ? sortText.toString() : null) {
@Override
protected String getText() {
return text;
}
@Override
protected void performRewrite(JavaFix.TransformationContext ctx) throws Exception {
Element resolved = toImport.resolve(ctx.getWorkingCopy());
if (resolved == null) {
return ;
}
WorkingCopy copy = ctx.getWorkingCopy();
CompilationUnitTree cut = GeneratorUtilities.get(copy).addImports(
copy.getCompilationUnit(),
Collections.singleton(resolved)
);
copy.rewrite(copy.getCompilationUnit(), cut);
}
}.toEditorFix();
}
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<>());
});
TextDocumentEdit te = new TextDocumentEdit(new VersionedTextDocumentIdentifier(params.getTextDocument().getUri(),
-1),
edits);
CodeAction action = new CodeAction(f.getText());
action.setDiagnostics(Collections.singletonList(diag));
action.setKind(CodeActionKind.QuickFix);
action.setEdit(new WorkspaceEdit(Collections.singletonList(Either.forLeft(te))));
result.add(Either.forRight(action));
} catch (IOException ex) {
//TODO: include stack trace:
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);
}
//TODO: copied from spi.editor.hints/.../FixData:
private List<Fix> sortFixes(Collection<Fix> fixes) {
List<Fix> result = new ArrayList<Fix>(fixes);
Collections.sort(result, new FixComparator());
return result;
}
private static final String DEFAULT_SORT_TEXT = "\uFFFF";
private static CharSequence getSortText(Fix f) {
if (f instanceof EnhancedFix) {
return ((EnhancedFix) f).getSortText();
} else {
return DEFAULT_SORT_TEXT;
}
}
private static final class FixComparator implements Comparator<Fix> {
public int compare(Fix o1, Fix o2) {
return compareText(getSortText(o1), getSortText(o2));
}
}
private static int compareText(CharSequence text1, CharSequence text2) {
int len = Math.min(text1.length(), text2.length());
for (int i = 0; i < len; i++) {
char ch1 = text1.charAt(i);
char ch2 = text2.charAt(i);
if (ch1 != ch2) {
return ch1 - ch2;
}
}
return text1.length() - text2.length();
}
//end copied
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
JavaSource source = getSource(params.getTextDocument().getUri());
if (source == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
CompletableFuture<List<? extends CodeLens>> result = new CompletableFuture<>();
try {
source.runUserActionTask(cc -> {
cc.toPhase(Phase.ELEMENTS_RESOLVED);
List<CodeLens> lens = new ArrayList<>();
//look for test methods:
for (ComputeTestMethods.Factory methodsFactory : Lookup.getDefault().lookupAll(ComputeTestMethods.Factory.class)) {
List<TestMethod> methods = methodsFactory.create().computeTestMethods(cc);
if (methods != null) {
for (TestMethod method : methods) {
Range range = new Range(Utils.createPosition(cc.getCompilationUnit(), method.start().getOffset()),
Utils.createPosition(cc.getCompilationUnit(), method.end().getOffset()));
List<Object> arguments = Arrays.asList(new Object[]{method.method().getFile().toURI(), method.method().getMethodName()});
lens.add(new CodeLens(range,
new Command("Run test", "java.run.codelens", arguments),
null));
lens.add(new CodeLens(range,
new Command("Debug test", "java.debug.codelens", arguments),
null));
}
}
}
//look for main methods:
new TreePathScanner<Void, Void>() {
public Void visitMethod(MethodTree tree, Void p) {
Element el = cc.getTrees().getElement(getCurrentPath());
if (el != null && el.getKind() == ElementKind.METHOD && SourceUtils.isMainMethod((ExecutableElement) el)) {
Range range = Utils.treeRange(cc, tree);
List<Object> arguments = Collections.singletonList(params.getTextDocument().getUri());
lens.add(new CodeLens(range,
new Command("Run main", "java.run.codelens", arguments),
null));
lens.add(new CodeLens(range,
new Command("Debug main", "java.debug.codelens", arguments),
null));
}
return null;
}
}.scan(cc.getCompilationUnit(), null);
result.complete(lens);
}, true);
} catch (IOException ex) {
result.completeExceptionally(ex);
}
return result;
}
@Override
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams arg0) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
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 CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
JavaSource source = getSource(params.getTextDocument().getUri());
if (source == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
CompletableFuture<List<FoldingRange>> result = new CompletableFuture<>();
try {
source.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
JavaElementFoldVisitor v = new JavaElementFoldVisitor(cc, cc.getCompilationUnit(), cc.getTrees().getSourcePositions(), doc, new FoldCreator<FoldingRange>() {
@Override
public FoldingRange createImportsFold(int start, int end) {
return createFold(start, end, FoldingRangeKind.Imports);
}
@Override
public FoldingRange createInnerClassFold(int start, int end) {
return createFold(start, end, FoldingRangeKind.Region);
}
@Override
public FoldingRange createCodeBlockFold(int start, int end) {
return createFold(start, end, FoldingRangeKind.Region);
}
@Override
public FoldingRange createJavadocFold(int start, int end) {
return createFold(start, end, FoldingRangeKind.Comment);
}
@Override
public FoldingRange createInitialCommentFold(int start, int end) {
return createFold(start, end, FoldingRangeKind.Comment);
}
private FoldingRange createFold(int start, int end, String kind) {
Position startPos = Utils.createPosition(cc.getCompilationUnit(), start);
Position endPos = Utils.createPosition(cc.getCompilationUnit(), end);
FoldingRange range = new FoldingRange(startPos.getLine(), endPos.getLine());
range.setStartCharacter(startPos.getCharacter());
range.setEndCharacter(endPos.getCharacter());
range.setKind(kind);
return range;
}
});
v.checkInitialFold();
v.scan(cc.getCompilationUnit(), null);
result.complete(v.getFolds());
}, true);
} catch (IOException ex) {
result.completeExceptionally(ex);
}
return result;
}
@Override
public void didOpen(DidOpenTextDocumentParams params) {
try {
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
// the doc was opened prior to request reception.
String text = params.getTextDocument().getText();
try {
if (doc == null) {
doc = ec.openDocument();
}
if (!text.contentEquals(doc.getText(0, doc.getLength()))) {
doc.remove(0, doc.getLength());
doc.insertString(0, text, null);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
openedDocuments.put(params.getTextDocument().getUri(), doc);
runDiagnoticTasks(params.getTextDocument().getUri());
} catch (IOException ex) {
throw new IllegalStateException(ex);
} finally {
reportNotificationDone("didOpen", params);
}
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
Document doc = openedDocuments.get(params.getTextDocument().getUri());
NbDocument.runAtomic((StyledDocument) doc, () -> {
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
try {
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) {
throw new IllegalStateException(ex);
}
}
});
runDiagnoticTasks(params.getTextDocument().getUri());
reportNotificationDone("didChange", params);
}
@Override
public void didClose(DidCloseTextDocumentParams 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
public void didSave(DidSaveTextDocumentParams arg0) {
//TODO: nothing for now?
}
private void runDiagnoticTasks(String uri) {
//XXX: cancelling/deferring the tasks!
diagnosticTasks.computeIfAbsent(uri, u -> {
return BACKGROUND_TASKS.create(() -> {
computeDiags(u, (info, doc) -> {
ErrorHintsProvider ehp = new ErrorHintsProvider();
return ehp.computeErrors(info, doc, "text/x-java"); //TODO: mimetype?
}, "errors", false);
BACKGROUND_TASKS.create(() -> {
computeDiags(u, (info, doc) -> {
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);
});
}).schedule(DELAY);
}
private static final int DELAY = 500;
private void computeDiags(String uri, ProduceErrors produceErrors, String keyPrefix, boolean update) {
try {
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() {
@Override
public void run(ResultIterator it) throws Exception {
CompilationController cc = CompilationController.get(it.getParserResult());
cc.toPhase(JavaSource.Phase.RESOLVED);
Map<String, ErrorDescription> id2Errors = new HashMap<>();
List<Diagnostic> diags = new ArrayList<>();
int idx = 0;
List<ErrorDescription> errors = produceErrors.computeErrors(cc, doc);
if (errors == null) {
errors = Collections.emptyList();
}
for (ErrorDescription err : errors) {
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;
case VERIFIER:
case WARNING: diag.setSeverity(DiagnosticSeverity.Warning); break;
case HINT: diag.setSeverity(DiagnosticSeverity.Hint); break;
default: diag.setSeverity(DiagnosticSeverity.Information); break;
}
String id = keyPrefix + ":" + idx++ + "-" + err.getId();
diag.setCode(id);
id2Errors.put(id, err);
diags.add(diag);
}
doc.putProperty("lsp-errors-" + keyPrefix, id2Errors);
doc.putProperty("lsp-errors-diags-" + keyPrefix, diags);
Map<String, ErrorDescription> mergedId2Errors = new HashMap<>();
List<Diagnostic> mergedDiags = new ArrayList<>();
for (String k : ERROR_KEYS) {
Map<String, ErrorDescription> prevErrors = (Map<String, ErrorDescription>) doc.getProperty("lsp-errors-" + k);
if (prevErrors != null) {
mergedId2Errors.putAll(prevErrors);
}
List<Diagnostic> prevDiags = (List<Diagnostic>) doc.getProperty("lsp-errors-diags-" + k);
if (prevDiags != null) {
mergedDiags.addAll(prevDiags);
}
}
doc.putProperty("lsp-errors", mergedId2Errors);
doc.putProperty("lsp-errors-diags", mergedDiags);
client.publishDiagnostics(new PublishDiagnosticsParams(uri, mergedDiags));
}
});
} catch (IOException | ParseException ex) {
throw new IllegalStateException(ex);
}
}
private static final String[] ERROR_KEYS = {"errors", "hints"};
private interface ProduceErrors {
public List<ErrorDescription> computeErrors(CompilationInfo info, Document doc) throws IOException;
}
private JavaSource getSource(String fileUri) {
Document doc = openedDocuments.get(fileUri);
if (doc == null) {
try {
FileObject file = Utils.fromUri(fileUri);
return JavaSource.forFileObject(file);
} catch (MalformedURLException ex) {
return null;
}
} else {
return JavaSource.forDocument(doc);
}
}
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 -> {
task.run(wc);
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);
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(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.
*/
static BiConsumer<String, Object> HOOK_NOTIFICATION = null;
}