Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion example/lets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ commands:
depends: [build]
cmd: echo ${LETS_COMMAND_NAME}

hello: echo Hello
hello:
cmd: echo Hello ${LETS_COMMAND_NAME} and $
env:
f: 1
options: |
Usage: lets ${LETS_COMMAND_NAME}

run:
depends: [test, hello]
cmd: |
set -ex
$L
echo Run
echo Rff
if [ -n "${LE}" ]; then
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package com.github.kindermax.intellijlets

import com.intellij.codeInsight.completion.*
import com.intellij.openapi.util.TextRange
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.lang.Language
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ProcessingContext
import org.jetbrains.yaml.psi.YAMLKeyValue
import org.jetbrains.yaml.psi.YAMLScalar

import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.psi.PsiFile

/**
* This class provides completions for environment variables in Lets YAML files.
* Supports:
* - Completion of `$` in `options` key
* - Completion of `$` and `$L` in `cmd` key (only works as a fallback
* if the cmd script is not detected as shell script, see LetsEnvVariableShellScriptCompletionContributor)
*/
class LetsEnvVariableCompletionContributor : CompletionContributor() {
init {
extend(
CompletionType.BASIC,
PlatformPatterns.psiElement().inside(YAMLScalar::class.java),
object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {
val element = parameters.position
val caret = parameters.editor.caretModel.currentCaret
val lineOffset = caret.visualLineStart
val prefixText = parameters.editor.document.getText(TextRange(lineOffset, caret.offset))

val keyValue = PsiTreeUtil.getParentOfType(element, YAMLKeyValue::class.java) ?: return

when (keyValue.keyText) {
"options" -> {
val prefixMatcher = result.withPrefixMatcher("$")
prefixMatcher.addElement(
createEnvVariableLookupElement("LETS_COMMAND_NAME")
)
}
"cmd" -> {
if (prefixText.endsWith("$")) {
val prefixMatcher = result.withPrefixMatcher("$")
BUILTIN_ENV_VARIABLES.forEach {
prefixMatcher.addElement(
createEnvVariableLookupElement(it)
)
}
} else if (prefixText.endsWith("\$L")) {
val prefixMatcher = result.withPrefixMatcher("\$L")
BUILTIN_ENV_VARIABLES.forEach {
prefixMatcher.addElement(
createEnvVariableLookupElement(it)
)
}
}
}
}
}
}
)
}

override fun beforeCompletion(context: CompletionInitializationContext) {
val offset = context.startOffset
val document = context.editor.document

// Ensure `$` is treated as a valid trigger for completion
if (offset > 0 && document.charsSequence[offset - 1] == '$') {
context.dummyIdentifier = "$" // This forces completion when `$` is typed
}
}
}

/**
* This class provides completions for environment variables in Lets YAML files,
* specifically for shell scripts in `cmd` key.
* In order for this completion contributor to work, cmd must be detected as the shell script language.
* If not detected as shell script, the completion will fallback to LetsEnvVariableCompletionContributor.
*/
class LetsEnvVariableShellScriptCompletionContributor : CompletionContributor() {
init {
extend(
CompletionType.BASIC,
PlatformPatterns.psiElement().withLanguage(Language.findLanguageByID("Shell Script")!!),
object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {
val element = parameters.position

// Retrieve the original YAML file from the injected Bash script
val injectedLanguageManager = InjectedLanguageManager.getInstance(element.project)
val yamlFile: PsiFile = injectedLanguageManager.getInjectionHost(element)?.containingFile ?: return

// Ensure it's a YAML file
if (yamlFile !is org.jetbrains.yaml.psi.YAMLFile) return

// Retrieve the correct offset in the original YAML file
val hostOffset = injectedLanguageManager.injectedToHost(element, element.textOffset)

// Find the corresponding element in the original YAML file
val elementAtOffset = yamlFile.findElementAt(hostOffset) ?: return
val yamlKeyValue = PsiTreeUtil.getParentOfType(elementAtOffset, YAMLKeyValue::class.java) ?: return

// Ensure we are inside `cmd`
if (yamlKeyValue.keyText != "cmd") return

val prefixText = parameters.editor.document.getText(TextRange(parameters.offset - 1, parameters.offset))

if (prefixText.endsWith("$")) {
val prefixMatcher = result.withPrefixMatcher("$")
BUILTIN_ENV_VARIABLES.forEach {
prefixMatcher.addElement(
createEnvVariableLookupElement(it)
)
}
} else if (prefixText.endsWith("\$L")) {
val prefixMatcher = result.withPrefixMatcher("\$L")
BUILTIN_ENV_VARIABLES.forEach {
prefixMatcher.addElement(
createEnvVariableLookupElement(it)
)
}
}
}
}
)
}

override fun beforeCompletion(context: CompletionInitializationContext) {
val offset = context.startOffset
val document = context.editor.document

// Ensure `$` is treated as a valid trigger for completion
if (offset > 0 && document.charsSequence[offset - 1] == '$') {
context.dummyIdentifier = "$" // This forces completion when `$` is typed
}
}
}


private fun createEnvVariableLookupElement(name: String): LookupElementBuilder {
return LookupElementBuilder.create("\${$name}")
.withPresentableText(name)
.withIcon(Icons.LetsYaml)
}
11 changes: 11 additions & 0 deletions src/main/kotlin/com/github/kindermax/intellijlets/constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ val COMMAND_LEVEL_KEYWORDS = listOf(
"ref",
"args",
)

val BUILTIN_ENV_VARIABLES = listOf(
"LETS_COMMAND_NAME",
"LETS_COMMAND_ARGS",
"LETS_COMMAND_WORK_DIR",
"LETS_CONFIG",
"LETS_CONFIG_DIR",
"LETS_SHELL",
)

// TODO: support LETSOPT_ and LETSCLI_ if options is available
8 changes: 8 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
order="first"
implementationClass="com.github.kindermax.intellijlets.LetsCompletionContributor"
/>
<completion.contributor
language="yaml"
implementationClass="com.github.kindermax.intellijlets.LetsEnvVariableCompletionContributor"
/>
<completion.contributor
language="Shell Script"
implementationClass="com.github.kindermax.intellijlets.LetsEnvVariableShellScriptCompletionContributor"
/>
<psi.referenceContributor
language="yaml"
implementation="com.github.kindermax.intellijlets.LetsReferenceContributor"
Expand Down