1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-02-28 02:45:59 +01:00

Extract StringListOption

While it is conceptually very similar to StringOption, the implementation of list vs not-list operations are very different, and having a separate type will allow us to do more interesting things with overloading when we introduce delegate properties
This commit is contained in:
Matt Ellis 2023-03-29 14:31:34 +01:00 committed by Alex Pláte
parent a6932cd5be
commit 9ade3365ff
10 changed files with 137 additions and 102 deletions
src
main/java/com/maddyhome/idea/vim
test/java/org/jetbrains/plugins/ideavim
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.group
import com.intellij.openapi.application.ApplicationNamesInfo
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
import com.maddyhome.idea.vim.options.UnsignedNumberOption
@ -42,7 +43,6 @@ public object IjOptions {
"idearefactormode",
"idearefactormode",
"select",
isList = false,
IjOptionConstants.ideaRefactorModeValues
)
)
@ -51,28 +51,24 @@ public object IjOptions {
"ideastatusicon",
"ideastatusicon",
"enabled",
isList = false,
IjOptionConstants.ideaStatusIconValues
)
)
public val ideavimsupport: StringOption = addOption(
StringOption(
public val ideavimsupport: StringListOption = addOption(
StringListOption(
"ideavimsupport",
"ideavimsupport",
"dialog",
isList = true,
IjOptionConstants.ideavimsupportValues
)
)
@JvmField public val ideawrite: StringOption =
addOption(StringOption("ideawrite", "ideawrite", "all", isList = false, IjOptionConstants.ideaWriteValues))
public val lookupkeys: StringOption = addOption(
StringOption(
addOption(StringOption("ideawrite", "ideawrite", "all", IjOptionConstants.ideaWriteValues))
public val lookupkeys: StringListOption = addOption(
StringListOption(
"lookupkeys",
"lookupkeys",
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>",
isList = true
)
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
)
public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", "octopushandler", false))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", "oldundo", true))

View File

@ -22,6 +22,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
@ -52,7 +53,7 @@ internal class OptionsState : ApplicationUsagesCollector() {
private infix fun StringEventField.withOption(option: StringOption) =
this.with(injector.globalOptions().getStringValue(option))
private infix fun StringListEventField.withOption(option: StringOption) =
private infix fun StringListEventField.withOption(option: StringListOption) =
this.with(injector.globalOptions().getStringListValues(option))
companion object {

View File

@ -21,6 +21,7 @@ import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionChangeListener
import com.maddyhome.idea.vim.options.OptionScope
import com.maddyhome.idea.vim.options.OptionValueAccessor
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
import com.maddyhome.idea.vim.options.UnsignedNumberOption
@ -275,7 +276,7 @@ private class VimOptionsInvocator : TestTemplateInvocationContextProvider {
when (option) {
is ToggleOption -> vimOption.limitedValues.map { option to if (it == "true") VimInt.ONE else VimInt.ZERO }
is NumberOption -> vimOption.limitedValues.map { option to VimInt(it) }
is StringOption -> {
is StringOption, is StringListOption -> {
vimOption.limitedValues.map { limitedValue -> option to VimString(limitedValue) }
}
@ -308,21 +309,21 @@ private class VimOptionsInvocator : TestTemplateInvocationContextProvider {
}
is StringOption -> {
if (option.isList) {
val boundedValues = option.boundedValues
if (boundedValues != null) {
val valuesCombinations = boundedValues.indices.map { index ->
kCombinations(boundedValues.toList(), index + 1)
.map { VimString(it.joinToString(",")) }
}.flatten()
valuesCombinations.map { option to it }
} else {
fail("Cannot generate values automatically. Please specify option values explicitly using 'limitedValues' field")
}
val boundedValues = option.boundedValues
boundedValues?.map { option to VimString(it) }
?: fail("Cannot generate values automatically. Please specify option values explicitly using 'limitedValues' field")
}
is StringListOption -> {
val boundedValues = option.boundedValues
if (boundedValues != null) {
val valuesCombinations = boundedValues.indices.map { index ->
kCombinations(boundedValues.toList(), index + 1)
.map { VimString(it.joinToString(",")) }
}.flatten()
valuesCombinations.map { option to it }
} else {
val boundedValues = option.boundedValues
boundedValues?.map { option to VimString(it) }
?: fail("Cannot generate values automatically. Please specify option values explicitly using 'limitedValues' field")
fail("Cannot generate values automatically. Please specify option values explicitly using 'limitedValues' field")
}
}

View File

@ -9,7 +9,7 @@
package org.jetbrains.plugins.ideavim.option
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.StringListOption
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -21,11 +21,10 @@ import org.junit.jupiter.api.TestInfo
class BoundedStringListOptionTest : VimTestCase() {
private val optionName = "myOpt"
private val defaultValue = "Monday,Tuesday"
private val option = StringOption(
private val option = StringListOption(
optionName,
optionName,
defaultValue,
true,
setOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"),
)

View File

@ -9,7 +9,7 @@
package org.jetbrains.plugins.ideavim.option
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.StringListOption
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -20,7 +20,7 @@ import org.junit.jupiter.api.TestInfo
class StringListOptionTest : VimTestCase() {
private val optionName = "myOpt"
private val option = StringOption(optionName, optionName, "", true, null)
private val option = StringListOption(optionName, optionName, "", null)
@BeforeEach
override fun setUp(testInfo: TestInfo) {

View File

@ -15,6 +15,7 @@ import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.options.OptionScope
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
import com.maddyhome.idea.vim.options.UnsignedNumberOption
@ -78,24 +79,26 @@ public object Options {
@JvmField public val hlsearch: ToggleOption = addOption(ToggleOption("hlsearch", "hls", false))
@JvmField public val ignorecase: ToggleOption = addOption(ToggleOption("ignorecase", "ic", false))
@JvmField public val incsearch: ToggleOption = addOption(ToggleOption("incsearch", "is", false))
public val keymodel: StringOption = addOption(StringOption(
"keymodel",
"km",
"${OptionConstants.keymodel_continueselect},${OptionConstants.keymodel_stopselect}",
isList = true,
setOf(
OptionConstants.keymodel_startsel,
OptionConstants.keymodel_stopsel,
OptionConstants.keymodel_stopselect,
OptionConstants.keymodel_stopvisual,
OptionConstants.keymodel_continueselect,
OptionConstants.keymodel_continuevisual
public val keymodel: StringListOption = addOption(
StringListOption(
"keymodel",
"km",
"${OptionConstants.keymodel_continueselect},${OptionConstants.keymodel_stopselect}",
setOf(
OptionConstants.keymodel_startsel,
OptionConstants.keymodel_stopsel,
OptionConstants.keymodel_stopselect,
OptionConstants.keymodel_stopvisual,
OptionConstants.keymodel_continueselect,
OptionConstants.keymodel_continuevisual
)
)
))
)
public val maxmapdepth: NumberOption = addOption(NumberOption("maxmapdepth", "mmd", 20))
@JvmField public val more: ToggleOption = addOption(ToggleOption("more", "more", true))
@JvmField public val nrformats: StringOption =
addOption(StringOption("nrformats", "nf", "hex", isList = true, setOf("octal", "hex", "alpha")))
@JvmField public val nrformats: StringListOption = addOption(
StringListOption("nrformats", "nf", "hex", setOf("octal", "hex", "alpha"))
)
@JvmField public val number: ToggleOption = addOption(ToggleOption("number", "nu", false))
@JvmField public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", "rnu", false))
public val scroll: NumberOption = addOption(NumberOption("scroll", "scr", 0))
@ -105,13 +108,12 @@ public object Options {
"selection",
"sel",
"inclusive",
isList = false,
setOf("old", "inclusive", "exclusive")
)
)
public val selectmode: StringOption = addOption(
StringOption(
"selectmode", "slm", "", isList = true,
public val selectmode: StringListOption = addOption(
StringListOption(
"selectmode", "slm", "",
setOf(
OptionConstants.selectmode_mouse,
OptionConstants.selectmode_key,
@ -131,8 +133,7 @@ public object Options {
StringOption(
"shellxescape",
"sxe",
if (injector.systemInfoService.isWindows) "\"&|<>()@^" else "",
isList = false
if (injector.systemInfoService.isWindows) "\"&|<>()@^" else ""
)
)
public val showcmd: ToggleOption = addOption(ToggleOption("showcmd", "sc", true))
@ -144,23 +145,21 @@ public object Options {
public val timeout: ToggleOption = addOption(ToggleOption("timeout", "to", true))
public val timeoutlen: UnsignedNumberOption = addOption(UnsignedNumberOption("timeoutlen", "tm", 1000))
public val undolevels: UnsignedNumberOption = addOption(UnsignedNumberOption("undolevels", "ul", 1000))
@JvmField public val viminfo: StringOption = addOption(StringOption("viminfo", "vi", "'100,<50,s10,h", isList = true))
public val virtualedit: StringOption = addOption(
StringOption(
@JvmField public val viminfo: StringListOption = addOption(StringListOption("viminfo", "vi", "'100,<50,s10,h"))
public val virtualedit: StringListOption = addOption(
StringListOption(
"virtualedit",
"ve",
"",
isList = false,
setOf("onemore", "block", "insert", "all")
)
)
public val visualbell: ToggleOption = addOption(ToggleOption("visualbell", "vb", false))
@JvmField public val whichwrap: StringOption = addOption(
StringOption(
@JvmField public val whichwrap: StringListOption = addOption(
StringListOption(
"whichwrap",
"ww",
"b,s",
true,
setOf("b", "s", "h", "l", "<", ">", "~", "[", "]")
)
)
@ -168,16 +167,14 @@ public object Options {
// More complex options, with additional validation, etc.
public val guicursor: StringOption = addOption(object : StringOption(
public val guicursor: StringListOption = addOption(object : StringListOption(
"guicursor", "gcr",
"n-v-c:block-Cursor/lCursor," +
"ve:ver35-Cursor," +
"o:hor50-Cursor," +
"i-ci:ver25-Cursor/lCursor," +
"r-cr:hor20-Cursor/lCursor," +
"sm:block-Cursor-blinkwait175-blinkoff150-blinkon175",
isList = true
) {
"sm:block-Cursor-blinkwait175-blinkoff150-blinkon175") {
override fun checkIfValueValid(value: VimDataType, token: String) {
super.checkIfValueValid(value, token)
val valueAsString = (value as VimString).value
@ -185,7 +182,7 @@ public object Options {
}
})
public val iskeyword: StringOption = addOption(object : StringOption("iskeyword", "isk", "@,48-57,_", isList = true) {
public val iskeyword: StringListOption = addOption(object : StringListOption("iskeyword", "isk", "@,48-57,_") {
override fun checkIfValueValid(value: VimDataType, token: String) {
super.checkIfValueValid(value, token)
if (KeywordOptionHelper.isValueInvalid((value as VimString).value)) {
@ -205,7 +202,7 @@ public object Options {
})
@JvmField
public val matchpairs: StringOption = addOption(object : StringOption("matchpairs", "mps", "(:),{:},[:]", isList = true) {
public val matchpairs: StringListOption = addOption(object : StringListOption("matchpairs", "mps", "(:),{:},[:]") {
override fun checkIfValueValid(value: VimDataType, token: String) {
super.checkIfValueValid(value, token)
for (v in split((value as VimString).value)) {
@ -259,14 +256,8 @@ public object Options {
// Note that IntelliJ overrides clipboard's default value to include the `ideaput` option.
// TODO: Technically, we should validate values, but that requires handling exclude, which is irrelevant to us
public val clipboard: StringOption = addOption(
StringOption(
"clipboard",
"cb",
"autoselect,exclude:cons\\|linux",
isList = true
)
)
public val clipboard: StringListOption =
addOption(StringListOption("clipboard", "cb", "autoselect,exclude:cons\\|linux"))
// IdeaVim specific options. Put any editor or IDE specific options in IjVimOptionService
public val ideaglobalmode: ToggleOption = addOption(ToggleOption("ideaglobalmode", "ideaglobalmode", false))

View File

@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionChangeListener
import com.maddyhome.idea.vim.options.OptionScope
import com.maddyhome.idea.vim.options.OptionValueAccessor
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
@ -117,9 +118,15 @@ public fun <T: VimDataType> VimOptionGroup.resetDefaultValue(option: Option<T>,
}
/**
* Checks if the given string option matches the value, or a string list contains the value
* Checks if the given string option matches the value
*/
public fun VimOptionGroup.hasValue(option: StringOption, scope: OptionScope, value: String): Boolean =
value == getOptionValue(option, scope).asString()
/**
* Checks if the given string list option contains the value
*/
public fun VimOptionGroup.hasValue(option: StringListOption, scope: OptionScope, value: String): Boolean =
value in option.split(getOptionValue(option, scope).asString())
/**
@ -127,7 +134,7 @@ public fun VimOptionGroup.hasValue(option: StringOption, scope: OptionScope, val
*
* E.g. the `fileencodings` option with value "ucs-bom,utf-8,default,latin1" will result listOf("ucs-bom", "utf-8", "default", "latin1")
*/
public fun VimOptionGroup.getStringListValues(option: StringOption, scope: OptionScope): List<String> {
public fun VimOptionGroup.getStringListValues(option: StringListOption, scope: OptionScope): List<String> {
return option.split(getOptionValue(option, scope).asString())
}

View File

@ -71,8 +71,50 @@ public abstract class Option<T : VimDataType>(public val name: String, public va
public abstract fun parseValue(value: String, token: String): VimDataType
}
public open class StringOption(name: String, abbrev: String, defaultValue: VimString, public val isList: Boolean = false, public val boundedValues: Collection<String>? = null) : Option<VimString>(name, abbrev, defaultValue) {
public constructor(name: String, abbrev: String, defaultValue: String, isList: Boolean = false, boundedValues: Collection<String>? = null) : this(name, abbrev, VimString(defaultValue), isList, boundedValues)
public open class StringOption(name: String, abbrev: String, defaultValue: VimString, public val boundedValues: Collection<String>? = null) : Option<VimString>(name, abbrev, defaultValue) {
public constructor(name: String, abbrev: String, defaultValue: String, boundedValues: Collection<String>? = null) : this(name, abbrev, VimString(defaultValue), boundedValues)
override fun checkIfValueValid(value: VimDataType, token: String) {
if (value !is VimString) {
throw exExceptionMessage("E474", token)
}
if (value.value.isEmpty()) {
return
}
if (boundedValues != null && !boundedValues.contains(value.value)) {
throw exExceptionMessage("E474", token)
}
}
override fun parseValue(value: String, token: String): VimString =
VimString(value).also { checkIfValueValid(it, token) }
public fun appendValue(currentValue: VimString, value: VimString): VimString =
VimString(currentValue.value + value.value)
public fun prependValue(currentValue: VimString, value: VimString): VimString =
VimString(value.value + currentValue.value)
public fun removeValue(currentValue: VimString, value: VimString): VimString {
// TODO: Not sure this is correct. Should replace just the first occurrence?
return VimString(currentValue.value.replace(value.value, ""))
}
}
/**
* Represents a string that is a comma-separated list of values
*/
public open class StringListOption(
name: String,
abbrev: String,
defaultValue: VimString,
public val boundedValues: Collection<String>? = null,
) : Option<VimString>(name, abbrev, defaultValue) {
public constructor(name: String, abbrev: String, defaultValue: String, boundedValues: Collection<String>? = null)
: this(name, abbrev, VimString(defaultValue), boundedValues)
override fun checkIfValueValid(value: VimDataType, token: String) {
if (value !is VimString) {
@ -92,44 +134,34 @@ public open class StringOption(name: String, abbrev: String, defaultValue: VimSt
VimString(value).also { checkIfValueValid(it, token) }
public fun appendValue(currentValue: VimString, value: VimString): VimString {
// TODO: What happens if we're trying to add a sublist that already exists?
if (split(currentValue.value).contains(value.value)) return currentValue
return VimString(joinValues(currentValue.value, value.value))
}
public fun prependValue(currentValue: VimString, value: VimString): VimString {
// TODO: What happens if we're trying to add a sublist that already exists?
if (split(currentValue.value).contains(value.value)) return currentValue
return VimString(joinValues(value.value, currentValue.value))
}
public fun removeValue(currentValue: VimString, value: VimString): VimString {
val newValue = if (isList) {
val valuesToRemove = split(value.value)
val elements = split(currentValue.value).toMutableList()
if (Collections.indexOfSubList(elements, valuesToRemove) != -1) {
// see `:help set`
// When the option is a list of flags, {value} must be
// exactly as they appear in the option. Remove flags
// one by one to avoid problems.
elements.removeAll(valuesToRemove)
}
elements.joinToString(separator = ",")
} else {
// TODO: Not sure this is correct. Should replace just the first occurrence?
currentValue.value.replace(value.value, "")
val valuesToRemove = split(value.value)
val elements = split(currentValue.value).toMutableList()
if (Collections.indexOfSubList(elements, valuesToRemove) != -1) {
// see `:help set`
// When the option is a list of flags, {value} must be
// exactly as they appear in the option. Remove flags
// one by one to avoid problems.
elements.removeAll(valuesToRemove)
}
return VimString(newValue)
return VimString(elements.joinToString(separator = ","))
}
public open fun split(value: String): List<String> {
return if (isList) {
value.split(",")
} else {
listOf(value)
}
}
public open fun split(value: String): List<String> = value.split(",")
private fun joinValues(first: String, second: String): String {
val separator = if (isList && first.isNotEmpty()) "," else ""
val separator = if (first.isNotEmpty()) "," else ""
return first + separator + second
}
}

View File

@ -33,18 +33,22 @@ public class OptionValueAccessor(private val optionGroup: VimOptionGroup, public
/** Gets the option value as a string */
public fun getStringValue(option: StringOption): String = getValue(option).value
/** Gets the option value as a string */
public fun getStringValue(option: StringListOption): String = getValue(option).value
/**
* Gets the option value as a string list
*
* @see hasValue
*/
public fun getStringListValues(option: StringOption): List<String> = optionGroup.getStringListValues(option, scope)
public fun getStringListValues(option: StringListOption): List<String> = optionGroup.getStringListValues(option, scope)
/** Checks if a string list option contains a value, or if a simple string value matches the given value
*
* If the option is a string option, the given value must match the entire string
*/
public fun hasValue(option: StringOption, value: String): Boolean = optionGroup.hasValue(option, scope, value)
public fun hasValue(option: StringListOption, value: String): Boolean = optionGroup.hasValue(option, scope, value)
/**
* Checks the option value is set/true

View File

@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionScope
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
@ -275,6 +276,7 @@ private fun formatKnownOptionValue(option: Option<out VimDataType>, scope: Optio
private fun appendValue(option: Option<VimDataType>, currentValue: VimDataType, value: VimDataType): VimDataType? {
return when (option) {
is StringOption -> option.appendValue(currentValue as VimString, value as VimString)
is StringListOption -> option.appendValue(currentValue as VimString, value as VimString)
is NumberOption -> option.addValues(currentValue as VimInt, value as VimInt)
else -> null
}
@ -283,6 +285,7 @@ private fun appendValue(option: Option<VimDataType>, currentValue: VimDataType,
private fun prependValue(option: Option<VimDataType>, currentValue: VimDataType, value: VimDataType): VimDataType? {
return when (option) {
is StringOption -> option.prependValue(currentValue as VimString, value as VimString)
is StringListOption -> option.prependValue(currentValue as VimString, value as VimString)
is NumberOption -> option.multiplyValues(currentValue as VimInt, value as VimInt)
else -> null
}
@ -291,6 +294,7 @@ private fun prependValue(option: Option<VimDataType>, currentValue: VimDataType,
private fun removeValue(option: Option<VimDataType>, currentValue: VimDataType, value: VimDataType): VimDataType? {
return when (option) {
is StringOption -> option.removeValue(currentValue as VimString, value as VimString)
is StringListOption -> option.removeValue(currentValue as VimString, value as VimString)
is NumberOption -> option.subtractValues(currentValue as VimInt, value as VimInt)
else -> null
}