blob: e40323a6ef6cb1b68fbe2e64b82972abdb84bc33 [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.lsp.client.bindings;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.SemanticTokens;
import org.eclipse.lsp4j.SemanticTokensLegend;
import org.eclipse.lsp4j.SemanticTokensParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.generator.JsonRpcData;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.editor.settings.AttributesUtilities;
import org.netbeans.api.editor.settings.FontColorSettings;
import org.netbeans.modules.lsp.client.LSPBindings;
import org.netbeans.modules.lsp.client.LSPBindings.BackgroundTask;
import org.netbeans.modules.lsp.client.Utils;
import org.netbeans.spi.editor.highlighting.HighlightsLayer;
import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
import org.netbeans.spi.editor.highlighting.ZOrder;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
/**
*
* @author lahvac
*/
public class SemanticHighlight implements BackgroundTask {
private final Document doc;
public SemanticHighlight(JTextComponent c) {
this.doc = c.getDocument();
}
@Override
public void run(LSPBindings bindings, FileObject file) {
try {
OffsetsBag target = new OffsetsBag(doc);
SemanticTokensParams params = new SemanticTokensParams(new TextDocumentIdentifier(Utils.toURI(file)));
SemanticTokens tokens = bindings.getTextDocumentService().semanticTokensFull(params).get();
List<Integer> data = tokens.getData();
int lastLine = 0;
int lastColumn = 0;
int offset = 0;
for (int i = 0; i < data.size(); i += 5) {
int deltaLine = data.get(i).intValue();
int deltaColumn = data.get(i + 1).intValue();
if (deltaLine == 0) {
lastColumn += deltaColumn;
offset += deltaColumn;
} else {
lastLine += deltaLine;
lastColumn = deltaColumn;
offset = Utils.getOffset(doc, new Position(lastLine, lastColumn));
}
if (data.get(i + 2).intValue() <= 0) {
continue; //XXX!
}
AttributeSet tokenHighlight = tokenHighlight(bindings, data.get(i + 3).intValue(), data.get(i + 4).intValue());
target.addHighlight(offset, offset + data.get(i + 2).intValue(), tokenHighlight);
}
getBag(doc).setHighlights(target);
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
}
private final Map<Integer, Map<Integer, AttributeSet>> tokenId2Highlight = new HashMap<>();
private AttributeSet tokenHighlight(LSPBindings bindings, int tokenId, int modifiers) {
return tokenId2Highlight.computeIfAbsent(tokenId, s -> new HashMap<>())
.computeIfAbsent(modifiers, mods -> {
SemanticTokensLegend legend = bindings.getInitResult().getCapabilities().getSemanticTokensProvider().getLegend();
List<String> tokenTypes = legend.getTokenTypes();
if (tokenId >= tokenTypes.size()) {
//invalid token id
return EMPTY;
}
String tokenName = tokenTypes.get(tokenId);
boolean isStatic = false;
boolean isDeclaration = false;
while (mods != 0) {
int mod = Integer.highestOneBit(mods);
switch (legend.getTokenModifiers().get(Integer.numberOfTrailingZeros(mod))) {
case "static": isStatic = true; break;
case "definition":
case "declaration": isDeclaration = true; break;
}
mods &= ~mod;
}
FontColorSettings fcs = MimeLookup.getLookup("text/textmate").lookup(FontColorSettings.class);
if (fcs == null) {
return EMPTY;
}
assert fcs != null;
AttributeSet colors = fcs.getTokenFontColors("mod-" + tokenName + (isDeclaration ? "-declaration" : ""));
AttributeSet statik = isStatic ? fcs.getTokenFontColors("mod-static") : null;
if (colors != null && statik != null) {
return AttributesUtilities.createComposite(colors, statik);
} else if (colors != null) {
return colors;
} else if (statik != null) {
return statik;
}
return EMPTY;
});
}
private static final AttributeSet EMPTY = AttributesUtilities.createImmutable();
private static OffsetsBag getBag(Document doc) {
OffsetsBag bag = (OffsetsBag) doc.getProperty(SemanticHighlight.class);
if (bag == null) {
doc.putProperty(SemanticHighlight.class, bag = new OffsetsBag(doc));
}
return bag;
}
@MimeRegistration(mimeType="", service=HighlightsLayerFactory.class)
public static final class HighlightsLayerFactoryImpl implements HighlightsLayerFactory {
@Override
public HighlightsLayer[] createLayers(Context context) {
return new HighlightsLayer[] {
HighlightsLayer.create(SemanticHighlight.class.getName(), ZOrder.SYNTAX_RACK.forPosition(1000), true, getBag(context.getDocument()))
};
}
}
// @JsonRpcData
// public static class SemanticTokensParams {
//
// private TextDocumentIdentifier textDocument;
//
// public SemanticTokensParams() {
// }
//
// public SemanticTokensParams(TextDocumentIdentifier textDocument) {
// this.textDocument = textDocument;
// }
//
// public TextDocumentIdentifier getTextDocument() {
// return textDocument;
// }
//
// public void setTextDocument(TextDocumentIdentifier textDocument) {
// this.textDocument = textDocument;
// }
//
// }
//
// @JsonRpcData
// public static class SemanticTokens {
// private List<Number> data;
//
// public List<Number> getData() {
// return data;
// }
//
// public void setData(List<Number> data) {
// this.data = data;
// }
//
// }
// public interface SemanticService {
// @JsonRequest("textDocument/semanticTokens/full")
// public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params);
// }
}