1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-10 12:34:06 +02:00

Added vimScript package & antlr grammar

This commit is contained in:
lippfi 2021-07-30 15:40:34 +03:00
parent 93109f1e19
commit 46788cc6c6
193 changed files with 6935 additions and 1241 deletions
.gitignoreCONTRIBUTING.mdbuild.gradle.ktsdoc
resources/META-INF
src/com/maddyhome/idea/vim
VimPlugin.java
action/macro
ex
extension
group
helper
regexp
ui
vimscript

3
.gitignore vendored
View File

@ -20,5 +20,8 @@
.teamcity/target
.teamcity/*.iml
# Generated by gradle task "generateGrammarSource"
src/com/maddyhome/idea/vim/vimscript/parser/generated
# Created by github automation
settings.xml

View File

@ -61,7 +61,9 @@ If you are looking for:
- Ex commands (`:set`, `:s`, `:nohlsearch`):
- Any particular ex command: package `com.maddyhome.idea.vim.ex.handler`.
- Ex command executor: `CommandHandler`.
- Vim script grammar: `Vimscript.g4`.
- Vim script parsing: package `com.maddyhome.idea.vim.vimscript.parser`.
- Vim script executor: `Executor`.
- Extensions:
- Extensions handler: `VimExtensionHandler`.

View File

@ -20,6 +20,7 @@ buildscript {
}
plugins {
antlr
java
kotlin("jvm") version "1.5.0"
@ -58,6 +59,11 @@ dependencies {
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
compileOnly("org.antlr:antlr4-runtime:4.9.2")
antlr("org.antlr:antlr4:4.9.2")
implementation("org.reflections:reflections:0.9.12")
}
// --- Compilation
@ -137,6 +143,12 @@ tasks {
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true)
}
generateGrammarSource {
maxHeapSize = "128m"
arguments.addAll(listOf("-package", "com.maddyhome.idea.vim.vimscript.parser.generated", "-visitor"))
outputDirectory = file("src/com/maddyhome/idea/vim/vimscript/parser/generated")
}
}
// --- Linting

2
doc

@ -1 +1 @@
Subproject commit 83aaa9801dddd9ba956e2d5f85534872a69b7f37
Subproject commit 9cfed4680f8f8d806d12b85df1cd1e2421a999c0

View File

@ -13,7 +13,6 @@
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.DumpLineHandler" names="dump[line]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.EditFileHandler" names="bro[wse],e[dit]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.ActionHandler" names="action"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.EchoHandler" names="ec[ho]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.ExitHandler" names="qa[ll],quita[ll],wqa[ll],xa[ll]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.FileHandler" names="f[ile]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.FindClassHandler" names="cla[ss]"/>
@ -25,7 +24,6 @@
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.HistoryHandler" names="his[tory]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.JoinLinesHandler" names="j[oin]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.JumpsHandler" names="ju[mps]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.LetHandler" names="let"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.mapping.MapHandler"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.mapping.UnMapHandler"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.mapping.MapClearHandler"/>

View File

@ -0,0 +1,6 @@
<idea-plugin>
<extensions defaultExtensionNs="IdeaVIM">
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.AbsFunctionHandler" name="abs"/>
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.SinFunctionHandler" name="sin"/>
</extensions>
</idea-plugin>

View File

@ -72,7 +72,10 @@
<with attribute="implementation" implements="com.maddyhome.idea.vim.extension.VimExtension"/>
</extensionPoint>
<extensionPoint name="vimLibraryFunction"
beanClass="com.maddyhome.idea.vim.vimscript.model.functions.FunctionBeanClass" dynamic="true">
<with attribute="implementation" implements="com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler"/>
</extensionPoint>
<!-- For internal use only -->
<extensionPoint name="vimExCommand" beanClass="com.maddyhome.idea.vim.ex.ExBeanClass" dynamic="true">
<with attribute="implementation" implements="com.maddyhome.idea.vim.ex.CommandHandler"/>
@ -108,6 +111,7 @@
<xi:include href="/META-INF/includes/VimExCommands.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimExtensions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimLibraryFunctions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">

View File

@ -42,8 +42,6 @@ import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.maddyhome.idea.vim.config.VimState;
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser;
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar;
import com.maddyhome.idea.vim.group.*;
import com.maddyhome.idea.vim.group.copy.PutGroup;
@ -54,8 +52,9 @@ import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable;
import com.maddyhome.idea.vim.ui.VimRcFileState;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.ex.ExCommand;
import com.maddyhome.idea.vim.vimscript.services.FunctionStorage;
import org.jdom.Element;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@ -63,11 +62,10 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.awt.*;
import java.io.File;
import java.util.List;
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc;
/**
* This plugin attempts to emulate the key binding and general functionality of Vim and gVim. See the supplied
@ -238,27 +236,10 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
ideavimrcRegistered = true;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
try {
VimScriptParser.INSTANCE.setExecutingVimScript(true);
executeIdeaVimRc();
}
finally {
VimScriptParser.INSTANCE.setExecutingVimScript(false);
}
executeIdeaVimRc();
}
}
public void executeIdeaVimRc() {
final File ideaVimRc = VimScriptParser.findIdeaVimRc();
if (ideaVimRc != null) {
LOG.info("Execute ideavimrc file: " + ideaVimRc.getAbsolutePath());
List<String> parsedLines = VimScriptParser.executeFile(ideaVimRc);
VimRcFileState.INSTANCE.saveFileState(ideaVimRc.getAbsolutePath(), parsedLines);
}
else {
LOG.info("ideavimrc file isn't found");
}
}
public static @NotNull PluginId getPluginId() {
return PluginId.getId(IDEAVIM_PLUGIN_ID);
@ -359,11 +340,14 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
RegisterActions.registerActions();
// Register ex handlers
CommandParser.INSTANCE.registerHandlers();
ExCommand.CommandHandlersTree.registerHandlers();
// Register extensions
VimExtensionRegistrar.registerExtensions();
// Register functions
FunctionStorage.INSTANCE.registerHandlers();
// Execute ~/.ideavimrc
registerIdeavimrc();
@ -391,7 +375,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
RegisterActions.unregisterActions();
// Unregister ex handlers
CommandParser.INSTANCE.unregisterHandlers();
ExCommand.CommandHandlersTree.unregisterHandlers();
}
private boolean stateUpdated = false;

View File

@ -25,10 +25,10 @@ import com.intellij.openapi.util.Ref
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.group.RegisterGroup
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.vimscript.Executor
class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
@ -41,21 +41,29 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() {
val project = PlatformDataKeys.PROJECT.getData(context)
val application = ApplicationManager.getApplication()
val res = Ref.create(false)
when (reg) {
'@' -> {
when {
reg == RegisterGroup.LAST_COMMAND_REGISTER || (reg == '@' && VimPlugin.getMacro().lastRegister == RegisterGroup.LAST_COMMAND_REGISTER) -> { // No write action
try {
var i = 0
while (i < cmd.count) {
res.set(Executor.executeLastCommand(editor, context))
if (!res.get()) {
break
}
i += 1
}
VimPlugin.getMacro().setLastRegister(reg)
} catch (e: ExException) {
res.set(false)
}
}
reg == '@' -> {
application.runWriteAction {
res.set(
VimPlugin.getMacro().playbackLastRegister(editor, context, project, cmd.count)
)
}
}
RegisterGroup.LAST_COMMAND_REGISTER -> { // No write action
try {
res.set(CommandParser.processLastCommand(editor, context, cmd.count))
} catch (e: ExException) {
res.set(false)
}
}
else -> {
application.runWriteAction {
res.set(

View File

@ -126,7 +126,7 @@ sealed class CommandHandler {
* @throws ExException if the range or argument is invalid or unable to run the command
*/
@Throws(ExException::class)
fun process(editor: Editor, context: DataContext, cmd: ExCommand, count: Int): Boolean {
fun process(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
checkArgs(cmd)
@ -140,8 +140,7 @@ sealed class CommandHandler {
is ForEachCaret -> {
editor.caretModel.runForEachCaret(
{ caret ->
var i = 0
while (i++ < count && res.get()) {
if (res.get()) {
res.set(execute(editor, caret, context, cmd))
}
},
@ -149,10 +148,7 @@ sealed class CommandHandler {
)
}
is SingleExecution -> {
var i = 0
while (i++ < count && res.get()) {
res.set(execute(editor, context, cmd))
}
res.set(execute(editor, context, cmd))
}
}

View File

@ -1,537 +0,0 @@
/*
* 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 com.maddyhome.idea.vim.ex
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.util.ThrowableComputable
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.GoalCommand
import com.maddyhome.idea.vim.ex.handler.GotoLineHandler
import com.maddyhome.idea.vim.ex.ranges.Range.Companion.createRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.group.HistoryGroup
import com.maddyhome.idea.vim.group.RegisterGroup
import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.helper.Msg
import java.util.regex.Pattern
/**
* Maintains a tree of Ex commands based on the required and optional parts of the command names. Parses and
* executes Ex commands entered by the user.
*/
object CommandParser {
private const val MAX_RECURSION = 100
private val TRIM_WHITESPACE = Pattern.compile("[ \\t]*(.*)[ \\t\\n\\r]+", Pattern.DOTALL)
val EX_COMMAND_EP = ExtensionPointName.create<ExBeanClass>("IdeaVIM.vimExCommand")
private val logger = logger<CommandParser>()
private val root = CommandNode()
fun unregisterHandlers() {
root.clear()
}
/**
* Registers all the supported Ex commands
*/
fun registerHandlers() {
EX_COMMAND_EP.extensions().forEach(ExBeanClass::register)
registerEpListener()
}
private fun registerEpListener() {
// IdeaVim doesn't support contribution to ex_command_ep extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload handlers on every EP change.
EX_COMMAND_EP.addChangeListener(
{
unregisterHandlers()
registerHandlers()
},
VimPlugin.getInstance()
)
}
/**
* Used to rerun the last Ex command, if any
*
* @param editor The editor to run the command in
* @param context The data context
* @param count The number of times to run the command
* @return True if the command succeeded, false if it failed or there was no previous command
* @throws ExException if any part of the command was invalid
*/
@kotlin.jvm.Throws(ExException::class)
fun processLastCommand(editor: Editor, context: DataContext, count: Int): Boolean {
val reg = VimPlugin.getRegister().getRegister(':') ?: return false
val text = reg.text ?: return false
processCommand(editor, context, text, count)
return true
}
/**
* Parse and execute an Ex command entered by the user
*
* @param editor The editor to run the command in
* @param context The data context
* @param cmd The text entered by the user
* @param count The count entered before the colon
* @throws ExException if any part of the command is invalid or unknown
*/
@kotlin.jvm.Throws(ExException::class)
fun processCommand(editor: Editor, context: DataContext, cmd: String, count: Int, skipHistory: Boolean = false) {
processCommand(editor, context, cmd, count, MAX_RECURSION, skipHistory)
}
@kotlin.jvm.Throws(ExException::class)
private fun processCommand(
editor: Editor,
context: DataContext,
cmd: String,
count: Int,
aliasCountdown: Int,
skipHistory: Boolean,
) {
// Nothing entered
if (cmd.isEmpty()) {
logger.warn("CMD is empty")
return
}
// Only save the command to the history if it is at the top of the stack.
// We don't want to save the aliases that will be executed, only the actual
// user input.
if (aliasCountdown == MAX_RECURSION && !skipHistory) {
// Save the command history
VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, cmd)
}
// If there is a command alias for the entered text, then process the alias and return that
// instead of the original command.
if (VimPlugin.getCommand().isAlias(cmd)) {
if (aliasCountdown > 0) {
val commandAlias = VimPlugin.getCommand().getAliasCommand(cmd, count)
when (commandAlias) {
is GoalCommand.Ex -> {
if (commandAlias.command.isEmpty()) {
logger.warn("Command alias is empty")
return
}
processCommand(editor, context, commandAlias.command, count, aliasCountdown - 1, skipHistory)
}
is GoalCommand.Call -> commandAlias.handler.execute(editor, context)
}.let { }
} else {
VimPlugin.showMessage(message("recursion.detected.maximum.alias.depth.reached"))
VimPlugin.indicateError()
logger.warn("Recursion detected, maximum alias depth reached. ")
}
return
}
// Parse the command
val command = parse(cmd)
val handler = getCommandHandler(command)
if (handler == null) {
val message = message(Msg.NOT_EX_CMD, command.command)
throw InvalidCommandException(message, null)
}
if (handler.argFlags.access === CommandHandler.Access.WRITABLE && !editor.document.isWritable) {
VimPlugin.indicateError()
logger.info("Trying to modify readonly document")
return
}
// Run the command
val runCommand = ThrowableComputable<Any?, ExException> {
val ok = handler.process(editor, context, command, count)
if (ok && !handler.argFlags.flags.contains(CommandHandler.Flag.DONT_SAVE_LAST)) {
VimPlugin.getRegister().storeTextSpecial(RegisterGroup.LAST_COMMAND_REGISTER, cmd)
}
null
}
when (handler.argFlags.access) {
CommandHandler.Access.WRITABLE -> ApplicationManager.getApplication().runWriteAction(runCommand)
CommandHandler.Access.READ_ONLY -> ApplicationManager.getApplication().runReadAction(runCommand)
CommandHandler.Access.SELF_SYNCHRONIZED -> runCommand.compute()
}
}
fun getCommandHandler(command: ExCommand): CommandHandler? {
val cmd = command.command
// If there is no command, just a range, use the 'goto line' handler
if (cmd.isEmpty()) {
return GotoLineHandler()
}
// See if the user entered a supported command by checking each character entered
var node: CommandNode = root
for (element in cmd) {
node = node.getChild(element) ?: return null
}
val handlerHolder = node.commandHandler
return handlerHolder?.instance
}
/**
* Parse the text entered by the user. This does not include the leading colon.
*
* @param cmd The user entered text
* @return The parse result
* @throws ExException if the text is syntactically incorrect
*/
@kotlin.jvm.Throws(ExException::class)
fun parse(cmd: String): ExCommand {
// This is a complicated state machine that should probably be rewritten
logger.debug { "processing `$cmd'" }
var state = State.START
val ranges = Ranges() // The list of ranges
val command = StringBuilder() // The command
val argument = StringBuilder() // The command's argument(s)
var location: StringBuffer? = null // The current range text
var offsetSign = 1 // Sign of current range offset
var offsetNumber = -1 // The value of the current range offset
var offsetTotal = 0 // The sum of all the current range offsets
var move = false // , vs. ; separated ranges (true=; false=,)
var patternType = 0.toChar() // ? or /
var backCount = 0 // Number of backslashes in a row in a pattern
var inBrackets = false // If inside [ ] range in a pattern
var error = ""
// Loop through each character. Treat the end of the string as a newline character
for (i in 0..cmd.length) {
var reprocess = true // Should the current character be reprocessed after a state change?
val ch = if (i == cmd.length) '\n' else cmd[i]
loop@ while (reprocess) {
when (state) {
State.START -> if (Character.isLetter(ch) || "~<>@=#*&!".indexOf(ch) >= 0) {
state = State.COMMAND
} else if (ch == ' ') {
state = State.START
reprocess = false
} else {
state = State.RANGE
}
State.COMMAND -> // For commands that start with a non-letter, treat other non-letter characters as part of
// the argument except for !, <, or >
if (Character.isLetter(ch) ||
command.isEmpty() && "~<>@=#*&!".indexOf(ch) >= 0 ||
command.isNotEmpty() && ch == command[command.length - 1] && "<>".indexOf(ch) >= 0
) {
command.append(ch)
reprocess = false
if (!Character.isLetter(ch) && "<>".indexOf(ch) < 0) {
state = State.CMD_ARG
}
} else {
state = State.CMD_ARG
}
State.CMD_ARG -> {
argument.append(ch)
reprocess = false
}
State.RANGE -> {
location = StringBuffer()
offsetTotal = 0
offsetNumber = -1
move = false
if (ch in '0'..'9') {
state = State.RANGE_LINE
} else if (ch == '.') {
state = State.RANGE_CURRENT
} else if (ch == '$') {
state = State.RANGE_LAST
} else if (ch == '%') {
state = State.RANGE_ALL
} else if (ch == '\'') {
state = State.RANGE_MARK
} else if (ch == '+' || ch == '-') {
location.append('.')
state = State.RANGE_OFFSET
} else if (ch == '\\') {
location.append(ch)
state = State.RANGE_SHORT_PATTERN
reprocess = false
} else if (ch == ',') {
location.append('.')
state = State.RANGE_MAYBE_DONE
} else if (ch == '/' || ch == '?') {
location.append(ch)
patternType = ch
backCount = 0
inBrackets = false
state = State.RANGE_PATTERN
reprocess = false
} else {
error = message(Msg.e_badrange, ch.toString())
state = State.ERROR
reprocess = false
}
}
State.RANGE_SHORT_PATTERN -> {
if (ch == '/' || ch == '?' || ch == '&') {
location!!.append(ch)
state = State.RANGE_PATTERN_MAYBE_DONE
} else {
error = message(Msg.e_backslash)
state = State.ERROR
}
reprocess = false
}
State.RANGE_PATTERN -> // No trailing / or ? required if there is no command so look for newline to tell us we are done
if (ch == '\n') {
location!!.append(patternType)
state = State.RANGE_MAYBE_DONE
} else {
// We need to skip over [ ] ranges. The ] is valid right after the [ or [^
location!!.append(ch)
val cBracketCond = !(
location[location.length - 2] == '[' ||
location.length >= 3 && location.substring(location.length - 3) == "[^]"
)
if (ch == '[' && !inBrackets) {
inBrackets = true
} else if (ch == ']' && inBrackets && cBracketCond) {
inBrackets = false
} else if (ch == '\\') {
backCount++
} else if (ch == patternType && !inBrackets &&
(location[location.length - 2] != '\\' || backCount % 2 == 0)
) {
state = State.RANGE_PATTERN_MAYBE_DONE
}
// No more backslashes
if (ch != '\\') {
backCount = 0
}
reprocess = false
}
State.RANGE_PATTERN_MAYBE_DONE -> if (ch == '/' || ch == '?') {
// Use a special character to separate pattern for later, easier, parsing
location!!.append('\u0000')
location.append(ch)
patternType = ch
backCount = 0
inBrackets = false
state = State.RANGE_PATTERN
reprocess = false
} else {
state = State.RANGE_MAYBE_DONE
}
State.RANGE_LINE -> if (ch in '0'..'9') {
location!!.append(ch)
state = State.RANGE_LINE_MAYBE_DONE
reprocess = false
} else {
state = State.RANGE_MAYBE_DONE
}
State.RANGE_LINE_MAYBE_DONE ->
state = if (ch in '0'..'9') {
State.RANGE_LINE
} else {
State.RANGE_MAYBE_DONE
}
State.RANGE_CURRENT -> {
location!!.append(ch)
state = State.RANGE_MAYBE_DONE
reprocess = false
}
State.RANGE_LAST -> {
location!!.append(ch)
state = State.RANGE_MAYBE_DONE
reprocess = false
}
State.RANGE_ALL -> {
location!!.append(ch)
state = State.RANGE_MAYBE_DONE
reprocess = false
}
State.RANGE_MARK -> {
location!!.append(ch)
state = State.RANGE_MARK_CHAR
reprocess = false
}
State.RANGE_MARK_CHAR -> {
location!!.append(ch)
state = State.RANGE_MAYBE_DONE
reprocess = false
}
State.RANGE_DONE -> {
val range = createRange(location.toString(), offsetTotal, move)
if (range == null) {
error = message(Msg.e_badrange, ch.toString())
state = State.ERROR
@Suppress("UNUSED_VALUE")
reprocess = false
break@loop
}
ranges.addRange(range)
// Could there be more ranges - nope - at end, start command
if (ch == ':' || ch == '\n') {
state = State.COMMAND
reprocess = false
} else if (Character.isLetter(ch) || "~<>@=#*&!".indexOf(ch) >= 0 || ch == ' ') {
state = State.START
} else {
state = State.RANGE
}
}
State.RANGE_MAYBE_DONE ->
state = if (ch == '+' || ch == '-' || ch in '0'..'9') {
// The range has an offset after it
State.RANGE_OFFSET
} else if (ch == ',' || ch == ';') {
State.RANGE_SEPARATOR
} else {
State.RANGE_DONE
}
State.RANGE_OFFSET -> {
// Figure out the sign of the offset and reset the offset value
offsetNumber = -1
offsetSign = 1
if (ch == '+') {
reprocess = false
} else if (ch == '-') {
offsetSign = -1
reprocess = false
}
state = State.RANGE_OFFSET_MAYBE_DONE
}
State.RANGE_OFFSET_MAYBE_DONE -> // We found an offset value
state = if (ch in '0'..'9') {
State.RANGE_OFFSET_NUM
} else {
State.RANGE_OFFSET_DONE
}
State.RANGE_OFFSET_DONE -> {
// No number implies a one
if (offsetNumber == -1) {
offsetNumber = 1
}
// Update offset total for this range
offsetTotal += offsetNumber * offsetSign
// Another offset
state = if (ch == '+' || ch == '-') {
State.RANGE_OFFSET
} else {
State.RANGE_MAYBE_DONE
}
}
State.RANGE_OFFSET_NUM -> // Update the value of the current offset
if (ch in '0'..'9') {
offsetNumber = (if (offsetNumber == -1) 0 else offsetNumber) * 10 + (ch - '0')
state = State.RANGE_OFFSET_MAYBE_DONE
reprocess = false
} else if (ch == '+' || ch == '-') {
state = State.RANGE_OFFSET_DONE
} else {
state = State.RANGE_OFFSET_MAYBE_DONE
}
State.RANGE_SEPARATOR -> {
if (ch == ',') {
move = false
} else if (ch == ';') {
move = true
}
state = State.RANGE_DONE
reprocess = false
}
State.ERROR -> {
}
}
}
// Oops - bad command string
if (state == State.ERROR) {
throw InvalidCommandException(error, cmd)
}
}
if (logger.isDebugEnabled) {
logger.debug("ranges = $ranges")
logger.debug("command = $command")
logger.debug("argument = $argument")
}
var argumentString = argument.toString()
val matcher = TRIM_WHITESPACE.matcher(argumentString)
if (matcher.matches()) {
argumentString = matcher.group(1)
}
return ExCommand(ranges, command.toString(), argumentString)
}
/** Adds a command handler to the parser */
fun addHandler(handlerHolder: ExBeanClass) {
// Iterator through each command name alias
val names: Array<CommandName> = when {
handlerHolder.names != null -> {
commands(*handlerHolder.names!!.split(",").toTypedArray())
}
handlerHolder.instance is ComplicatedNameExCommand -> {
(handlerHolder.instance as ComplicatedNameExCommand).names
}
else -> throw RuntimeException("Cannot create an ex command: $handlerHolder")
}
for (name in names) {
var node = root
var text = name.required
// Build a tree for each character in the required portion of the command name
for (i in 0 until text.length - 1) {
var cn = node.getChild(text[i])
if (cn == null) {
cn = node.addChild(text[i], null)
}
node = cn
}
// For the last character we need to add the actual handler
var cn = node.getChild(text[text.length - 1])
if (cn == null) {
cn = node.addChild(text[text.length - 1], handlerHolder)
} else {
cn.commandHandler = handlerHolder
}
node = cn
// Now add the handler for each character in the optional portion of the command name
text = name.optional
for (i in text.indices) {
cn = node.getChild(text[i])
if (cn == null) {
cn = node.addChild(text[i], handlerHolder)
} else if (cn.commandHandler == null) {
cn.commandHandler = handlerHolder
}
node = cn
}
}
}
private enum class State {
START, COMMAND,
CMD_ARG,
RANGE, RANGE_LINE, RANGE_CURRENT, RANGE_LAST, RANGE_MARK, RANGE_MARK_CHAR, RANGE_ALL, RANGE_PATTERN,
RANGE_SHORT_PATTERN, RANGE_PATTERN_MAYBE_DONE, RANGE_OFFSET, RANGE_OFFSET_NUM, RANGE_OFFSET_DONE,
RANGE_LINE_MAYBE_DONE, RANGE_OFFSET_MAYBE_DONE, RANGE_SEPARATOR, RANGE_MAYBE_DONE, RANGE_DONE, ERROR
}
}

View File

@ -37,7 +37,7 @@ class ExBeanClass : BaseKeyedLazyInstance<CommandHandler>() {
logger<ExBeanClass>().error("IdeaVim doesn't accept contributions to `vimActions` extension points. Please create a plugin using `VimExtension`. Plugin to blame: ${this.pluginDescriptor.pluginId}")
return
}
CommandParser.addHandler(this)
ExCommand.addHandler(this)
}
}

View File

@ -15,16 +15,218 @@
* 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 com.maddyhome.idea.vim.ex
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.util.ThrowableComputable
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.GoalCommand
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExCommand.Constants.MAX_RECURSION
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.group.HistoryGroup
import com.maddyhome.idea.vim.group.RegisterGroup
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.vimscript.Executor
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimContext
import javax.swing.JTextArea
class ExCommand(val ranges: Ranges, val command: String, var argument: String) {
data class ExCommand(val ranges: Ranges, var command: String, var argument: String, val originalString: String) :
Executable {
private val logger = logger<ExCommand>()
private object Constants {
const val MAX_RECURSION = 100
}
override fun execute(
editor: Editor?,
context: DataContext?,
vimContext: VimContext,
skipHistory: Boolean,
): ExecutionResult {
processCommand(editor, context, MAX_RECURSION, skipHistory)
return ExecutionResult.Success
}
@kotlin.jvm.Throws(ExException::class)
private fun processCommand(
editor: Editor?,
context: DataContext?,
aliasCountdown: Int,
skipHistory: Boolean,
) {
if (aliasCountdown == MAX_RECURSION && !skipHistory) {
VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, originalString)
}
if (command.isEmpty()) {
logger.warn("CMD is empty")
return
}
// If there is a command alias for the entered text, then process the alias and return that
// instead of the original command.
if (VimPlugin.getCommand().isAlias(command)) {
// ugly piece of s to support nullable editor and data context
val nonNullEditor: Editor = editor ?: TextComponentEditorImpl(null, JTextArea())
val nonNullContext: DataContext = context ?: DataContext.EMPTY_CONTEXT
if (aliasCountdown > 0) {
val commandAlias = VimPlugin.getCommand().getAliasCommand(originalString, 1)
when (commandAlias) {
is GoalCommand.Ex -> {
if (commandAlias.command.isEmpty()) {
logger.warn("Command alias is empty")
return
}
Executor.execute(commandAlias.command, editor, context, skipHistory)
}
// todo nullable editor & context
is GoalCommand.Call -> commandAlias.handler.execute(nonNullEditor, nonNullContext)
}.let { }
} else {
VimPlugin.showMessage(MessageHelper.message("recursion.detected.maximum.alias.depth.reached"))
VimPlugin.indicateError()
logger.warn("Recursion detected, maximum alias depth reached. ")
}
return
}
// Parse the command
val handler = getCommandHandler()
if (handler == null) {
val message = MessageHelper.message(Msg.NOT_EX_CMD, command)
throw InvalidCommandException(message, null)
}
if (editor != null) {
if (handler.argFlags.access === CommandHandler.Access.WRITABLE && !editor.document.isWritable) {
VimPlugin.indicateError()
logger.info("Trying to modify readonly document")
return
}
}
// ugly piece of s to support nullable editor and data context
val nonNullEditor: Editor = editor ?: TextComponentEditorImpl(null, JTextArea())
val nonNullContext: DataContext = context ?: DataContext.EMPTY_CONTEXT
// Run the command
val runCommand = ThrowableComputable<Any?, ExException> {
// todo nullable editor & context
val ok = handler.process(nonNullEditor, nonNullContext, this)
if (ok && !handler.argFlags.flags.contains(CommandHandler.Flag.DONT_SAVE_LAST)) {
val commandAsString = command + if (argument.isNotBlank()) " $argument" else ""
VimPlugin.getRegister().storeTextSpecial(RegisterGroup.LAST_COMMAND_REGISTER, commandAsString)
}
null
}
when (handler.argFlags.access) {
CommandHandler.Access.WRITABLE -> ApplicationManager.getApplication().runWriteAction(runCommand)
CommandHandler.Access.READ_ONLY -> ApplicationManager.getApplication().runReadAction(runCommand)
CommandHandler.Access.SELF_SYNCHRONIZED -> runCommand.compute()
}
}
fun getCommandHandler(): CommandHandler? {
// See if the user entered a supported command by checking each character entered
var node: CommandNode = root
for (char in command) {
node = node.getChild(char) ?: return null
}
val handlerHolder = node.commandHandler
return handlerHolder?.instance
}
companion object CommandHandlersTree {
val EX_COMMAND_EP = ExtensionPointName.create<ExBeanClass>("IdeaVIM.vimExCommand")
private val root = CommandNode()
fun unregisterHandlers() {
root.clear()
}
/**
* Registers all the supported Ex commands
*/
fun registerHandlers() {
EX_COMMAND_EP.extensions().forEach(ExBeanClass::register)
registerEpListener()
}
private fun registerEpListener() {
// IdeaVim doesn't support contribution to ex_command_ep extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload handlers on every EP change.
EX_COMMAND_EP.addChangeListener(
{
unregisterHandlers()
registerHandlers()
},
VimPlugin.getInstance()
)
}
/** Adds a command handler to the parser */
fun addHandler(handlerHolder: ExBeanClass) {
// Iterator through each command name alias
val names: Array<CommandName> = when {
handlerHolder.names != null -> {
commands(*handlerHolder.names!!.split(",").toTypedArray())
}
handlerHolder.instance is ComplicatedNameExCommand -> {
(handlerHolder.instance as ComplicatedNameExCommand).names
}
else -> throw RuntimeException("Cannot create an ex command: $handlerHolder")
}
for (name in names) {
var node = root
var text = name.required
// Build a tree for each character in the required portion of the command name
for (i in 0 until text.length - 1) {
var cn = node.getChild(text[i])
if (cn == null) {
cn = node.addChild(text[i], null)
}
node = cn
}
// For the last character we need to add the actual handler
var cn = node.getChild(text[text.length - 1])
if (cn == null) {
cn = node.addChild(text[text.length - 1], handlerHolder)
} else {
cn.commandHandler = handlerHolder
}
node = cn
// Now add the handler for each character in the optional portion of the command name
text = name.optional
for (i in text.indices) {
cn = node.getChild(text[i])
if (cn == null) {
cn = node.addChild(text[i], handlerHolder)
} else if (cn.commandHandler == null) {
cn.commandHandler = handlerHolder
}
node = cn
}
}
}
}
fun getLine(editor: Editor): Int = ranges.getLine(editor)
fun getLine(editor: Editor, caret: Caret): Int = ranges.getLine(editor, caret)

View File

@ -30,7 +30,6 @@ import com.maddyhome.idea.vim.ex.CommandHandlerFlags
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.group.CommandGroup.Companion.BLACKLISTED_ALIASES
import com.maddyhome.idea.vim.helper.MessageHelper
import org.jetbrains.annotations.NonNls
@ -38,7 +37,7 @@ import org.jetbrains.annotations.NonNls
/**
* @author Elliot Courant
*/
class CmdHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
class CmdHandler : CommandHandler.SingleExecution() {
override val argFlags: CommandHandlerFlags = flags(RANGE_FORBIDDEN, ARGUMENT_OPTIONAL, READ_ONLY)
// Static definitions needed for aliases.
@ -53,11 +52,6 @@ class CmdHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
const val zeroOrOneArguments = "?"
const val moreThanZeroArguments = "+"
}
override fun execute(cmd: ExCommand) {
this.addAlias(cmd, null)
}
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
if (cmd.argument.trim().isEmpty()) {
return this.listAlias(editor, "")

View File

@ -23,11 +23,12 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.vimscript.model.commands.Command
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
class CopyTextHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_REQUIRED, Access.WRITABLE)
@ -38,7 +39,8 @@ class CopyTextHandler : CommandHandler.SingleExecution() {
val range = cmd.getTextRange(editor, caret, false)
val text = EditorHelper.getText(editor, range.startOffset, range.endOffset)
val arg = CommandParser.parse(cmd.argument)
val goToLineCommand = VimscriptParser.parseCommand(cmd.argument) as Command
val arg = ExCommand(goToLineCommand.commandRanges, "", "", cmd.argument)
val line = arg.ranges.getFirstLine(editor, caret)
val transferableData = VimPlugin.getRegister().getTransferableData(editor, range, text)

View File

@ -1,44 +0,0 @@
/*
* 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 com.maddyhome.idea.vim.ex.handler
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
/**
* @author vlan
*/
class EchoHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val env = VimScriptGlobalEnvironment.getInstance()
val globals = env.variables
val value = VimScriptParser.evaluate(cmd.argument, globals)
val text = VimScriptParser.expressionToString(value) + "\n"
ExOutputModel.getInstance(editor).output(text)
return true
}
}

View File

@ -23,7 +23,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.ranges.LineRange
@ -36,6 +35,7 @@ import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.regexp.CharPointer
import com.maddyhome.idea.vim.regexp.RegExp
import com.maddyhome.idea.vim.vimscript.Executor
class GlobalHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
@ -202,9 +202,9 @@ class GlobalHandler : CommandHandler.SingleExecution() {
// TODO: 26.05.2021 What about folds?
editor.caretModel.moveToOffset(lineStartOffset)
if (cmd == null || cmd.isEmpty() || (cmd.length == 1 && cmd[0] == '\n')) {
CommandParser.processCommand(editor, context, "p", 1, skipHistory = true)
Executor.execute("p", editor, context, true)
} else {
CommandParser.processCommand(editor, context, cmd, 1, skipHistory = true)
Executor.execute(cmd, editor, context, true)
}
}

View File

@ -1,64 +0,0 @@
/*
* 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 com.maddyhome.idea.vim.ex.handler
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandHandler.Access.READ_ONLY
import com.maddyhome.idea.vim.ex.CommandHandler.ArgumentFlag.ARGUMENT_OPTIONAL
import com.maddyhome.idea.vim.ex.CommandHandler.RangeFlag.RANGE_REQUIRED
import com.maddyhome.idea.vim.ex.CommandHandlerFlags
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.helper.EditorHelper
import kotlin.math.min
/**
* This handles Ex commands that just specify a range which translates to moving the cursor to the line given by the
* range.
*/
class GotoLineHandler : CommandHandler.ForEachCaret() {
override val argFlags: CommandHandlerFlags = flags(RANGE_REQUIRED, ARGUMENT_OPTIONAL, READ_ONLY)
/**
* Moves the cursor to the line entered by the user
*
* @param editor The editor to perform the action in
* @param caret The caret to perform the action on
* @param context The data context
* @param cmd The complete Ex command including range, command, and arguments
* @return True if able to perform the command, false if not
*/
override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean {
val line = min(cmd.getLine(editor, caret), EditorHelper.getLineCount(editor) - 1)
if (line >= 0) {
val offset = VimPlugin.getMotion().moveCaretToLineWithStartOfLineOption(editor, line, caret)
MotionGroup.moveCaret(editor, caret, offset)
return true
}
MotionGroup.moveCaret(editor, caret, 0)
return false
}
}

View File

@ -1,73 +0,0 @@
/*
* 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 com.maddyhome.idea.vim.ex.handler
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import java.util.regex.Pattern
/**
* @author vlan
*/
class LetHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
@Throws(ExException::class)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
execute(cmd)
return true
}
@Throws
override fun execute(cmd: ExCommand) {
val argument = cmd.argument
if (argument.trim().isEmpty()) {
showVariables()
} else {
val matcher = SIMPLE_ASSIGNMENT.matcher(argument)
if (matcher.matches()) {
val name = matcher.group(1)
// TODO: Check that 'name' is global
val expression = matcher.group(2)
val env = VimScriptGlobalEnvironment.getInstance()
val globals = env.variables
val value = VimScriptParser.evaluate(expression, globals)
globals[name] = value
} else {
throw ExException("Only simple '=' assignments are supported in 'let' expressions")
}
}
}
@Throws(ExException::class)
private fun showVariables() {
throw ExException("'let' without arguments is not supported yet")
}
companion object {
private val SIMPLE_ASSIGNMENT = Pattern.compile("((?:[gswtblav]:)?[A-Za-z_][A-Za-z_0-9]*)[ \\t]*=[ \\t]*(.*)")
}
}

