blob: 8a5263189136e2d146a2ce2f1165242198ebfa66 [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.google.gson.JsonPrimitive;
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.NewClassTree;
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.sun.source.util.Trees;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.IntFunction;
import java.util.logging.Level;
import java.util.logging.Logger;
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.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import org.eclipse.lsp4j.ClientCapabilities;
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.CompletionItemTag;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.ConfigurationItem;
import org.eclipse.lsp4j.ConfigurationParams;
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.ImplementationParams;
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.SemanticTokens;
import org.eclipse.lsp4j.SemanticTokensCapabilities;
import org.eclipse.lsp4j.SemanticTokensLegend;
import org.eclipse.lsp4j.SemanticTokensParams;
import org.eclipse.lsp4j.SemanticTokensServerFull;
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.SignatureHelp;
import org.eclipse.lsp4j.SignatureHelpParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.TypeDefinitionParams;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WillSaveTextDocumentParams;
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.annotations.common.CheckForNull;
import org.netbeans.api.editor.document.LineDocument;
import org.netbeans.api.editor.document.LineDocumentUtils;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.java.source.ui.ElementOpen;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.lsp.Completion;
import org.netbeans.api.lsp.HyperlinkLocation;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.editor.indent.spi.CodeStylePreferences;
import org.netbeans.modules.editor.java.GoToSupport;
import org.netbeans.modules.editor.java.GoToSupport.GoToTarget;
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.ColoringAttributes;
import org.netbeans.modules.java.editor.base.semantic.MarkOccurrencesHighlighterBase;
import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
import org.netbeans.modules.java.editor.base.semantic.SemanticHighlighterBase;
import org.netbeans.modules.java.editor.base.semantic.SemanticHighlighterBase.ErrorDescriptionSetter;
import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
import org.netbeans.modules.java.editor.overridden.ComputeOverriding;
import org.netbeans.modules.java.editor.overridden.ElementDescription;
import org.netbeans.modules.java.hints.OrganizeImports;
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.lsp.server.LspServerState;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
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.Parser;
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.api.WhereUsedQueryConstants;
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.ErrorDescription;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.lsp.ErrorProvider;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.ProjectConfiguration;
import org.netbeans.spi.project.ProjectConfigurationProvider;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.text.NbDocument;
import org.openide.text.PositionBounds;
import org.openide.util.BaseUtilities;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
import org.openide.util.Union2;
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 Logger LOG = Logger.getLogger(TextDocumentServiceImpl.class.getName());
private static final String COMMAND_RUN_SINGLE = "java.run.single"; // NOI18N
private static final String COMMAND_DEBUG_SINGLE = "java.debug.single"; // NOI18N
private static final String NETBEANS_JAVADOC_LOAD_TIMEOUT = "netbeans.javadoc.load.timeout";// NOI18N
private static final String NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS = "netbeans.java.onSave.organizeImports";// NOI18N
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);
/**
* File URIs touched / queried by the client.
*/
private Map<String, Instant> knownFiles = new HashMap<>();
/**
* Documents actually opened by the client.
*/
private final Map<String, RequestProcessor.Task> diagnosticTasks = new HashMap<>();
private final LspServerState server;
private NbCodeLanguageClient client;
TextDocumentServiceImpl(LspServerState server) {
this.server = server;
Lookup.getDefault().lookup(RefreshDocument.class).register(this);
}
private void reRunDiagnostics() {
for (String doc : server.getOpenedDocuments().getUris()) {
runDiagnosticTasks(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();
}
}
}
private final AtomicInteger javadocTimeout = new AtomicInteger(-1);
private List<Completion> lastCompletions = null;
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
lastCompletions = new ArrayList<>();
AtomicInteger index = new AtomicInteger(0);
final CompletionList completionList = new CompletionList();
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(Either.forRight(completionList));
}
try {
String uri = params.getTextDocument().getUri();
FileObject file = fromURI(uri);
if (file == null) {
return CompletableFuture.completedFuture(Either.forRight(completionList));
}
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
if (!(doc instanceof LineDocument)) {
return CompletableFuture.completedFuture(Either.forRight(completionList));
}
ConfigurationItem conf = new ConfigurationItem();
conf.setScopeUri(uri);
conf.setSection(NETBEANS_JAVADOC_LOAD_TIMEOUT);
return client.configuration(new ConfigurationParams(Collections.singletonList(conf))).thenApply(c -> {
if (c != null && !c.isEmpty()) {
javadocTimeout.set(((JsonPrimitive)c.get(0)).getAsInt());
}
final int caret = Utils.getOffset((LineDocument) doc, params.getPosition());
List<CompletionItem> items = new ArrayList<>();
Completion.Context context = params.getContext() != null
? new Completion.Context(Completion.TriggerKind.valueOf(params.getContext().getTriggerKind().name()),
params.getContext().getTriggerCharacter() == null || params.getContext().getTriggerCharacter().isEmpty() ? null : params.getContext().getTriggerCharacter().charAt(0))
: null;
Preferences prefs = CodeStylePreferences.get(doc, "text/x-java").getPreferences();
String point = prefs.get("classMemberInsertionPoint", null);
try {
prefs.put("classMemberInsertionPoint", CodeStyle.InsertionPoint.CARET_LOCATION.name());
boolean isComplete = Completion.collect(doc, caret, context, completion -> {
CompletionItem item = new CompletionItem(completion.getLabel());
if (completion.getKind() != null) {
item.setKind(CompletionItemKind.valueOf(completion.getKind().name()));
}
if (completion.getTags() != null) {
item.setTags(completion.getTags().stream().map(tag -> CompletionItemTag.valueOf(tag.name())).collect(Collectors.toList()));
}
if (completion.getDetail() != null && completion.getDetail().isDone()) {
item.setDetail(completion.getDetail().getNow(null));
}
if (completion.getDocumentation() != null && completion.getDocumentation().isDone()) {
String documentation = completion.getDocumentation().getNow(null);
if (documentation != null) {
MarkupContent markup = new MarkupContent();
markup.setKind("markdown");
markup.setValue(html2MD(documentation));
item.setDocumentation(markup);
}
}
if (completion.isPreselect()) {
item.setPreselect(true);
}
item.setSortText(completion.getSortText());
item.setFilterText(completion.getFilterText());
item.setInsertText(completion.getInsertText());
if (completion.getInsertTextFormat() != null) {
item.setInsertTextFormat(InsertTextFormat.valueOf(completion.getInsertTextFormat().name()));
}
org.netbeans.api.lsp.TextEdit edit = completion.getTextEdit();
if (edit != null) {
item.setTextEdit(Either.forLeft(new TextEdit(new Range(Utils.createPosition(file, edit.getStartOffset()), Utils.createPosition(file, edit.getEndOffset())), edit.getNewText())));
}
if (completion.getAdditionalTextEdits() != null && completion.getAdditionalTextEdits().isDone()) {
List<org.netbeans.api.lsp.TextEdit> additionalTextEdits = completion.getAdditionalTextEdits().getNow(null);
if (additionalTextEdits != null && !additionalTextEdits.isEmpty()) {
item.setAdditionalTextEdits(additionalTextEdits.stream().map(ed -> {
return new TextEdit(new Range(Utils.createPosition(file, ed.getStartOffset()), Utils.createPosition(file, ed.getEndOffset())), ed.getNewText());
}).collect(Collectors.toList()));
}
}
if (completion.getCommitCharacters() != null) {
item.setCommitCharacters(completion.getCommitCharacters().stream().map(ch -> ch.toString()).collect(Collectors.toList()));
}
lastCompletions.add(completion);
item.setData(new CompletionData(uri, index.getAndIncrement()));
items.add(item);
});
if (!isComplete) {
completionList.setIsIncomplete(true);
}
} finally {
if (point != null) {
prefs.put("classMemberInsertionPoint", point);
} else {
prefs.remove("classMemberInsertionPoint");
}
}
completionList.setItems(items);
return Either.forRight(completionList);
});
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
public static final class CompletionData {
public String uri;
public int index;
public CompletionData() {
}
public CompletionData(String uri, int index) {
this.uri = uri;
this.index = index;
}
@Override
public String toString() {
return "CompletionData{" + "uri=" + uri + ", index=" + index + '}';
}
}
@Override
public void connect(LanguageClient client) {
this.client = (NbCodeLanguageClient)client;
}
private int[] coloring2TokenType;
private int[] coloring2TokenModifier;
{
coloring2TokenType = new int[ColoringAttributes.values().length];
Arrays.fill(coloring2TokenType, -1);
coloring2TokenModifier = new int[ColoringAttributes.values().length];
Arrays.fill(coloring2TokenModifier, -1);
}
public void init(ClientCapabilities clientCapabilities, ServerCapabilities severCapabilities) {
SemanticTokensCapabilities semanticTokens = clientCapabilities != null && clientCapabilities.getTextDocument() != null ? clientCapabilities.getTextDocument().getSemanticTokens() : null;
if (semanticTokens != null) {
SemanticTokensWithRegistrationOptions cap = new SemanticTokensWithRegistrationOptions();
cap.setFull(new SemanticTokensServerFull(false));
Set<String> knownTokenTypes = semanticTokens.getTokenTypes() != null ? new HashSet<>(semanticTokens.getTokenTypes()) : Collections.emptySet();
Map<String, Integer> tokenLegend = new LinkedHashMap<>();
for (Entry<ColoringAttributes, List<String>> e : COLORING_TO_TOKEN_TYPE_CANDIDATES.entrySet()) {
for (String candidate : e.getValue()) {
if (knownTokenTypes.contains(candidate)) {
coloring2TokenType[e.getKey().ordinal()] = tokenLegend.computeIfAbsent(candidate, c -> tokenLegend.size());
break;
}
}
}
Set<String> knownTokenModifiers = semanticTokens.getTokenModifiers() != null ? new HashSet<>(semanticTokens.getTokenModifiers()) : Collections.emptySet();
Map<String, Integer> modifiersLegend = new LinkedHashMap<>();
for (Entry<ColoringAttributes, List<String>> e : COLORING_TO_TOKEN_MODIFIERS_CANDIDATES.entrySet()) {
for (String candidate : e.getValue()) {
if (knownTokenModifiers.contains(candidate)) {
coloring2TokenModifier[e.getKey().ordinal()] = modifiersLegend.computeIfAbsent(candidate, c -> 1 << modifiersLegend.size());
break;
}
}
}
SemanticTokensLegend legend = new SemanticTokensLegend(new ArrayList<>(tokenLegend.keySet()), new ArrayList<>(modifiersLegend.keySet()));
cap.setLegend(legend);
severCapabilities.setSemanticTokensProvider(cap);
}
}
@Override
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem ci) {
JsonObject rawData = (JsonObject) ci.getData();
if (rawData != null) {
CompletionData data = new Gson().fromJson(rawData, CompletionData.class);
Completion completion = lastCompletions.get(data.index);
if (completion != null) {
FileObject file = fromURI(data.uri);
if (file != null) {
CompletableFuture<CompletionItem> result = new CompletableFuture<>();
WORKER.post(() -> {
if (completion.getDetail() != null) {
try {
String detail = completion.getDetail().get();
if (detail != null) {
ci.setDetail(detail);
}
} catch (Exception ex) {
}
}
if (completion.getAdditionalTextEdits() != null) {
try {
List<org.netbeans.api.lsp.TextEdit> additionalTextEdits = completion.getAdditionalTextEdits().get();
if (additionalTextEdits != null && !additionalTextEdits.isEmpty()) {
ci.setAdditionalTextEdits(additionalTextEdits.stream().map(ed -> {
return new TextEdit(new Range(Utils.createPosition(file, ed.getStartOffset()), Utils.createPosition(file, ed.getEndOffset())), ed.getNewText());
}).collect(Collectors.toList()));
}
} catch (Exception ex) {
}
}
if (completion.getDocumentation() != null) {
try {
int timeout = javadocTimeout.get();
String documentation = timeout < 0
? completion.getDocumentation().get()
: timeout == 0 ? completion.getDocumentation().getNow(null)
: completion.getDocumentation().get(timeout, TimeUnit.MILLISECONDS);
if (documentation != null) {
MarkupContent markup = new MarkupContent();
markup.setKind("markdown");
markup.setValue(html2MD(documentation));
ci.setDocumentation(markup);
}
} catch (Exception ex) {
}
}
result.complete(ci);
});
return result;
}
}
}
return CompletableFuture.completedFuture(ci);
}
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) {
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(null);
}
String uri = params.getTextDocument().getUri();
FileObject file = fromURI(uri);
Document doc = server.getOpenedDocuments().getDocument(uri);
if (file == null || !(doc instanceof LineDocument)) {
return CompletableFuture.completedFuture(null);
}
return org.netbeans.api.lsp.Hover.getContent(doc, Utils.getOffset((LineDocument) doc, params.getPosition())).thenApply(content -> {
if (content != null) {
MarkupContent markup = new MarkupContent();
markup.setKind("markdown");
markup.setValue(html2MD(content));
return new Hover(markup);
}
return null;
});
}
@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) {
try {
String uri = params.getTextDocument().getUri();
Document doc = server.getOpenedDocuments().getDocument(uri);
if (doc instanceof LineDocument) {
FileObject file = Utils.fromUri(uri);
if (file != null) {
int offset = Utils.getOffset((LineDocument) doc, params.getPosition());
return HyperlinkLocation.resolve(doc, offset).thenApply(locs -> {
return Either.forLeft(locs.stream().map(location -> {
FileObject fo = location.getFileObject();
return new Location(Utils.toUri(fo), new Range(Utils.createPosition(fo, location.getStartOffset()), Utils.createPosition(fo, location.getEndOffset())));
}).collect(Collectors.toList()));
});
}
}
} catch (MalformedURLException ex) {
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> typeDefinition(TypeDefinitionParams params) {
try {
String uri = params.getTextDocument().getUri();
Document doc = server.getOpenedDocuments().getDocument(uri);
if (doc instanceof LineDocument) {
FileObject file = Utils.fromUri(uri);
if (file != null) {
int offset = Utils.getOffset((LineDocument) doc, params.getPosition());
return HyperlinkLocation.resolveTypeDefinition(doc, offset).thenApply(locs -> {
return Either.forLeft(locs.stream().map(location -> {
FileObject fo = location.getFileObject();
return new Location(Utils.toUri(fo), new Range(Utils.createPosition(fo, location.getStartOffset()), Utils.createPosition(fo, location.getEndOffset())));
}).collect(Collectors.toList()));
});
}
}
} catch (MalformedURLException ex) {
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> implementation(ImplementationParams params) {
return usages(params.getTextDocument().getUri(), params.getPosition(), true, false).thenApply(locations -> Either.forLeft(locations));
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
return usages(params.getTextDocument().getUri(), params.getPosition(), false, params.getContext().isIncludeDeclaration());
}
private CompletableFuture<List<? extends Location>> usages(String uri, Position position, boolean implementations, boolean includeDeclaration) {
final Project[] projects = server.openedProjects().getNow(null);
// shortcut: if the projects are not yet initialized, return empty:
if (projects == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
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 = getJavaSource(uri);
if (js == null) {
result.complete(new ArrayList<>());
return;
}
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);
if (doc instanceof LineDocument) {
TreePath path = cc.getTreeUtilities().pathFor(Utils.getOffset((LineDocument) doc, position));
query[0] = new WhereUsedQuery(Lookups.singleton(TreePathHandle.create(path, cc)));
if (implementations) {
query[0].putValue(WhereUsedQueryConstants.FIND_SUBCLASSES, true);
query[0].putValue(WhereUsedQueryConstants.FIND_OVERRIDING_METHODS, true);
query[0].putValue(WhereUsedQuery.FIND_REFERENCES, false);
} else if (includeDeclaration) {
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);
}
}
}
}
}
}, true);
if (cancel.get()) return ;
List<FileObject> sourceRoots = new ArrayList<>();
for (Project project : projects) {
Sources sources = ProjectUtils.getSources(project);
for (SourceGroup sourceGroup : sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) {
sourceRoots.add(sourceGroup.getRootFolder());
}
}
if (!sourceRoots.isEmpty()) {
query[0].getContext().add(org.netbeans.modules.refactoring.api.Scope.create(sourceRoots, null, null, true));
}
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 ;
FileObject parentFile = re.getParentFile();
if (parentFile.isData()) {
locations.add(new Location(Utils.toUri(parentFile), 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 bounds != null
? new Range(new Position(bounds.getBegin().getLine(), bounds.getBegin().getColumn()),
new Position(bounds.getEnd().getLine(), bounds.getEnd().getColumn()))
: new Range(new Position(0, 0), new Position(0, 0));
}
@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 = getJavaSource(params.getTextDocument().getUri());
List<DocumentHighlight> result = new ArrayList<>();
if (js == null) {
return CompletableFuture.completedFuture(result);
}
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
if (doc instanceof LineDocument) {
int offset = Utils.getOffset((LineDocument) 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) {
return server.openedProjects().thenCompose(projects -> {
JavaSource js = getJavaSource(params.getTextDocument().getUri());
if (js == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>();
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Trees trees = cc.getTrees();
CompilationUnitTree cu = cc.getCompilationUnit();
if (cu.getPackage() != null) {
TreePath tp = trees.getPath(cu, cu.getPackage());
Element el = trees.getElement(tp);
if (el != null && el.getKind() == ElementKind.PACKAGE) {
String name = Utils.label(cc, el, true);
SymbolKind kind = Utils.elementKind2SymbolKind(el.getKind());
Range range = Utils.treeRange(cc, tp.getLeaf());
result.add(Either.forRight(new DocumentSymbol(name, kind, range, range)));
}
}
for (Element tel : cc.getTopLevelElements()) {
DocumentSymbol ds = element2DocumentSymbol(cc, tel);
if (ds != null)
result.add(Either.forRight(ds));
}
}, true);
} catch (IOException ex) {
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
return CompletableFuture.completedFuture(result);
});
}
private static DocumentSymbol element2DocumentSymbol(CompilationInfo info, Element el) {
TreePath path = info.getTrees().getPath(el);
if (path == null)
return null;
TreeUtilities tu = info.getTreeUtilities();
if (tu.isSynthetic(path))
return null;
Range range = Utils.treeRange(info, path.getLeaf());
if (range == null)
return null;
Range selection = Utils.selectionRange(info, path.getLeaf());
List<DocumentSymbol> children = new ArrayList<>();
for (Element c : el.getEnclosedElements()) {
DocumentSymbol ds = element2DocumentSymbol(info, c);
if (ds != null) {
children.add(ds);
}
}
if (path.getLeaf().getKind() == Kind.METHOD || path.getLeaf().getKind() == Kind.VARIABLE) {
children.addAll(getAnonymousInnerClasses(info, path));
}
String name = Utils.label(info, el, false);
String detail = Utils.detail(info, el, false);
return new DocumentSymbol(name, Utils.elementKind2SymbolKind(el.getKind()), range, selection != null ? selection : range, detail, children);
}
private static List<DocumentSymbol> getAnonymousInnerClasses(CompilationInfo info, TreePath path) {
List<DocumentSymbol> inner = new ArrayList<>();
new TreePathScanner<Void, Void>() {
@Override
public Void visitNewClass(NewClassTree node, Void p) {
if (node.getClassBody() != null) {
Range range = Utils.treeRange(info, node);
if (range != null) {
Range selectionRange = Utils.treeRange(info, node.getIdentifier());
Element e = info.getTrees().getElement(new TreePath(getCurrentPath(), node.getClassBody()));
if (e != null) {
Element te = info.getTrees().getElement(new TreePath(getCurrentPath(), node.getIdentifier()));
if (te != null) {
String name = "new " + te.getSimpleName() + "() {...}";
List<DocumentSymbol> children = new ArrayList<>();
for (Element c : e.getEnclosedElements()) {
DocumentSymbol ds = element2DocumentSymbol(info, c);
if (ds != null) {
children.add(ds);
}
}
inner.add(new DocumentSymbol(name, Utils.elementKind2SymbolKind(e.getKind()), range, selectionRange, null, children));
}
}
}
}
return null;
}
}.scan(path, null);
return inner;
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
Document doc = server.getOpenedDocuments().getDocument(params.getTextDocument().getUri());
if (!(doc instanceof LineDocument)) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
List<Either<Command, CodeAction>> result = new ArrayList<>();
Range range = params.getRange();
int startOffset = Utils.getOffset((LineDocument) doc, range.getStart());
int endOffset = Utils.getOffset((LineDocument) doc, range.getEnd());
if (startOffset == endOffset) {
int lineStartOffset = LineDocumentUtils.getLineStart((LineDocument) doc, startOffset);
int lineEndOffset;
try {
lineEndOffset = LineDocumentUtils.getLineEnd((LineDocument) doc, endOffset);
} catch (BadLocationException ex) {
lineEndOffset = endOffset;
}
ArrayList<Diagnostic> diagnostics = new ArrayList<>(params.getContext().getDiagnostics());
if (diagnostics.isEmpty()) {
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");
if (id2Errors != null) {
for (Entry<String, org.netbeans.api.lsp.Diagnostic> entry : id2Errors.entrySet()) {
org.netbeans.api.lsp.Diagnostic err = entry.getValue();
if (err.getDescription() == null || err.getDescription().isEmpty()) {
continue;
}
if (err.getSeverity() == org.netbeans.api.lsp.Diagnostic.Severity.Error) {
if (err.getEndPosition().getOffset() < startOffset || err.getStartPosition().getOffset() > endOffset) {
continue;
}
} else {
if (err.getEndPosition().getOffset() < lineStartOffset || err.getStartPosition().getOffset() > lineEndOffset) {
continue;
}
}
Optional<Diagnostic> diag = diagnostics.stream().filter(d -> entry.getKey().equals(d.getCode().getLeft())).findFirst();
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());
if (diag.isPresent()) {
action.setDiagnostics(Collections.singletonList(diag.get()));
}
action.setKind(kind(err.getSeverity()));
if (inputAction.getCommand() != null) {
action.setCommand(new Command(inputAction.getCommand().getTitle(), inputAction.getCommand().getCommand(), Arrays.asList(params.getTextDocument().getUri())));
}
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));
}
}
}
}
}
final CompletableFuture<List<Either<Command, CodeAction>>> resultFuture = new CompletableFuture<>();
Source source = Source.create(doc);
BACKGROUND_TASKS.post(() -> {
try {
ParserManager.parse(Collections.singleton(source), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
//code generators:
for (CodeActionsProvider codeGenerator : Lookup.getDefault().lookupAll(CodeActionsProvider.class)) {
try {
for (CodeAction codeAction : codeGenerator.getCodeActions(resultIterator, params)) {
result.add(Either.forRight(codeAction));
}
} catch (Exception ex) {
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
}
//introduce hints:
CompilationController cc = CompilationController.get(resultIterator.getParserResult());
if (cc != null) {
cc.toPhase(JavaSource.Phase.RESOLVED);
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)));
}
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) {
}
}
}
}
}
}
}
});
} catch (ParseException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
} finally {
resultFuture.complete(result);
}
});
return resultFuture;
}
@Override
public CompletableFuture<CodeAction> resolveCodeAction(CodeAction unresolved) {
JsonObject data = (JsonObject) unresolved.getData();
if (data != null) {
String providerClass = data.getAsJsonPrimitive(CodeActionsProvider.CODE_ACTIONS_PROVIDER_CLASS).getAsString();
for (CodeActionsProvider codeGenerator : Lookup.getDefault().lookupAll(CodeActionsProvider.class)) {
try {
if (codeGenerator.getClass().getName().equals(providerClass)) {
return codeGenerator.resolve(client, unresolved, data.get(CodeActionsProvider.DATA));
}
} catch (Exception ex) {
}
}
}
return CompletableFuture.completedFuture(unresolved);
}
@NbBundle.Messages({"# {0} - method name", "LBL_Run=Run {0}",
"# {0} - method name", "LBL_Debug=Debug {0}",
"# {0} - method name", "# {1} - configuration name", "LBL_RunWith=Run {0} with {1}",
"# {0} - method name", "# {1} - configuration name", "LBL_DebugWith=Debug {0} with {1}"})
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
String uri = params.getTextDocument().getUri();
Source source = getSource(uri);
if (source == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
CompletableFuture<List<? extends CodeLens>> result = new CompletableFuture<>();
try {
ParserManager.parseWhenScanFinished(Collections.singleton(source), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
Parser.Result parserResult = resultIterator.getParserResult();
//look for main methods:
List<CodeLens> lens = new ArrayList<>();
CompilationController cc = CompilationController.get(parserResult);
if (cc != null) {
cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
AtomicReference<List<Pair<String, String>>> projectConfigurations = new AtomicReference<>();
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());
String method = el.getSimpleName().toString();
lens.add(new CodeLens(range,
new Command(Bundle.LBL_Run(method), COMMAND_RUN_SINGLE, arguments),
null));
lens.add(new CodeLens(range,
new Command(Bundle.LBL_Debug(method), COMMAND_DEBUG_SINGLE, arguments),
null));
// Run and Debug configurations:
List<Pair<String, String>> configs = projectConfigurations.accumulateAndGet(null, (l, nul) -> l == null ? getProjectConfigurations(source) : l);
for (Pair<String, String> config : configs) {
String runConfig = config.first();
if (runConfig != null) {
lens.add(new CodeLens(range,
new Command(Bundle.LBL_RunWith(method, runConfig), COMMAND_RUN_SINGLE, Arrays.asList(params.getTextDocument().getUri(), null, runConfig)),
null));
}
String debugConfig = config.second();
if (debugConfig != null) {
lens.add(new CodeLens(range,
new Command(Bundle.LBL_DebugWith(method, debugConfig), COMMAND_DEBUG_SINGLE, Arrays.asList(params.getTextDocument().getUri(), null, debugConfig)),
null));
}
}
}
return null;
}
}.scan(cc.getCompilationUnit(), null);
}
result.complete(lens);
}
});
} catch (ParseException ex) {
result.completeExceptionally(ex);
}
return result;
}
private List<Pair<String, String>> getProjectConfigurations(Source source) {
FileObject fo = source.getFileObject();
Project p = FileOwnerQuery.getOwner(fo);
if (p != null) {
ProjectConfigurationProvider<ProjectConfiguration> configProvider = p.getLookup().lookup(ProjectConfigurationProvider.class);
ActionProvider actionProvider = p.getLookup().lookup(ActionProvider.class);
List<Pair<String, String>> configDispNames = new ArrayList<>();
if (configProvider != null && actionProvider != null) {
boolean skippedFirst = false;
for (ProjectConfiguration configuration : configProvider.getConfigurations()) {
if (skippedFirst) {
String runConfig = null;
String debugConfig = null;
Lookup configLookup = Lookups.fixed(fo, configuration);
if (isConfigurationAction(configProvider, actionProvider, configLookup, ActionProvider.COMMAND_RUN_SINGLE)) {
runConfig = configuration.getDisplayName();
}
if (isConfigurationAction(configProvider, actionProvider, configLookup, ActionProvider.COMMAND_DEBUG_SINGLE)) {
debugConfig = configuration.getDisplayName();
}
if (runConfig != null || debugConfig != null) {
configDispNames.add(Pair.of(runConfig, debugConfig));
}
} else {
// Ignore the default config
skippedFirst = true;
}
}
}
return configDispNames;
}
return Collections.emptyList();
}
private static boolean isConfigurationAction(ProjectConfigurationProvider<ProjectConfiguration> configProvider, ActionProvider actionProvider, Lookup configLookup, String action) {
return configProvider.configurationsAffectAction(action) && actionProvider.isActionEnabled(action, configLookup);
}
@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) {
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(null);
}
JavaSource source = getJavaSource(params.getTextDocument().getUri());
if (source == null) {
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Either<Range, PrepareRenameResult>> result = new CompletableFuture<>();
try {
source.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
if (!(doc instanceof LineDocument)) {
result.complete(null);
}
int pos = Utils.getOffset((LineDocument) 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) {
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(new WorkspaceEdit());
}
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 = getJavaSource(params.getTextDocument().getUri());
if (js == null) {
result.completeExceptionally(new FileNotFoundException(params.getTextDocument().getUri()));
return;
}
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);
if (doc instanceof LineDocument) {
TreePath path = cc.getTreeUtilities().pathFor(Utils.getOffset((LineDocument) 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(Utils.toUri(modified), /*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 = getJavaSource(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 = fromURI(params.getTextDocument().getUri(), true);
if (file == null) {
return;
}
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()));
}
server.getOpenedDocuments().notifyOpened(params.getTextDocument().getUri(), doc);
// attempt to open the directly owning project, delay diagnostics after project open:
server.asyncOpenFileOwner(file).thenRun(() ->
runDiagnosticTasks(params.getTextDocument().getUri())
);
} catch (IOException ex) {
throw new IllegalStateException(ex);
} finally {
reportNotificationDone("didOpen", params);
}
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
Document doc = server.getOpenedDocuments().getDocument(uri);
if (doc != null) {
NbDocument.runAtomic((StyledDocument) doc, () -> {
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
try {
int start = Utils.getOffset((LineDocument) doc, change.getRange().getStart());
int end = Utils.getOffset((LineDocument) doc, change.getRange().getEnd());
doc.remove(start, end - start);
doc.insertString(start, change.getText(), null);
} catch (BadLocationException ex) {
throw new IllegalStateException(ex);
}
}
});
}
runDiagnosticTasks(params.getTextDocument().getUri());
reportNotificationDone("didChange", params);
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
try {
String uri = params.getTextDocument().getUri();
// the order here is important ! As the file may cease to exist, it's
// important that the doucment is already gone form the client.
server.getOpenedDocuments().notifyClosed(uri);
FileObject file = fromURI(uri, true);
if (file == null) {
return;
}
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
ec.close();
} finally {
reportNotificationDone("didClose", params);
}
}
@Override
public CompletableFuture<List<TextEdit>> willSaveWaitUntil(WillSaveTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
JavaSource js = getJavaSource(uri);
if (js == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
ConfigurationItem conf = new ConfigurationItem();
conf.setScopeUri(uri);
conf.setSection(NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS);
return client.configuration(new ConfigurationParams(Collections.singletonList(conf))).thenApply(c -> {
if (c != null && !c.isEmpty() && ((JsonPrimitive) c.get(0)).getAsBoolean()) {
try {
List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
wc.toPhase(JavaSource.Phase.RESOLVED);
if (wc.getDiagnostics().isEmpty()) {
OrganizeImports.doOrganizeImports(wc, null, false);
}
});
return edits;
} catch (IOException ex) {}
}
return Collections.emptyList();
});
}
@Override
public void didSave(DidSaveTextDocumentParams arg0) {
//TODO: nothing for now?
}
CompletableFuture<List<? extends Location>> superImplementations(String uri, Position position) {
JavaSource js = getJavaSource(uri);
List<GoToTarget> targets = new ArrayList<>();
LineMap[] thisFileLineMap = new LineMap[1];
try {
if (js != null) {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
if (doc instanceof LineDocument) {
int offset = Utils.getOffset((LineDocument) doc, position);
TreeUtilities treeUtilities = cc.getTreeUtilities();
TreePath path = treeUtilities.getPathElementOfKind(EnumSet.of(Kind.CLASS, Kind.INTERFACE, Kind.ENUM, Kind.ANNOTATION_TYPE, Kind.METHOD), treeUtilities.pathFor(offset));
if (path != null) {
Trees trees = cc.getTrees();
Element resolved = trees.getElement(path);
if (resolved != null) {
if (resolved.getKind() == ElementKind.METHOD) {
Map<ElementHandle<? extends Element>, List<ElementDescription>> overriding = new ComputeOverriding(new AtomicBoolean()).process(cc);
List<ElementDescription> eds = overriding.get(ElementHandle.create(resolved));
if (eds != null) {
for (ElementDescription ed : eds) {
Element el = ed.getHandle().resolve(cc);
TreePath tp = trees.getPath(el);
long startPos = tp != null && cc.getCompilationUnit() == tp.getCompilationUnit() ? trees.getSourcePositions().getStartPosition(cc.getCompilationUnit(), tp.getLeaf()) : -1;
if (startPos >= 0) {
long endPos = trees.getSourcePositions().getEndPosition(cc.getCompilationUnit(), tp.getLeaf());
targets.add(new GoToTarget(cc.getSnapshot().getOriginalOffset((int) startPos),
cc.getSnapshot().getOriginalOffset((int) endPos), GoToSupport.getNameSpan(tp.getLeaf(), treeUtilities),
null, null, null, ed.getDisplayName(), true));
} else {
TypeElement te = el != null ? cc.getElementUtilities().outermostTypeElement(el) : null;
targets.add(new GoToTarget(-1, -1, null, ed.getOriginalCPInfo(), ed.getHandle(), getResourceName(te, ed.getHandle()), ed.getDisplayName(), true));
}
}
}
} else if (resolved.getKind().isClass() || resolved.getKind().isInterface()) {
List<TypeMirror> superTypes = new ArrayList<>();
superTypes.add(((TypeElement)resolved).getSuperclass());
superTypes.addAll(((TypeElement)resolved).getInterfaces());
for (TypeMirror superType : superTypes) {
if (superType.getKind() == TypeKind.DECLARED) {
Element el = ((DeclaredType) superType).asElement();
TreePath tp = trees.getPath(el);
long startPos = tp != null && cc.getCompilationUnit() == tp.getCompilationUnit() ? trees.getSourcePositions().getStartPosition(cc.getCompilationUnit(), tp.getLeaf()) : -1;
if (startPos >= 0) {
long endPos = trees.getSourcePositions().getEndPosition(cc.getCompilationUnit(), tp.getLeaf());
targets.add(new GoToTarget(cc.getSnapshot().getOriginalOffset((int) startPos),
cc.getSnapshot().getOriginalOffset((int) endPos), GoToSupport.getNameSpan(tp.getLeaf(), treeUtilities),
null, null, null, cc.getElementUtilities().getElementName(el, false).toString(), true));
} else {
TypeElement te = el != null ? cc.getElementUtilities().outermostTypeElement(el) : null;
targets.add(new GoToTarget(-1, -1, null, cc.getClasspathInfo(), ElementHandle.create(el), getResourceName(te, null),
cc.getElementUtilities().getElementName(el, false).toString(), true));
}
}
}
}
thisFileLineMap[0] = cc.getCompilationUnit().getLineMap();
}
}
}
}, true);
}
} catch (IOException ex) {
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
CompletableFuture<Location>[] futures = targets.stream().map(target -> gotoTarget2Location(uri, target, thisFileLineMap[0])).toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(futures).thenApply(value -> {
ArrayList<Location> locations = new ArrayList<>(futures.length);
for (CompletableFuture<Location> future : futures) {
Location location = future.getNow(null);
if (location != null) {
locations.add(location);
}
}
return locations;
});
}
private CompletableFuture<Location> gotoTarget2Location(String uri, GoToTarget target, LineMap lineMap) {
Location location = null;
if (target != null && target.success) {
if (target.offsetToOpen < 0) {
final CompletableFuture<ElementOpen.Location> future = ElementOpen.getLocation(target.cpInfo, target.elementToOpen, target.resourceName);
return future.thenApply(loc -> {
if (loc != null) {
FileObject fo = loc.getFileObject();
return new Location(Utils.toUri(fo), new Range(Utils.createPosition(fo, loc.getStartOffset()), Utils.createPosition(fo, loc.getEndOffset())));
}
return null;
});
} else {
int start = target.nameSpan != null ? target.nameSpan[0] : target.offsetToOpen;
int end = target.nameSpan != null ? target.nameSpan[1] : target.endPos;
location = new Location(uri, new Range(Utils.createPosition(lineMap, start), Utils.createPosition(lineMap, end)));
}
}
return CompletableFuture.completedFuture(location);
}
private void runDiagnosticTasks(String uri) {
if (server.openedProjects().getNow(null) == null) {
return;
}
diagnosticTasks.computeIfAbsent(uri, u -> {
return BACKGROUND_TASKS.create(() -> {
Document originalDoc = server.getOpenedDocuments().getDocument(uri);
long originalVersion = documentVersion(originalDoc);
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, -1, ErrorProvider.Kind.HINTS, originalVersion);
Document doc = server.getOpenedDocuments().getDocument(uri);
if (documentVersion(doc) == originalVersion) {
publishDiagnostics(uri, hintDiags);
}
}).schedule(DELAY);
}
});
}).schedule(DELAY);
}
private static final int DELAY = 500;
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) {
// the file does not exist.
return result;
}
try {
String keyPrefix = key(errorKind);
EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
Document doc = ec.openDocument();
Map<String, org.netbeans.api.lsp.Diagnostic> id2Errors = new HashMap<>();
ErrorProvider errorProvider = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc))
.lookup(ErrorProvider.class);
List<? extends org.netbeans.api.lsp.Diagnostic> errors;
if (errorProvider != null) {
ErrorProvider.Context context = new ErrorProvider.Context(file, offset, errorKind);
class CancelListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
checkCancel();
}
@Override
public void removeUpdate(DocumentEvent e) {
checkCancel();
}
private void checkCancel() {
if (documentVersion(doc) != originalVersion) {
context.cancel();
}
}
@Override
public void changedUpdate(DocumentEvent e) {}
}
CancelListener l = new CancelListener();
try {
doc.addDocumentListener(l);
l.checkCancel();
errors = errorProvider.computeErrors(context);
} finally {
doc.removeDocumentListener(l);
}
} else {
errors = null;
}
if (errors == null) {
errors = Collections.emptyList();
}
if (documentVersion(doc) != originalVersion) {
return result;
}
for (org.netbeans.api.lsp.Diagnostic err : errors) {
String id = err.getCode();
id2Errors.put(id, err);
}
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);
if (prevErrors != null) {
mergedId2Errors.putAll(prevErrors);
}
}
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())),
err.getDescription());
switch (err.getSeverity()) {
case Error: diag.setSeverity(DiagnosticSeverity.Error); break;
case Warning: diag.setSeverity(DiagnosticSeverity.Warning); break;
case Hint: diag.setSeverity(DiagnosticSeverity.Hint); break;
case Information: diag.setSeverity(DiagnosticSeverity.Information); break;
default: throw new IllegalStateException("Unknown severity: " + err.getSeverity());
}
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);
}
return result;
}
private String key(ErrorProvider.Kind errorKind) {
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);
}
/**
* Converts URI to a FileObject. Can try harder using refresh on the filesystem, useful
* for didOpen or didClose notification which may have been fired because the client changed files on the dist.
* @param uri the uri
* @param tryHard if true, will refresh the filesystems first.
* @return FileObject or null if missing.
*/
private FileObject fromURI(String uri, boolean tryHard) {
try {
FileObject file = Utils.fromUri(uri);
if (tryHard) {
if (file != null ) {
file.refresh(true);
} else {
URI parentU = BaseUtilities.normalizeURI(URI.create(uri).resolve(".."));
FileObject parentF = Utils.fromUri(parentU.toString());
if (parentF != null) {
parentF.refresh(true);
file = Utils.fromUri(uri);
}
}
}
if (file != null && file.isValid()) {
return file;
}
missingFileDiscovered(uri);
} catch (MalformedURLException ex) {
if (!uri.startsWith("untitled:")) {
LOG.log(Level.WARNING, "Invalid file URL: " + uri, ex);
}
}
return null;
}
/**
* Handles file disappearance. The method should be called whenever a file that ought to exist
* is not found on the disk. The method should fire any necessary updates to the client to synchronize the
* state, i.e. remove obsolete diagnostics for the file.
* @param uri file URI
*/
private void missingFileDiscovered(String uri) {
if (server.getOpenedDocuments().getDocument(uri) != null) {
// do not report anything, the document is still opened in the editor
return;
}
Instant last = knownFiles.remove(uri);
if (last == null) {
return;
}
client.publishDiagnostics(new PublishDiagnosticsParams(uri, new ArrayList<>()));
}
/**
* Publishes diagnostics to the client. Remembers the file, so that if the file vanishes (i.e. event is received, or
* it's reported as missing during some processing), the client will get an empty diagnostics message for this file to
* clear out its problem view.
* @param uri file URI
* @param mergedDiags the diagnostics
*/
private void publishDiagnostics(String uri, List<Diagnostic> mergedDiags) {
knownFiles.put(uri, Instant.now());
client.publishDiagnostics(new PublishDiagnosticsParams(uri, mergedDiags));
}
private static final String[] ERROR_KEYS = {"errors", "hints"};
private interface ProduceErrors {
public List<ErrorDescription> computeErrors(CompilationInfo info, Document doc) throws IOException;
}
@CheckForNull
public JavaSource getJavaSource(String fileUri) {
Document doc = server.getOpenedDocuments().getDocument(fileUri);
if (doc == null) {
FileObject file = fromURI(fileUri);
if (file == null) {
return null;
}
return JavaSource.forFileObject(file);
} else {
return JavaSource.forDocument(doc);
}
}
@CheckForNull
public Source getSource(String fileUri) {
Document doc = server.getOpenedDocuments().getDocument(fileUri);
if (doc == null) {
FileObject file = fromURI(fileUri);
if (file == null) {
return null;
}
return Source.create(file);
} else {
return Source.create(doc);
}
}
public static List<TextEdit> modify2TextEdits(JavaSource js, Task<WorkingCopy> task) throws IOException {//TODO: is this still used?
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 String getResourceName(TypeElement te, ElementHandle<?> handle) {
String qualifiedName = null;
if (te != null) {
qualifiedName = te.getQualifiedName().toString();
} else if (handle != null && (handle.getKind().isClass() || handle.getKind().isInterface())) {
qualifiedName = handle.getQualifiedName();
}
return qualifiedName != null ? qualifiedName.replace('.', '/') + ".class" : null;
}
private static long documentVersion(Document doc) {
Object ver = doc != null ? doc.getProperty("version") : null;
return ver instanceof Number ? ((Number) ver).longValue() : -1;
}
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;
private static final Map<ColoringAttributes, List<String>> COLORING_TO_TOKEN_TYPE_CANDIDATES = new HashMap<ColoringAttributes, List<String>>() {{
put(ColoringAttributes.FIELD, Arrays.asList("member"));
put(ColoringAttributes.RECORD_COMPONENT, Arrays.asList("member"));
put(ColoringAttributes.LOCAL_VARIABLE, Arrays.asList("variable"));
put(ColoringAttributes.PARAMETER, Arrays.asList("parameter"));
put(ColoringAttributes.METHOD, Arrays.asList("method", "function"));
put(ColoringAttributes.CONSTRUCTOR, Arrays.asList("method", "function"));
put(ColoringAttributes.CLASS, Arrays.asList("class"));
put(ColoringAttributes.RECORD, Arrays.asList("class"));
put(ColoringAttributes.INTERFACE, Arrays.asList("interface"));
put(ColoringAttributes.ANNOTATION_TYPE, Arrays.asList("interface"));
put(ColoringAttributes.ENUM, Arrays.asList("enum"));
put(ColoringAttributes.TYPE_PARAMETER_DECLARATION, Arrays.asList("typeParameter"));
}};
private static final Map<ColoringAttributes, List<String>> COLORING_TO_TOKEN_MODIFIERS_CANDIDATES = new HashMap<ColoringAttributes, List<String>>() {{
put(ColoringAttributes.ABSTRACT, Arrays.asList("abstract"));
put(ColoringAttributes.DECLARATION, Arrays.asList("declaration"));
put(ColoringAttributes.DEPRECATED, Arrays.asList("deprecated"));
put(ColoringAttributes.STATIC, Arrays.asList("static"));
}};
@Override
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
JavaSource js = getJavaSource(params.getTextDocument().getUri());
List<Integer> result = new ArrayList<>();
if (js != null) {
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.RESOLVED);
Document doc = cc.getSnapshot().getSource().getDocument(true);
new SemanticHighlighterBase() {
@Override
protected boolean process(CompilationInfo info, Document doc) {
process(info, doc, new ErrorDescriptionSetter() {
@Override
public void setHighlights(Document doc, Collection<Pair<int[], ColoringAttributes.Coloring>> highlights, Map<int[], String> preText) {
//...nothing
}
@Override
public void setColorings(Document doc, Map<Token, ColoringAttributes.Coloring> colorings) {
int line = 0;
long column = 0;
int lastLine = 0;
long currentLineStart = 0;
long nextLineStart = info.getCompilationUnit().getLineMap().getStartPosition(line + 2);
Map<Token, ColoringAttributes.Coloring> ordered = new TreeMap<>((t1, t2) -> t1.offset(null) - t2.offset(null));
ordered.putAll(colorings);
for (Entry<Token, ColoringAttributes.Coloring> e : ordered.entrySet()) {
int currentOffset = e.getKey().offset(null);
while (nextLineStart < currentOffset) {
line++;
currentLineStart = nextLineStart;
nextLineStart = info.getCompilationUnit().getLineMap().getStartPosition(line + 2);
column = 0;
}
Optional<Integer> tokenType = e.getValue().stream().map(c -> coloring2TokenType[c.ordinal()]).collect(Collectors.maxBy((v1, v2) -> v1.intValue() - v2.intValue()));
int modifiers = 0;
for (ColoringAttributes c : e.getValue()) {
int mod = coloring2TokenModifier[c.ordinal()];
if (mod != (-1)) {
modifiers |= mod;
}
}
if (tokenType.isPresent()) {
result.add(line - lastLine);
result.add((int) (currentOffset - currentLineStart - column));
result.add(e.getKey().length());
result.add(tokenType.get());
result.add(modifiers);
lastLine = line;
column = currentOffset - currentLineStart;
}
}
}
});
return true;
}
}.process(cc, doc);
}, true);
} catch (IOException ex) {
//TODO: include stack trace:
client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
}
}
SemanticTokens tokens = new SemanticTokens(result);
return CompletableFuture.completedFuture(tokens);
}
}