mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-07-30 09:59:08 +02:00
Variable locking
This commit is contained in:
parent
fc81c6329b
commit
0da18b81b6
src
main
antlr
java/com/maddyhome/idea/vim/vimscript
model
commands
datatypes
parser/visitors
services
test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands
@ -114,7 +114,7 @@ command:
|
||||
| MARK_COMMAND | JUMPS | J_LOWERCASE | JOIN_LINES | HISTORY | GO_TO_CHAR | SYMBOL | FIND | CLASS | F_LOWERCASE
|
||||
| FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD
|
||||
| T_LOWERCASE | COPY | CMD_CLEAR | EXCLAMATION | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII
|
||||
| ACTIONLIST | ACTION
|
||||
| ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR
|
||||
)
|
||||
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR)) (NEW_LINE | BAR)*
|
||||
#CommandWithComment|
|
||||
@ -666,6 +666,8 @@ UNMAP: 'unm' | 'nun' | 'vu' | 'xu' | 'sunm' | 'ou' | 'iu' | 'cu
|
||||
| 'unma' | 'nunma' | 'vunma' | 'xunma' | 'sunma' | 'ounma' | 'iunma' | 'cunma'
|
||||
| 'unmap' | 'nunmap' | 'vunmap' | 'xunmap' | 'sunmap' | 'ounmap' | 'iunmap' | 'cunmap';
|
||||
EXECUTE: 'exe' | 'exec' | 'execu' | 'execut' | 'execute';
|
||||
LOCKVAR: 'lockv' | 'lockva' | 'lockvar';
|
||||
UNLOCKVAR: 'unlo' | 'unloc' | 'unlock' | 'unlockv' | 'unlockva' | 'unlockvar';
|
||||
|
||||
// Types
|
||||
DIGIT: [0-9];
|
||||
|
@ -55,6 +55,9 @@ data class LetCommand(
|
||||
throw ExException("E46: Cannot change read-only variable \"${variable.toString(editor, context, parent)}\"")
|
||||
}
|
||||
val leftValue = VariableService.getNullableVariableValue(variable, editor, context, parent)
|
||||
if (leftValue?.isLocked == true && leftValue.lockOwner?.name == variable.name) {
|
||||
throw ExException("E741: Value is locked: ${variable.toString(editor, context, parent)}")
|
||||
}
|
||||
val rightValue = expression.evaluate(editor, context, parent)
|
||||
VariableService.storeVariable(variable, operator.getNewValue(leftValue, rightValue), editor, context, this)
|
||||
}
|
||||
@ -68,8 +71,16 @@ data class LetCommand(
|
||||
}
|
||||
val expressionValue = expression.evaluate(editor, context, this)
|
||||
var valueToStore = if (dictKey in containerValue.dictionary) {
|
||||
if (containerValue.dictionary[dictKey]!!.isLocked) {
|
||||
// todo better exception message
|
||||
throw ExException("E741: Value is locked: ${variable.originalString}")
|
||||
}
|
||||
operator.getNewValue(containerValue.dictionary[dictKey]!!, expressionValue)
|
||||
} else {
|
||||
if (containerValue.isLocked) {
|
||||
// todo better exception message
|
||||
throw ExException("E741: Value is locked: ${variable.originalString}")
|
||||
}
|
||||
expressionValue
|
||||
}
|
||||
if (valueToStore is VimFuncref && !valueToStore.isSelfFixed &&
|
||||
@ -88,6 +99,9 @@ data class LetCommand(
|
||||
if (index > containerValue.values.size - 1) {
|
||||
throw ExException("E684: list index out of range: $index")
|
||||
}
|
||||
if (containerValue.values[index].isLocked) {
|
||||
throw ExException("E741: Value is locked: ${variable.originalString}")
|
||||
}
|
||||
containerValue.values[index] = operator.getNewValue(containerValue.values[index], expression.evaluate(editor, context, parent))
|
||||
}
|
||||
is VimBlob -> TODO()
|
||||
|
@ -0,0 +1,67 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.commands
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.ex.ExException
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
|
||||
import com.maddyhome.idea.vim.vimscript.services.VariableService
|
||||
|
||||
/**
|
||||
* see :h lockvar
|
||||
*/
|
||||
class LockVarCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges, argument) {
|
||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
// todo doesn't throw proper vim exceptions in case of wrong arguments
|
||||
override fun processCommand(editor: Editor, context: DataContext): ExecutionResult {
|
||||
val variableAndDepth = parseVariableAndDepth(argument)
|
||||
VariableService.lockVariable(variableAndDepth.first, variableAndDepth.second, editor, context, parent)
|
||||
return ExecutionResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* see :h unlockvar
|
||||
*/
|
||||
class UnlockVarCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges, argument) {
|
||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
// todo doesn't throw proper vim exceptions in case of wrong arguments
|
||||
override fun processCommand(editor: Editor, context: DataContext): ExecutionResult {
|
||||
val variableAndDepth = parseVariableAndDepth(argument)
|
||||
VariableService.unlockVariable(variableAndDepth.first, variableAndDepth.second, editor, context, parent)
|
||||
return ExecutionResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVariableAndDepth(argument: String): Pair<Variable, Int> {
|
||||
val variable: String
|
||||
var arg = argument
|
||||
var depth = 2
|
||||
if (arg.startsWith("!")) {
|
||||
depth = 100
|
||||
arg = arg.substring(1)
|
||||
}
|
||||
val splittedArg = arg.trim().split(" ")
|
||||
when (splittedArg.size) {
|
||||
1 -> variable = splittedArg[0]
|
||||
2 -> {
|
||||
depth = splittedArg[0].toIntOrNull() ?: 2
|
||||
variable = splittedArg[1]
|
||||
}
|
||||
else -> throw ExException("Unknown error during lockvar command execution")
|
||||
}
|
||||
return Pair(parseVariable(variable), depth)
|
||||
}
|
||||
|
||||
private fun parseVariable(variable: String): Variable {
|
||||
val splittedString = variable.split(":")
|
||||
return when (splittedString.size) {
|
||||
1 -> Variable(null, splittedString[0])
|
||||
2 -> Variable(Scope.getByValue(splittedString[0]), splittedString[1])
|
||||
else -> throw ExException("Unknown error during lockvar command execution")
|
||||
}
|
||||
}
|
@ -22,6 +22,14 @@ class VimBlob : VimDataType() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.datatypes
|
||||
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
|
||||
|
||||
sealed class VimDataType {
|
||||
|
||||
abstract fun asDouble(): Double
|
||||
@ -19,4 +21,10 @@ sealed class VimDataType {
|
||||
}
|
||||
|
||||
abstract fun deepCopy(level: Int = 100): VimDataType
|
||||
|
||||
var lockOwner: Variable? = null
|
||||
var isLocked: Boolean = false
|
||||
|
||||
abstract fun lockVar(depth: Int)
|
||||
abstract fun unlockVar(depth: Int)
|
||||
}
|
||||
|
@ -42,4 +42,22 @@ data class VimDictionary(val dictionary: LinkedHashMap<VimString, VimDataType>)
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
this.isLocked = true
|
||||
if (depth > 1) {
|
||||
for (value in dictionary.values) {
|
||||
value.lockVar(depth - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
this.isLocked = false
|
||||
if (depth > 1) {
|
||||
for (value in dictionary.values) {
|
||||
value.unlockVar(depth - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,4 +26,12 @@ data class VimFloat(val value: Double) : VimDataType() {
|
||||
override fun deepCopy(level: Int): VimFloat {
|
||||
return copy()
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
this.isLocked = true
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
this.isLocked = false
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,14 @@ data class VimFuncref(
|
||||
return copy()
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
this.isLocked = true
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
this.isLocked = false
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
LAMBDA,
|
||||
FUNCREF,
|
||||
|
@ -22,6 +22,14 @@ data class VimInt(val value: Int) : VimDataType() {
|
||||
return copy()
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
this.isLocked = true
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
this.isLocked = false
|
||||
}
|
||||
|
||||
operator fun compareTo(b: Int): Int = this.value.compareTo(b)
|
||||
|
||||
companion object {
|
||||
|
@ -36,4 +36,22 @@ data class VimList(val values: MutableList<VimDataType>) : VimDataType() {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
this.isLocked = true
|
||||
if (depth > 1) {
|
||||
for (value in values) {
|
||||
value.lockVar(depth - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
this.isLocked = false
|
||||
if (depth > 1) {
|
||||
for (value in values) {
|
||||
value.unlockVar(depth - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,4 +65,12 @@ data class VimString(val value: String) : VimDataType() {
|
||||
override fun deepCopy(level: Int): VimString {
|
||||
return copy()
|
||||
}
|
||||
|
||||
override fun lockVar(depth: Int) {
|
||||
this.isLocked = true
|
||||
}
|
||||
|
||||
override fun unlockVar(depth: Int) {
|
||||
this.isLocked = false
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import com.maddyhome.idea.vim.vimscript.model.commands.HistoryCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.JoinLinesCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.JumpsCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.LetCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.LockVarCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.MarkCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.MarksCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.MoveTextCommand
|
||||
@ -74,6 +75,7 @@ import com.maddyhome.idea.vim.vimscript.model.commands.TabCloseCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.TabOnlyCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.UndoCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.UnknownCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.UnlockVarCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.WriteAllCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.WriteCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.WriteNextFileCommand
|
||||
@ -784,6 +786,15 @@ object CommandVisitor : VimscriptBaseVisitor<Command>() {
|
||||
"ounmap" to UnMapCommand::class,
|
||||
"iunmap" to UnMapCommand::class,
|
||||
"cunmap" to UnMapCommand::class,
|
||||
"lockv" to LockVarCommand::class,
|
||||
"lockva" to LockVarCommand::class,
|
||||
"lockvar" to LockVarCommand::class,
|
||||
"unlo" to UnlockVarCommand::class,
|
||||
"unloc" to UnlockVarCommand::class,
|
||||
"unlock" to UnlockVarCommand::class,
|
||||
"unlockv" to UnlockVarCommand::class,
|
||||
"unlockva" to UnlockVarCommand::class,
|
||||
"unlockvar" to UnlockVarCommand::class,
|
||||
)
|
||||
|
||||
private fun getCommandByName(commandName: String): KClass<out Command> {
|
||||
|
@ -52,6 +52,21 @@ object VariableService {
|
||||
}
|
||||
}
|
||||
|
||||
fun isVariableLocked(variable: Variable, editor: Editor, context: DataContext, parent: Executable): Boolean {
|
||||
return getNullableVariableValue(variable, editor, context, parent)?.isLocked ?: false
|
||||
}
|
||||
|
||||
fun lockVariable(variable: Variable, depth: Int, editor: Editor, context: DataContext, parent: Executable) {
|
||||
val value = getNullableVariableValue(variable, editor, context, parent) ?: return
|
||||
value.lockOwner = variable
|
||||
value.lockVar(depth)
|
||||
}
|
||||
|
||||
fun unlockVariable(variable: Variable, depth: Int, editor: Editor, context: DataContext, parent: Executable) {
|
||||
val value = getNullableVariableValue(variable, editor, context, parent) ?: return
|
||||
value.unlockVar(depth)
|
||||
}
|
||||
|
||||
fun storeVariable(variable: Variable, value: VimDataType, editor: Editor, context: DataContext, parent: Executable) {
|
||||
val scope = variable.scope ?: getDefaultVariableScope(parent)
|
||||
when (scope) {
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2021 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.jetbrains.plugins.ideavim.ex.implementation.commands
|
||||
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
class LockVarCommandTest : VimTestCase() {
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test lock int variable`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = 10"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x = 15"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked: x")
|
||||
}
|
||||
|
||||
fun `test unlock int variable`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = 10"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("unlockvar x"))
|
||||
typeText(commandToKeys("let x = 15"))
|
||||
assertPluginError(false)
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("15\n")
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test lock list variable`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = [1]"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x = 15"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked: x")
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test lock list variable 2`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = [1]"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x += [2]"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked: x")
|
||||
}
|
||||
|
||||
fun `test reassigning assigned locked value`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = 10"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let y = x"))
|
||||
typeText(commandToKeys("echo y"))
|
||||
assertExOutput("10\n")
|
||||
typeText(commandToKeys("let y = 15"))
|
||||
typeText(commandToKeys("echo y"))
|
||||
assertExOutput("15\n")
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test list elements are also locked`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = [1, 2]"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x[0] = 15"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked")
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("[1, 2]\n")
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test dict elements are also locked`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = {'one': 1}"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x.two = 2"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked")
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("{'one': 1}\n")
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test can modify dict elements but not the dict itself`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = {'one': 1}"))
|
||||
typeText(commandToKeys("lockvar 1 x"))
|
||||
typeText(commandToKeys("let x.one = 42"))
|
||||
assertPluginError(false)
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("{'one': 42}\n")
|
||||
typeText(commandToKeys("let x.two = 2"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked")
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("{'one': 42}\n")
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test dict elements are also locked 2`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = {'one': 1}"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x['two'] = 2"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked")
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("{'one': 1}\n")
|
||||
}
|
||||
|
||||
fun `test default lock depth`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = {'list': [1]}"))
|
||||
typeText(commandToKeys("lockvar x"))
|
||||
typeText(commandToKeys("let x.list[0] = 42"))
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("{'list': [42]}\n")
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test custom lock depth`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("let x = {'list': [1]}"))
|
||||
typeText(commandToKeys("lockvar 3 x"))
|
||||
typeText(commandToKeys("let x.list[0] = 42"))
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E741: Value is locked")
|
||||
typeText(commandToKeys("echo x"))
|
||||
assertExOutput("{'list': [1]}\n")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user