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
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