Implement highlighter support for eclipse language servers

This commit is contained in:
Kris De Volder
2017-10-04 14:43:20 -07:00
parent 9a0b5c8023
commit bbabae3bd6
21 changed files with 415 additions and 38 deletions

View File

@@ -26,4 +26,11 @@
unpack="false">
</plugin>
<plugin
id="org.springframework.tooling.ls.eclipse.commons"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false">
</plugin>
</feature>

View File

@@ -14,7 +14,8 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.9.0",
org.eclipse.lsp4j,
org.eclipse.ui.workbench,
org.eclipse.jface,
org.eclipse.xtext.xbase.lib
org.eclipse.xtext.xbase.lib,
org.springframework.tooling.ls.eclipse.commons
Import-Package: com.google.gson;version="2.7.0",
org.eclipse.jface.preference,
org.eclipse.lsp4j.jsonrpc.messages;version="0.1.0.v20170117-0759",

View File

@@ -7,6 +7,7 @@
<server
class="org.springframework.tooling.boot.java.ls.DelegatingStreamConnectionProvider"
id="org.eclipse.languageserver.languages.springbootjava"
clientImpl="org.springframework.tooling.ls.eclipse.commons.STS4LanguageClientImpl"
label="Spring Boot Java Language Server">
</server>
<contentTypeMapping

View File

