Skip to content

Commit

Permalink
Merge pull request #48 from lets-cli/complete-global-env-in-cmd
Browse files Browse the repository at this point in the history
complete global env in cmd
  • Loading branch information
kindermax authored Mar 8, 2025
2 parents 34aa0bc + 8d942d6 commit 6bfb65b
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 132 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

## [Unreleased]

### Added

- Add reference for commands in `ref`
- Add completion for commands in `ref`
- Add completion for env variables in `cmd` scripts from global and command `env`

### Internal

- Refactor completions in a way to use LetsPsiUtils
- Drop `Config`, refactor into `ConfigParser
- Drop `LetsCompletionHelper`
- Introduce `YamlContextType` enum instead of boolean functions for determining context type (position type)

## [0.0.17] - 2025-03-08

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ File type recognition for `lets.yaml` and `lets.*.yaml` configs
- [x] Complete command `options` with code snippet
- [x] Complete commands in `depends` with code snippet
- [x] Complete commands in `depends` from mixins
- [ ] Complete env mode in `env` with code snippet
- [x] Complete env mode in `env` with code snippet
- [x] Complete `LETS*` environment variables in cmd scripts
- [ ] Complete environment variables for checksum
- [ ] Complete environment variables from global and command `env` in cmd scripts
- [x] Complete environment variables from global and command `env` in cmd scripts
- [ ] Complete environment variables in `args`
- **Go To Definition**
- [x] Navigate to definitions of `mixins` files
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pluginGroup = com.github.kindermax.intellijlets
pluginName = intellij-lets
pluginRepositoryUrl = https://github.com/lets-cli/intellij-lets
pluginVersion = 0.0.17
pluginVersion = 0.0.18

pluginSinceBuild = 241
pluginUntilBuild =
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeInsight.template.impl.TextExpression
import com.intellij.util.ProcessingContext
import org.jetbrains.yaml.psi.YAMLFile

Expand All @@ -17,8 +19,8 @@ object LetsCompletionProvider : CompletionProvider<CompletionParameters>() {
context: ProcessingContext,
result: CompletionResultSet,
) {
when (LetsCompletionHelper.detectContext(parameters.position)) {
LetsCompletionHelper.YamlContextType.RootLevel -> {
when (LetsPsiUtils.detectContext(parameters.position)) {
YamlContextType.RootLevel -> {
val yamlFile = parameters.originalFile as YAMLFile
val usedKeywords = LetsPsiUtils.getUsedKeywords(yamlFile)
val suggestions = when (usedKeywords.size) {
Expand All @@ -36,36 +38,95 @@ object LetsCompletionProvider : CompletionProvider<CompletionParameters>() {
}
)
}
LetsCompletionHelper.YamlContextType.ShellLevel -> {

YamlContextType.ShellLevel -> {
result.addAllElements(DEFAULT_SHELLS.map { keyword -> createLookupElement(keyword) })
}
LetsCompletionHelper.YamlContextType.CommandLevel -> {

YamlContextType.CommandLevel -> {
val currentCommand = LetsPsiUtils.findCurrentCommand(parameters.position, YamlContextType.CommandLevel)
result.addAllElements(
COMMAND_LEVEL_KEYWORDS.map { keyword ->
when (keyword) {
"options" -> createOptionsElement()
"options" -> createOptionsElement(currentCommand?.name)
"depends" -> createDependsElement()
"env" -> createCommandKeyNewLineElement(keyword)
else -> createCommandKeyElement(keyword)
}
}
)
}
LetsCompletionHelper.YamlContextType.DependsLevel -> {
val suggestions = LetsCompletionHelper.getDependsSuggestions(parameters)

YamlContextType.DependsLevel -> {
val suggestions = getDependsSuggestions(parameters)
result.addAllElements(
suggestions.map { keyword -> createLookupElement(keyword) }
)
}
LetsCompletionHelper.YamlContextType.RefLevel -> {
val suggestions = LetsCompletionHelper.getRefSuggestions(parameters)

YamlContextType.RefLevel -> {
val suggestions = getRefSuggestions(parameters)
result.addAllElements(
suggestions.map { keyword -> createLookupElement(keyword) }
)
}
LetsCompletionHelper.YamlContextType.Unknown -> return

YamlContextType.EnvLevel -> {
result.addAllElements(
listOf(
createEnvStringElement(),
createEnvShellElement()
)
)
}

YamlContextType.Unknown -> return
}
}

/**
* Get all possible commands suggestions for a `depends`, except:
* - itself
* - already specified commands in depends
* - other commands which depend on current command
*/
private fun getDependsSuggestions(parameters: CompletionParameters): List<String> {
val yamlFile = parameters.originalFile as? YAMLFile ?: return emptyList()
val allCommands = LetsPsiUtils.findAllCommands(yamlFile)
val currentCommand = LetsPsiUtils.findCurrentCommand(parameters.position) ?: return emptyList()

val excludeList = mutableSetOf<String>()
// exclude itself
excludeList.add(currentCommand.name)
// exclude commands already in depends list
excludeList.addAll(currentCommand.depends)

// exclude commands which depends on current command (eliminate recursive dependencies)
for (command in allCommands.filter { c -> c.name != currentCommand.name }) {
if (command.depends.contains(currentCommand.name)) {
excludeList.add(command.name)
}
}

return allCommands
.filterNot { command -> excludeList.contains(command.name) }
.map { it.name }
.toList()
}

/**
* Get all possible commands suggestions for a `ref`, except:
* - itself
* Since ref is a YAMLScalar, only one command is suggested.
*/
private fun getRefSuggestions(parameters: CompletionParameters): List<String> {
val yamlFile = parameters.originalFile as? YAMLFile ?: return emptyList()
val allCommands = LetsPsiUtils.findAllCommands(yamlFile)
val currentCommand = LetsPsiUtils.findCurrentCommand(parameters.position) ?: return emptyList()
// Exclude itself from suggestions and return only one suggestion
return allCommands.filterNot { it.name == currentCommand.name }
.map { it.name }
}
}

private fun createLookupElement(text: String): LookupElement {
Expand Down Expand Up @@ -116,11 +177,11 @@ private fun createCommandKeyNewLineElement(text: String): LookupElement {
.withInsertHandler(CommandKeyInsertionHandler(newLine = true))
}

private fun createOptionsElement(): LookupElement {
private fun createOptionsElement(name: String?): LookupElement {
return LookupElementBuilder
.create("options")
.withIcon(Icons.LetsYaml)
.withInsertHandler(OptionsInsertionHandler())
.withInsertHandler(OptionsInsertionHandler(name))
}

private fun createDependsElement(): LookupElement {
Expand All @@ -130,10 +191,83 @@ private fun createDependsElement(): LookupElement {
.withInsertHandler(DependsInsertionHandler())
}

private class OptionsInsertionHandler : InsertHandler<LookupElement> {
private fun createEnvStringElement(): LookupElement {
return LookupElementBuilder
.create("")
.withIcon(Icons.LetsYaml)
.withInsertHandler(EnvStringInsertionHandler())
.withPresentableText("KEY: VALUE (Simple key-value pair)")
}

private fun createEnvShellElement(): LookupElement {
return LookupElementBuilder
.create("")
.withIcon(Icons.LetsYaml)
.withInsertHandler(EnvShellInsertionHandler())
.withPresentableText("KEY: sh (Shell script value)")
}

/**
* Creates template for environment variables with the following structure:
* <KEY>: <VALUE>
* User must replace `KEY` and `VALUE` with actual values.
*/
private class EnvStringInsertionHandler : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val project = context.project
val editor = context.editor

// Create a live template
val manager = TemplateManager.getInstance(project)
val template = manager.createTemplate("", "")
template.isToReformat = true

// Add placeholders
template.addTextSegment("")
template.addVariable("KEY", TextExpression("ENV_KEY"), true)
template.addTextSegment(": ")
template.addVariable("VALUE", TextExpression("ENV_VALUE"), true)

// Start the template
manager.startTemplate(editor, template)
}
}

/**
* Creates template for environment variables in shell mode with the following structure:
* <KEY>:
* sh: <caret>
*
* User must replace `KEY` with actual value and write shell script in the next line.
*/
private class EnvShellInsertionHandler : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val padding = "".padStart(4)

val project = context.project
val editor = context.editor

// Create a live template
val manager = TemplateManager.getInstance(project)
val template = manager.createTemplate("", "")
template.isToReformat = true

// Add placeholders
template.addTextSegment("")
template.addVariable("KEY", TextExpression("ENV_KEY"), true)
template.addTextSegment(":\n${padding}sh: ")

// Start the template
manager.startTemplate(editor, template)
}
}

private class OptionsInsertionHandler(name: String?) : InsertHandler<LookupElement> {
val name = name ?: ""

override fun handleInsert(context: InsertionContext, item: LookupElement) {
val padding = "".padStart(COMMAND_CHILD_PADDING)
context.document.insertString(context.selectionEndOffset, ": |\n${padding}Usage: lets ")
context.document.insertString(context.selectionEndOffset, ": |\n${padding}Usage: lets $name")
context.editor.caretModel.moveToOffset(context.selectionEndOffset)
}
}
Expand Down
Loading

0 comments on commit 6bfb65b

Please sign in to comment.