From a6fdfe701a5e3533712d68fdfdf458bac94cfff0 Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Fri, 18 Sep 2020 11:09:15 +0200 Subject: [PATCH 1/8] fix issue with generic code completion that is not displayed in list Signed-off-by: Luca Stocchi --- .../tektoncd/completion/DictionaryContributor.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java index a02c9c93b..dc2096e96 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java @@ -13,8 +13,7 @@ import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.completion.CompletionType; import com.intellij.patterns.PlatformPatterns; -import org.jetbrains.yaml.YAMLTokenTypes; -import org.jetbrains.yaml.psi.YAMLScalar; +import org.jetbrains.yaml.psi.YAMLPsiElement; public class DictionaryContributor extends CompletionContributor { public DictionaryContributor() { @@ -37,10 +36,10 @@ public DictionaryContributor() { // code completion not related to a specific place in a file. It doesn't depend on any tag extend(CompletionType.BASIC, PlatformPatterns - .psiElement(YAMLTokenTypes.TEXT) + .psiElement() .withParent( PlatformPatterns - .psiElement(YAMLScalar.class)), + .psiElement(YAMLPsiElement.class)), new GeneralCompletionProvider()); } } From 81ff9dd029aa5df7dbd480217a9da2505482b09d Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Fri, 18 Sep 2020 19:28:20 +0200 Subject: [PATCH 2/8] add initial code completion for when clause (#209) Signed-off-by: Luca Stocchi --- .../completion/BaseCompletionProvider.java | 79 +++++++++ .../completion/DictionaryContributor.java | 4 + .../completion/GeneralCompletionProvider.java | 166 ++++++++++++++++-- .../OperatorInWhenClauseCodeCompletion.java | 42 +++++ .../RunAfterCompletionProvider.java | 68 ++----- .../devtools/intellij/tektoncd/tkn/Tkn.java | 18 +- .../intellij/tektoncd/tkn/TknCli.java | 9 + src/test/resources/condition.yaml | 20 ++- 8 files changed, 337 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java create mode 100644 src/main/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletion.java diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java new file mode 100644 index 000000000..1a3859c0d --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. + ******************************************************************************/ +package com.redhat.devtools.intellij.tektoncd.completion; + +import com.fasterxml.jackson.databind.JsonNode; +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.redhat.devtools.intellij.common.utils.YAMLHelper; +import com.redhat.devtools.intellij.tektoncd.tkn.Tkn; +import com.redhat.devtools.intellij.tektoncd.utils.TreeHelper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.StreamSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class BaseCompletionProvider extends CompletionProvider { + Logger logger = LoggerFactory.getLogger(BaseCompletionProvider.class); + + protected Tkn getClient(CompletionParameters parameters) { + return TreeHelper.getTkn(parameters.getEditor().getProject()); + } + + /** + * Return a list of tasks present in the pipeline except the current task and others if specified + * + * @param parameters data related to the current document + * @param currentTaskElement the psielement representing the task where the user is typing in + * @param tasksToExclude additional tasks to exclude from the resulting list + * @return + */ + protected List getFilteredTasksInPipeline(CompletionParameters parameters, PsiElement currentTaskElement, List tasksToExclude) { + List tasks = new ArrayList<>(); + + try { + String yamlUntilTask = parameters.getEditor().getDocument().getText(new TextRange(0, currentTaskElement.getTextOffset())); + long taskPosition = 0; + try { + JsonNode tasksNodeUntilSelected = YAMLHelper.getValueFromYAML(yamlUntilTask, new String[]{"spec"} ); + if (tasksNodeUntilSelected.has("tasks")) { + taskPosition = StreamSupport.stream(tasksNodeUntilSelected.get("tasks").spliterator(),true).count(); + } + } catch (IOException e) { + logger.warn("Error: " + e.getLocalizedMessage()); + } + + // get all tasks node found in the pipeline and add valid options to lookup list + String yaml = parameters.getEditor().getDocument().getText(); + JsonNode tasksNode = YAMLHelper.getValueFromYAML(yaml, new String[]{"spec", "tasks"} ); + int cont = 0; + if (tasksNode != null) { + for (JsonNode item : tasksNode) { + if (item != null && cont != taskPosition) { + String name = item.has("name") ? item.get("name").asText("") : ""; + if (!name.isEmpty() && !tasksToExclude.contains(name)) { + tasks.add(name); + } + } + cont++; + } + } + } catch (IOException e) { + logger.warn("Error: " + e.getLocalizedMessage()); + } + + return tasks; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java index dc2096e96..dfcf24925 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java @@ -29,6 +29,10 @@ public DictionaryContributor() { extend(CompletionType.BASIC, YamlElementPatternHelper.getMultipleLineScalarKey("runAfter"), new RunAfterCompletionProvider()); + // operator - when clause + extend(CompletionType.BASIC, + YamlElementPatternHelper.getAfterParentScalarKeyInSequence("Operator", "when"), + new OperatorInWhenClauseCodeCompletion()); // resource in pipeline extend(CompletionType.BASIC, YamlElementPatternHelper.getAfterParentScalarKeyInSequence("resource", "resources", "inputs", "outputs"), diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java index c77fe65eb..0f84e9c84 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java @@ -10,11 +10,14 @@ ******************************************************************************/ package com.redhat.devtools.intellij.tektoncd.completion; +import com.fasterxml.jackson.databind.JsonNode; import com.intellij.codeInsight.completion.CompletionParameters; -import com.intellij.codeInsight.completion.CompletionProvider; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.psi.PsiElement; import com.intellij.util.ProcessingContext; +import com.redhat.devtools.intellij.common.utils.YAMLHelper; +import com.redhat.devtools.intellij.tektoncd.tkn.Tkn; import com.redhat.devtools.intellij.tektoncd.tkn.component.field.Input; import com.redhat.devtools.intellij.tektoncd.tkn.component.field.Output; import com.redhat.devtools.intellij.tektoncd.utils.model.ConfigurationModel; @@ -22,6 +25,7 @@ import com.redhat.devtools.intellij.tektoncd.utils.model.resources.ConditionConfigurationModel; import com.redhat.devtools.intellij.tektoncd.utils.model.resources.PipelineConfigurationModel; import com.redhat.devtools.intellij.tektoncd.utils.model.resources.TaskConfigurationModel; +import io.fabric8.tekton.pipeline.v1beta1.Task; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -38,7 +42,7 @@ /** * This provider is called for code completion that does not belong to a specific place in the file. */ -public class GeneralCompletionProvider extends CompletionProvider { +public class GeneralCompletionProvider extends BaseCompletionProvider { Logger logger = LoggerFactory.getLogger(GeneralCompletionProvider.class); private static final String[] workspaceVariables = new String[]{ "path", "claim", "volume" }; @@ -48,6 +52,7 @@ public class GeneralCompletionProvider extends CompletionProvider the prefix is "$(params." @@ -77,27 +82,28 @@ protected void addCompletions(@NotNull CompletionParameters parameters, Processi * @param insertOffset the position where the lookup has to be copied on * @return */ - private List getInputsLookups(CompletionParameters parameters, String prefix, String completionPrefix, int insertOffset) { + private List getVariablesLookups(CompletionParameters parameters, String prefix, String completionPrefix, int insertOffset) { String configuration = parameters.getEditor().getDocument().getText(); ConfigurationModel model = ConfigurationModelFactory.getModel(configuration); if (model == null) return Collections.emptyList(); - return getLookupsByKind(model, prefix, completionPrefix, insertOffset); + return getLookupsByKind(parameters, model, prefix, completionPrefix, insertOffset); } /** * Get lookups for the current parameter based on the kind * + * @param parameters * @param model the model built by the configuration * @param prefix the prefix we are really using. E.g the line we are in is "value: test -f $(params." -> the prefix is "$(params." * @param completionPrefix the prefix we need to add to the lookup to make it be shown by IJ. E.g the line we are in is "value: test -f $(params." -> the completionPrefix is "test -f $(params." * @param insertOffset the position where the lookup has to be copied on * @return */ - private List getLookupsByKind(ConfigurationModel model, String prefix, String completionPrefix, int insertOffset) { + private List getLookupsByKind(CompletionParameters parameters, ConfigurationModel model, String prefix, String completionPrefix, int insertOffset) { switch (model.getKind().toLowerCase()) { case "pipeline": - return getLookupsPipeline(model, prefix, completionPrefix, insertOffset); + return getLookupsPipeline(parameters, model, prefix, completionPrefix, insertOffset); case "task": return getLookupsTask(model, prefix, completionPrefix, insertOffset); case "condition": @@ -110,14 +116,26 @@ private List getLookupsByKind(ConfigurationModel model, St /** * Get lookups for the opened pipeline configuration * + * @param parameters * @param model the model built by the configuration * @param prefix the prefix we are really using. E.g the line we are in is "value: test -f $(params." -> the prefix is "$(params." * @param completionPrefix the prefix we need to add to the lookup to make it be shown by IJ. E.g the line we are in is "value: test -f $(params." -> the completionPrefix is "test -f $(params." * @param insertOffset the position where the lookup has to be copied on * @return */ - private List getLookupsPipeline(ConfigurationModel model, String prefix, String completionPrefix, int insertOffset) { - return getParamLookups(((PipelineConfigurationModel)model).getParams(), prefix, completionPrefix, insertOffset); + private List getLookupsPipeline(CompletionParameters parameters, ConfigurationModel model, String prefix, String completionPrefix, int insertOffset) { + List lookups = new ArrayList<>(); + + // get lookups for params + lookups.addAll(getParamLookups(((PipelineConfigurationModel)model).getParams(), prefix, completionPrefix, insertOffset)); + + // get lookups for tasks result + String headPrefix_8 = prefix.length() > 8 ? prefix.substring(0, 8) : prefix; + if ("$(tasks.".contains(headPrefix_8)) { + lookups.addAll(getTasksInPipelineLookups(parameters, model.getNamespace(), prefix, completionPrefix, insertOffset)); + } + + return lookups; } /** @@ -256,6 +274,45 @@ private List getInnerResourceLookups(List resource return lookups; } + /** + * Get lookups for tasks in pipeline + * + * @param parameters data related to the current document + * @param namespace the namespace the pipeline belongs to + * @param prefix the prefix we are really using. E.g the line we are in is "value: test -f $(params." -> the prefix is "$(params." + * @param completionPrefix the prefix we need to add to the lookup to make it be shown by IJ. E.g the line we are in is "value: test -f $(params." -> the completionPrefix is "test -f $(params." + * @param insertOffset the position where the lookup has to be copied on + * @return + */ + private List getTasksInPipelineLookups(CompletionParameters parameters, String namespace, String prefix, String completionPrefix, int insertOffset) { + List lookups = new ArrayList<>(); + // get lookups for tasks result + String headPrefix_8 = prefix.length() > 8 ? prefix.substring(0, 8) : prefix; + if ("$(tasks.".contains(headPrefix_8)) { + // check if a task has already been picked up and we need to show specific result completion - e.g $(tasks.foo. + String task = getResource("tasks", prefix); + if (task != null) { + String field = getField("tasks", task, prefix); + if (field != null) { + String actualTaskName = getActualTaskNameByTaskNameGivenInPipeline(parameters, task); + if (!actualTaskName.isEmpty()) { + lookups.addAll(getLookupsBySelectedTaskAndField(parameters, namespace, actualTaskName, "tasks." + task, field, completionPrefix, insertOffset)); + } + } else { + lookups.addAll(getLookupsBySelectedTask("tasks." + task, completionPrefix, insertOffset)); + } + } else { + PsiElement currentTask = parameters.getPosition().getParent().getParent().getParent().getParent().getParent().getParent().getParent().getContext(); + // get list of all tasks available in pipeline except the one in which we are typing + List tasksInPipeline = getFilteredTasksInPipeline(parameters, currentTask, Collections.emptyList()); + tasksInPipeline.stream().forEach(taskName -> { + lookups.add(createInnerLookup("tasks." + taskName, completionPrefix, insertOffset)); + }); + } + } + return lookups; + } + /** * Get lookups for workspace * @@ -308,6 +365,22 @@ private String getResource(String type, String prefix) { return null; } + /** + * Retrieve the name of a resource + * + * @param type the resource type (workspaces/resource.input/resource.output....) + * @param resource the name of the resource chosen + * @param prefix the prefix we are really using. E.g the line we are in is "value: test -f $(params.foo." -> the prefix is "$(params.foo." + * @return the name of the field. E.g (type: tasks, prefix: $(tasks.foo.results) -> results + */ + private String getField(String type, String resource, String prefix) { + Matcher matcher = Pattern.compile("\\$\\(" + type + "\\." + resource + "\\.([^.]+)\\.").matcher(prefix); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + /** * It return lookups for a workspace * @@ -353,6 +426,79 @@ private List getLookupsByResource(String resource, String return lookups; } + /** + * It return lookups for a task + * + * @param taskPrefix prefix + task name in pipeline (e.g tasks.foo) + * @param completionPrefix the prefix we need to add to the lookup to make it be shown by IJ. E.g the line we are in is "value: test -f $(params." -> the completionPrefix is "test -f $(params." + * @param insertOffset the position where the lookup has to be copied on + * @return + */ + private List getLookupsBySelectedTask(String taskPrefix, String completionPrefix, int insertOffset) { + List lookups = new ArrayList<>(); + Arrays.stream(taskFields).forEach(field -> { + String lookup = taskPrefix + "." + field; + lookups.add(LookupElementBuilder.create(completionPrefix + field) + .withPresentableText(field) + .withLookupString(field) + .withInsertHandler(new VariableCompletionAutoInsertHandler(lookup, insertOffset))); + }); + return lookups; + } + + /** + * it returns lookups for all values belonging to the task field + * + * @param parameters + * @param namespace the namespace to use to find the task + * @param actualTaskName the actual name of the task to get (N.B: it may be different from the name of the task in the pipeline) + * @param taskPrefix prefix + task name in pipeline + field (e.g tasks.foo) + * @param field field to take the values (e.g results) + * @param completionPrefix the prefix we need to add to the lookup to make it be shown by IJ. E.g the line we are in is "value: test -f $(params." -> the completionPrefix is "test -f $(params." + * @param insertOffset the position where the lookup has to be copied on + * @return + */ + private List getLookupsBySelectedTaskAndField(CompletionParameters parameters, String namespace, String actualTaskName, String taskPrefix, String field, String completionPrefix, int insertOffset) { + List lookups = new ArrayList<>(); + Tkn client = getClient(parameters); + try { + Task task = client.getTask(namespace, actualTaskName); + task.getSpec().getResults().forEach(item -> { + String lookup = taskPrefix + "." + field + "." + item.getName(); + lookups.add(LookupElementBuilder.create(completionPrefix + item.getName()) + .withPresentableText(item.getName()) + .withLookupString(item.getName()) + .withInsertHandler(new VariableCompletionAutoInsertHandler(lookup, insertOffset))); + }); + } catch (IOException e) { + logger.warn(e.getLocalizedMessage()); + } + return lookups; + } + + private String getActualTaskNameByTaskNameGivenInPipeline(CompletionParameters parameters, String taskName) { + String actualName = ""; + String fullDocumentYaml = parameters.getEditor().getDocument().getText(); + try { + JsonNode tasksNode = YAMLHelper.getValueFromYAML(fullDocumentYaml, new String[]{"spec", "tasks"} ); + if (tasksNode != null) { + for (JsonNode item : tasksNode) { + if (item != null) { + String name = item.has("name") ? item.get("name").asText("") : ""; + if (!name.isEmpty() && name.equalsIgnoreCase(taskName)) { + actualName = item.has("taskRef") ? item.get("taskRef").has("name") ? item.get("taskRef").get("name").asText("") : "" : ""; + break; + } + } + } + } + } catch (IOException e) { + logger.warn(e.getLocalizedMessage()); + } + + return actualName; + } + private String[] getVariablesByType(String type) { switch (type.toLowerCase()) { case "git": diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletion.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletion.java new file mode 100644 index 000000000..96ae02bbd --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletion.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. + ******************************************************************************/ +package com.redhat.devtools.intellij.tektoncd.completion; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.util.ProcessingContext; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public class OperatorInWhenClauseCodeCompletion extends CompletionProvider { + + private final static String[] operators = new String[] { "in", "notin" }; + + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { + result.addAllElements(getOperatorsLookups()); + result.stopHere(); + } + + private List getOperatorsLookups() { + List lookups = new ArrayList<>(); + Arrays.stream(operators).forEach(operator -> { + lookups.add(LookupElementBuilder.create(operator) + .withPresentableText(operator) + .withLookupString(operator)); + }); + return lookups; + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/RunAfterCompletionProvider.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/RunAfterCompletionProvider.java index 3d693c157..84cc2aa1b 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/RunAfterCompletionProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/RunAfterCompletionProvider.java @@ -10,27 +10,18 @@ ******************************************************************************/ package com.redhat.devtools.intellij.tektoncd.completion; -import com.fasterxml.jackson.databind.JsonNode; import com.intellij.codeInsight.completion.CompletionParameters; -import com.intellij.codeInsight.completion.CompletionProvider; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.util.ProcessingContext; -import com.redhat.devtools.intellij.common.utils.YAMLHelper; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class RunAfterCompletionProvider extends CompletionProvider { - Logger logger = LoggerFactory.getLogger(ConditionCompletionProvider.class); +public class RunAfterCompletionProvider extends BaseCompletionProvider { @Override protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { @@ -39,48 +30,21 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull } private List getTasksLookups(CompletionParameters parameters) { - List lookups = new ArrayList<>(); - - try { - // get the list of tasks already added in the runAfter array to avoid showing them again - List tasksAlreadyAdded = Arrays.asList(parameters.getPosition().getParent().getParent().getParent().getText().split("\n")); - tasksAlreadyAdded = tasksAlreadyAdded.stream().map(task -> task.trim().replaceFirst("- ", "")).collect(Collectors.toList()); - - // get current task node position - PsiElement currentTask = parameters.getPosition().getParent().getParent().getParent().getParent().getParent().getContext(); - String yamlUntilTask = parameters.getEditor().getDocument().getText(new TextRange(0, currentTask.getTextOffset())); - long taskPosition = 0; - try { - JsonNode tasksNodeUntilSelected = YAMLHelper.getValueFromYAML(yamlUntilTask, new String[]{"spec"} ); - if (tasksNodeUntilSelected.has("tasks")) { - taskPosition = StreamSupport.stream(tasksNodeUntilSelected.get("tasks").spliterator(),true).count(); - } - } catch (IOException e) { - logger.warn("Error: " + e.getLocalizedMessage()); - } - - // get all tasks node found in the pipeline and add valid options to lookup list - String yaml = parameters.getEditor().getDocument().getText(); - JsonNode tasksNode = YAMLHelper.getValueFromYAML(yaml, new String[]{"spec", "tasks"} ); - int cont = 0; - if (tasksNode != null) { - for (JsonNode item : tasksNode) { - if (item != null && cont != taskPosition) { - String name = item.has("name") ? item.get("name").asText("") : ""; - if (!name.isEmpty() && !tasksAlreadyAdded.contains(name)) { - lookups.add(0, LookupElementBuilder.create(name) - .withPresentableText(name) - .withLookupString(name)); - } - } - cont++; - } - } - } catch (IOException e) { - logger.warn("Error: " + e.getLocalizedMessage()); - } - - return lookups; + List tasksLookups = new ArrayList<>(); + // get the list of tasks already added in the runAfter array to avoid showing them again + List tasksAlreadyAdded = Arrays.asList(parameters.getPosition().getParent().getParent().getParent().getText().split("\n")); + tasksAlreadyAdded = tasksAlreadyAdded.stream().map(task -> task.trim().replaceFirst("- ", "")).collect(Collectors.toList()); + + // get current task node position + PsiElement currentTask = parameters.getPosition().getParent().getParent().getParent().getParent().getParent().getContext(); + + List tasksInPipeline = getFilteredTasksInPipeline(parameters, currentTask, tasksAlreadyAdded); + tasksInPipeline.stream().forEach(task -> { + tasksLookups.add(0, LookupElementBuilder.create(task) + .withPresentableText(task) + .withLookupString(task)); + }); + return tasksLookups; } } diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java index 58c0380eb..e7ef454f3 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java @@ -225,7 +225,7 @@ public interface Tkn { /** * - * @param namespace the namespace of the task + * @param namespace the namespace of the triggerTemplate * @param triggerTemplate the triggerTemplate to use * @return triggerTemplate configuration * @throws IOException if communication errored @@ -235,7 +235,7 @@ public interface Tkn { /** * Get triggerBinding configuration in YAML * - * @param namespace the namespace of the task + * @param namespace the namespace of the triggerBinding * @param triggerBinding the triggerBinding to use * @return triggerBinding configuration * @throws IOException if communication errored @@ -245,7 +245,7 @@ public interface Tkn { /** * Get clusterTriggerBinding configuration in YAML * - * @param namespace the namespace of the task + * @param namespace the namespace of the clusterTriggerBinding * @param ctb the clusterTriggerBinding to use * @return clusterTriggerBinding configuration * @throws IOException if communication errored @@ -255,13 +255,23 @@ public interface Tkn { /** * Get eventListener configuration in YAML * - * @param namespace the namespace of the task + * @param namespace the namespace of the eventListener * @param eventListener the eventListener to use * @return eventListener configuration * @throws IOException if communication errored */ String getEventListenerYAML(String namespace, String eventListener) throws IOException; + /** + * Get the task by its name + * + * @param namespace the namespace of the task + * @param task the task to use + * @return the task object + * @throws IOException if communication errored + */ + Task getTask(String namespace, String task) throws IOException; + /** * Delete a list of pipelines * diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java index b38d734de..bd0aeecba 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java @@ -254,6 +254,15 @@ public String getEventListenerYAML(String namespace, String eventListener) throw return ExecHelper.execute(command, envVars, "eventlistener", "describe", eventListener, "-n", namespace, "-o", "yaml"); } + @Override + public Task getTask(String namespace, String task) throws IOException { + try { + return client.adapt(TektonClient.class).v1beta1().tasks().inNamespace(namespace).withName(task).get(); + } catch (KubernetesClientException e) { + throw new IOException(e); + } + } + @Override public void deletePipelines(String namespace, List pipelines, boolean deleteRelatedResources) throws IOException { if (deleteRelatedResources) { diff --git a/src/test/resources/condition.yaml b/src/test/resources/condition.yaml index a7063e52d..2b4597b40 100644 --- a/src/test/resources/condition.yaml +++ b/src/test/resources/condition.yaml @@ -4,6 +4,20 @@ metadata: name: foo namespace: tekton spec: - check: - image: alpine - script: 'test -f' \ No newline at end of file + - name: foo + taskRef: + kind: Task + name: echo-hello-world + when: + - Input: $(params.n2) + Operator: in + Values: + - "2" + - Input: 'ciccio' + Operator: notin + Values: + - 'ciccio' + - Input: 'ccc' + Operator: notin + Values: + - 'dsa' \ No newline at end of file From 8ecb2dfcbb33424835e6c55744f20ae013945188 Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Fri, 18 Sep 2020 19:31:44 +0200 Subject: [PATCH 3/8] restore old test content Signed-off-by: Luca Stocchi --- src/test/resources/condition.yaml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/test/resources/condition.yaml b/src/test/resources/condition.yaml index 2b4597b40..a7063e52d 100644 --- a/src/test/resources/condition.yaml +++ b/src/test/resources/condition.yaml @@ -4,20 +4,6 @@ metadata: name: foo namespace: tekton spec: - - name: foo - taskRef: - kind: Task - name: echo-hello-world - when: - - Input: $(params.n2) - Operator: in - Values: - - "2" - - Input: 'ciccio' - Operator: notin - Values: - - 'ciccio' - - Input: 'ccc' - Operator: notin - Values: - - 'dsa' \ No newline at end of file + check: + image: alpine + script: 'test -f' \ No newline at end of file From 17313de33ef5a81922f16075e08ef82557e006db Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Sat, 19 Sep 2020 18:22:37 +0200 Subject: [PATCH 4/8] add tests Signed-off-by: Luca Stocchi --- .../GenericCompletionProviderTest.java | 15 ++++++++++ ...peratorInWhenClauseCodeCompletionTest.java | 29 +++++++++++++++++++ .../completion/generic/pipeline10.yaml | 19 ++++++++++++ .../completion/generic/pipeline11.yaml | 19 ++++++++++++ .../completion/generic/pipeline12.yaml | 22 ++++++++++++++ .../completion/generic/pipeline13.yaml | 22 ++++++++++++++ .../completion/generic/pipeline5.yaml | 5 +--- .../completion/generic/pipeline7.yaml | 5 +--- .../completion/generic/pipeline9.yaml | 13 +++++++++ .../operatorInWhenClause/pipeline1.yaml | 16 ++++++++++ 10 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletionTest.java create mode 100644 src/test/resources/completion/generic/pipeline10.yaml create mode 100644 src/test/resources/completion/generic/pipeline11.yaml create mode 100644 src/test/resources/completion/generic/pipeline12.yaml create mode 100644 src/test/resources/completion/generic/pipeline13.yaml create mode 100644 src/test/resources/completion/generic/pipeline9.yaml create mode 100644 src/test/resources/completion/operatorInWhenClause/pipeline1.yaml diff --git a/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/GenericCompletionProviderTest.java b/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/GenericCompletionProviderTest.java index 66b5c54b6..eb9dced5e 100644 --- a/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/GenericCompletionProviderTest.java +++ b/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/GenericCompletionProviderTest.java @@ -37,6 +37,21 @@ public void testPipelineCompletionWithParamsAndKeywordPartiallyTyped() { assertOrderedEquals(getSuggestionsForFile("pipeline8.yaml"), "$(parparams.param1", "$(parparams.param2"); } + @Test + public void testPipelineCompletionWithOneTask() { + assertTrue(getSuggestionsForFile("pipeline9.yaml").isEmpty()); + } + + @Test + public void testPipelineCompletionWithMultipleTasks() { + assertOrderedEquals(getSuggestionsForFile("pipeline10.yaml"), "$(tasks.step2", "$(tasks.step3"); + } + + @Test + public void testPipelineCompletionWithTasksAndParams() { + assertOrderedEquals(getSuggestionsForFile("pipeline13.yaml"), "$(params.param1", "$(params.param2", "$(tasks.step2", "$(tasks.step3"); + } + ///////////////////////////////////////////////////////// /// TASK - VARIABLES ///////////////////////////////////////////////////////// diff --git a/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletionTest.java b/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletionTest.java new file mode 100644 index 000000000..d96a5b61a --- /dev/null +++ b/src/test/java/com/redhat/devtools/intellij/tektoncd/completion/OperatorInWhenClauseCodeCompletionTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. + ******************************************************************************/ +package com.redhat.devtools.intellij.tektoncd.completion; + +import org.junit.Test; + + +import static com.intellij.testFramework.UsefulTestCase.assertOrderedEquals; + +public class OperatorInWhenClauseCodeCompletionTest extends BaseCompletionProviderTest{ + + @Test + public void testCompletionOperatorInWhenClause() { + assertOrderedEquals(getSuggestionsForFile("pipeline1.yaml"), "in", "notin"); + } + + @Override + public String getTestDataPath() { + return "src/test/resources/completion/operatorInWhenClause"; + } +} diff --git a/src/test/resources/completion/generic/pipeline10.yaml b/src/test/resources/completion/generic/pipeline10.yaml new file mode 100644 index 000000000..7dfbb997e --- /dev/null +++ b/src/test/resources/completion/generic/pipeline10.yaml @@ -0,0 +1,19 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: foo + namespace: tekton +spec: + tasks: + - name: step1 + taskRef: + name: task1 + params: + - name: foo2 + value: $( + - name: step2 + taskRef: + name: task1 + - name: step3 + taskRef: + name: task1 \ No newline at end of file diff --git a/src/test/resources/completion/generic/pipeline11.yaml b/src/test/resources/completion/generic/pipeline11.yaml new file mode 100644 index 000000000..411522a19 --- /dev/null +++ b/src/test/resources/completion/generic/pipeline11.yaml @@ -0,0 +1,19 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: foo + namespace: tekton +spec: + params: + - name: param1 + - name: param2 + tasks: + - name: step1 + taskRef: + name: task1 + params: + - name: foo2 + value: $(tas + - name: step2 + taskRef: + name: task1 \ No newline at end of file diff --git a/src/test/resources/completion/generic/pipeline12.yaml b/src/test/resources/completion/generic/pipeline12.yaml new file mode 100644 index 000000000..89dd8a2bc --- /dev/null +++ b/src/test/resources/completion/generic/pipeline12.yaml @@ -0,0 +1,22 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: foo + namespace: tekton +spec: + params: + - name: param1 + - name: param2 + tasks: + - name: step1 + taskRef: + name: task1 + params: + - name: foo2 + value: $(tasks.step2. + - name: step2 + taskRef: + name: task1 + - name: step3 + taskRef: + name: task1 \ No newline at end of file diff --git a/src/test/resources/completion/generic/pipeline13.yaml b/src/test/resources/completion/generic/pipeline13.yaml new file mode 100644 index 000000000..4414186bc --- /dev/null +++ b/src/test/resources/completion/generic/pipeline13.yaml @@ -0,0 +1,22 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: foo + namespace: tekton +spec: + params: + - name: param1 + - name: param2 + tasks: + - name: step1 + taskRef: + name: task1 + params: + - name: foo2 + value: $( + - name: step2 + taskRef: + name: task1 + - name: step3 + taskRef: + name: task1 \ No newline at end of file diff --git a/src/test/resources/completion/generic/pipeline5.yaml b/src/test/resources/completion/generic/pipeline5.yaml index cf38c0f14..bc64cfa37 100644 --- a/src/test/resources/completion/generic/pipeline5.yaml +++ b/src/test/resources/completion/generic/pipeline5.yaml @@ -10,7 +10,4 @@ spec: name: task1 params: - name: foo2 - value: $( - - name: step2 - taskRef: - name: task1 \ No newline at end of file + value: $( \ No newline at end of file diff --git a/src/test/resources/completion/generic/pipeline7.yaml b/src/test/resources/completion/generic/pipeline7.yaml index a20c57812..8793f8f98 100644 --- a/src/test/resources/completion/generic/pipeline7.yaml +++ b/src/test/resources/completion/generic/pipeline7.yaml @@ -13,7 +13,4 @@ spec: name: task1 params: - name: foo2 - value: $( - - name: step2 - taskRef: - name: task1 \ No newline at end of file + value: $( \ No newline at end of file diff --git a/src/test/resources/completion/generic/pipeline9.yaml b/src/test/resources/completion/generic/pipeline9.yaml new file mode 100644 index 000000000..bc64cfa37 --- /dev/null +++ b/src/test/resources/completion/generic/pipeline9.yaml @@ -0,0 +1,13 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: foo + namespace: tekton +spec: + tasks: + - name: step1 + taskRef: + name: task1 + params: + - name: foo2 + value: $( \ No newline at end of file diff --git a/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml b/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml new file mode 100644 index 000000000..d439f1a4b --- /dev/null +++ b/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml @@ -0,0 +1,16 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: foo + namespace: tekton +spec: + params: + - name: param1 + - name: param2 + tasks: + - name: step1 + taskRef: + name: task1 + when: + - Input: 'foo' + Operator: \ No newline at end of file From 3e3d9abdaa0929a941a77899029e24adb802f1ea Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Tue, 22 Sep 2020 15:15:31 +0200 Subject: [PATCH 5/8] add virtual file system Signed-off-by: Luca Stocchi --- .../devtools/intellij/tektoncd/Constants.java | 2 + .../completion/GeneralCompletionProvider.java | 21 ++- .../listener/TreeExpansionListener.java | 2 +- .../devtools/intellij/tektoncd/tkn/Tkn.java | 21 +-- .../intellij/tektoncd/tkn/TknCli.java | 22 +-- .../utils/TektonVirtualFileManager.java | 132 ++++++++++++++++++ .../intellij/tektoncd/utils/TreeHelper.java | 38 +++++ .../intellij/tektoncd/utils/WatchHandler.java | 35 ++++- 8 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TektonVirtualFileManager.java diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/Constants.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/Constants.java index e754df688..73dadf0a7 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/Constants.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/Constants.java @@ -28,6 +28,8 @@ public class Constants { public static final String KIND_TRIGGERBINDINGS = "triggerbindings"; public static final String KIND_CLUSTERTRIGGERBINDINGS = "clustertriggerbindings"; public static final String KIND_EVENTLISTENER = "eventlisteners"; + public static final String KIND_PIPELINERUNS = "pipelineruns"; + public static final String KIND_TASKRUNS = "taskruns"; public static final String KIND_PIPELINE = "pipeline"; public static final String KIND_PIPELINERUN = "pipelinerun"; diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java index 0f84e9c84..e8cea23d0 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/GeneralCompletionProvider.java @@ -11,20 +11,24 @@ package com.redhat.devtools.intellij.tektoncd.completion; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Strings; import com.intellij.codeInsight.completion.CompletionParameters; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.util.ProcessingContext; import com.redhat.devtools.intellij.common.utils.YAMLHelper; -import com.redhat.devtools.intellij.tektoncd.tkn.Tkn; import com.redhat.devtools.intellij.tektoncd.tkn.component.field.Input; import com.redhat.devtools.intellij.tektoncd.tkn.component.field.Output; +import com.redhat.devtools.intellij.tektoncd.utils.TektonVirtualFileManager; import com.redhat.devtools.intellij.tektoncd.utils.model.ConfigurationModel; import com.redhat.devtools.intellij.tektoncd.utils.model.ConfigurationModelFactory; import com.redhat.devtools.intellij.tektoncd.utils.model.resources.ConditionConfigurationModel; import com.redhat.devtools.intellij.tektoncd.utils.model.resources.PipelineConfigurationModel; import com.redhat.devtools.intellij.tektoncd.utils.model.resources.TaskConfigurationModel; +import io.fabric8.kubernetes.client.utils.Serialization; import io.fabric8.tekton.pipeline.v1beta1.Task; import java.io.IOException; import java.util.ArrayList; @@ -39,6 +43,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASK; +import static com.redhat.devtools.intellij.tektoncd.Constants.NAMESPACE; + /** * This provider is called for code completion that does not belong to a specific place in the file. */ @@ -132,7 +140,12 @@ private List getLookupsPipeline(CompletionParameters param // get lookups for tasks result String headPrefix_8 = prefix.length() > 8 ? prefix.substring(0, 8) : prefix; if ("$(tasks.".contains(headPrefix_8)) { - lookups.addAll(getTasksInPipelineLookups(parameters, model.getNamespace(), prefix, completionPrefix, insertOffset)); + String namespace = model.getNamespace(); + if (Strings.isNullOrEmpty(namespace)) { + VirtualFile vf = FileDocumentManager.getInstance().getFile(parameters.getEditor().getDocument()); + namespace = vf.getUserData(NAMESPACE); + } + lookups.addAll(getTasksInPipelineLookups(parameters, namespace, prefix, completionPrefix, insertOffset)); } return lookups; @@ -460,9 +473,9 @@ private List getLookupsBySelectedTask(String taskPrefix, S */ private List getLookupsBySelectedTaskAndField(CompletionParameters parameters, String namespace, String actualTaskName, String taskPrefix, String field, String completionPrefix, int insertOffset) { List lookups = new ArrayList<>(); - Tkn client = getClient(parameters); try { - Task task = client.getTask(namespace, actualTaskName); + VirtualFile taskVF = TektonVirtualFileManager.getInstance(parameters.getEditor().getProject()).findResource(namespace, KIND_TASK, actualTaskName); + Task task = Serialization.unmarshal(taskVF.getInputStream(), Task.class); task.getSpec().getResults().forEach(item -> { String lookup = taskPrefix + "." + field + "." + item.getName(); lookups.add(LookupElementBuilder.create(completionPrefix + item.getName()) diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/listener/TreeExpansionListener.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/listener/TreeExpansionListener.java index 943073e27..e1822de6f 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/listener/TreeExpansionListener.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/listener/TreeExpansionListener.java @@ -22,7 +22,7 @@ public class TreeExpansionListener implements TreeWillExpandListener { public void treeWillExpand(TreeExpansionEvent treeExpansionEvent) { ParentableNode> expandingElement = StructureTreeAction.getElement(treeExpansionEvent.getPath().getLastPathComponent()); if (WatchHandler.get().canBeWatched(expandingElement)) { - WatchHandler.get().setWatch(expandingElement, treeExpansionEvent.getPath()); + WatchHandler.get().setWatchByNode(expandingElement, treeExpansionEvent.getPath()); } } diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java index e7ef454f3..02c0e2f08 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/Tkn.java @@ -262,16 +262,6 @@ public interface Tkn { */ String getEventListenerYAML(String namespace, String eventListener) throws IOException; - /** - * Get the task by its name - * - * @param namespace the namespace of the task - * @param task the task to use - * @return the task object - * @throws IOException if communication errored - */ - Task getTask(String namespace, String task) throws IOException; - /** * Delete a list of pipelines * @@ -542,6 +532,17 @@ public interface Tkn { */ Watch watchPipelineRuns(String namespace, Watcher watcher) throws IOException; + /** + * Set a watch on a specific Task resource + * + * @param namespace the namespace to use + * @param task the name of the task + * @param watcher the watcher to call when a new event is received + * @return the watch object + * @throws IOException if communication errored + */ + Watch watchTask(String namespace, String task, Watcher watcher) throws IOException; + /** * Set a watch on Task resources * diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java index bd0aeecba..13815e4d0 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/tkn/TknCli.java @@ -27,15 +27,14 @@ import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.tekton.client.TektonClient; +import io.fabric8.tekton.pipeline.v1alpha1.Condition; import io.fabric8.tekton.pipeline.v1beta1.ClusterTask; import io.fabric8.tekton.pipeline.v1beta1.ClusterTaskList; import io.fabric8.tekton.pipeline.v1beta1.Pipeline; import io.fabric8.tekton.pipeline.v1beta1.PipelineList; import io.fabric8.tekton.pipeline.v1beta1.Task; import io.fabric8.tekton.pipeline.v1beta1.TaskList; -import io.fabric8.tekton.pipeline.v1alpha1.Condition; import io.fabric8.tekton.resource.v1alpha1.PipelineResource; - import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -47,6 +46,7 @@ import java.util.TreeMap; import java.util.stream.Collectors; + import static com.redhat.devtools.intellij.tektoncd.Constants.FLAG_INPUTRESOURCEPIPELINE; import static com.redhat.devtools.intellij.tektoncd.Constants.FLAG_INPUTRESOURCETASK; import static com.redhat.devtools.intellij.tektoncd.Constants.FLAG_OUTPUTRESOURCE; @@ -254,15 +254,6 @@ public String getEventListenerYAML(String namespace, String eventListener) throw return ExecHelper.execute(command, envVars, "eventlistener", "describe", eventListener, "-n", namespace, "-o", "yaml"); } - @Override - public Task getTask(String namespace, String task) throws IOException { - try { - return client.adapt(TektonClient.class).v1beta1().tasks().inNamespace(namespace).withName(task).get(); - } catch (KubernetesClientException e) { - throw new IOException(e); - } - } - @Override public void deletePipelines(String namespace, List pipelines, boolean deleteRelatedResources) throws IOException { if (deleteRelatedResources) { @@ -500,6 +491,15 @@ public Watch watchPipelineRuns(String namespace, Watcher watcher) throws IOException { + try { + return client.adapt(TektonClient.class).v1beta1().tasks().inNamespace(namespace).withName(task).watch(watcher); + } catch (KubernetesClientException e) { + throw new IOException(e); + } + } + @Override public Watch watchTasks(String namespace, Watcher watcher) throws IOException { try { diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TektonVirtualFileManager.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TektonVirtualFileManager.java new file mode 100644 index 000000000..4ef1a2f19 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TektonVirtualFileManager.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. + ******************************************************************************/ +package com.redhat.devtools.intellij.tektoncd.utils; + +import com.google.common.base.Strings; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.LightVirtualFile; +import com.redhat.devtools.intellij.tektoncd.tkn.Tkn; +import gnu.trove.THashMap; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.Watcher; +import java.io.IOException; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_CLUSTERTASKS; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_CLUSTERTRIGGERBINDINGS; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_CONDITIONS; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_EVENTLISTENER; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_PIPELINE; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_PIPELINERUN; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_RESOURCES; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASK; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASKRUN; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TRIGGERBINDINGS; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TRIGGERTEMPLATES; + +public class TektonVirtualFileManager { + static Logger logger = LoggerFactory.getLogger(TektonVirtualFileManager.class); + + private static TektonVirtualFileManager INSTANCE; + private final Map tektonFiles = new THashMap<>(); + private Tkn tkncli; + + private TektonVirtualFileManager(Project project) { + tkncli = TreeHelper.getTkn(project); + } + + public static TektonVirtualFileManager getInstance(@NotNull Project project) { + if (INSTANCE == null) { + INSTANCE = new TektonVirtualFileManager(project); + } + return INSTANCE; + } + + public VirtualFile findResource(String namespace, String kind, String resourceName) throws IOException { + String id = getId(namespace, kind, resourceName); + VirtualFile file = tektonFiles.get(id); + if (file == null) { + file = getResourceRemotely(namespace, kind, resourceName); + WatchHandler.get().setWatchByResourceName(tkncli, namespace, kind, resourceName, getWatcher(namespace, kind, resourceName)); + tektonFiles.put(id, file); + } + return file; + } + + private Watcher getWatcher(String namespace, String kind, String resourceName) { + String id = getId(namespace, kind, resourceName); + TektonVirtualFileManager tvfm = this; + return new Watcher() { + @Override + public void eventReceived(Action action, T resource) { + switch (action) { + case MODIFIED: { + try { + VirtualFile file = tvfm.getResourceRemotely(namespace, kind, resourceName); + tektonFiles.put(id, file); + } catch (IOException e) { + logger.warn(e.getLocalizedMessage()); + } + } + case DELETED: { + tektonFiles.remove(id); + } + } + } + + @Override + public void onClose(KubernetesClientException cause) { } + }; + } + + private VirtualFile getResourceRemotely(String namespace, String kind, String resourceName) throws IOException { + String content = ""; + if (kind.equalsIgnoreCase(KIND_PIPELINE)) { + content = tkncli.getPipelineYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_RESOURCES)) { + content = tkncli.getResourceYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_TASK)) { + content = tkncli.getTaskYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_CLUSTERTASKS)) { + content = tkncli.getClusterTaskYAML(resourceName); + } else if (kind.equalsIgnoreCase(KIND_CONDITIONS)) { + content = tkncli.getConditionYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_TRIGGERTEMPLATES)) { + content = tkncli.getTriggerTemplateYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_TRIGGERBINDINGS)) { + content = tkncli.getTriggerBindingYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_CLUSTERTRIGGERBINDINGS)) { + content = tkncli.getClusterTriggerBindingYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_EVENTLISTENER)) { + content = tkncli.getEventListenerYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_TASKRUN)) { + content = tkncli.getTaskRunYAML(namespace, resourceName); + } else if (kind.equalsIgnoreCase(KIND_PIPELINERUN)){ + content = tkncli.getPipelineRunYAML(namespace, resourceName); + } + return new LightVirtualFile(resourceName, content); + } + + private String getId(String namespace, String kind, String resourceName) { + String id = ""; + if (!Strings.isNullOrEmpty(namespace)) { + id += namespace + "-"; + } + id += kind + "-" + resourceName; + return id; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TreeHelper.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TreeHelper.java index 9742947e2..b038987b5 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TreeHelper.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/TreeHelper.java @@ -22,15 +22,22 @@ import com.redhat.devtools.intellij.tektoncd.Constants; import com.redhat.devtools.intellij.tektoncd.tkn.Tkn; import com.redhat.devtools.intellij.tektoncd.tree.ClusterTaskNode; +import com.redhat.devtools.intellij.tektoncd.tree.ClusterTasksNode; import com.redhat.devtools.intellij.tektoncd.tree.ClusterTriggerBindingNode; import com.redhat.devtools.intellij.tektoncd.tree.ConditionNode; +import com.redhat.devtools.intellij.tektoncd.tree.ConditionsNode; import com.redhat.devtools.intellij.tektoncd.tree.EventListenerNode; import com.redhat.devtools.intellij.tektoncd.tree.ParentableNode; import com.redhat.devtools.intellij.tektoncd.tree.PipelineNode; import com.redhat.devtools.intellij.tektoncd.tree.PipelineRunNode; +import com.redhat.devtools.intellij.tektoncd.tree.PipelineRunsNode; +import com.redhat.devtools.intellij.tektoncd.tree.PipelinesNode; import com.redhat.devtools.intellij.tektoncd.tree.ResourceNode; +import com.redhat.devtools.intellij.tektoncd.tree.ResourcesNode; import com.redhat.devtools.intellij.tektoncd.tree.TaskNode; import com.redhat.devtools.intellij.tektoncd.tree.TaskRunNode; +import com.redhat.devtools.intellij.tektoncd.tree.TaskRunsNode; +import com.redhat.devtools.intellij.tektoncd.tree.TasksNode; import com.redhat.devtools.intellij.tektoncd.tree.TektonRootNode; import com.redhat.devtools.intellij.tektoncd.tree.TektonTreeStructure; import com.redhat.devtools.intellij.tektoncd.tree.TriggerBindingNode; @@ -48,10 +55,14 @@ import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_CLUSTERTRIGGERBINDINGS; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_CONDITIONS; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_EVENTLISTENER; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_PIPELINE; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_PIPELINERUN; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_PIPELINERUNS; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_PIPELINES; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_RESOURCES; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASK; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASKRUN; +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASKRUNS; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASKS; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TRIGGERBINDINGS; import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TRIGGERTEMPLATES; @@ -124,6 +135,33 @@ public static Pair getYAMLAndKindFromNode(ParentableNode node return Pair.create(content, kind); } + public static String getKindByNode(ParentableNode node) { + String kind = ""; + + if (node instanceof PipelinesNode) { + kind = KIND_PIPELINES; + } else if (node instanceof PipelineNode) { + kind = KIND_PIPELINE; + } else if (node instanceof PipelineRunsNode) { + kind = KIND_PIPELINERUNS; + } else if (node instanceof PipelineRunNode) { + kind = KIND_PIPELINERUN; + } else if (node instanceof ResourcesNode) { + kind = KIND_RESOURCES; + } else if (node instanceof TasksNode) { + kind = KIND_TASKS; + } else if (node instanceof TaskNode) { + kind = KIND_TASK; + } else if (node instanceof TaskRunsNode) { + kind = KIND_TASKRUNS; + } else if (node instanceof ClusterTasksNode) { + kind = KIND_CLUSTERTASKS; + } else if (node instanceof ConditionsNode) { + kind = KIND_CONDITIONS; + } + return kind; + } + public static void openTektonResourceInEditor(TreePath path) { if (path == null) { return; diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java index fd8465960..ecd205982 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java @@ -38,6 +38,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import static com.redhat.devtools.intellij.tektoncd.Constants.KIND_TASK; + public class WatchHandler { private static final Logger logger = LoggerFactory.getLogger(WatchHandler.class); private Map watches; @@ -55,7 +58,31 @@ public static WatchHandler get() { return instance; } - public void setWatch(ParentableNode element, TreePath treePath) { + public void setWatchByResourceName(Tkn tkn, String namespace, String kind, String resourceName, Watcher watcher) { + String watchId = getWatchId(namespace, kind + "-" + resourceName); + Watch watch = null; + WatchNodes wn = null; + + if (this.watches.containsKey(watchId)) { + return; + } + + try { + if (kind.equalsIgnoreCase(KIND_TASK)) { + watch = tkn.watchTask(namespace, resourceName, watcher); + } + wn = new WatchNodes(watch); + } catch (IOException e) { + logger.warn("Error: " + e.getLocalizedMessage()); + } + + if (wn != null) { + watches.put(watchId, wn); + } + + } + + public void setWatchByNode(ParentableNode element, TreePath treePath) { Tkn tkn = element.getRoot().getTkn(); String namespace = element.getNamespace(); @@ -107,6 +134,12 @@ public void setWatch(ParentableNode element, TreePath treePath) { } } + /*private void createAndAddWatch(Tkn tkn, String namespace, String kind, String watchId, Watcher watcher, TreePath... treePath) { + + + + }*/ + public void removeWatch(ParentableNode element, TreePath treePath) { String watchId = getWatchId(element); if (watches.containsKey(watchId)) { From 751098aa9ffef6a47869cbe90fed52f9819dac08 Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Tue, 22 Sep 2020 15:30:05 +0200 Subject: [PATCH 6/8] clean Signed-off-by: Luca Stocchi --- .../devtools/intellij/tektoncd/utils/WatchHandler.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java index ecd205982..6baa797a9 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/utils/WatchHandler.java @@ -134,12 +134,6 @@ public void setWatchByNode(ParentableNode element, TreePath treePath) { } } - /*private void createAndAddWatch(Tkn tkn, String namespace, String kind, String watchId, Watcher watcher, TreePath... treePath) { - - - - }*/ - public void removeWatch(ParentableNode element, TreePath treePath) { String watchId = getWatchId(element); if (watches.containsKey(watchId)) { From 442251b4e6121c76ede3447cf06324e41691c240 Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Tue, 29 Sep 2020 17:15:24 +0200 Subject: [PATCH 7/8] fix when operator completion uppercase Signed-off-by: Luca Stocchi --- .../intellij/tektoncd/completion/BaseCompletionProvider.java | 2 +- .../intellij/tektoncd/completion/DictionaryContributor.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java index 1a3859c0d..ccab68c50 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/BaseCompletionProvider.java @@ -49,7 +49,7 @@ protected List getFilteredTasksInPipeline(CompletionParameters parameter try { JsonNode tasksNodeUntilSelected = YAMLHelper.getValueFromYAML(yamlUntilTask, new String[]{"spec"} ); if (tasksNodeUntilSelected.has("tasks")) { - taskPosition = StreamSupport.stream(tasksNodeUntilSelected.get("tasks").spliterator(),true).count(); + taskPosition = StreamSupport.stream(tasksNodeUntilSelected.get("tasks").spliterator(), true).count(); } } catch (IOException e) { logger.warn("Error: " + e.getLocalizedMessage()); diff --git a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java index dfcf24925..7963d2ad6 100644 --- a/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java +++ b/src/main/java/com/redhat/devtools/intellij/tektoncd/completion/DictionaryContributor.java @@ -31,7 +31,7 @@ public DictionaryContributor() { new RunAfterCompletionProvider()); // operator - when clause extend(CompletionType.BASIC, - YamlElementPatternHelper.getAfterParentScalarKeyInSequence("Operator", "when"), + YamlElementPatternHelper.getAfterParentScalarKeyInSequence("operator", "when"), new OperatorInWhenClauseCodeCompletion()); // resource in pipeline extend(CompletionType.BASIC, From 31270f86fe5e8925f2c98af84d41e03a9466f15d Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Tue, 29 Sep 2020 18:15:28 +0200 Subject: [PATCH 8/8] fixed test Signed-off-by: Luca Stocchi --- .../resources/completion/operatorInWhenClause/pipeline1.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml b/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml index d439f1a4b..d1cf92480 100644 --- a/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml +++ b/src/test/resources/completion/operatorInWhenClause/pipeline1.yaml @@ -12,5 +12,5 @@ spec: taskRef: name: task1 when: - - Input: 'foo' - Operator: \ No newline at end of file + - input: 'foo' + operator: \ No newline at end of file