@@ -12,6 +12,7 @@ package org.springframework.tooling.boot.java.ls;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
@@ -57,38 +58,6 @@ public class SpringBootJavaLanguageServer extends ProcessStreamConnectionProvide
setWorkingDirectory(workingDir);
}
public void handleMessage(Message message, LanguageServer languageServer, String rootPath) {
if (message instanceof NotificationMessage) {
NotificationMessage notificationMessage = (NotificationMessage) message;
if ("sts/progress".equals(notificationMessage.getMethod())) {
JsonObject params = (JsonObject) notificationMessage.getParams();
String status = params.has("statusMsg") ? params.get("statusMsg").getAsString() : "";
showStatusMessage(status);
}
}
}
private void showStatusMessage(final String status) {
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
IStatusLineManager statusLineManager = getStatusLineManager();
if (statusLineManager != null) {
statusLineManager.setMessage(status);
}
}
});
}
private IStatusLineManager getStatusLineManager() {
try {
return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getEditorSite().getActionBars().getStatusLineManager();
}
catch (NullPointerException e) {
return null;
}
}
protected String getJDKLocation() {
File jre = new File(System.getProperty("java.home"));
File javaExecutable = StandardVMType.findJavaExecutable(jre);

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.springframework.tooling.ls.eclipse.commons</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -0,0 +1,18 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Eclipse Language Server Commons
Bundle-SymbolicName: org.springframework.tooling.ls.eclipse.commons;singleton:=true
Bundle-Version: 4.0.0.qualifier
Bundle-Activator: org.springframework.tooling.ls.eclipse.commons.LsEclipseCommonsActivator
Bundle-Vendor: Pivotal, Inc.
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.lsp4e;bundle-version="0.3.0",
org.eclipse.lsp4j.jsonrpc,
org.eclipse.lsp4j,
org.eclipse.ui.workbench.texteditor,
org.eclipse.jface.text,
com.google.guava
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Export-Package: org.springframework.tooling.ls.eclipse.commons

View File

@@ -0,0 +1,5 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.xml

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension point="org.eclipse.ui.editors.annotationTypes">
<type name="org.springframework.tooling.bootinfo"></type>
</extension>
<extension point="org.eclipse.ui.editors.markerAnnotationSpecification">
<specification
annotationType="org.springframework.tooling.bootinfo"
colorPreferenceKey="STS4BootMarkerIndicationColor"
colorPreferenceValue="23,104,17"
contributesToHeader="false"
highlightPreferenceKey="STS4BootMarkerHighlighting"
highlightPreferenceValue="true"
icon="icons/boot-icon.png"
label="Boot Dynamic Info"
overviewRulerPreferenceKey="STS4BootMarkerIndicationInOverviewRuler"
overviewRulerPreferenceValue="true"
presentationLayer="4"
showInNextPrevDropdownToolbarAction="false"
textPreferenceKey="STS4BootMarkerIndication"
textPreferenceValue="false"
textStylePreferenceKey="STS4BootMarkerTextStyle"
textStylePreferenceValue="DASHED_BOX"
verticalRulerPreferenceKey="STS4BootMarkerIndicationInVerticalRuler"
verticalRulerPreferenceValue="true">
</specification>
</extension>
</plugin>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.ide</groupId>
<artifactId>org.springframework.boot.ide.servers</artifactId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>org.springframework.tooling.ls.eclipse.commons</artifactId>
<packaging>eclipse-plugin</packaging>
</project>

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.tooling.ls.eclipse.commons;
import java.util.List;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentIdentifier;
public class HighlightParams {
//TODO: Identical copy of this exists in org.springframework.ide.vscode.commons.languageserver.HighlightParams
// But because that is only built in plain maven jar (not osgi) we can't use it. We should find a solution
// for this somehow (i.e. build a version of some of our 'plain maven' jars as osgi bundles and publish on
// a update site.
private TextDocumentIdentifier doc;
private List<Range> ranges;
public HighlightParams() {
}
public HighlightParams(TextDocumentIdentifier doc, List<Range> ranges) {
super();
this.doc = doc;
this.ranges = ranges;
}
public TextDocumentIdentifier getDoc() {
return doc;
}
public void setDoc(TextDocumentIdentifier doc) {
this.doc = doc;
}
public List<Range> getRanges() {
return ranges;
}
public void setRanges(List<Range> ranges) {
this.ranges = ranges;
}
@Override
public String toString() {
return "HighlightParams [doc=" + doc + ", ranges=" + ranges + "]";
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.tooling.ls.eclipse.commons;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
/**
* The activator class controls the plug-in life cycle
*/
public class LsEclipseCommonsActivator extends AbstractUIPlugin {
// The plug-in ID
public static final String PLUGIN_ID = "org.springframework.tooling.ls.eclipse.commons"; //$NON-NLS-1$
// The shared instance
private static LsEclipseCommonsActivator plugin;
/**
* The constructor
*/
public LsEclipseCommonsActivator() {
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
}
/**
* Returns the shared instance
*
* @return the shared instance
*/
public static LsEclipseCommonsActivator getDefault() {
return plugin;
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.tooling.ls.eclipse.commons;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.services.LanguageClient;
/**
* Some 'custom' extensions to standard LSP {@link LanguageClient}.
*
* @author Kris De Volder
*/
public interface STS4LanguageClient extends LanguageClient {
@JsonNotification("sts/highlight")
void highlight(HighlightParams highlights);
// TODO: @JsonNotification("sts/progress")
// void progress(ProgressParams progressEvent);
//
// TODO: @JsonRequest("sts/moveCursor")
// CompletableFuture<Object> moveCursor(CursorMovement cursorMovement);
}

View File

@@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.tooling.ls.eclipse.commons;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.LanguageClientImpl;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo;
import org.eclipse.lsp4j.Range;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@SuppressWarnings("restriction")
public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4LanguageClient {
private static final String ANNOTION_TYPE_ID = "org.springframework.tooling.bootinfo";
class UpdateHighlights extends UIJob {
private String target;
UpdateHighlights(String target) {
super("Update highlights");
this.target = target;
setSystem(true);
schedule();
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
IWorkbenchWindow ww = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (ww!=null) {
IWorkbenchPage page = ww.getActivePage();
if (page!=null) {
IEditorPart editorPart = page.getActiveEditor();
if (editorPart instanceof AbstractTextEditor) {
try {
Method m = AbstractTextEditor.class.getDeclaredMethod("getSourceViewer");
m.setAccessible(true);
ISourceViewer sourceViewer = (ISourceViewer) m.invoke(editorPart);
if (sourceViewer!=null) {
IAnnotationModel annotationModel = sourceViewer.getAnnotationModel();
if (annotationModel!=null) {
IDocument doc = sourceViewer.getDocument();
if (sourceViewer!=null) {
if (doc!=null && annotationModel instanceof IAnnotationModelExtension) {
updateAnnotations(target, doc, (IAnnotationModelExtension) annotationModel);
}
}
}
}
} catch (Exception e) {
//ignore reflection errors
}
}
}
}
return Status.OK_STATUS;
}
};
/**
* Latest highlight request params. It is sufficient to only remember the last request per uri, because
* each new request is expected to replace the previous highlights.
*/
private Map<String, List<Range>> currentHighlights = new HashMap<>();
/**
* Current markers... indexed per document uri, needed sp we to be removed upon next update.
*/
private Map<String, Annotation[]> currentAnnotations = new HashMap<>();
private synchronized void updateAnnotations(String target, IDocument doc, IAnnotationModelExtension annotationModel) {
if (target!=null) {
Collection<LSPDocumentInfo> infos = LanguageServiceAccessor.getLSPDocumentInfosFor(doc, (x) -> true);
for (LSPDocumentInfo docInfo : infos) {
URI uri = docInfo.getFileUri();
if (uri!=null && uri.toString().equals(target)) {
Annotation[] toRemove = currentAnnotations.get(target);
if (toRemove==null) {
toRemove = new Annotation[0];
}
Map<Annotation, Position> newAnnotations = createAnnotations(doc, currentHighlights.get(target));
annotationModel.replaceAnnotations(toRemove, newAnnotations);
currentAnnotations.put(target, newAnnotations.keySet().toArray(new Annotation[newAnnotations.size()]));
}
}
}
}
private Map<Annotation, Position> createAnnotations(IDocument doc, List<Range> highlights) {
ImmutableMap.Builder<Annotation, Position> annotations = ImmutableMap.builder();
if (highlights==null) {
highlights = ImmutableList.of();
}
for (Range rng : highlights) {
try {
int start = LSPEclipseUtils.toOffset(rng.getStart(), doc);
int end = LSPEclipseUtils.toOffset(rng.getEnd(), doc);
Position e_rng = new Position(start, Math.max(0, end-start));
annotations.put(new Annotation(ANNOTION_TYPE_ID, false, null), e_rng);
} catch (BadLocationException e) {
//ignore invalid highlights
}
}
return annotations.build();
}
@Override
public synchronized void highlight(HighlightParams highlights) {
String target = highlights.getDoc().getUri();
if (target!=null) {
currentHighlights.put(target, highlights.getRanges());
new UpdateHighlights(target);
}
}
}

View File

@@ -12,6 +12,7 @@ package org.springframework.tooling.properties.ls;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
@@ -58,7 +59,8 @@ public class SpringBootPropertiesLanguageServer extends ProcessStreamConnectionP
setWorkingDirectory(workingDir);
}
public void handleMessage(Message message, LanguageServer languageServer, String rootPath) {
@Override
public void handleMessage(Message message, LanguageServer languageServer, URI rootPath) {
if (message instanceof NotificationMessage) {
NotificationMessage notificationMessage = (NotificationMessage) message;
if ("sts/progress".equals(notificationMessage.getMethod())) {

View File

@@ -27,6 +27,8 @@
</licenses>
<modules>
<module>org.springframework.tooling.ls.eclipse.commons</module>
<module>org.springframework.tooling.cloudfoundry.manifest.ls</module>
<module>org.springframework.tooling.cloudfoundry.manifest.ls.feature</module>
<module>org.springframework.tooling.ls.repository</module>

View File

@@ -21,7 +21,7 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEd
/**
* Some 'custom' extensions to standard LSP {@link LanguageClient}.
*
* @author
* @author Kris De Volder
*/
public interface STS4LanguageClient extends LanguageClient {

View File

@@ -459,5 +459,4 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl
Assert.isLegal(this.testListener==null);
testListener = languageServerTestListener;
}
}

View File

@@ -43,8 +43,6 @@ export class HighlightService {
refresh(uri : String) {
let editor = VSCode.window.activeTextEditor;
let activeUri = editor.document.uri.toString();
console.log("Refreshing uri :" + uri);
console.log(" active editor uri :" + editor.document.uri);
if (uri===activeUri) {
//We only update highlights in the active editor for now
let highlights : Range[] = this.highlights.get(uri) || [];