View File

@ -25,7 +25,6 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidRangeException
@ -36,6 +35,8 @@ import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.vimscript.model.commands.Command
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import kotlin.math.min
class MoveTextHandler : CommandHandler.SingleExecution() {
@ -50,7 +51,8 @@ class MoveTextHandler : CommandHandler.SingleExecution() {
val texts = ArrayList<String>(caretCount)
val ranges = ArrayList<TextRange>(caretCount)
var line = editor.fileSize
val command = CommandParser.parse(cmd.argument)
val goToLineCommand = VimscriptParser.parseCommand(cmd.argument) as Command
val command = ExCommand(goToLineCommand.commandRanges, "", "", cmd.argument)
var lastRange: TextRange? = null
for (caret in carets) {

View File

@ -27,21 +27,16 @@ import com.maddyhome.idea.vim.ex.CommandHandler.RangeFlag.RANGE_FORBIDDEN
import com.maddyhome.idea.vim.ex.CommandHandlerFlags
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar
/**
* This handler is created to support `Plug` command from vim-plug and `Plugin` command from vundle.
*/
class PlugHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
class PlugHandler : CommandHandler.SingleExecution() {
override val argFlags: CommandHandlerFlags = flags(RANGE_FORBIDDEN, ARGUMENT_REQUIRED, READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean = doExecute(cmd)
override fun execute(cmd: ExCommand) {
doExecute(cmd)
}
private fun doExecute(cmd: ExCommand): Boolean {
val argument = cmd.argument
val firstChar = argument[0]

View File

@ -25,11 +25,11 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandHandler.Flag.DONT_SAVE_LAST
import com.maddyhome.idea.vim.ex.CommandHandlerFlags
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.vimscript.Executor
class RepeatHandler : CommandHandler.ForEachCaret() {
override val argFlags: CommandHandlerFlags =
@ -51,13 +51,13 @@ class RepeatHandler : CommandHandler.ForEachCaret() {
)
if (arg == ':') {
return CommandParser.processLastCommand(editor, context, 1)
return Executor.executeLastCommand(editor, context)
}
val reg = VimPlugin.getRegister().getPlaybackRegister(arg) ?: return false
val text = reg.text ?: return false
CommandParser.processCommand(editor, context, text, 1)
Executor.execute(text, editor, context, false)
return true
}
}

View File

@ -23,19 +23,14 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.option.OptionsManager
class SetHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
class SetHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand) =
parseOptionLine(editor, cmd, true)
override fun execute(cmd: ExCommand) {
parseOptionLine(null, cmd, false)
}
private fun parseOptionLine(editor: Editor?, cmd: ExCommand, failOnBad: Boolean) =
OptionsManager.parseOptionLine(editor, cmd.argument, failOnBad)
}

View File

@ -24,22 +24,17 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
class SetHandlerHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
class SetHandlerHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
return doCommand(cmd)
}
override fun execute(cmd: ExCommand) {
doCommand(cmd)
}
private fun doCommand(cmd: ExCommand): Boolean {
if (cmd.argument.isBlank()) return false

View File

@ -23,23 +23,18 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import com.maddyhome.idea.vim.vimscript.Executor
import java.io.File
/**
* @author vlan
*/
class SourceHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler {
class SourceHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_REQUIRED, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
execute(cmd)
return true
}
override fun execute(cmd: ExCommand) {
val path = expandUser(cmd.argument.trim())
VimScriptParser.executeFile(File(path))
Executor.executeFile(File(path))
return true
}
private fun expandUser(path: String): String {

View File

@ -32,18 +32,13 @@ import com.maddyhome.idea.vim.ex.ComplicatedNameExCommand
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.commands
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
class MapClearHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler, ComplicatedNameExCommand {
class MapClearHandler : CommandHandler.SingleExecution(), ComplicatedNameExCommand {
override val argFlags: CommandHandlerFlags = flags(RANGE_FORBIDDEN, ARGUMENT_FORBIDDEN, READ_ONLY)
override val names: Array<CommandName> = COMMAND_NAMES
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean = executeCommand(cmd)
override fun execute(cmd: ExCommand) {
executeCommand(cmd)
}
private fun executeCommand(cmd: ExCommand): Boolean {
val commandInfo = COMMAND_INFOS.find { cmd.command.startsWith(it.prefix) } ?: return false

View File

@ -32,7 +32,6 @@ import com.maddyhome.idea.vim.ex.commands
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.handler.mapping.MapHandler.SpecialArgument.EXPR
import com.maddyhome.idea.vim.ex.handler.mapping.MapHandler.SpecialArgument.SCRIPT
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.key.MappingOwner
import org.jetbrains.annotations.NonNls
@ -42,7 +41,7 @@ import javax.swing.KeyStroke
/**
* @author vlan
*/
class MapHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler, ComplicatedNameExCommand {
class MapHandler : CommandHandler.SingleExecution(), ComplicatedNameExCommand {
override val names: Array<CommandName> = COMMAND_NAMES
override val argFlags: CommandHandlerFlags =
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
@ -52,11 +51,6 @@ class MapHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler, Co
return executeCommand(cmd, editor)
}
@Throws(ExException::class)
override fun execute(cmd: ExCommand) {
executeCommand(cmd, null)
}
@Throws(ExException::class)
private fun executeCommand(cmd: ExCommand, editor: Editor?): Boolean {
val commandInfo = COMMAND_INFOS.find { cmd.command.startsWith(it.prefix) } ?: return false

View File

@ -32,19 +32,14 @@ import com.maddyhome.idea.vim.ex.ComplicatedNameExCommand
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.commands
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
class UnMapHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler, ComplicatedNameExCommand {
class UnMapHandler : CommandHandler.SingleExecution(), ComplicatedNameExCommand {
override val argFlags: CommandHandlerFlags = flags(RANGE_FORBIDDEN, ARGUMENT_REQUIRED, READ_ONLY)
override val names: Array<CommandName> = COMMAND_NAMES
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean = executeCommand(cmd)
override fun execute(cmd: ExCommand) {
executeCommand(cmd)
}
private fun executeCommand(cmd: ExCommand): Boolean {
val commandInfo = COMMAND_INFOS.find { cmd.command.startsWith(it.prefix) } ?: return false
val argument = cmd.argument

View File

@ -63,6 +63,22 @@ sealed class Range(
protected abstract fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Range) return false
if (offset != other.offset) return false
if (isMove != other.isMove) return false
return true
}
override fun hashCode(): Int {
var result = offset
result = 31 * result + isMove.hashCode()
return result
}
companion object {
/**
* Factory method used to create an appropriate range based on the range text
@ -153,6 +169,23 @@ class LineNumberRange : Range {
}
override fun toString(): String = "LineNumberRange[line=$line, ${super.toString()}]"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LineNumberRange
if (line != other.line) return false
if (offset != other.offset) return false
if (isMove != other.isMove) return false
return true
}
override fun hashCode(): Int {
val prime = 31
return isMove.hashCode() + prime * offset.hashCode() + prime * prime * line.hashCode()
}
private var line: Int
@ -181,6 +214,23 @@ class MarkRange(private val mark: Char, offset: Int, move: Boolean) : Range(offs
override fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int = getRangeLine(editor, lastZero)
override fun toString(): String = "MarkRange[mark=$mark, ${super.toString()}]"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MarkRange
if (mark != other.mark) return false
if (offset != other.offset) return false
if (isMove != other.isMove) return false
return true
}
override fun hashCode(): Int {
val prime = 31
return prime * prime * mark.hashCode() + prime * offset.hashCode() + isMove.hashCode()
}
}
/**
@ -279,6 +329,28 @@ class SearchRange(pattern: String, offset: Int, move: Boolean) : Range(offset, m
override fun toString(): String = "SearchRange[patterns=$patterns, ${super.toString()}]"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SearchRange
if (patterns != other.patterns) return false
if (directions != other.directions) return false
if (offset != other.offset) return false
if (isMove != other.isMove) return false
return true
}
override fun hashCode(): Int {
var result = patterns.hashCode()
result = 31 * result + directions.hashCode()
result = 31 * result + offset.hashCode()
result = 31 * result + isMove.hashCode()
return result
}
private val patterns: MutableList<String?> = mutableListOf()
private val directions: MutableList<Direction> = mutableListOf()

View File

@ -188,6 +188,29 @@ class Ranges {
@NonNls
override fun toString(): String = "Ranges[ranges=$ranges]"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Ranges) return false
if (startLine != other.startLine) return false
if (endLine != other.endLine) return false
if (count != other.count) return false
if (defaultLine != other.defaultLine) return false
if (done != other.done) return false
if (ranges != other.ranges) return false
return true
}
override fun hashCode(): Int {
var result = startLine
result = 31 * result + endLine
result = 31 * result + count
result = 31 * result + defaultLine
result = 31 * result + done.hashCode()
result = 31 * result + ranges.hashCode()
return result
}
private var startLine = 0
private var endLine = 0

View File

@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.ex.vimscript;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@ -25,7 +26,10 @@ import java.util.Map;
/**
* @author vlan
* @deprecated use {@link com.maddyhome.idea.vim.vimscript.services.VariableService} instead
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "0.72")
public class VimScriptGlobalEnvironment {
private static final VimScriptGlobalEnvironment ourInstance = new VimScriptGlobalEnvironment();

View File

@ -1,213 +0,0 @@
/*
* 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 com.maddyhome.idea.vim.ex.vimscript
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ui.VimRcFileState
import org.jetbrains.annotations.NonNls
import java.io.File
import java.io.IOException
import java.nio.file.Paths
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* @author vlan
*/
object VimScriptParser {
// [VERSION UPDATE] 203+ Annotation should be replaced with @NlsSafe
@NonNls
private const val VIMRC_FILE_NAME = "ideavimrc"
// [VERSION UPDATE] 203+ Annotation should be replaced with @NlsSafe
@NonNls
private val HOME_VIMRC_PATHS = arrayOf(".$VIMRC_FILE_NAME", "_$VIMRC_FILE_NAME")
// [VERSION UPDATE] 203+ Annotation should be replaced with @NlsSafe
@NonNls
private val XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME
private val DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"")
private val SINGLE_QUOTED_STRING = Pattern.compile("'([^']*)'")
private val REFERENCE_EXPR = Pattern.compile("([A-Za-z_][A-Za-z_0-9]*)")
private val DEC_NUMBER = Pattern.compile("(\\d+)")
// This is a pattern used in ideavimrc parsing for a long time. It removes all trailing/leading spaced and blank lines
private val EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *")
var executingVimScript = false
@JvmStatic
fun findIdeaVimRc(): File? {
val homeDirName = System.getProperty("user.home")
// Check whether file exists in home dir
if (homeDirName != null) {
for (fileName in HOME_VIMRC_PATHS) {
val file = File(homeDirName, fileName)
if (file.exists()) {
return file
}
}
}
// Check in XDG config directory
val xdgConfigHomeProperty = System.getenv("XDG_CONFIG_HOME")
val xdgConfig = if (xdgConfigHomeProperty == null || xdgConfigHomeProperty == "") {
if (homeDirName != null) Paths.get(homeDirName, ".config", XDG_VIMRC_PATH).toFile() else null
} else {
File(xdgConfigHomeProperty, XDG_VIMRC_PATH)
}
return if (xdgConfig != null && xdgConfig.exists()) xdgConfig else null
}
private val newIdeaVimRcTemplate = """
"" Source your .vimrc
"source ~/.vimrc
"" -- Suggested options --
" Show a few lines of context around the cursor. Note that this makes the
" text scroll if you mouse-click near the start or end of the window.
set scrolloff=5
" Do incremental searching.
set incsearch
" Don't use Ex mode, use Q for formatting.
map Q gq
"" -- Map IDE actions to IdeaVim -- https://jb.gg/abva4t
"" Map \r to the Reformat Code action
"map \r <Action>(ReformatCode)
"" Map <leader>d to start debug
"map <leader>d <Action>(Debug)
"" Map \b to toggle the breakpoint on the current line
"map \b <Action>(ToggleLineBreakpoint)
" Find more examples here: https://jb.gg/share-ideavimrc
""".trimIndent()
fun findOrCreateIdeaVimRc(): File? {
val found = findIdeaVimRc()
if (found != null) return found
val homeDirName = System.getProperty("user.home")
if (homeDirName != null) {
for (fileName in HOME_VIMRC_PATHS) {
try {
val file = File(homeDirName, fileName)
file.createNewFile()
file.writeText(newIdeaVimRcTemplate)
VimRcFileState.filePath = file.absolutePath
return file
} catch (ignored: IOException) {
// Try to create one of two files
}
}
}
return null
}
@JvmStatic
fun executeFile(file: File): List<String> {
val data = try {
readFile(file)
} catch (ignored: IOException) {
return emptyList()
}
executeText(data)
return data
}
fun executeText(vararg text: String) {
executeText(listOf(*text))
}
fun executeText(text: List<String>) {
for (line in text) {
// TODO: Build a proper parse tree for a VimL file instead of ignoring potentially nested lines (VIM-669)
if (line.startsWith(" ") || line.startsWith("\t")) continue
val lineToExecute = if (line.startsWith(":")) line.substring(1) else line
try {
val command = CommandParser.parse(lineToExecute)
val commandHandler = CommandParser.getCommandHandler(command)
if (commandHandler is VimScriptCommandHandler) {
commandHandler.execute(command)
}
} catch (ignored: ExException) {
}
}
}
@Throws(ExException::class)
fun evaluate(expression: String, globals: Map<String?, Any?>): Any {
// This evaluator is very basic, no proper parsing whatsoever. It is here as the very first step necessary to
// support mapleader, VIM-650. See also VIM-669.
var m: Matcher = DOUBLE_QUOTED_STRING.matcher(expression)
if (m.matches()) return m.group(1)
m = SINGLE_QUOTED_STRING.matcher(expression)
if (m.matches()) return m.group(1)
m = REFERENCE_EXPR.matcher(expression)
if (m.matches()) {
val name = m.group(1)
val value = globals[name]
return value ?: throw ExException("Undefined variable: $name")
}
m = DEC_NUMBER.matcher(expression)
if (m.matches()) return m.group(1).toInt()
throw ExException("Invalid expression: $expression")
}
@Throws(ExException::class)
fun expressionToString(value: Any): String {
// TODO: Return meaningful value representations
return when (value) {
is String -> value
is Int -> value.toString()
else -> throw ExException("Cannot convert '$value' to string")
}
}
@Throws(IOException::class)
fun readFile(file: File): List<String> {
val lines = ArrayList<String>()
file.forEachLine { line -> lineProcessor(line, lines) }
return lines
}
fun readText(data: CharSequence): List<String> {
val lines = ArrayList<String>()
EOL_SPLIT_PATTERN.split(data).forEach { line -> lineProcessor(line, lines) }
return lines
}
fun lineProcessor(line: String, lines: ArrayList<String>) {
val trimmedLine = line.trim()
if (trimmedLine.isBlank()) return
lines += trimmedLine
}
}

View File

@ -21,13 +21,13 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionPointListener
import com.intellij.openapi.extensions.PluginDescriptor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import com.maddyhome.idea.vim.key.MappingOwner.Plugin.Companion.remove
import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.option.OptionsManager.addOption
import com.maddyhome.idea.vim.option.OptionsManager.isSet
import com.maddyhome.idea.vim.option.OptionsManager.removeOption
import com.maddyhome.idea.vim.option.ToggleOption
import com.maddyhome.idea.vim.vimscript.Executor
object VimExtensionRegistrar {
private val registeredExtensions: MutableSet<String> = HashSet()
@ -77,7 +77,7 @@ object VimExtensionRegistrar {
}
private fun initExtension(extensionBean: ExtensionBeanClass, name: String) {
if (VimScriptParser.executingVimScript) {
if (Executor.executingVimScript) {
delayedExtensionEnabling += extensionBean
} else {
extensionBean.instance.init()

View File

@ -25,7 +25,6 @@ import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment;
import com.maddyhome.idea.vim.extension.VimExtension;
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
import com.maddyhome.idea.vim.handler.TextObjectActionHandler;
@ -34,6 +33,11 @@ import com.maddyhome.idea.vim.helper.MessageHelper;
import com.maddyhome.idea.vim.helper.VimNlsSafe;
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
import com.maddyhome.idea.vim.vimscript.model.VimContext;
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
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;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -172,10 +176,11 @@ public class VimArgTextObjExtension implements VimExtension {
@Nullable
private static String bracketPairsVariable() {
final VimScriptGlobalEnvironment env = VimScriptGlobalEnvironment.getInstance();
final Object value = env.getVariables().get("g:argtextobj_pairs");
if (value instanceof String) {
return (String) value;
// todo global scope & nullable editor context != good
final Object value = VariableService.INSTANCE
.getNullableVariableValue(new Variable(Scope.GLOBAL_VARIABLE, "argtextobj_pairs"), null, null, new VimContext());
if (value instanceof VimString) {
return ((VimString)value).getValue();
}
return null;
}

View File

@ -32,13 +32,17 @@ import com.intellij.openapi.util.Disposer
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimProjectService
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.option.StrictMode
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
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
import org.jetbrains.annotations.NonNls
import java.awt.Color
import java.awt.Font
@ -48,10 +52,10 @@ import java.util.concurrent.TimeUnit
const val DEFAULT_HIGHLIGHT_DURATION: Long = 300
@NonNls
private const val HIGHLIGHT_DURATION_VARIABLE_NAME = "g:highlightedyank_highlight_duration"
private val HIGHLIGHT_DURATION_VARIABLE_NAME = Variable(Scope.GLOBAL_VARIABLE, "highlightedyank_highlight_duration")
@NonNls
private const val HIGHLIGHT_COLOR_VARIABLE_NAME = "g:highlightedyank_highlight_color"
private val HIGHLIGHT_COLOR_VARIABLE_NAME = Variable(Scope.GLOBAL_VARIABLE, "highlightedyank_highlight_color")
private var defaultHighlightTextColor: Color? = null
private fun getDefaultHighlightTextColor(): Color {
@ -199,17 +203,17 @@ class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
}
}
private fun <T> extractVariable(variableName: String, default: T, extractFun: (value: String) -> T): T {
val env = VimScriptGlobalEnvironment.getInstance()
val value = env.variables[variableName]
private fun <T> extractVariable(variable: Variable, default: T, extractFun: (value: String) -> T): T {
// todo something smarter
val value = VariableService.getNullableVariableValue(variable, null, null, VimContext())
if (value is String) {
if (value is VimString) {
return try {
extractFun(value)
extractFun(value.value)
} catch (e: Exception) {
@VimNlsSafe val message = MessageHelper.message(
"highlightedyank.invalid.value.of.0.1",
variableName,
(if (variable.scope != null) variable.scope.c + ":" + variable.name else variable.name),
e.message ?: ""
)
VimPlugin.showMessage(message)

View File

@ -44,7 +44,6 @@ import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.group.KeyGroup
import com.maddyhome.idea.vim.helper.MessageHelper
@ -56,6 +55,11 @@ import com.maddyhome.idea.vim.key.Node
import com.maddyhome.idea.vim.key.RequiredShortcut
import com.maddyhome.idea.vim.key.RootNode
import com.maddyhome.idea.vim.key.addLeafs
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
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
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
import javax.swing.SwingConstants
@ -283,7 +287,7 @@ class NerdTree : VimExtension {
registerCommand("j", NerdAction.ToIj("Tree-selectNext"))
registerCommand("k", NerdAction.ToIj("Tree-selectPrevious"))
registerCommand(
"g:NERDTreeMapActivateNode", "o",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapActivateNode"), "o",
NerdAction.Code { project, dataContext, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
@ -301,7 +305,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapPreview", "go",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapPreview"), "go",
NerdAction.Code { _, dataContext, _ ->
CommonDataKeys.NAVIGATABLE_ARRAY
.getData(dataContext)
@ -310,7 +314,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapOpenInTab", "t",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapOpenInTab"), "t",
NerdAction.Code { _, dataContext, _ ->
// FIXME: 22.01.2021 Doesn't work correct
CommonDataKeys.NAVIGATABLE_ARRAY
@ -320,7 +324,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapOpenInTabSilent", "T",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapOpenInTabSilent"), "T",
NerdAction.Code { _, dataContext, _ ->
// FIXME: 22.01.2021 Doesn't work correct
CommonDataKeys.NAVIGATABLE_ARRAY
@ -331,10 +335,10 @@ class NerdTree : VimExtension {
)
// TODO: 21.01.2021 Should option in left split
registerCommand("g:NERDTreeMapOpenVSplit", "s", NerdAction.ToIj("OpenInRightSplit"))
registerCommand(Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapOpenVSplit"), "s", NerdAction.ToIj("OpenInRightSplit"))
// TODO: 21.01.2021 Should option in above split
registerCommand(
"g:NERDTreeMapOpenSplit", "i",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapOpenSplit"), "i",
NerdAction.Code { project, _, event ->
val file = event.getData(CommonDataKeys.VIRTUAL_FILE) ?: return@Code
val splitters = FileEditorManagerEx.getInstanceEx(project).splitters
@ -343,7 +347,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapPreviewVSplit", "gs",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapPreviewVSplit"), "gs",
NerdAction.Code { project, context, event ->
val file = event.getData(CommonDataKeys.VIRTUAL_FILE) ?: return@Code
val splitters = FileEditorManagerEx.getInstanceEx(project).splitters
@ -355,7 +359,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapPreviewSplit", "gi",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapPreviewSplit"), "gi",
NerdAction.Code { project, context, event ->
val file = event.getData(CommonDataKeys.VIRTUAL_FILE) ?: return@Code
val splitters = FileEditorManagerEx.getInstanceEx(project).splitters
@ -366,7 +370,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapOpenRecursively", "O",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapOpenRecursively"), "O",
NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.expandAll(tree)
@ -376,7 +380,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapCloseChildren", "X",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapCloseChildren"), "X",
NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.collapse(tree)
@ -386,7 +390,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapCloseDir", "x",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapCloseDir"), "x",
NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code
@ -402,9 +406,9 @@ class NerdTree : VimExtension {
}
}
)
registerCommand("g:NERDTreeMapJumpRoot", "P", NerdAction.ToIj("Tree-selectFirst"))
registerCommand(Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapJumpRoot"), "P", NerdAction.ToIj("Tree-selectFirst"))
registerCommand(
"g:NERDTreeMapJumpParent", "p",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapJumpParent"), "p",
NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code
@ -417,7 +421,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapJumpFirstChild", "K",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapJumpFirstChild"), "K",
NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code
@ -429,7 +433,7 @@ class NerdTree : VimExtension {
}
)
registerCommand(
"g:NERDTreeMapJumpLastChild", "J",
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapJumpLastChild"), "J",
NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code
@ -450,13 +454,29 @@ class NerdTree : VimExtension {
tree.scrollRowToVisible(expectedRow)
}
)
registerCommand("g:NERDTreeMapJumpNextSibling", "<C-J>", NerdAction.ToIj("Tree-selectNextSibling"))
registerCommand("g:NERDTreeMapJumpPrevSibling", "<C-K>", NerdAction.ToIj("Tree-selectPreviousSibling"))
registerCommand("g:NERDTreeMapRefresh", "r", NerdAction.ToIj("SynchronizeCurrentFile"))
registerCommand("g:NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("g:NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("g:NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
registerCommand("g:NERDTreeMapToggleZoom", "A", NerdAction.ToIj("MaximizeToolWindow"))
registerCommand(
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapJumpNextSibling"),
"<C-J>",
NerdAction.ToIj("Tree-selectNextSibling")
)
registerCommand(
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapJumpPrevSibling"),
"<C-K>",
NerdAction.ToIj("Tree-selectPreviousSibling")
)
registerCommand(
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapRefresh"),
"r",
NerdAction.ToIj("SynchronizeCurrentFile")
)
registerCommand(Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapRefreshRoot"), "R", NerdAction.ToIj("Synchronize"))
registerCommand(Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapMenu"), "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand(Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapQuit"), "q", NerdAction.ToIj("HideActiveWindow"))
registerCommand(
Variable(Scope.GLOBAL_VARIABLE, "NERDTreeMapToggleZoom"),
"A",
NerdAction.ToIj("MaximizeToolWindow")
)
registerCommand(
"/",
@ -501,8 +521,14 @@ class NerdTree : VimExtension {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
}
private fun registerCommand(variable: String, default: String, action: NerdAction) {
val mappings = VimScriptGlobalEnvironment.getInstance().variables.getOrDefault(variable, default).toString()
private fun registerCommand(variable: Variable, default: String, action: NerdAction) {
// fixme here we assume that scope is global
val variableValue = VariableService.getNullableVariableValue(variable, null, null, VimContext())
val mappings = if (variableValue is VimString) {
variableValue.value
} else {
default
}
actionsRoot.addLeafs(mappings, action)
}

View File

@ -165,4 +165,12 @@ public class MacroGroup {
stroke.getKeyChar() == KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_PRESSED : KeyEvent.KEY_TYPED,
System.currentTimeMillis(), stroke.getModifiers(), stroke.getKeyCode(), stroke.getKeyChar());
}
public char getLastRegister() {
return lastRegister;
}
public void setLastRegister(char lastRegister) {
this.lastRegister = lastRegister;
}
}

View File

@ -39,7 +39,6 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfo
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
@ -47,6 +46,7 @@ import com.maddyhome.idea.vim.listener.FindActionId
import com.maddyhome.idea.vim.option.ClipboardOptionsData
import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
import com.maddyhome.idea.vim.vimscript.services.VimRcService
import java.awt.datatransfer.StringSelection
import java.io.File
import javax.swing.KeyStroke
@ -238,12 +238,12 @@ class NotificationService(private val project: Project?) {
@Suppress("DialogTitleCapitalization")
class OpenIdeaVimRcAction(private val notification: Notification?) : DumbAwareAction(
if (VimScriptParser.findIdeaVimRc() != null) "Open ~/.ideavimrc" else "Create ~/.ideavimrc"
if (VimRcService.findIdeaVimRc() != null) "Open ~/.ideavimrc" else "Create ~/.ideavimrc"
)/*, LightEditCompatible*/ {
override fun actionPerformed(e: AnActionEvent) {
val eventProject = e.project
if (eventProject != null) {
val ideaVimRc = VimScriptParser.findOrCreateIdeaVimRc()
val ideaVimRc = VimRcService.findOrCreateIdeaVimRc()
if (ideaVimRc != null) {
OpenFileAction.openFile(ideaVimRc.path, eventProject)
// Do not expire a notification. The user should see what they are entering
@ -259,7 +259,7 @@ class NotificationService(private val project: Project?) {
override fun update(e: AnActionEvent) {
super.update(e)
val actionText = if (VimScriptParser.findIdeaVimRc() != null) "Open ~/.ideavimrc" else "Create ~/.ideavimrc"
val actionText = if (VimRcService.findIdeaVimRc() != null) "Open ~/.ideavimrc" else "Create ~/.ideavimrc"
e.presentation.text = actionText
}
}
@ -275,7 +275,7 @@ class NotificationService(private val project: Project?) {
val eventProject = e.project
enableOption()
if (eventProject != null) {
val ideaVimRc = VimScriptParser.findOrCreateIdeaVimRc()
val ideaVimRc = VimRcService.findOrCreateIdeaVimRc()
if (ideaVimRc != null && ideaVimRc.canWrite()) {
ideaVimRc.appendText(appendableText)
notification.expire()

View File

@ -37,12 +37,12 @@ import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.Command;
import com.maddyhome.idea.vim.command.CommandState;
import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.ExException;
import com.maddyhome.idea.vim.ex.InvalidCommandException;
import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.Executor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -124,7 +124,7 @@ public class ProcessGroup {
if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
CommandParser.INSTANCE.processCommand(editor, context, text, 1, false);
Executor.INSTANCE.execute(text, editor, context, false);
}
catch (ExException e) {
VimPlugin.showMessage(e.getMessage());

View File

@ -19,7 +19,11 @@
package com.maddyhome.idea.vim.helper;
import com.intellij.openapi.util.text.StringUtil;
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment;
import com.maddyhome.idea.vim.vimscript.model.VimContext;
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
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;
import org.apache.commons.codec.binary.Base64;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
@ -165,10 +169,12 @@ public class StringHelper {
}
private static @Nullable List<KeyStroke> parseMapLeader(@NotNull String s) {
// todo global scope != good
if ("leader".equalsIgnoreCase(s)) {
final Object mapLeader = VimScriptGlobalEnvironment.getInstance().getVariables().get("mapleader");
if (mapLeader instanceof String) {
return stringToKeys((String)mapLeader);
final Object mapLeader = VariableService.INSTANCE
.getNullableVariableValue(new Variable(Scope.GLOBAL_VARIABLE, "mapleader"), null, null, new VimContext());
if (mapLeader instanceof VimString) {
return stringToKeys(((VimString)mapLeader).getValue());
}
else {
return stringToKeys("\\");

View File

@ -2075,6 +2075,80 @@ public class RegExp {
return r;
}
public boolean vim_string_contains_regexp(@NotNull regmmatch_T rmp, @NotNull String string) {
reg_match = null;
reg_mmatch = rmp;
ireg_ic = rmp.rmm_ic;
regprog_T prog;
CharPointer s;
int retval = 0;
reg_tofree = null;
prog = reg_mmatch.regprog;
CharPointer line = new CharPointer(string);
reg_startpos = reg_mmatch.startpos;
reg_endpos = reg_mmatch.endpos;
/* Be paranoid... */
if (prog == null) {
VimPlugin.showMessage(MessageHelper.message(Msg.e_null));
return false;
}
/* Check validity of program. */
if (prog_magic_wrong()) {
return false;
}
/* If pattern contains "\c" or "\C": overrule value of ireg_ic */
if ((prog.regflags & RF_ICASE) != 0) {
ireg_ic = true;
}
else if ((prog.regflags & RF_NOICASE) != 0) {
ireg_ic = false;
}
/* If there is a "must appear" string, look for it. */
if (prog.regmust != null) {
char c;
c = prog.regmust.charAt();
s = line;
while ((s = cstrchr(s, c)) != null) {
if (cstrncmp(s, prog.regmust, prog.regmlen) == 0) {
break; /* Found it. */
}
s.inc();
}
if (s == null) /* Not present. */ {
// goto the end;
return false;
}
}
regline = line.ref(0);
reglnum = 0;
out_of_stack = false;
int col = 0;
/* Simplest case: Anchored match need be tried only once. */
char c;
c = regline.charAt(col);
if (prog.regstart == '\u0000' ||
prog.regstart == c ||
(ireg_ic && Character.toLowerCase(prog.regstart) == Character.toLowerCase(c))) {
retval = regtry(prog, col);
}
if (out_of_stack) {
VimPlugin.showMessage(MessageHelper.message(Msg.E363));
}
return retval > 0;
}
/*
* Match a regexp against a string ("line" points to the string) or multiple
* lines ("line" is null, use reg_getline()).

View File

@ -30,14 +30,13 @@ import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup.Companion.ACTION_GROUP
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import com.maddyhome.idea.vim.vimscript.services.VimRcService
import com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc
import icons.VimIcons
import it.unimi.dsi.fastutil.ints.IntArrayList
import org.jetbrains.annotations.TestOnly
import java.util.regex.Pattern
/**
* This file contains a "reload ~/.ideavimrc file" action functionality.
@ -50,43 +49,38 @@ import java.util.regex.Pattern
*/
object VimRcFileState {
// List of hashes of non-empty trimmed lines
private val state = IntArrayList()
// Hash of .ideavimrc parsed to Script class
private var state: Int? = null
// ModificationStamp. Can be taken only from document. Doesn't play a big role, but can help speed up [equalTo]
private var modificationStamp = 0L
// This is a pattern used in ideavimrc parsing for a long time. It removes all trailing/leading spaced and blank lines
@Suppress("unused")
private val EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *")
var filePath: String? = null
private val saveStateListeners = ArrayList<() -> Unit>()
fun saveFileState(filePath: String, data: List<String>) {
fun saveFileState(filePath: String, text: String) {
this.filePath = FileUtil.toSystemDependentName(filePath)
state.clear()
for (line in data) {
state.add(line.hashCode())
}
val script = VimscriptParser.parse(text)
state = script.hashCode()
saveStateListeners.forEach { it() }
}
fun saveFileState(filePath: String) {
val vimRcFile = VimRcService.findIdeaVimRc()
val ideaVimRcText = vimRcFile?.readText() ?: ""
saveFileState(filePath, ideaVimRcText)
}
fun equalTo(document: Document): Boolean {
val fileModificationStamp = document.modificationStamp
if (fileModificationStamp == modificationStamp) return true
val stateSize = state.size
var i = 0
VimScriptParser.readText(document.charsSequence).forEach { line ->
if (i >= stateSize) return false
if (state.getInt(i) != line.hashCode()) return false
i++
val documentString = document.charsSequence.toString()
val script = VimscriptParser.parse(documentString)
if (script.hashCode() != state) {
return false
}
if (i < stateSize) return false
modificationStamp = fileModificationStamp
return true
@ -94,7 +88,7 @@ object VimRcFileState {
@TestOnly
fun clear() {
state.clear()
state = null
modificationStamp = 0
filePath = null
}
@ -138,10 +132,8 @@ class ReloadVimRc : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
FileDocumentManager.getInstance().saveDocumentAsIs(editor.document)
VimPlugin.getInstance().executeIdeaVimRc()
executeIdeaVimRc()
}
}

View File

@ -29,7 +29,6 @@ import com.intellij.openapi.editor.ScrollingModel;
import com.intellij.ui.DocumentAdapter;
import com.intellij.util.IJSwingUtilities;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.ExCommand;
import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.group.MotionGroup;
@ -39,6 +38,7 @@ import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.ui.ExPanelBorder;
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -312,7 +312,7 @@ public class ExEntryPanel extends JPanel {
private @Nullable ExCommand getIncsearchCommand(@Nullable String commandText) {
if (commandText == null) return null;
try {
final ExCommand exCommand = CommandParser.INSTANCE.parse(commandText);
final ExCommand exCommand = (ExCommand) VimscriptParser.INSTANCE.parseCommand(commandText);
final String command = exCommand.getCommand();
// TODO: Add global, vglobal, smagic and snomagic here when the commands are supported
if ("substitute".startsWith(command)) {

View File

@ -0,0 +1,66 @@
/*
* 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 com.maddyhome.idea.vim.vimscript
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import java.io.File
import java.io.IOException
object Executor {
private val logger = logger<Executor>()
var executingVimScript = false
@kotlin.jvm.Throws(ExException::class)
fun execute(script: String, editor: Editor?, context: DataContext?, skipHistory: Boolean) {
try {
val executableUnit = VimscriptParser.parse(script)
val vimContext = VimContext()
executableUnit.execute(editor, context, vimContext, skipHistory)
} catch (e: ExException) {
VimPlugin.showMessage(e.message)
VimPlugin.indicateError()
}
}
fun execute(script: String, skipHistory: Boolean = true) {
execute(script, null, null, skipHistory)
}
@JvmStatic
fun executeFile(file: File) {
try {
execute(file.readText(), false)
} catch (ignored: IOException) {
}
}
@kotlin.jvm.Throws(ExException::class)
fun executeLastCommand(editor: Editor, context: DataContext): Boolean {
val reg = VimPlugin.getRegister().getRegister(':') ?: return false
val text = reg.text ?: return false
execute(text, editor, context, false)
return true
}
}

View File

@ -0,0 +1,9 @@
package com.maddyhome.idea.vim.vimscript.model
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
interface Executable {
fun execute(editor: Editor?, context: DataContext?, vimContext: VimContext, skipHistory: Boolean = true): ExecutionResult
}

View File

@ -0,0 +1,31 @@
/*
* 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 com.maddyhome.idea.vim.vimscript.model
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
sealed class ExecutionResult {
object Success : ExecutionResult()
object Error : ExecutionResult()
object Break : ExecutionResult()
object Continue : ExecutionResult()
class Return(val value: VimDataType) : ExecutionResult()
}

View File

@ -0,0 +1,24 @@
package com.maddyhome.idea.vim.vimscript.model
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
data class Script(val units: List<Executable>) : Executable {
override fun execute(
editor: Editor?,
context: DataContext?,
vimContext: VimContext,
skipHistory: Boolean,
): ExecutionResult {
var latestResult: ExecutionResult = ExecutionResult.Success
for (unit in units) {
if (latestResult is ExecutionResult.Success) {
latestResult = unit.execute(editor, context, vimContext, skipHistory)
} else {
break
}
}
return latestResult
}
}

View File

@ -0,0 +1,53 @@
/*
* 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 com.maddyhome.idea.vim.vimscript.model
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import java.util.*
class VimContext {
val locations: Deque<CurrentLocation> = LinkedList()
val functionVariables: Deque<MutableMap<String, VimDataType>> = LinkedList()
val localVariables: Deque<MutableMap<String, VimDataType>> = LinkedList()
init {
locations.push(CurrentLocation.SCRIPT)
}
fun enterFunction() {
locations.push(CurrentLocation.FUNCTION)
localVariables.push(mutableMapOf())
functionVariables.push(mutableMapOf())
}
fun leaveFunction() {
locations.pop()
localVariables.pop()
functionVariables.pop()
}
fun getScriptName(): String {
return ".ideavimrc"
}
}
enum class CurrentLocation {
FUNCTION, // default variable scope is "l:"
SCRIPT, // default variable scope is "g:"
}

View File

@ -0,0 +1,183 @@
package com.maddyhome.idea.vim.vimscript.model.commands
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.MissingRangeException
import com.maddyhome.idea.vim.ex.NoRangeAllowedException
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.group.HistoryGroup
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimContext
import java.util.*
sealed class Command(var commandRanges: Ranges, val originalString: String) : Executable {
abstract val argFlags: CommandHandlerFlags
protected open val optFlags: EnumSet<CommandFlags> = noneOfEnum()
abstract class ForEachCaret(ranges: Ranges, originalString: String) :
Command(ranges, originalString = originalString) {
abstract fun processCommand(
editor: Editor,
caret: Caret,
context: DataContext,
vimContext: VimContext,
): ExecutionResult
}
abstract class SingleExecution(ranges: Ranges, originalString: String) :
Command(ranges, originalString = originalString) {
abstract fun processCommand(editor: Editor?, context: DataContext?, vimContext: VimContext): ExecutionResult
}
@Throws(ExException::class)
override fun execute(
editor: Editor?,
context: DataContext?,
vimContext: VimContext,
skipHistory: Boolean,
): ExecutionResult {
if (!skipHistory) {
VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, originalString)
}
checkRanges()
var result: ExecutionResult = ExecutionResult.Success
if (editor != null && context != null) {
if (editor.inVisualMode && Flag.SAVE_VISUAL !in argFlags.flags) {
editor.exitVisualMode()
}
when (this) {
is ForEachCaret -> {
editor.caretModel.runForEachCaret(
{ caret ->
if (result is ExecutionResult.Success) {
result = processCommand(editor, caret, context, vimContext)
}
},
true
)
}
is SingleExecution -> result = processCommand(editor, context, vimContext)
}
} else {
if (this is SingleExecution) {
result = processCommand(editor, context, vimContext)
} else {
// todo something smarter
throw RuntimeException("ForEachCaret command was passed with nullable Editor or DataContext")
}
}
if (result !is ExecutionResult.Success) {
VimPlugin.indicateError()
}
return result
}
private fun checkRanges() {
if (RangeFlag.RANGE_FORBIDDEN == argFlags.rangeFlag && commandRanges.size() != 0) {
VimPlugin.showMessage(MessageHelper.message(Msg.e_norange))
throw NoRangeAllowedException()
}
if (RangeFlag.RANGE_REQUIRED == argFlags.rangeFlag && commandRanges.size() == 0) {
VimPlugin.showMessage(MessageHelper.message(Msg.e_rangereq))
throw MissingRangeException()
}
if (RangeFlag.RANGE_IS_COUNT == argFlags.rangeFlag) {
commandRanges.setDefaultLine(1)
}
}
enum class RangeFlag {
/**
* Indicates that a range must be specified with this command
*/
RANGE_REQUIRED,
/**
* Indicates that a range is optional for this command
*/
RANGE_OPTIONAL,
/**
* Indicates that a range can't be specified for this command
*/
RANGE_FORBIDDEN,
/**
* Indicates that the command takes a count, not a range - effects default
* Works like RANGE_OPTIONAL
*/
RANGE_IS_COUNT
}
enum class ArgumentFlag {
/**
* Indicates that an argument must be specified with this command
*/
ARGUMENT_REQUIRED,
/**
* Indicates that an argument is optional for this command
*/
ARGUMENT_OPTIONAL,
/**
* Indicates that an argument can't be specified for this command
*/
ARGUMENT_FORBIDDEN
}
enum class Access {
/**
* Indicates that this is a command that modifies the editor
*/
WRITABLE,
/**
* Indicates that this command does not modify the editor
*/
READ_ONLY,
/**
* Indicates that this command handles writability by itself
*/
SELF_SYNCHRONIZED
}
enum class Flag {
DONT_SAVE_LAST,
/**
* This command should not exit visual mode.
*
* Vim exits visual mode before command execution, but in this case :action will work incorrect.
* With this flag visual mode will not be exited while command execution.
*/
SAVE_VISUAL
}
data class CommandHandlerFlags(
val rangeFlag: RangeFlag,
val access: Access,
val flags: Set<Flag>,
)
fun getLine(editor: Editor, caret: Caret): Int = commandRanges.getLine(editor, caret)
}

View File

@ -0,0 +1,38 @@
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.VimContext
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
import com.maddyhome.idea.vim.vimscript.services.FunctionStorage
data class DelfunctionCommand(
val ranges: Ranges,
val scope: Scope?,
val name: String,
val ignoreIfMissing: Boolean,
val commandString: String,
) : Command.SingleExecution(ranges, commandString) {
override val argFlags = CommandHandlerFlags(RangeFlag.RANGE_FORBIDDEN, Access.READ_ONLY, emptySet())
override fun processCommand(editor: Editor?, context: DataContext?, vimContext: VimContext): ExecutionResult {
if (ignoreIfMissing) {
try {
FunctionStorage.deleteFunction(name, vimContext, scope)
} catch (e: ExException) {
if (e.message != null && e.message!!.startsWith("E130")) {
// "ignoreIfMissing" flag handles the "E130: Unknown function" exception
} else {
throw e
}
}
} else {
FunctionStorage.deleteFunction(name, vimContext, scope)
}
return ExecutionResult.Success
}
}

View File

@ -0,0 +1,25 @@
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.ExOutputModel
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
class EchoCommand(val ranges: Ranges, val args: List<Expression>, val commandString: String) :
Command.SingleExecution(ranges, commandString) {
override val argFlags = CommandHandlerFlags(RangeFlag.RANGE_FORBIDDEN, Access.READ_ONLY, emptySet())
override fun processCommand(editor: Editor?, context: DataContext?, vimContext: VimContext): ExecutionResult.Success {
if (editor != null) {
val text = args.joinToString(separator = " ", postfix = "\n") {
it.evaluate(editor, context, vimContext).toString()
}
ExOutputModel.getInstance(editor).output(text)
}
return ExecutionResult.Success
}
}

View File

@ -0,0 +1,36 @@
package com.maddyhome.idea.vim.vimscript.model.commands
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimContext
import java.lang.Integer.min
data class GoToLineCommand(val ranges: Ranges, val commandString: String) :
Command.ForEachCaret(ranges, commandString) {
override val argFlags = CommandHandlerFlags(RangeFlag.RANGE_REQUIRED, Access.READ_ONLY, emptySet())
override fun processCommand(
editor: Editor,
caret: Caret,
context: DataContext,
vimContext: VimContext,
): ExecutionResult {
val line = min(this.getLine(editor, caret), EditorHelper.getLineCount(editor) - 1)
if (line >= 0) {
val offset = VimPlugin.getMotion().moveCaretToLineWithStartOfLineOption(editor, line, caret)
MotionGroup.moveCaret(editor, caret, offset)
return ExecutionResult.Success
}
MotionGroup.moveCaret(editor, caret, 0)
return ExecutionResult.Error
}
}

View File

@ -0,0 +1,139 @@
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.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimBlob
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.model.expressions.EnvVariableExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.OneElementSublistExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.OptionExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.Register
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SublistExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.AssignmentOperator
import com.maddyhome.idea.vim.vimscript.services.VariableService
data class LetCommand(
val ranges: Ranges,
val variable: Expression,
val operator: AssignmentOperator,
val expression: Expression,
val commandString: String,
) : Command.SingleExecution(ranges, commandString) {
override val argFlags = CommandHandlerFlags(RangeFlag.RANGE_FORBIDDEN, Access.READ_ONLY, emptySet())
@Throws(ExException::class)
override fun processCommand(editor: Editor?, context: DataContext?, vimContext: VimContext): ExecutionResult {
when (variable) {
is Variable -> {
VariableService.storeVariable(
variable, operator.getNewValue(variable, expression, editor, context, vimContext),
editor, context, vimContext
)
}
is OneElementSublistExpression -> {
if (variable.expression is Variable) {
val variableValue = VariableService.getNonNullVariableValue(variable.expression, editor, context, vimContext)
when (variableValue) {
is VimDictionary -> {
val dictKey = VimString(variable.index.evaluate(editor, context, vimContext).asString())
if (operator != AssignmentOperator.ASSIGNMENT && !variableValue.dictionary.containsKey(dictKey)) {
throw ExException("E716: Key not present in Dictionary: $dictKey")
}
if (variableValue.dictionary.containsKey(dictKey)) {
variableValue.dictionary[dictKey] =
operator.getNewValue(
SimpleExpression(variableValue.dictionary[dictKey]!!), expression, editor,
context, vimContext
)
} else {
variableValue.dictionary[dictKey] = expression.evaluate(editor, context, vimContext)
}
}
is VimList -> {
// we use Integer.parseInt(........asString()) because in case if index's type is Float, List, Dictionary etc
// vim throws the same error as the asString() method
val index = Integer.parseInt(variable.index.evaluate(editor, context, vimContext).asString())
if (index > variableValue.values.size - 1) {
throw ExException("E684: list index out of range: $index")
}
variableValue.values[index] = operator.getNewValue(
SimpleExpression(variableValue.values[index]), expression, editor, context, vimContext
)
}
is VimBlob -> TODO()
else -> throw ExException("E689: Can only index a List, Dictionary or Blob")
}
} else {
throw ExException("E121: Undefined variable")
}
}
is SublistExpression -> {
if (variable.expression is Variable) {
val variableValue = VariableService.getNonNullVariableValue(variable.expression, editor, context, vimContext)
if (variableValue is VimList) {
// we use Integer.parseInt(........asString()) because in case if index's type is Float, List, Dictionary etc
// vim throws the same error as the asString() method
val from = Integer.parseInt(variable.from?.evaluate(editor, context, vimContext)?.toString() ?: "0")
val to = Integer.parseInt(
variable.to?.evaluate(editor, context, vimContext)?.toString()
?: (variableValue.values.size - 1).toString()
)
val expressionValue = expression.evaluate(editor, context, vimContext)
if (expressionValue !is VimList && expressionValue !is VimBlob) {
throw ExException("E709: [:] requires a List or Blob value")
} else if (expressionValue is VimList) {
if (expressionValue.values.size < to - from + 1) {
throw ExException("E711: List value does not have enough items")
} else if (variable.to != null && expressionValue.values.size > to - from + 1) {
throw ExException("E710: List value has more items than targets")
}
val newListSize = expressionValue.values.size - (to - from + 1) + variableValue.values.size
var i = from
if (newListSize > variableValue.values.size) {
while (i < variableValue.values.size) {
variableValue.values[i] = expressionValue.values[i - from]
i += 1
}
while (i < newListSize) {
variableValue.values.add(expressionValue.values[i - from])
i += 1
}
} else {
while (i <= to) {
variableValue.values[i] = expressionValue.values[i - from]
i += 1
}
}
} else if (expressionValue is VimBlob) {
TODO()
}
} else {
throw ExException("wrong variable type")
}
}
}
is OptionExpression -> TODO() // they can be local and global btw
is EnvVariableExpression -> TODO()
is Register -> TODO()
else -> throw ExException("E121: Undefined variable")
}
return ExecutionResult.Success
}
}

View File

@ -0,0 +1,36 @@
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.ranges.Ranges
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.AssignmentOperator
// todo
data class SubstituteCommand(
val ranges: Ranges,
val variable: Expression,
val operator: AssignmentOperator,
val expression: Expression,
val commandString: String,
) : Command.SingleExecution(ranges, commandString) {
override val argFlags = CommandHandlerFlags(RangeFlag.RANGE_OPTIONAL, Access.SELF_SYNCHRONIZED, emptySet())
override fun processCommand(editor: Editor?, context: DataContext?, vimContext: VimContext): ExecutionResult {
var result: ExecutionResult = ExecutionResult.Success
// if (editor != null) {
// for (caret in editor.caretModel.allCarets) {
// val lineRange = this.getLineRange(editor, caret)
// if (!VimPlugin.getSearch().processSubstituteCommand(editor, caret, lineRange, cmd.command, cmd.argument)) {
// result = ExecutionResult.Error
// }
// }
// } else {
// TODO()
// }
return result
}
}

View File

@ -0,0 +1,20 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
class VimBlob : VimDataType() {
override fun asDouble(): Double {
TODO("Not yet implemented")
}
override fun asString(): String {
TODO("Not yet implemented")
}
override fun asBoolean(): Boolean {
TODO("empty must be falsy (0z), otherwise - truthy (like 0z00 0z01 etc)")
}
override fun toString(): String {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,18 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
sealed class VimDataType {
abstract fun asDouble(): Double
// string value that is used in arithmetic expressions (concatenation etc.)
abstract fun asString(): String
// string value that is used in echo-like commands
override fun toString(): String {
throw NotImplementedError("implement me :(")
}
open fun asBoolean(): Boolean {
return asDouble() != 0.0
}
}

View File

@ -0,0 +1,30 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
import com.maddyhome.idea.vim.ex.ExException
data class VimDictionary(val dictionary: LinkedHashMap<VimString, VimDataType>) : VimDataType() {
override fun asDouble(): Double {
throw ExException("E728: Using a Dictionary as a Number")
}
override fun asString(): String {
throw ExException("E731: Using a Dictionary as a String")
}
override fun toString(): String {
val result = StringBuffer("{")
result.append(dictionary.map { it.stringOfEntry() }.joinToString(separator = ", "))
result.append("}")
return result.toString()
}
private fun Map.Entry<VimString, VimDataType>.stringOfEntry(): String {
val valueString = if (this.value is VimString) "'${this.value}'" else this.value.toString()
return "'${this.key}': $valueString"
}
override fun asBoolean(): Boolean {
throw ExException("E728: Using a Dictionary as a Number")
}
}

View File

@ -0,0 +1,21 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
import com.maddyhome.idea.vim.ex.ExException
import java.math.BigDecimal
import java.math.RoundingMode
data class VimFloat(val value: Double) : VimDataType() {
override fun asDouble(): Double {
return value
}
override fun asString(): String {
throw ExException("E806: using Float as a String")
}
override fun toString(): String {
val bigDecimal = BigDecimal(value).setScale(6, RoundingMode.HALF_UP)
return bigDecimal.toDouble().toString()
}
}

View File

@ -0,0 +1,37 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
data class VimInt(val value: Int) : VimDataType() {
constructor(octalDecimalOrHexNumber: String) :
this(parseNumber(octalDecimalOrHexNumber))
override fun asDouble(): Double {
return value.toDouble()
}
override fun asString(): String {
return value.toString()
}
override fun toString(): String {
return value.toString()
}
}
private fun parseNumber(octalDecimalOrHexNumber: String): Int {
val n = octalDecimalOrHexNumber.toLowerCase()
return when {
n.matches(Regex("[-]?0[x][0-9a-f]+")) -> {
n.replaceFirst("0x", "").toInt(16)
}
n.matches(Regex("[-]?[0][0-7]+")) -> {
n.toInt(8)
}
n.matches(Regex("[-]?[0-9]+")) -> {
n.toInt()
}
else -> {
0
}
}
}

View File

@ -0,0 +1,25 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
import com.maddyhome.idea.vim.ex.ExException
data class VimList(val values: MutableList<VimDataType>) : VimDataType() {
override fun asDouble(): Double {
throw ExException("E745: Using a List as a Number")
}
override fun asString(): String {
throw ExException("E730: Using a List as a String")
}
override fun toString(): String {
val result = StringBuffer("[")
result.append(values.joinToString(separator = ", ") { if (it is VimString) "'$it'" else it.toString() })
result.append("]")
return result.toString()
}
override fun asBoolean(): Boolean {
throw ExException("E745: Using a List as a Number")
}
}

View File

@ -0,0 +1,62 @@
package com.maddyhome.idea.vim.vimscript.model.datatypes
data class VimString(val value: String) : VimDataType() {
// todo refactoring
override fun asDouble(): Double {
val text: String = value.toLowerCase()
val intString = StringBuilder()
if (text.startsWith("0x")) {
var i = 2
while (i < text.length && (text[i].isDigit() || text[i] in 'a'..'f')) {
intString.append(text[i])
++i
}
return if (intString.isEmpty()) 0.0 else Integer.parseInt(intString.toString(), 16).toDouble()
} else if (text.startsWith("-0x")) {
var i = 3
while (i < text.length && (text[i].isDigit() || text[i] in 'a'..'f')) {
intString.append(text[i])
++i
}
return if (intString.isEmpty()) 0.0 else -Integer.parseInt(intString.toString(), 16).toDouble()
} else if (text.startsWith("0")) {
var i = 1
while (i < text.length && text[i].isDigit()) {
intString.append(text[i])
++i
}
return if (intString.isEmpty()) 0.0 else Integer.parseInt(intString.toString(), 8).toDouble()
} else if (text.startsWith("-0")) {
var i = 2
while (i < text.length && text[i].isDigit()) {
intString.append(text[i])
++i
}
return if (intString.isEmpty()) 0.0 else -Integer.parseInt(intString.toString(), 8).toDouble()
} else if (text.startsWith("-")) {
var i = 1
while (i < text.length && text[i].isDigit()) {
intString.append(text[i])
++i
}
return if (intString.isEmpty()) 0.0 else -Integer.parseInt(intString.toString()).toDouble()
} else {
var i = 0
while (i < text.length && text[i].isDigit()) {
intString.append(text[i])
++i
}
return if (intString.isEmpty()) 0.0 else Integer.parseInt(intString.toString()).toDouble()
}
}
override fun asString(): String {
return value
}
override fun toString(): String {
return value
}
}

View File

@ -0,0 +1,17 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.BinaryOperator
data class BinExpression(val left: Expression, val right: Expression, val operator: BinaryOperator) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
return operator.handler.performOperation(
left.evaluate(editor, context, vimContext),
right.evaluate(editor, context, vimContext)
)
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
data class DictionaryExpression(val dictionary: LinkedHashMap<Expression, Expression>) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val dict: LinkedHashMap<VimString, VimDataType> = linkedMapOf()
for ((key, value) in dictionary) {
dict[VimString(key.evaluate(editor, context, vimContext).asString())] = value.evaluate(editor, context, vimContext)
}
return VimDictionary(dict)
}
}

View File

@ -0,0 +1,13 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
data class EnvVariableExpression(val variableName: String) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,11 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
abstract class Expression {
abstract fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType
}

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.services.FunctionStorage
data class FunctionCallExpression(val scope: Scope?, val functionName: String, val arguments: List<Expression>) :
Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val handler = FunctionStorage.getFunctionHandler(functionName, vimContext, scope = scope)
return handler.executeFunction(this, editor, context, vimContext)
}
}

View File

@ -0,0 +1,33 @@
/*
* 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 com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
data class ListExpression(val list: MutableList<Expression>) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val evaluatedList = list.map { it.evaluate(editor, context, vimContext) }.toMutableList()
return VimList(evaluatedList)
}
}

View File

@ -0,0 +1,44 @@
/*
* 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 com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
data class OneElementSublistExpression(val index: Expression, val expression: Expression) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val expressionValue = expression.evaluate(editor, context, vimContext)
if (expressionValue is VimDictionary) {
return expressionValue.dictionary[VimString(index.evaluate(editor, context, vimContext).asString())]
?: throw ExException(
"E716: Key not present in Dictionary: \"${
index.evaluate(editor, context, vimContext).asString()
}\""
)
} else {
return SublistExpression(index, index, expression).evaluate(editor, context, vimContext)
}
}
}

View File

@ -0,0 +1,29 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.option.ListOption
import com.maddyhome.idea.vim.option.NumberOption
import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.option.StringOption
import com.maddyhome.idea.vim.option.ToggleOption
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
data class OptionExpression(val optionName: String) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val option = OptionsManager.getOption(optionName) ?: throw ExException("E518: Unknown option: $optionName")
return when (option) {
is ListOption -> VimList(option.values().map { VimString(it) }.toMutableList())
is NumberOption -> VimInt(option.value())
is StringOption -> VimString(option.value)
is ToggleOption -> VimInt(if (option.value) 1 else 0)
else -> throw RuntimeException("Unknown option class passed to option expression: ${option.javaClass}")
}
}
}

View File

@ -0,0 +1,17 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
data class Register(val char: Char) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val register = VimPlugin.getRegister().getRegister(char) ?: throw ExException("Register is not supported yet")
return register.rawText?.let { VimString(it) } ?: VimString("")
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 com.maddyhome.idea.vim.vimscript.model.expressions
enum class Scope(val c: String) {
BUFFER_VARIABLE("b"),
WINDOW_VARIABLE("w"),
TABPAGE_VARIABLE("t"),
GLOBAL_VARIABLE("g"),
LOCAL_VARIABLE("l"),
SCRIPT_VARIABLE("s"),
FUNCTION_VARIABLE("a"),
VIM_VARIABLE("v");
companion object {
fun getByValue(s: String): Scope {
return values().first { it.c == s }
}
}
}

View File

@ -0,0 +1,13 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
data class SimpleExpression(val data: VimDataType) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
return data
}
}

View File

@ -0,0 +1,43 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
data class SublistExpression(val from: Expression?, val to: Expression?, val expression: Expression) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
val expressionValue = expression.evaluate(editor, context, vimContext)
val arraySize = when (expressionValue) {
is VimDictionary -> throw ExException("E719: Cannot slice a Dictionary")
is VimList -> expressionValue.values.size
else -> expressionValue.asString().length
}
var fromInt = Integer.parseInt(from?.evaluate(editor, context, vimContext)?.asString() ?: "0")
if (fromInt < 0) {
fromInt += arraySize
}
var toInt = Integer.parseInt(to?.evaluate(editor, context, vimContext)?.asString() ?: (arraySize - 1).toString())
if (toInt < 0) {
toInt += arraySize
}
return if (expressionValue is VimList) {
if (fromInt <= toInt) {
VimList(expressionValue.values.subList(fromInt, toInt + 1))
} else {
VimList(mutableListOf())
}
} else {
if (fromInt <= toInt) {
VimString(expressionValue.asString().substring(fromInt, toInt + 1))
} else {
VimString("")
}
}
}
}

View File

@ -0,0 +1,17 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
data class TernaryExpression(val condition: Expression, val then: Expression, val otherwise: Expression) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
return if (condition.evaluate(editor, context, vimContext).asDouble() != 0.0) {
then.evaluate(editor, context, vimContext)
} else {
otherwise.evaluate(editor, context, vimContext)
}
}
}

View File

@ -0,0 +1,14 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.UnaryOperator
data class UnaryExpression(val operator: UnaryOperator, val expression: Expression) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
return operator.handler.performOperation(expression.evaluate(editor, context, vimContext))
}
}

View File

@ -0,0 +1,14 @@
package com.maddyhome.idea.vim.vimscript.model.expressions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.services.VariableService
data class Variable(val scope: Scope?, val name: String) : Expression() {
override fun evaluate(editor: Editor?, context: DataContext?, vimContext: VimContext): VimDataType {
return VariableService.getNonNullVariableValue(this, editor, context, vimContext)
}
}

View File

@ -0,0 +1,51 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.vimscript.model.VimContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.AdditionHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.ConcatenationHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.DivisionHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.ModulusHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.MultiplicationHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.SubtractionHandler
enum class AssignmentOperator(val value: String) {
ASSIGNMENT("="),
ADDITION("+="),
SUBTRACTION("-="),
MULTIPLICATION("*="),
DIVISION("/="),
MODULUS("%="),
CONCATENATION(".=");
companion object {
fun getByValue(value: String): AssignmentOperator {
return values().first { it.value == value }
}
}
fun getNewValue(
variable: Expression,
value: Expression,
editor: Editor?,
context: DataContext?,
vimContext: VimContext,
): VimDataType {
val valueValue = value.evaluate(editor, context, vimContext)
return when (this) {
ASSIGNMENT -> valueValue
ADDITION -> AdditionHandler.performOperation(variable.evaluate(editor, context, vimContext), valueValue)
SUBTRACTION -> SubtractionHandler.performOperation(variable.evaluate(editor, context, vimContext), valueValue)
MULTIPLICATION -> MultiplicationHandler.performOperation(
variable.evaluate(editor, context, vimContext),
valueValue
)
DIVISION -> DivisionHandler.performOperation(variable.evaluate(editor, context, vimContext), valueValue)
MODULUS -> ModulusHandler.performOperation(variable.evaluate(editor, context, vimContext), valueValue)
CONCATENATION -> ConcatenationHandler.performOperation(variable.evaluate(editor, context, vimContext), valueValue)
}
}
}

View File

@ -0,0 +1,87 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.AdditionHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.BinaryOperatorHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.ConcatenationHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.DivisionHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.DoesntMatchHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.DoesntMatchIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.EqualsCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.EqualsHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.EqualsIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.GreaterCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.GreaterHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.GreaterIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.GreaterOrEqualsCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.GreaterOrEqualsHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.GreaterOrEqualsIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.IsCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.IsHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.IsIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.IsNotCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.IsNotHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.IsNotIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LessCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LessHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LessIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LessOrEqualsCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LessOrEqualsHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LessOrEqualsIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LogicalAndHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.LogicalOrHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.MatchesCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.MatchesHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.MatchesIgnoreCaseHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.ModulusHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.MultiplicationHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.SubtractionHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.UnequalsCaseSensitiveHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.UnequalsHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary.UnequalsIgnoreCaseHandler
enum class BinaryOperator(val value: String, val handler: BinaryOperatorHandler) {
MULTIPLICATION("*", MultiplicationHandler),
DIVISION("/", DivisionHandler),
ADDITION("+", AdditionHandler),
SUBTRACTION("-", SubtractionHandler),
CONCATENATION(".", ConcatenationHandler),
LESS("<", LessHandler),
LESS_IGNORE_CASE("<?", LessIgnoreCaseHandler),
LESS_CASE_SENSITIVE("<#", LessCaseSensitiveHandler),
GREATER(">", GreaterHandler),
GREATER_IGNORE_CASE(">?", GreaterIgnoreCaseHandler),
GREATER_CASE_SENSITIVE(">#", GreaterCaseSensitiveHandler),
EQUALS("==", EqualsHandler),
EQUALS_IGNORE_CASE("==?", EqualsIgnoreCaseHandler),
EQUALS_CASE_SENSITIVE("==#", EqualsCaseSensitiveHandler),
UNEQUALS("!=", UnequalsHandler),
UNEQUALS_IGNORE_CASE("!=?", UnequalsIgnoreCaseHandler),
UNEQUALS_CASE_SENSITIVE("!=#", UnequalsCaseSensitiveHandler),
GREATER_OR_EQUALS(">=", GreaterOrEqualsHandler),
GREATER_OR_EQUALS_IGNORE_CASE(">=?", GreaterOrEqualsIgnoreCaseHandler),
GREATER_OR_EQUALS_CASE_SENSITIVE(">=#", GreaterOrEqualsCaseSensitiveHandler),
LESS_OR_EQUALS("<=", LessOrEqualsHandler),
LESS_OR_EQUALS_IGNORE_CASE("<=?", LessOrEqualsIgnoreCaseHandler),
LESS_OR_EQUALS_CASE_SENSITIVE("<=#", LessOrEqualsCaseSensitiveHandler),
MODULUS("%", ModulusHandler),
LOGICAL_AND("&&", LogicalAndHandler),
LOGICAL_OR("||", LogicalOrHandler),
IS("is", IsHandler),
IS_IGNORE_CASE("is?", IsIgnoreCaseHandler),
IS_CASE_SENSITIVE("is#", IsCaseSensitiveHandler),
IS_NOT("isnot", IsNotHandler),
IS_NOT_IGNORE_CASE("isnot?", IsNotIgnoreCaseHandler),
IS_NOT_CASE_SENSITIVE("isnot#", IsNotCaseSensitiveHandler),
MATCHES("=~", MatchesHandler),
MATCHES_IGNORE_CASE("=~?", MatchesIgnoreCaseHandler),
MATCHES_CASE_SENSITIVE("=~#", MatchesCaseSensitiveHandler),
DOESNT_MATCH("!~", DoesntMatchHandler),
DOESNT_MATCH_IGNORE_CASE("!~?", DoesntMatchIgnoreCaseHandler),
DOESNT_MATCH_CASE_SENSITIVE("!~#", DoesntMatchIgnoreCaseHandler);
companion object {
fun getByValue(value: String): BinaryOperator? {
return values().firstOrNull { it.value == value }
}
}
}

View File

@ -0,0 +1,18 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.unary.MinusOperatorHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.unary.NotOperatorHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.unary.PlusOperatorHandler
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.unary.UnaryOperatorHandler
enum class UnaryOperator(val value: String, val handler: UnaryOperatorHandler) {
NOT("!", NotOperatorHandler),
PLUS("+", PlusOperatorHandler),
MINUS("-", MinusOperatorHandler);
companion object {
fun getByValue(value: String): UnaryOperator {
return values().first { it.value == value }
}
}
}

View File

@ -0,0 +1,21 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
object AdditionHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimFloat || right is VimFloat) {
VimFloat(left.asDouble() + right.asDouble())
} else if (left is VimList && right is VimList) {
val newList = ArrayList(left.values)
newList.addAll(right.values)
VimList(newList)
} else {
VimInt((left.asDouble() + right.asDouble()).toInt())
}
}
}

View File

@ -0,0 +1,8 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
abstract class BinaryOperatorHandler {
abstract fun performOperation(left: VimDataType, right: VimDataType): VimDataType
}

View File

@ -0,0 +1,20 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.option.ToggleOption
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
abstract class BinaryOperatorWithIgnoreCaseOption(
private val caseInsensitiveImpl: BinaryOperatorHandler,
private val caseSensitiveImpl: BinaryOperatorHandler,
) : BinaryOperatorHandler() {
private fun shouldIgnoreCase(): Boolean {
return (OptionsManager.getOption("ignorecase") as ToggleOption).isSet
}
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (shouldIgnoreCase()) caseInsensitiveImpl.performOperation(left, right) else
caseSensitiveImpl.performOperation(left, right)
}
}

View File

@ -0,0 +1,11 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object ConcatenationHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return VimString(left.asString() + right.asString())
}
}

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
object DivisionHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimFloat || right is VimFloat) {
VimFloat(left.asDouble() / right.asDouble())
} else {
VimInt((left.asDouble() / right.asDouble()).toInt())
}
}
}

View File

@ -0,0 +1,12 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.services.PatternService
object DoesntMatchCaseSensitiveHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (PatternService.matches(left.asString(), right.asString(), ignoreCase = false)) VimInt(1) else VimInt(0)
}
}

View File

@ -0,0 +1,3 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
object DoesntMatchHandler : BinaryOperatorWithIgnoreCaseOption(DoesntMatchIgnoreCaseHandler, DoesntMatchCaseSensitiveHandler)

View File

@ -0,0 +1,12 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.services.PatternService
object DoesntMatchIgnoreCaseHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (PatternService.matches(left.asString(), right.asString(), ignoreCase = true)) VimInt(1) else VimInt(0)
}
}

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object EqualsCaseSensitiveHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString && right is VimString) {
VimInt(if (left.asString() == right.asString()) 1 else 0)
} else {
VimInt(if (left.asDouble() == right.asDouble()) 1 else 0)
}
}
}

View File

@ -0,0 +1,3 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
object EqualsHandler : BinaryOperatorWithIgnoreCaseOption(EqualsIgnoreCaseHandler, EqualsCaseSensitiveHandler)

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object EqualsIgnoreCaseHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString && right is VimString) {
VimInt(if (left.asString().compareTo(right.asString(), ignoreCase = true) == 0) 1 else 0)
} else {
VimInt(if (left.asDouble() == right.asDouble()) 1 else 0)
}
}
}

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object GreaterCaseSensitiveHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString && right is VimString) {
VimInt(if (left.asString() > right.asString()) 1 else 0)
} else {
VimInt(if (left.asDouble() > right.asDouble()) 1 else 0)
}
}
}

View File

@ -0,0 +1,3 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
object GreaterHandler : BinaryOperatorWithIgnoreCaseOption(GreaterIgnoreCaseHandler, GreaterCaseSensitiveHandler)

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object GreaterIgnoreCaseHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString || right is VimString) {
VimInt(if (left.asString().compareTo(right.asString(), ignoreCase = true) > 0) 1 else 0)
} else {
VimInt(if (left.asDouble() > right.asDouble()) 1 else 0)
}
}
}

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object GreaterOrEqualsCaseSensitiveHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString || right is VimString) {
VimInt(if (left.asString() < right.asString()) 0 else 1)
} else {
VimInt(if (left.asDouble() < right.asDouble()) 0 else 1)
}
}
}

View File

@ -0,0 +1,3 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
object GreaterOrEqualsHandler : BinaryOperatorWithIgnoreCaseOption(GreaterIgnoreCaseHandler, GreaterCaseSensitiveHandler)

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object GreaterOrEqualsIgnoreCaseHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString || right is VimString) {
VimInt(if (left.asString().compareTo(right.asString(), ignoreCase = true) < 0) 0 else 1)
} else {
VimInt(if (left.asDouble() < right.asDouble()) 0 else 1)
}
}
}

View File

@ -0,0 +1,11 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
object IsCaseSensitiveHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return VimInt(if (left == right) 1 else 0)
}
}

View File

@ -0,0 +1,3 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
object IsHandler : BinaryOperatorWithIgnoreCaseOption(IsIgnoreCaseHandler, IsCaseSensitiveHandler)

View File

@ -0,0 +1,16 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
object IsIgnoreCaseHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return if (left is VimString && right is VimString) {
VimInt(if (left.value.compareTo(right.value, ignoreCase = true) == 0) 1 else 0)
} else {
VimInt(if (left == right) 1 else 0)
}
}
}

View File

@ -0,0 +1,11 @@
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
object IsNotCaseSensitiveHandler : BinaryOperatorHandler() {
override fun performOperation(left: VimDataType, right: VimDataType): VimDataType {
return VimInt(if (left != right) 1 else 0)
}
}

Some files were not shown because too many files have changed in this diff Show More