Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.sjrx.intellij.plugins.systemdunitfiles.completion

import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.progress.ProgressManager
import com.intellij.patterns.PlatformPatterns
Expand All @@ -12,6 +14,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileProperty
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.SemanticDataRepository
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.fileClass
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.Combinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.GrammarOptionValue
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.nextTokenChoices
import net.sjrx.intellij.plugins.systemdunitfiles.settings.ExperimentalSettings
Expand Down Expand Up @@ -100,7 +103,7 @@ class UnitFileValueCompletionContributor : CompletionContributor() {
val word = pre.substring(split)
val choices = combinator.nextTokenChoices(pre.substring(0, split))
if (choices.any { it.length > word.length && it.startsWith(word) }) {
resultSet.withPrefixMatcher(word).addAllElements(choices.map { LookupElementBuilder.create(it) })
resultSet.withPrefixMatcher(word).addAllElements(lookupElements(choices, combinator))
return
}
}
Expand All @@ -110,7 +113,40 @@ class UnitFileValueCompletionContributor : CompletionContributor() {
ProgressManager.checkCanceled()
val choices = combinator.nextTokenChoices(pre)
if (choices.isNotEmpty()) {
resultSet.withPrefixMatcher("").addAllElements(choices.map { LookupElementBuilder.create(it) })
resultSet.withPrefixMatcher("").addAllElements(lookupElements(choices, combinator))
}
}

private fun lookupElements(choices: Set<String>, combinator: Combinator): List<LookupElement> {
val handler = chainingInsertHandler(combinator)
return choices.map { LookupElementBuilder.create(it).withInsertHandler(handler) }
}

/**
* After a choice is accepted, walk the grammar forward: while the only thing it can accept next is
* a single forced separator (punctuation, e.g. "=" after a partition designator), insert it
* automatically, then re-open completion. So accepting "home" yields "home=" with the policy-flag
* popup, rather than stopping on a separator the user must type by hand.
*/
private fun chainingInsertHandler(combinator: Combinator) = InsertHandler<LookupElement> { context, _ ->
context.commitDocument()
val element = context.file.findElementAt(maxOf(0, context.tailOffset - 1)) ?: return@InsertHandler
val property = PsiTreeUtil.getParentOfType(element, UnitFileProperty::class.java) ?: return@InsertHandler
val valueStart = property.valueNode?.psi?.textRange?.startOffset ?: return@InsertHandler
val document = context.document

var caret = context.tailOffset
var guard = 0
while (guard++ < 8 && caret in valueStart..document.textLength) {
val pre = document.charsSequence.subSequence(valueStart, caret).toString()
val separator = combinator.nextTokenChoices(pre).singleOrNull() ?: break
// Only auto-insert a forced punctuation separator; never a content token the user should pick.
if (separator.isEmpty() || separator.any { it.isLetterOrDigit() }) break
document.insertString(caret, separator)
caret += separator.length
}
context.commitDocument()
context.editor.caretModel.moveToOffset(caret)
AutoPopupController.getInstance(context.project).scheduleAutoPopup(context.editor)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,14 @@ class ImagePolicyCompletionTest : AbstractUnitFileTest() {
setupFileInEditor("file.service", "[Service]\nRootImagePolicy=root=${COMPLETION_POSITION}")
assertContainsElements(basicCompletionResultStrings, "verity", "signed", "encrypted")
}

@Test
fun testAcceptingPartitionChainsTheEqualsSeparator() {
// Accepting a partition designator auto-inserts the forced "=" (then re-opens completion),
// so the user goes straight to choosing a policy flag.
enableNewEngine()
setupFileInEditor("file.service", "[Service]\nRootImagePolicy=hom${COMPLETION_POSITION}")
myFixture.completeBasic() // single match "home" -> auto-inserted -> handler appends "="
myFixture.checkResult("[Service]\nRootImagePolicy=home=${COMPLETION_POSITION}")
}
}
Loading