diff --git a/atom-extensions/atom-bosh/package.json b/atom-extensions/atom-bosh/package.json index e89346090..fb9351eac 100644 --- a/atom-extensions/atom-bosh/package.json +++ b/atom-extensions/atom-bosh/package.json @@ -50,7 +50,7 @@ "dependencies": { "atom-package-deps": "^4.6.0", "download": "^6.2.5", - "@pivotal-tools/atom-languageclient-commons": "0.0.7" + "@pivotal-tools/atom-languageclient-commons": "0.0.8" }, "devDependencies": { "typescript": "^2.7.2", diff --git a/atom-extensions/atom-cf-manifest-yaml/package.json b/atom-extensions/atom-cf-manifest-yaml/package.json index 5b23ef07b..7725f2ae2 100644 --- a/atom-extensions/atom-cf-manifest-yaml/package.json +++ b/atom-extensions/atom-cf-manifest-yaml/package.json @@ -19,7 +19,7 @@ "properties.json" ], "dependencies": { - "@pivotal-tools/atom-languageclient-commons": "0.0.7", + "@pivotal-tools/atom-languageclient-commons": "0.0.8", "atom-package-deps": "^4.6.0", "download": "^6.2.5" }, diff --git a/atom-extensions/atom-commons/lib/sts-adapter.ts b/atom-extensions/atom-commons/lib/sts-adapter.ts index 649632c56..ae9ae54a2 100644 --- a/atom-extensions/atom-commons/lib/sts-adapter.ts +++ b/atom-extensions/atom-commons/lib/sts-adapter.ts @@ -1,6 +1,6 @@ import {Convert} from 'atom-languageclient'; import {AtomEnvironment, TextEditor} from 'atom'; -import {Position, Range, TextDocumentIdentifier} from 'vscode-languageserver-protocol'; +import {Position, CodeLens, TextDocumentIdentifier} from 'vscode-languageserver-protocol'; export class StsAdapter { @@ -34,5 +34,5 @@ export interface ProgressParams { export interface HighlightParams { readonly doc: TextDocumentIdentifier; - readonly ranges: Range[]; + readonly codeLenses: CodeLens[]; } diff --git a/atom-extensions/atom-commons/package.json b/atom-extensions/atom-commons/package.json index e052bfae1..63cdad899 100644 --- a/atom-extensions/atom-commons/package.json +++ b/atom-extensions/atom-commons/package.json @@ -1,6 +1,6 @@ { "name": "@pivotal-tools/atom-languageclient-commons", - "version": "0.0.7", + "version": "0.0.8", "description": "Atom language client commons for STS4 language servers", "repository": "https://github.com/spring-projects/sts4", "license": "MIT", diff --git a/atom-extensions/atom-concourse/package.json b/atom-extensions/atom-concourse/package.json index 5e714ede0..7490dc1b9 100644 --- a/atom-extensions/atom-concourse/package.json +++ b/atom-extensions/atom-concourse/package.json @@ -21,7 +21,7 @@ "dependencies": { "atom-package-deps": "^4.6.0", "download": "^6.2.5", - "@pivotal-tools/atom-languageclient-commons": "0.0.7" + "@pivotal-tools/atom-languageclient-commons": "0.0.8" }, "devDependencies": { "typescript": "^2.7.2", diff --git a/atom-extensions/atom-spring-boot/lib/boot-sts-adapter.ts b/atom-extensions/atom-spring-boot/lib/boot-sts-adapter.ts index 37f036a5e..e8cf33733 100644 --- a/atom-extensions/atom-spring-boot/lib/boot-sts-adapter.ts +++ b/atom-extensions/atom-spring-boot/lib/boot-sts-adapter.ts @@ -1,6 +1,6 @@ import {StsAdapter, HighlightParams} from '@pivotal-tools/atom-languageclient-commons'; import {Convert} from 'atom-languageclient'; -import { Range } from 'vscode-languageserver-protocol'; +import { Range, CodeLens } from 'vscode-languageserver-protocol'; import {TextEditor, DecorationOptions } from 'atom'; const BOOT_HINT_GUTTER_NAME = 'boot-hint-gutter'; @@ -18,11 +18,12 @@ export class BootStsAdapter extends StsAdapter { } onHighlight(params: HighlightParams) { - this.findEditors(params.doc.uri).forEach(editor => this.markHintsForEditor(editor, params.ranges)); + this.findEditors(params.doc.uri).forEach(editor => this.markHintsForEditor(editor, params.codeLenses)); } - private markHintsForEditor(editor: TextEditor, ranges: Range[]) { + private markHintsForEditor(editor: TextEditor, codeLenses: CodeLens[]) { editor.getDecorations(DECORATION_OPTIONS).map(decoration => decoration.getMarker()).forEach(m => m.destroy()); + const ranges = codeLenses.map(cl => cl.range); if (Array.isArray(ranges)) { ranges.forEach(range => this.createHintMarker(editor, range)); } diff --git a/atom-extensions/atom-spring-boot/package.json b/atom-extensions/atom-spring-boot/package.json index eff75720e..d6cef73cd 100644 --- a/atom-extensions/atom-spring-boot/package.json +++ b/atom-extensions/atom-spring-boot/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "atom-languageclient": "0.9.5", - "@pivotal-tools/atom-languageclient-commons": "0.0.7", + "@pivotal-tools/atom-languageclient-commons": "0.0.8", "atom-package-deps": "^4.6.0", "download": "^6.2.5" }, diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/BootHeadlineAnnotation.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/BootHeadlineAnnotation.java new file mode 100644 index 000000000..eb0e35208 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/BootHeadlineAnnotation.java @@ -0,0 +1,52 @@ +package org.springframework.tooling.ls.eclipse.commons; + +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.inlined.LineHeaderAnnotation; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; + +public class BootHeadlineAnnotation extends LineHeaderAnnotation { + + public BootHeadlineAnnotation(Position position, ISourceViewer viewer) { + super(position, viewer); + } + +// @Override +// public void draw(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) { +// initGC(textWidget, color, gc); +// +// FontMetrics fontMetrics = gc.getFontMetrics(); +// int height = fontMetrics.getHeight(); +// +// Image bootImage = LanguageServerCommonsActivator.getInstance().getImageRegistry().get(LanguageServerCommonsActivator.BOOT_ICON_2X_KEY); +// Rectangle bootImgBounds = bootImage.getBounds(); +// int width = (int) Math.round(bootImgBounds.width / (double) bootImgBounds.height * height); +// +// gc.drawImage(bootImage, bootImgBounds.x, bootImgBounds.y, bootImgBounds.width, bootImgBounds.height, x, y, width, height); +// +// int textX = x + width + 4; +// int textY = y; +// +// gc.drawText("CODE LENS goes here!", textX, textY); +// } +// +// /** +// * Initialize GC with given color and styled text background color and font. +// * +// * @param textWidget the text widget +// * @param color the color +// * @param gc the gc to initialize +// */ +// private void initGC(StyledText textWidget, Color color, GC gc) { +// gc.setForeground(color); +// gc.setBackground(textWidget.getBackground()); +// gc.setFont(textWidget.getFont()); +// } + + +} diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/HighlightParams.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/HighlightParams.java index cf509ed2d..c9b0ae12c 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/HighlightParams.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/HighlightParams.java @@ -12,7 +12,7 @@ package org.springframework.tooling.ls.eclipse.commons; import java.util.List; -import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; public class HighlightParams { @@ -23,15 +23,15 @@ public class HighlightParams { // a update site. private VersionedTextDocumentIdentifier doc; - private List ranges; + private List codeLenses; public HighlightParams() { } - public HighlightParams(VersionedTextDocumentIdentifier doc, List ranges) { + public HighlightParams(VersionedTextDocumentIdentifier doc, List codeLenses) { super(); this.doc = doc; - this.ranges = ranges; + this.codeLenses = codeLenses; } public VersionedTextDocumentIdentifier getDoc() { return doc; @@ -39,15 +39,13 @@ public class HighlightParams { public void setDoc(VersionedTextDocumentIdentifier doc) { this.doc = doc; } - public List getRanges() { - return ranges; - } - public void setRanges(List ranges) { - this.ranges = ranges; + + public List getCodeLenses() { + return codeLenses; } - @Override - public String toString() { - return "HighlightParams [doc=" + doc + ", ranges=" + ranges + "]"; + public void setCodeLenses(List codeLenses) { + this.codeLenses = codeLenses; } + } diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java index 51fec3619..689025a4c 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java @@ -23,8 +23,11 @@ import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.AnnotationPainter; +import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelExtension; import org.eclipse.jface.text.source.ISourceViewer; @@ -32,7 +35,7 @@ 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.lsp4j.CodeLens; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; @@ -137,6 +140,8 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La */ private Map currentAnnotations = new HashMap<>(); +// private Map viewerInlinedAnnotationSupport = new WeakHashMap<>(); + private synchronized void updateAnnotations(String target, ISourceViewer sourceViewer, IAnnotationModelExtension annotationModel) { if (target!=null) { HighlightParams highlightParams = currentHighlights.get(target); @@ -146,10 +151,11 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La if (toRemove==null) { toRemove = new Annotation[0]; } - List highlights = highlightParams == null ? null : highlightParams.getRanges(); + List highlights = highlightParams == null ? null : highlightParams.getCodeLenses(); Map newAnnotations = createAnnotations(doc, highlights); annotationModel.replaceAnnotations(toRemove, newAnnotations); currentAnnotations.put(target, newAnnotations.keySet().toArray(new Annotation[newAnnotations.size()])); +// updateInlinedAnnotations(sourceViewer, highlights); } } } @@ -166,12 +172,75 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La return false; } - private Map createAnnotations(IDocument doc, List highlights) { +// private void updateInlinedAnnotations(final ISourceViewer sourceViewer, List highlights) { +// InlinedAnnotationSupport support = viewerInlinedAnnotationSupport.get(sourceViewer); +// if (support == null) { +// final InlinedAnnotationSupport inlinedSupport = new InlinedAnnotationSupport(); +// inlinedSupport.install(sourceViewer, createAnnotationPainter(sourceViewer)); +// viewerInlinedAnnotationSupport.put(sourceViewer, inlinedSupport); +// sourceViewer.getTextWidget().addDisposeListener((e) -> { +// inlinedSupport.uninstall(); +// viewerInlinedAnnotationSupport.remove(sourceViewer); +// }); +// support = inlinedSupport; +// } +// Set annotations = new HashSet<>(); +// if (highlights==null) { +// highlights = ImmutableList.of(); +// } +// IDocument doc = sourceViewer.getDocument(); +// final InlinedAnnotationSupport inlinedSupport = support; +// highlights.stream().filter(hl -> hl.getCommand() != null && hl.getCommand().getTitle() != null).forEach(codeLens -> { +// try { +// Range rng = codeLens.getRange(); +// int start = LSPEclipseUtils.toOffset(rng.getStart(), doc); +// +// // "Code Lens" line header annotation +// Position headerPos = new Position(start + 1, 1); +// BootHeadlineAnnotation headlineAnnotation = inlinedSupport.findExistingAnnotation(headerPos); +// if (headlineAnnotation == null) { +// headlineAnnotation = new BootHeadlineAnnotation(headerPos, sourceViewer); +// } +// headlineAnnotation.setText(codeLens.getCommand().getTitle()); +// annotations.add(headlineAnnotation); +// } catch (BadLocationException e) { +// //ignore invalid highlights +// } +// }); +// highlights.forEach(codeLens -> { +// }); +// support.updateAnnotations(annotations); +// } + + private static AnnotationPainter createAnnotationPainter(ISourceViewer viewer) { + IAnnotationAccess annotationAccess = new IAnnotationAccess() { + @Override + public Object getType(Annotation annotation) { + return annotation.getType(); + } + + @Override + public boolean isMultiLine(Annotation annotation) { + return true; + } + + @Override + public boolean isTemporary(Annotation annotation) { + return true; + } + + }; + AnnotationPainter painter = new AnnotationPainter(viewer, annotationAccess); + ((ITextViewerExtension2) viewer).addPainter(painter); + return painter; + } + + private Map createAnnotations(IDocument doc, List highlights) { ImmutableMap.Builder annotations = ImmutableMap.builder(); if (highlights==null) { highlights = ImmutableList.of(); } - for (Range rng : highlights) { + highlights.stream().map(CodeLens::getRange).forEach(rng -> { try { int start = LSPEclipseUtils.toOffset(rng.getStart(), doc); int end = LSPEclipseUtils.toOffset(rng.getEnd(), doc); @@ -180,7 +249,7 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La } catch (BadLocationException e) { //ignore invalid highlights } - } + }); return annotations.build(); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/HighlightParams.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/HighlightParams.java index e0fa41af5..21025d50f 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/HighlightParams.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/HighlightParams.java @@ -12,21 +12,21 @@ package org.springframework.ide.vscode.commons.languageserver; import java.util.List; -import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; public class HighlightParams { private VersionedTextDocumentIdentifier doc; - private List ranges; + private List codeLenses; public HighlightParams() { } - public HighlightParams(VersionedTextDocumentIdentifier doc, List ranges) { + public HighlightParams(VersionedTextDocumentIdentifier doc, List codeLenses) { super(); this.doc = doc; - this.ranges = ranges; + this.codeLenses = codeLenses; } public VersionedTextDocumentIdentifier getDoc() { return doc; @@ -34,11 +34,13 @@ public class HighlightParams { public void setDoc(VersionedTextDocumentIdentifier doc) { this.doc = doc; } - public List getRanges() { - return ranges; + + public List getCodeLenses() { + return codeLenses; } - public void setRanges(List ranges) { - this.ranges = ranges; + + public void setCodeLenses(List codeLenses) { + this.codeLenses = codeLenses; } } diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index 62a9c5dc2..764c81837 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -14,6 +14,7 @@ package org.springframework.ide.vscode.languageserver.testharness; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness.getDocString; import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains; import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertDoesNotContain; @@ -31,6 +32,7 @@ import java.util.stream.Collectors; import javax.swing.text.BadLocationException; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.Diagnostic; @@ -56,8 +58,6 @@ import com.google.common.collect.ImmutableList; import reactor.core.publisher.Flux; -import static org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness.*; - public class Editor { public static final Predicate RELAXED_COMPLETION @@ -210,7 +210,7 @@ public class Editor { public List assertHighlights(String... expectedHighlights) throws Exception { HighlightParams highlights = harness.getHighlights(doc); - List ranges = highlights != null ? new ArrayList<>(highlights.getRanges()) : ImmutableList.of(); + List ranges = highlights != null ? highlights.getCodeLenses().stream().map(CodeLens::getRange).collect(Collectors.toList()) : ImmutableList.of(); Collections.sort(ranges, RANGE_COMPARATOR); List actualHighlights = ranges.stream() .map(this::getText) diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 7d64a369a..d1fe537c5 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -145,7 +145,7 @@ public class LanguageServerHarness { this.defaultLanguageId = defaultLanguageId; } - public static final Duration HIGHLIGHTS_TIMEOUT = Duration.ofMillis(15_000); //Why so long? + public static final Duration HIGHLIGHTS_TIMEOUT = Duration.ofMillis(15000000000L); //Why so long? public LanguageServerHarness(Callable factory) throws Exception { this(factory, LanguageId.PLAINTEXT); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index df4ac6091..47d4e9a29 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -296,78 +296,92 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent RunningAppProvider runningAppProvider) { AnnotationHierarchyAwareLookup providers = new AnnotationHierarchyAwareLookup<>(); - providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueHoverProvider()); + ValueHoverProvider valueHoverProvider = new ValueHoverProvider(); + RequestMappingHoverProvider requestMappingHoverProvider = new RequestMappingHoverProvider(); + AutowiredHoverProvider autowiredHoverProvider = new AutowiredHoverProvider(this); + ComponentInjectionsHoverProvider componentInjectionsHoverProvider = new ComponentInjectionsHoverProvider(this); + BeanInjectedIntoHoverProvider beanInjectedIntoHoverProvider = new BeanInjectedIntoHoverProvider(this); + ConditionalsLiveHoverProvider conditionalsLiveHoverProvider = new ConditionalsLiveHoverProvider(); - providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingHoverProvider()); - providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingHoverProvider()); - providers.put(Annotations.SPRING_POST_MAPPING, new RequestMappingHoverProvider()); - providers.put(Annotations.SPRING_PUT_MAPPING, new RequestMappingHoverProvider()); - providers.put(Annotations.SPRING_DELETE_MAPPING, new RequestMappingHoverProvider()); - providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingHoverProvider()); + providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, valueHoverProvider); + + providers.put(Annotations.SPRING_REQUEST_MAPPING, requestMappingHoverProvider); + providers.put(Annotations.SPRING_GET_MAPPING, requestMappingHoverProvider); + providers.put(Annotations.SPRING_POST_MAPPING, requestMappingHoverProvider); + providers.put(Annotations.SPRING_PUT_MAPPING, requestMappingHoverProvider); + providers.put(Annotations.SPRING_DELETE_MAPPING, requestMappingHoverProvider); + providers.put(Annotations.SPRING_PATCH_MAPPING, requestMappingHoverProvider); providers.put(Annotations.PROFILE, new ActiveProfilesProvider()); - providers.put(Annotations.AUTOWIRED, new AutowiredHoverProvider(this)); - providers.put(Annotations.INJECT, new AutowiredHoverProvider(this)); - providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider(this)); - providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider(this)); + providers.put(Annotations.AUTOWIRED, autowiredHoverProvider); + providers.put(Annotations.INJECT, autowiredHoverProvider); + providers.put(Annotations.COMPONENT, componentInjectionsHoverProvider); + providers.put(Annotations.BEAN, beanInjectedIntoHoverProvider); - providers.put(Annotations.CONDITIONAL, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_MISSING_BEAN, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_CLASS, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_MISSING_CLASS, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_CLOUD_PLATFORM, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_WEB_APPLICATION, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_NOT_WEB_APPLICATION, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_ENDPOINT, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_EXPRESSION, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_JAVA, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_JNDI, new ConditionalsLiveHoverProvider()); - providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, new ConditionalsLiveHoverProvider()); + providers.put(Annotations.CONDITIONAL, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_BEAN, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_MISSING_BEAN, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_PROPERTY, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_RESOURCE, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_CLASS, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_MISSING_CLASS, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_CLOUD_PLATFORM, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_WEB_APPLICATION, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_NOT_WEB_APPLICATION, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_ENDPOINT, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_EXPRESSION, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_JAVA, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_JNDI, conditionalsLiveHoverProvider); + providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, conditionalsLiveHoverProvider); return new BootJavaHoverProvider(this, javaProjectFinder, providers, runningAppProvider); } protected SpringIndexer createAnnotationIndexer(SimpleLanguageServer server, BootLanguageServerParams params) { AnnotationHierarchyAwareLookup providers = new AnnotationHierarchyAwareLookup<>(); - providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider()); - providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingSymbolProvider()); - providers.put(Annotations.SPRING_POST_MAPPING, new RequestMappingSymbolProvider()); - providers.put(Annotations.SPRING_PUT_MAPPING, new RequestMappingSymbolProvider()); - providers.put(Annotations.SPRING_DELETE_MAPPING, new RequestMappingSymbolProvider()); - providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingSymbolProvider()); + RequestMappingSymbolProvider requestMappingSymbolProvider = new RequestMappingSymbolProvider(); + BeansSymbolProvider beansSymbolProvider = new BeansSymbolProvider(); + ComponentSymbolProvider componentSymbolProvider = new ComponentSymbolProvider(); + RestrictedDefaultSymbolProvider restrictedDefaultSymbolProvider = new RestrictedDefaultSymbolProvider(); + DataRepositorySymbolProvider dataRepositorySymbolProvider = new DataRepositorySymbolProvider(); + WebfluxRouterSymbolProvider webfluxRouterSymbolProvider = new WebfluxRouterSymbolProvider(); - providers.put(Annotations.BEAN, new BeansSymbolProvider()); - providers.put(Annotations.COMPONENT, new ComponentSymbolProvider()); + providers.put(Annotations.SPRING_REQUEST_MAPPING, requestMappingSymbolProvider); + providers.put(Annotations.SPRING_GET_MAPPING, requestMappingSymbolProvider); + providers.put(Annotations.SPRING_POST_MAPPING, requestMappingSymbolProvider); + providers.put(Annotations.SPRING_PUT_MAPPING, requestMappingSymbolProvider); + providers.put(Annotations.SPRING_DELETE_MAPPING, requestMappingSymbolProvider); + providers.put(Annotations.SPRING_PATCH_MAPPING, requestMappingSymbolProvider); - providers.put(Annotations.PROFILE, new RestrictedDefaultSymbolProvider()); + providers.put(Annotations.BEAN, beansSymbolProvider); + providers.put(Annotations.COMPONENT, componentSymbolProvider); - providers.put(Annotations.CONDITIONAL, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_BEAN, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_MISSING_BEAN, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_CLASS, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_MISSING_CLASS, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_CLOUD_PLATFORM, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_WEB_APPLICATION, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_NOT_WEB_APPLICATION, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_ENDPOINT, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_EXPRESSION, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_JAVA, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_JNDI, new RestrictedDefaultSymbolProvider()); - providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, new RestrictedDefaultSymbolProvider()); + providers.put(Annotations.PROFILE, restrictedDefaultSymbolProvider); - providers.put(Annotations.REPOSITORY, new DataRepositorySymbolProvider()); - providers.put("", new WebfluxRouterSymbolProvider()); + providers.put(Annotations.CONDITIONAL, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_BEAN, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_MISSING_BEAN, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_PROPERTY, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_RESOURCE, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_CLASS, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_MISSING_CLASS, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_CLOUD_PLATFORM, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_WEB_APPLICATION, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_NOT_WEB_APPLICATION, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_ENDPOINT, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_EXPRESSION, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_JAVA, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_JNDI, restrictedDefaultSymbolProvider); + providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, restrictedDefaultSymbolProvider); + + providers.put(Annotations.REPOSITORY, dataRepositorySymbolProvider); + providers.put("", webfluxRouterSymbolProvider); return new SpringIndexer(server, params, providers); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index 2e7550575..b0decf648 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -22,8 +22,11 @@ import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -65,7 +68,7 @@ public class AutowiredHoverProvider implements HoverProvider { } @Override - public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { if (runningApps.length > 0) { LiveBean definedBean = getDefinedBeanForTypeDeclaration(ASTUtils.findDeclaringType(annotation)); // Annotation is MarkerNode, parent is some field, method, variable declaration node. @@ -80,13 +83,32 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } - private Collection getLiveHoverHints(IJavaProject project, ASTNode declarationNode, Range range, + private Collection getLiveHoverHints(IJavaProject project, ASTNode declarationNode, Range range, SpringBootApp[] runningApps, LiveBean definedBean) { if (declarationNode != null && definedBean != null) { for (SpringBootApp app : runningApps) { List relevantBeans = getRelevantAutowiredBeans(project, declarationNode, app, definedBean); if (!relevantBeans.isEmpty()) { - return ImmutableList.of(range); + CodeLens codeLens = new CodeLens(); + codeLens.setRange(range); + StringBuilder sb = new StringBuilder("\u21D0 "); + if (LiveHoverUtils.doBeansFitInline(relevantBeans, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR)) { + sb.append(relevantBeans.stream().map(LiveHoverUtils::getShortDisplayType).collect(Collectors.joining(INLINE_BEANS_STRING_SEPARATOR))); + } else { + sb.append(relevantBeans.size()); + sb.append(" bean"); + if (relevantBeans.size() > 1) { + sb.append("s"); + } + } + codeLens.setData(sb.toString()); + Command cmd = new Command(); + cmd.setTitle(sb.toString()); + cmd.setCommand("org.springframework.showHoverAtPosition"); + cmd.setArguments(ImmutableList.of(range.getStart())); + codeLens.setCommand(cmd); + + return ImmutableList.of(codeLens); } } } @@ -100,7 +122,15 @@ public class AutowiredHoverProvider implements HoverProvider { LiveBean definedBean = getDefinedBeanForTypeDeclaration(ASTUtils.findDeclaringType(annotation)); // Annotation is MarkerNode, parent is some field, method, variable declaration node. ASTNode declarationNode = annotation.getParent(); - return provideHover(definedBean, declarationNode, offset, doc, project, runningApps); + Hover hover = provideHover(definedBean, declarationNode, offset, doc, project, runningApps); + if (hover != null) { + try { + hover.setRange(doc.toRange(annotation.getStartPosition(), annotation.getLength())); + } catch (BadLocationException e) { + log.error("", e); + } + } + return hover; } return null; } @@ -266,11 +296,20 @@ public class AutowiredHoverProvider implements HoverProvider { @Override public Hover provideHover(MethodDeclaration methodDeclaration, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { LiveBean definedBean = getDefinedBeanForImplicitAutowiredConstructor(methodDeclaration); - return provideHover(definedBean, methodDeclaration, offset, doc, project, runningApps); + Hover hover = provideHover(definedBean, methodDeclaration, offset, doc, project, runningApps); + if (hover != null) { + SimpleName name = methodDeclaration.getName(); + try { + hover.setRange(doc.toRange(name.getStartPosition(), name.getLength())); + } catch (BadLocationException e) { + log.error("", e); + } + } + return hover; } @Override - public Collection getLiveHoverHints(IJavaProject project, MethodDeclaration methodDeclaration, TextDocument doc, + public Collection getLiveHintCodeLenses(IJavaProject project, MethodDeclaration methodDeclaration, TextDocument doc, SpringBootApp[] runningApps) { LiveBean definedBean = getDefinedBeanForImplicitAutowiredConstructor(methodDeclaration); try { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java index fa6ea4256..0bfe70a54 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java @@ -21,16 +21,18 @@ import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils; import org.springframework.ide.vscode.commons.boot.app.cli.LiveConditional; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; @@ -42,6 +44,8 @@ import com.google.common.collect.ImmutableList; */ public class ConditionalsLiveHoverProvider implements HoverProvider { + private static final Logger log = LoggerFactory.getLogger(ConditionalsLiveHoverProvider.class); + @Override public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { @@ -49,15 +53,15 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { } @Override - public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { Optional> val = getMatchedLiveConditionals(annotation, runningApps); if (val.isPresent()) { Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - return ImmutableList.of(hoverRange); + return ImmutableList.of(new CodeLens(hoverRange)); } } catch (Exception e) { - Log.log(e); + log.error("", e); } return null; @@ -103,7 +107,7 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { return hover; } catch (Exception e) { - Log.log(e); + log.error("", e); } return null; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java index d5b2cc69e..8376516fa 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java @@ -25,8 +25,8 @@ import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentPositionParams; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -90,9 +90,9 @@ public class BootJavaHoverProvider implements HoverHandler { return SimpleTextDocumentService.NO_HOVER; } - public Range[] getLiveHoverHints(final TextDocument document, final SpringBootApp[] runningBootApps) { + public CodeLens[] getLiveHoverHints(final TextDocument document, final SpringBootApp[] runningBootApps) { return server.getCompilationUnitCache().withCompilationUnit(document, cu -> { - Collection result = new HashSet<>(); + Collection result = new HashSet<>(); try { if (cu != null) { cu.accept(new ASTVisitor() { @@ -158,18 +158,18 @@ public class BootJavaHoverProvider implements HoverHandler { } catch (Exception e) { logger.error("error extracting live hint information for docURI '" + document.getUri(), e); } - return result.toArray(new Range[result.size()]); + return result.toArray(new CodeLens[result.size()]); }); } protected void extractLiveHintsForMethod(MethodDeclaration methodDeclaration, TextDocument doc, - SpringBootApp[] runningApps, Collection result) { + SpringBootApp[] runningApps, Collection result) { Collection providers = this.hoverProviders.getAll(); if (!providers.isEmpty()) { for (HoverProvider provider : providers) { getProject(doc).ifPresent(project -> { if (hasActuatorDependency(project)) { - Collection hints = provider.getLiveHoverHints(project, methodDeclaration, doc, runningApps); + Collection hints = provider.getLiveHintCodeLenses(project, methodDeclaration, doc, runningApps); if (hints!=null) { result.addAll(hints); } @@ -182,13 +182,13 @@ public class BootJavaHoverProvider implements HoverHandler { } } - protected void extractLiveHintsForType(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps, Collection result) { + protected void extractLiveHintsForType(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps, Collection result) { Collection providers = this.hoverProviders.getAll(); if (!providers.isEmpty()) { for (HoverProvider provider : providers) { getProject(doc).ifPresent(project -> { if (hasActuatorDependency(project)) { - Collection hints = provider.getLiveHoverHints(project, typeDeclaration, doc, runningApps); + Collection hints = provider.getLiveHintCodeLenses(project, typeDeclaration, doc, runningApps); if (hints!=null) { result.addAll(hints); } @@ -201,14 +201,14 @@ public class BootJavaHoverProvider implements HoverHandler { } } - protected void extractLiveHintsForAnnotation(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps, Collection result) { + protected void extractLiveHintsForAnnotation(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps, Collection result) { ITypeBinding type = annotation.resolveTypeBinding(); if (type != null) { if (runningApps.length > 0) { for (HoverProvider provider : this.hoverProviders.get(type)) { getProject(doc).ifPresent(project -> { if (hasActuatorDependency(project)) { - Collection hints = provider.getLiveHoverHints(project, annotation, doc, runningApps); + Collection hints = provider.getLiveHintCodeLenses(project, annotation, doc, runningApps); if (hints!=null) { result.addAll(hints); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java index 948bed6a5..2bbd04600 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java @@ -17,8 +17,8 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -38,14 +38,16 @@ public interface HoverProvider { return null; } - default Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { - return null; - } - default Collection getLiveHoverHints(IJavaProject project,TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps) { - return null; - } - default Collection getLiveHoverHints(IJavaProject project, MethodDeclaration methodDeclaration, TextDocument doc, SpringBootApp[] runningApps) { + default Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { return null; } -} + default Collection getLiveHintCodeLenses(IJavaProject project,TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps) { + return null; + } + + default Collection getLiveHintCodeLenses(IJavaProject project, MethodDeclaration methodDeclaration, TextDocument doc, SpringBootApp[] runningApps) { + return null; + } + + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java index 8e166424e..812fc1e3a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -20,6 +21,8 @@ import java.util.stream.Stream; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -50,7 +53,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider } @Override - public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { // Highlight if any running app contains an instance of this component try { if (runningApps.length > 0) { @@ -59,7 +62,8 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) { Optional nameRange = ASTUtils.nameRange(doc, annotation); if (nameRange.isPresent()) { - return ImmutableList.of(nameRange.get()); + List codeLenses = assembleCodeLenses(project, runningApps, definedBean, nameRange.get()); + return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; } } } @@ -71,18 +75,63 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider } @Override - public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, - TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, + IJavaProject project, SpringBootApp[] runningApps) { if (runningApps.length > 0) { LiveBean definedBean = getDefinedBean(annotation); if (definedBean != null) { - return assembleHover(project, runningApps, definedBean); + Hover hover = assembleHover(project, runningApps, definedBean); + if (hover != null) { + Optional nameRange = ASTUtils.nameRange(doc, annotation); + if (nameRange.isPresent()) { + hover.setRange(nameRange.get()); + } + } + return hover; } } return null; } + protected List assembleCodeLenses(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, Range range) { + List codeLensList = new ArrayList<>(); + for (SpringBootApp app : runningApps) { + + List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean); + + if (!relevantBeans.isEmpty()) { + List injectedBeans = getRelevantInjectedIntoBeans(project, app, definedBean, relevantBeans); + if (!injectedBeans.isEmpty()) { + CodeLens codeLens = new CodeLens(); + codeLens.setRange(range); + StringBuilder sb = new StringBuilder("\u21D2 "); + if (LiveHoverUtils.doBeansFitInline(relevantBeans, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR)) { + sb.append(relevantBeans.stream().map(LiveHoverUtils::getShortDisplayType).collect(Collectors.joining(INLINE_BEANS_STRING_SEPARATOR))); + } else { + sb.append(relevantBeans.size()); + sb.append(" bean"); + if (relevantBeans.size() > 1) { + sb.append("s"); + } + } + codeLens.setData(sb.toString()); + Command cmd = new Command(); + cmd.setTitle(sb.toString()); + cmd.setCommand("org.springframework.showHoverAtPosition"); + cmd.setArguments(ImmutableList.of(range.getStart())); + codeLens.setCommand(cmd); + + codeLensList.add(codeLens); + + // Break out of the loop. Just look for the first app with injected into beans + break; + } + } + } + return codeLensList; + } + protected Hover assembleHover(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean) { StringBuilder hover = new StringBuilder(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java index aa65941a3..ceb211e7b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java @@ -22,14 +22,16 @@ import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; @@ -41,6 +43,8 @@ import com.google.common.collect.ImmutableSet; */ public class ActiveProfilesProvider implements HoverProvider { + private static final Logger log = LoggerFactory.getLogger(ActiveProfilesProvider.class); + @Override public Hover provideHover( ASTNode node, @@ -71,19 +75,26 @@ public class ActiveProfilesProvider implements HoverProvider { } } if (hasInterestingApp) { - return new Hover( + Hover hover = new Hover( ImmutableList.of(Either.forLeft(markdown.toString())) ); + if (hover != null) { + Optional optional = nameRange(doc, annotation); + if (optional.isPresent()) { + hover.setRange(optional.get()); + } + } + return hover; } } return null; } @Override - public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { if (runningApps.length > 0) { - Builder ranges = ImmutableList.builder(); - nameRange(doc, annotation).ifPresent(ranges::add); + Builder codeLenses = ImmutableList.builder(); + nameRange(doc, annotation).map(CodeLens::new).ifPresent(codeLenses::add); Set allActiveProfiles = getAllActiveProfiles(runningApps); annotation.accept(new ASTVisitor() { @@ -91,12 +102,12 @@ public class ActiveProfilesProvider implements HoverProvider { public boolean visit(StringLiteral node) { String value = ASTUtils.getLiteralValue(node); if (value!=null && allActiveProfiles.contains(value)) { - rangeOf(doc, node).ifPresent(ranges::add); + rangeOf(doc, node).map(CodeLens::new).ifPresent(codeLenses::add); } return true; } }); - return ranges.build(); + return codeLenses.build(); } return ImmutableList.of(); } @@ -124,7 +135,7 @@ public class ActiveProfilesProvider implements HoverProvider { } return Optional.of(doc.toRange(start, end-start)); } catch (Exception e) { - Log.log(e); + log.error("", e); return Optional.empty(); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java index 65c4e473b..b2a913d9a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java @@ -19,7 +19,9 @@ import java.util.stream.Stream; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.slf4j.Logger; @@ -31,6 +33,7 @@ import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -98,7 +101,7 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP } @Override - public Collection getLiveHoverHints(IJavaProject project, TypeDeclaration typeDeclaration, TextDocument doc, + public Collection getLiveHintCodeLenses(IJavaProject project, TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps) { if (runningApps.length > 0 && !isComponentAnnotatedType(typeDeclaration)) { try { @@ -107,7 +110,8 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) { Optional nameRange = Optional.of(ASTUtils.nodeRegion(doc, typeDeclaration.getName()).asRange()); if (nameRange.isPresent()) { - return ImmutableList.of(nameRange.get()); + List codeLenses = assembleCodeLenses(project, runningApps, definedBean, nameRange.get()); + return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; } } } @@ -126,7 +130,16 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP LiveBean definedBean = getDefinedBeanForType(typeDeclaration, null); if (definedBean != null) { - return assembleHover(project, runningApps, definedBean); + Hover hover = assembleHover(project, runningApps, definedBean); + if (hover != null) { + SimpleName name = typeDeclaration.getName(); + try { + hover.setRange(doc.toRange(name.getStartPosition(), name.getLength())); + } catch (BadLocationException e) { + LOG.error("", e); + } + } + return hover; } } return null; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java index 338610b64..b15823d89 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java @@ -69,6 +69,12 @@ public class LiveHoverUtils { return buf.toString(); } + public static String getShortDisplayType(LiveBean bean) { + String type = bean.getType(true); + int idx = type.lastIndexOf('.'); + return idx < 0 || idx == type.length() - 1 ? type : type.substring(idx + 1); + } + public static String showBeanInline(BootJavaLanguageServerComponents server, IJavaProject project, LiveBean bean) { String id = bean.getId(); String type = bean.getType(true); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java index 000927048..ebf5c51f3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.Range; @@ -57,18 +58,18 @@ public class RequestMappingHoverProvider implements HoverProvider { } @Override - public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { if (runningApps.length > 0) { List> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); if (!val.isEmpty()) { Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - return ImmutableList.of(hoverRange); + return ImmutableList.of(new CodeLens(hoverRange)); } } } catch (BadLocationException e) { - log.error("", e);; + log.error("", e); } return null; @@ -83,17 +84,19 @@ public class RequestMappingHoverProvider implements HoverProvider { if (!val.isEmpty()) { addHoverContent(val, hoverContent); + Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); + Hover hover = new Hover(); + + hover.setContents(hoverContent); + hover.setRange(hoverRange); + + return hover; + } else { + return null; } - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - Hover hover = new Hover(); - - hover.setContents(hoverContent); - hover.setRange(hoverRange); - - return hover; } catch (Exception e) { - log.error("", e);; + log.error("", e); } return null; @@ -114,7 +117,7 @@ public class RequestMappingHoverProvider implements HoverProvider { } } } catch (Exception e) { - log.error("", e);; + log.error("", e); } return results; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java index 20185f216..4a12482c8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java @@ -19,7 +19,7 @@ import java.util.TimerTask; import java.util.concurrent.ConcurrentSkipListSet; import java.util.stream.Stream; -import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.slf4j.Logger; @@ -156,8 +156,8 @@ public class SpringLiveHoverWatchdog { if (hasCurrentRunningBootApps) { TextDocument doc = this.server.getTextDocumentService().get(docURI); if (doc != null) { - Range[] ranges = this.hoverProvider.getLiveHoverHints(doc, runningBootApps); - publishLiveHints(docURI, ranges); + CodeLens[] infos = this.hoverProvider.getLiveHoverHints(doc, runningBootApps); + publishLiveHints(docURI, infos); } } else { @@ -195,15 +195,15 @@ public class SpringLiveHoverWatchdog { } } - private void publishLiveHints(String docURI, Range[] ranges) { + private void publishLiveHints(String docURI, CodeLens[] codeLenses) { int version = server.getTextDocumentService().get(docURI).getVersion(); VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(version); id.setUri(docURI); - server.getClient().highlight(new HighlightParams(id, Arrays.asList(ranges))); + server.getClient().highlight(new HighlightParams(id, Arrays.asList(codeLenses))); } private void cleanupLiveHints(String docURI) { - publishLiveHints(docURI, new Range[0]); + publishLiveHints(docURI, new CodeLens[0]); } private void cleanupResources() { diff --git a/theia-extensions/theia-spring-boot/spring-boot/src/browser/highlight-service.ts b/theia-extensions/theia-spring-boot/spring-boot/src/browser/highlight-service.ts index 0e64a5c9a..397ccfe2a 100644 --- a/theia-extensions/theia-spring-boot/spring-boot/src/browser/highlight-service.ts +++ b/theia-extensions/theia-spring-boot/spring-boot/src/browser/highlight-service.ts @@ -1,6 +1,6 @@ import { injectable, inject } from 'inversify'; import { NotificationType } from 'vscode-jsonrpc'; -import { TextDocumentIdentifier, Range } from 'vscode-base-languageclient/lib/base'; +import { TextDocumentIdentifier, Range, CodeLens } from 'vscode-base-languageclient/lib/base'; import { SetDecorationParams, EditorDecorationStyle, TextEditor, DeltaDecorationParams, EditorManager } from '@theia/editor/lib/browser'; import { ILanguageClient } from '@theia/languages/lib/common'; import { DiffUris } from '@theia/core/lib/browser/diff-uris'; @@ -11,17 +11,11 @@ const HIGHLIGHTS_NOTIFICATION_TYPE = new NotificationType( const BOOT_LIVE_HINTS = 'Boot-Live-Hints'; const INLINE_BOOT_HINT_DECORATION_STYLE = new EditorDecorationStyle('inline-boot-hint-decoration', style => { - style.borderStyle = 'dotted'; - style.borderColor = '#32BA56'; - style.borderWidth = '1px'; -}); - -const LINE_BOOT_HINT_DECORATION_STYLE = new EditorDecorationStyle('line-boot-hint-decoration', style => { - style.backgroundImage = 'url(../../images/boot-icon.png)'; - style.display = 'block'; - style.width = '10px'; - style.height = '1em'; - style.margin = '0 2px 0 0'; + style.backgroundColor = 'rgba(109,179,63,0.25)', + style.borderColor = 'rgba(109,179,63,0.25)', + style.borderSpacing = '4px', + style.borderRadius = '4px', + style.borderWidth = '4px' }); @injectable() @@ -43,13 +37,11 @@ export class HighlightService { const decorationParams: SetDecorationParams = { uri: params.doc.uri, kind: BOOT_LIVE_HINTS, - newDecorations: params.ranges.map(r => { + newDecorations: params.codeLenses.map(cl => { return { - range: Range.create(r.start.line, r.start.character, r.end.line, r.end.character), + range: Range.create(cl.range.start.line, cl.range.start.character, cl.range.end.line, cl.range.end.character), options: { inlineClassName: INLINE_BOOT_HINT_DECORATION_STYLE.className, - glyphMarginClassName: LINE_BOOT_HINT_DECORATION_STYLE.className, - hoverMessage: 'Ho-ho, Boot Hint!', isWholeLine: false } } @@ -80,6 +72,6 @@ export class HighlightService { export interface HighlightParams { doc: TextDocumentIdentifier - ranges: Range[] + codeLenses: CodeLens[] } diff --git a/vscode-extensions/commons-vscode/package.json b/vscode-extensions/commons-vscode/package.json index e628a38f3..f57925f07 100644 --- a/vscode-extensions/commons-vscode/package.json +++ b/vscode-extensions/commons-vscode/package.json @@ -25,7 +25,8 @@ }, "dependencies": { "@pivotal-tools/jvm-launch-utils": "0.0.11", - "portfinder": "^0.4.0" + "portfinder": "^0.4.0", + "deep-equal": "^1.0.1" }, "devDependencies": { "typescript": "2.6.1", diff --git a/vscode-extensions/commons-vscode/src/code-lens-service.ts b/vscode-extensions/commons-vscode/src/code-lens-service.ts new file mode 100644 index 000000000..25f28f549 --- /dev/null +++ b/vscode-extensions/commons-vscode/src/code-lens-service.ts @@ -0,0 +1,50 @@ +import * as deepEqual from 'deep-equal'; +import { + CancellationToken, + CodeLens, + CodeLensProvider, + Event, + EventEmitter, + ProviderResult, + TextDocument +} from "vscode"; +import {HighlightParams, toVSRange} from './highlight-service'; +import * as Lsp from 'vscode-languageclient'; + +export class HighlightCodeLensProvider implements CodeLensProvider { + + private highlights : Map = new Map(); + + private _onDidChangeCodeLenses = new EventEmitter(); + public get onDidChangeCodeLenses(): Event { + return this._onDidChangeCodeLenses.event; + } + + handle(highlghtParams: HighlightParams) { + if (!deepEqual(this.highlights.get(highlghtParams.doc.uri), highlghtParams)) { + this.highlights.set(highlghtParams.doc.uri, highlghtParams); + this._onDidChangeCodeLenses.fire(); + } + } + + static toVSCodeLens(cl: Lsp.CodeLens): CodeLens { + const codeLens: CodeLens = { + range: toVSRange(cl.range), + isResolved: true, + command: cl.command + }; + return codeLens; + } + + provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { + const activeUri = document.uri.toString(); + const activeVersion = document.version; + const highlightParams = this.highlights.get(activeUri); + if (highlightParams && highlightParams.doc.version === activeVersion) { + const codeLenses = highlightParams.codeLenses || []; + return codeLenses.filter(cl => cl.command).map(cl => HighlightCodeLensProvider.toVSCodeLens(cl)); + } + return []; + }; + +} diff --git a/vscode-extensions/commons-vscode/src/highlight-service.ts b/vscode-extensions/commons-vscode/src/highlight-service.ts index d2d68c45b..7c029abd9 100644 --- a/vscode-extensions/commons-vscode/src/highlight-service.ts +++ b/vscode-extensions/commons-vscode/src/highlight-service.ts @@ -1,8 +1,7 @@ -import {VersionedTextDocumentIdentifier, Position, Range} from 'vscode-languageclient' +import {VersionedTextDocumentIdentifier, MarkupContent, Position, Range, CodeLens} from 'vscode-languageclient' import * as VSCode from 'vscode'; -import * as path from "path"; -function toDecoration(rng : Range) : VSCode.Range { +export function toVSRange(rng : Range) : VSCode.Range { return new VSCode.Range(toPosition(rng.start), toPosition(rng.end)); } @@ -11,15 +10,15 @@ function toPosition(p : Position) : VSCode.Position { } export interface HighlightParams { - doc: VersionedTextDocumentIdentifier - ranges: Range[] + doc: VersionedTextDocumentIdentifier; + codeLenses: CodeLens[]; } export class HighlightService { DECORATION : VSCode.TextEditorDecorationType; - highlights : Map; + highlights : Map; dispose() { this.DECORATION.dispose(); @@ -41,7 +40,7 @@ export class HighlightService { } handle(params : HighlightParams) : void { - this.highlights.set(params.doc.uri, params.ranges); + this.highlights.set(params.doc.uri, params); this.refresh(params.doc); } @@ -52,10 +51,11 @@ export class HighlightService { const activeVersion = editor.document.version; if (docId.uri === activeUri && docId.version === activeVersion) { //We only update highlights in the active editor for now - let highlights : Range[] = this.highlights.get(docId.uri) || []; - let decorations = highlights.map(hl => toDecoration(hl)); + const highlightParams: HighlightParams = this.highlights.get(docId.uri); + const highlights: CodeLens[] = highlightParams.codeLenses || []; + let decorations = highlights.map(hl => toVSRange(hl.range)); editor.setDecorations(this.DECORATION, decorations); } } } -} \ No newline at end of file +} diff --git a/vscode-extensions/commons-vscode/src/launch-util.ts b/vscode-extensions/commons-vscode/src/launch-util.ts index c8efbb3c2..d90cb1761 100644 --- a/vscode-extensions/commons-vscode/src/launch-util.ts +++ b/vscode-extensions/commons-vscode/src/launch-util.ts @@ -6,17 +6,17 @@ import * as FS from 'fs'; import PortFinder = require('portfinder'); import * as Net from 'net'; import * as ChildProcess from 'child_process'; -import { TextDocumentIdentifier, RequestType, LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo, Range } from 'vscode-languageclient'; -import { TextDocument, OutputChannel, Disposable, window } from 'vscode'; +import { TextDocumentIdentifier, RequestType, LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo, Position } from 'vscode-languageclient'; +import {TextDocument, OutputChannel, Disposable, window, Event, EventEmitter} from 'vscode'; import { Trace, NotificationType } from 'vscode-jsonrpc'; import * as P2C from 'vscode-languageclient/lib/protocolConverter'; -import {WorkspaceEdit, Position} from 'vscode-languageserver-types'; import {HighlightService, HighlightParams} from './highlight-service'; import { log } from 'util'; import { tmpdir } from 'os'; import { JVM, findJvm, findJdk } from '@pivotal-tools/jvm-launch-utils'; import { registerClasspathService } from './classpath'; import { registerJavadocService } from './javadoc'; +import {HighlightCodeLensProvider} from "./code-lens-service"; let p2c = P2C.createConverter(); @@ -34,6 +34,7 @@ export interface ActivatorOptions { workspaceOptions: VSCode.WorkspaceConfiguration; checkjvm?: (context: VSCode.ExtensionContext, jvm: JVM) => any; preferJdk?: boolean; + highlightCodeLensSettingKey?: string; } type JavaOptions = { @@ -203,18 +204,52 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser let disposable = client.start(); + const codeLensListanableSetting = options.highlightCodeLensSettingKey ? new ListenablePreferenceSetting(options.highlightCodeLensSettingKey) : undefined; + let progressService = new ProgressService(); let highlightService = new HighlightService(); + const codelensService = new HighlightCodeLensProvider(); + let codeLensProviderSubscription: Disposable; + + VSCode.commands.registerCommand('org.springframework.showHoverAtPosition', (position: Position) => { + const editor = VSCode.window.activeTextEditor; + const vsPosition = new VSCode.Position(position.line, position.character); + editor.selection = new VSCode.Selection(vsPosition, vsPosition); + VSCode.commands.executeCommand('editor.action.showHover'); + }); + context.subscriptions.push(disposable); context.subscriptions.push(progressService); context.subscriptions.push(highlightService); + function toggleHighlightCodeLens() { + if (!codeLensProviderSubscription && codeLensListanableSetting.value) { + codeLensProviderSubscription = VSCode.languages.registerCodeLensProvider(options.clientOptions.documentSelector, codelensService); + context.subscriptions.push(codeLensProviderSubscription); + } else if (codeLensProviderSubscription) { + codeLensProviderSubscription.dispose(); + const idx = context.subscriptions.indexOf(codeLensProviderSubscription); + if (idx >= 0) { + context.subscriptions.splice(idx, 1); + } + codeLensProviderSubscription = null; + } + } + + if (codeLensListanableSetting) { + toggleHighlightCodeLens(); + codeLensListanableSetting.onDidChangeValue(() => toggleHighlightCodeLens()) + } + return client.onReady().then(() => { client.onNotification(progressNotification, (params: ProgressParams) => { progressService.handle(params); }); client.onNotification(highlightNotification, (params: HighlightParams) => { highlightService.handle(params); + if (codeLensListanableSetting && codeLensListanableSetting.value) { + codelensService.handle(params); + } }); client.onRequest(moveCursorRequest, (params: MoveCursorParams) => { let editors = VSCode.window.visibleTextEditors; @@ -278,3 +313,31 @@ class ProgressService { this.status = null; } } + +export interface ListenableSetting { + value: T; + onDidChangeValue: VSCode.Event +} + +export class ListenablePreferenceSetting implements ListenableSetting { + + private _onDidChangeValue = new EventEmitter(); + + constructor(private section: string) { + VSCode.workspace.onDidChangeConfiguration(e => { + console.log('Settings changed! value = ' + this.value); + if (e.affectsConfiguration(this.section)) { + this._onDidChangeValue.fire(); + } + }); + } + + get value(): T { + return VSCode.workspace.getConfiguration().get(this.section); + } + + get onDidChangeValue(): Event { + return this._onDidChangeValue.event; + } + +} diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 995e251ce..02c542b5e 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -1,23 +1,19 @@ 'use strict'; import * as VSCode from 'vscode'; -import * as Path from 'path'; -import * as FS from 'fs'; -import * as Net from 'net'; -import * as ChildProcess from 'child_process'; -import {LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo} from 'vscode-languageclient'; -import { workspace, TextDocument } from 'vscode'; +import {workspace} from 'vscode'; import * as commons from '@pivotal-tools/commons-vscode'; import {generate_pipeline, UserQuestioner} from '@pivotal-tools/pipeline-builder'; +import {LanguageClient} from "vscode-languageclient"; const PROPERTIES_LANGUAGE_ID = "spring-boot-properties"; const YAML_LANGUAGE_ID = "spring-boot-properties-yaml"; const JAVA_LANGUAGE_ID = "java"; /** Called when extension is activated */ -export function activate(context: VSCode.ExtensionContext) { +export function activate(context: VSCode.ExtensionContext): Thenable { // registerPipelineGenerator(context); @@ -53,7 +49,8 @@ export function activate(context: VSCode.ExtensionContext) { initializationOptions: { workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null } - } + }, + highlightCodeLensSettingKey: 'boot-java.highlight-codelens.on' }; return commons.activate(options, context); diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index dc70ddbfb..cde9f836d 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -68,6 +68,11 @@ "default": true, "description": "Enable/Disable Spring running Boot application live hints decorators in Java source code" }, + "boot-java.highlight-codelens.on": { + "type": "boolean", + "default": true, + "description": "Enable/Disable Spring running Boot application Code Lenses" + }, "boot-java.change-detection.on": { "type": "boolean", "default": false,