diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java index 4ad5ca7d0..545abaab3 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** * Provides existing Cloud Foundry client params, like target and credentials, * from the CLI config.json in the file system. - * + * * */ public class CfCliParamsProvider implements ClientParamsProvider { @@ -39,7 +39,7 @@ public class CfCliParamsProvider implements ClientParamsProvider { /* * (non-Javadoc) - * + * * @see org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget. * ClientParamsProvider#getParams() */ diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java index 810a3d43f..be991cb69 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposal.java @@ -30,5 +30,6 @@ public interface ICompletionProposal { String getDetail(); Renderable getDocumentation(); + default String getFilterText() { return getLabel(); } } diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index 3d6ecd561..a5079232e 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -105,7 +105,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { item.setLabel(completion.getLabel()); item.setKind(completion.getKind()); item.setSortText(sortkeys.next()); - item.setFilterText(completion.getLabel()); + item.setFilterText(completion.getFilterText()); item.setDetail(completion.getDetail()); item.setDocumentation(toMarkdown(completion.getDocumentation())); adaptEdits(item, doc, completion.getTextEdit()); diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/CompletionFactory.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/CompletionFactory.java index b3079856c..7979aac84 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/CompletionFactory.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/CompletionFactory.java @@ -24,16 +24,13 @@ public interface CompletionFactory { ICompletionProposal beanProperty(IDocument doc, String contextProperty, YType contextType, String query, YTypedProperty p, double score, DocumentEdits edits, YTypeUtil typeUtil); ICompletionProposal valueProposal(String value, String query, String label, YType type, double score, DocumentEdits edits, YTypeUtil typeUtil); - + /** - * Creates a completion with an EMPTY value and a display message indicating some error condition. The purpose of this is - * to show the user a completion with some meaningful information on why completion values were not fetched. - * - * @param message the error message to display to the user in the completion - * @param query - * @param type - * @param edits - * @return non-null proposal + * Create a fake completion proposal that represents an error message. Such a proposal, when applied does nothing. Its main purpose is to + * show a (possibly lengthy) error message to the user. + *

+ * If the error message is long you can include a ": " to divide the string into a 'short message' and a longer explanation. The longer explanation + * will be chopped-off from the message and displayed as a 'doc string'. */ - ICompletionProposal errorMessage(String message, String query, YType type, DocumentEdits edits, YTypeUtil typeUtil); + ICompletionProposal errorMessage(String query, String message); } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java index 350bf48a2..edbd158ee 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java @@ -16,6 +16,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.Document import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates; import org.springframework.ide.vscode.commons.yaml.schema.YType; @@ -23,7 +24,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; public class DefaultCompletionFactory implements CompletionFactory { - + private static final int ERROR_COMPLETION_SCORE = -10000000; public static class BeanPropertyProposal extends ScoreableProposal { @@ -48,7 +49,7 @@ public class DefaultCompletionFactory implements CompletionFactory { this.edits = edits; this.typeUtil = typeUtil; } - + @Override public double getBaseScore() { return baseScore; @@ -79,7 +80,7 @@ public class DefaultCompletionFactory implements CompletionFactory { return YPropertyInfoTemplates.createCompletionDocumentation(contextProperty, contextType, p); } } - + public class ValueProposal extends ScoreableProposal { private String value; @@ -119,7 +120,7 @@ public class DefaultCompletionFactory implements CompletionFactory { public DocumentEdits getTextEdit() { return edits; } - + @Override public String toString() { return "ValueProposal("+value+")"; @@ -135,38 +136,42 @@ public class DefaultCompletionFactory implements CompletionFactory { return null; } } - - public static final class ErrorProposal extends ScoreableProposal { - private final String label; - private DocumentEdits edits; - private YTypeUtil typeUtil; - private YType type; - public ErrorProposal(String label, YType type, DocumentEdits edits, YTypeUtil typeUtil) { - this.label = label; - this.edits = edits; - this.typeUtil = typeUtil; - this.type = type; + public static final class ErrorProposal extends ScoreableProposal { + private final String longMessage; + private String shortMessage; + private String filterText; + + public ErrorProposal(String query, String longMessage) { + this.filterText = query; + int split = longMessage.indexOf(": "); + if (split>0) { + this.shortMessage = longMessage.substring(0, split); + this.longMessage = longMessage.substring(split+2); + } else { + this.longMessage = longMessage; + this.shortMessage = longMessage; + } } @Override public DocumentEdits getTextEdit() { - return edits; + return new DocumentEdits(null); } @Override public String getLabel() { - return label; + return shortMessage; } @Override public CompletionItemKind getKind() { - return CompletionItemKind.Value; + return CompletionItemKind.Text; } @Override public Renderable getDocumentation() { - return null; + return Renderables.text(longMessage); } @Override @@ -181,7 +186,12 @@ public class DefaultCompletionFactory implements CompletionFactory { @Override public String getDetail() { - return typeUtil.niceTypeName(type); + return "Error"; + } + + @Override + public String getFilterText() { + return filterText; } } @@ -196,8 +206,7 @@ public class DefaultCompletionFactory implements CompletionFactory { } @Override - public ICompletionProposal errorMessage(String message, String query, YType type, DocumentEdits edits, YTypeUtil typeUtil) { - final String label = message; - return new ErrorProposal(label, type, edits, typeUtil); + public ICompletionProposal errorMessage(String query, String longMessage) { + return new ErrorProposal(query, longMessage); } } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index aa35d098a..af0554a0e 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -158,8 +158,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { try { values = typeUtil.getHintValues(type, getSchemaContext()); } catch (Exception e) { - DocumentEdits edits = new DocumentEdits(doc.getDocument()); - return ImmutableList.of(completionFactory().errorMessage(getMessage(e), query, type, edits, typeUtil)); + return ImmutableList.of(completionFactory().errorMessage(query, getMessage(e))); } if (values!=null) { ArrayList completions = new ArrayList<>(); diff --git a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index f49f91f1f..20ea262e3 100644 --- a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -444,7 +444,7 @@ public class Editor { assertEquals(expectedHover, hover.getContents().toString()); } - public void assertCompletionDetails(String expectLabel, String expectDetail, String expectDocSnippet) throws Exception { + public CompletionItem assertCompletionDetails(String expectLabel, String expectDetail, String expectDocSnippet) throws Exception { CompletionItem it = harness.resolveCompletionItem(assertCompletionWithLabel(expectLabel)); if (expectDetail!=null) { assertEquals(expectDetail, it.getDetail()); @@ -452,6 +452,7 @@ public class Editor { if (expectDocSnippet!=null) { assertContains(expectDocSnippet, it.getDocumentation()); } + return it; } protected CompletionItem assertCompletionWithLabel(String expectLabel) throws Exception { diff --git a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/TestAsserts.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/TestAsserts.java index 0431db23b..7e0d60fd9 100644 --- a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/TestAsserts.java +++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/TestAsserts.java @@ -11,8 +11,11 @@ package org.springframework.ide.vscode.languageserver.testharness; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import java.util.Collection; + public class TestAsserts { public static void assertContains(String needle, String haystack) { @@ -26,4 +29,14 @@ public class TestAsserts { fail("Found: "+needle+"\n in \n"+haystack); } } + + public static T assertOneElement(Collection collection) { + assertEquals("Wrong number of elements in "+ collection, 1, collection.size()); + for (T t : collection) { + return t; + } + throw new AssertionError("No elements found"); + } + + } diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java index c67d74d2a..cf28e20d8 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java @@ -39,10 +39,10 @@ public abstract class AbstractCFHintsProvider implements Callable call() throws Exception { - + try { List targets = targetCache.getOrCreate(); - + // Do NOT wrap the results in another list. Allow null values to return // as the reconcile framework expects null if hints failed to be resolved return getHints(targets); @@ -59,7 +59,7 @@ public abstract class AbstractCFHintsProvider implements Callable getHints(List targets) throws Exception; diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java index ddfafd032..71b046169 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java +++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -15,7 +15,9 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.*; import java.io.IOException; +import java.util.List; +import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.junit.Before; @@ -27,9 +29,13 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; +import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; +import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.*; + import com.google.common.collect.ImmutableList; public class ManifestYamlEditorTest { @@ -844,6 +850,57 @@ public class ManifestYamlEditorTest { assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); } + @Test + public void servicesContentAssistShowErrorMessageWhenNotLoggedIn() throws Exception { + reset(cloudfoundry.paramsProvider); + + String exceptionMessage = "Please login to cf"; + + when(cloudfoundry.paramsProvider.getParams()).thenThrow(new NoTargetsException(exceptionMessage)); + + String textBefore = + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - <*>"; + Editor editor = harness.newEditor( + textBefore + ); + + //Applying the single completion should do nothing in the editor: + editor.assertCompletions(textBefore); + + //The message from the exception should appear in the 'doc string': + editor.assertCompletionDetails("No Cloudfoundry Targets", "Error", exceptionMessage); + + } + + @Test + public void servicesContentAssistShowErrorMessageWhenNotLoggedIn_nonEmptyQueryString() throws Exception { + reset(cloudfoundry.paramsProvider); + + String exceptionMessage = "Please login to cf"; + + when(cloudfoundry.paramsProvider.getParams()).thenThrow(new NoTargetsException(exceptionMessage)); + + String textBefore = + "applications:\n" + + "- name: foo\n" + + " services:\n" + + " - something<*>"; + Editor editor = harness.newEditor( + textBefore + ); + + //Applying the single completion should do nothing in the editor: + editor.assertCompletions(textBefore); + + //The message from the exception should appear in the 'doc string': + CompletionItem completion = editor.assertCompletionDetails("No Cloudfoundry Targets", "Error", exceptionMessage); + //query string should match the 'filter text' otherwise vscode will filter the item and it will be gone! + assertEquals("something", completion.getFilterText()); + } + ////////////////////////////////////////////////////////////////////////////// private void assertCompletions(String textBefore, String... textAfter) throws Exception {