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:
parent
93109f1e19
commit
46788cc6c6
.gitignoreCONTRIBUTING.mdbuild.gradle.ktsdoc
resources/META-INF
src/com/maddyhome/idea/vim
VimPlugin.java
action/macro
ex
CommandHandler.ktCommandParser.ktExBeanClass.ktExCommand.kt
handler
CmdHandler.ktCopyTextHandler.ktEchoHandler.ktGlobalHandler.ktGotoLineHandler.ktLetHandler.ktMoveTextHandler.ktPlugHandler.ktRepeatHandler.ktSetHandler.ktSetHandlerHandler.ktSourceHandler.kt
mapping
ranges
vimscript
extension
group
helper
regexp
ui
vimscript
Executor.kt
model
Executable.ktExecutionResult.ktScript.ktVimContext.kt
commands
datatypes
expressions
BinExpression.ktDictionaryExpression.ktEnvVariableExpression.ktExpression.ktFunctionCallExpression.ktListExpression.ktOneElementSublistExpression.ktOptionExpression.ktRegister.ktScope.ktSimpleExpression.ktSublistExpression.ktTernaryExpression.ktUnaryExpression.ktVariable.kt
operators
AssignmentOperator.ktBinaryOperator.ktUnaryOperator.kt
handlers/binary
AdditionHandler.ktBinaryOperatorHandler.ktBinaryOperatorWithIgnoreCaseOption.ktConcatenationHandler.ktDivisionHandler.ktDoesntMatchCaseSensitiveHandler.ktDoesntMatchHandler.ktDoesntMatchIgnoreCaseHandler.ktEqualsCaseSensitiveHandler.ktEqualsHandler.ktEqualsIgnoreCaseHandler.ktGreaterCaseSensitiveHandler.ktGreaterHandler.ktGreaterIgnoreCaseHandler.ktGreaterOrEqualsCaseSensitiveHandler.ktGreaterOrEqualsHandler.ktGreaterOrEqualsIgnoreCaseHandler.ktIsCaseSensitiveHandler.ktIsHandler.ktIsIgnoreCaseHandler.ktIsNotCaseSensitiveHandler.kt
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
@ -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`.
|
||||
|
@ -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
2
doc
@ -1 +1 @@
|
||||
Subproject commit 83aaa9801dddd9ba956e2d5f85534872a69b7f37
|
||||
Subproject commit 9cfed4680f8f8d806d12b85df1cd1e2421a999c0
|
@ -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"/>
|
||||
|
6
resources/META-INF/includes/VimLibraryFunctions.xml
Normal file
6
resources/META-INF/includes/VimLibraryFunctions.xml
Normal 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>
|
@ -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">
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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, "")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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]*(.*)")
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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());
|
||||
|
@ -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("\\");
|
||||
|
@ -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()).
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
66
src/com/maddyhome/idea/vim/vimscript/Executor.kt
Normal file
66
src/com/maddyhome/idea/vim/vimscript/Executor.kt
Normal 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
|
||||
}
|
||||
}
|
9
src/com/maddyhome/idea/vim/vimscript/model/Executable.kt
Normal file
9
src/com/maddyhome/idea/vim/vimscript/model/Executable.kt
Normal 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
|
||||
}
|
@ -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()
|
||||
}
|
24
src/com/maddyhome/idea/vim/vimscript/model/Script.kt
Normal file
24
src/com/maddyhome/idea/vim/vimscript/model/Script.kt
Normal 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
|
||||
}
|
||||
}
|
53
src/com/maddyhome/idea/vim/vimscript/model/VimContext.kt
Normal file
53
src/com/maddyhome/idea/vim/vimscript/model/VimContext.kt
Normal 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:"
|
||||
}
|
183
src/com/maddyhome/idea/vim/vimscript/model/commands/Command.kt
Normal file
183
src/com/maddyhome/idea/vim/vimscript/model/commands/Command.kt
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
@ -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("")
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
|
||||
|
||||
object DoesntMatchHandler : BinaryOperatorWithIgnoreCaseOption(DoesntMatchIgnoreCaseHandler, DoesntMatchCaseSensitiveHandler)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
|
||||
|
||||
object EqualsHandler : BinaryOperatorWithIgnoreCaseOption(EqualsIgnoreCaseHandler, EqualsCaseSensitiveHandler)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
|
||||
|
||||
object GreaterHandler : BinaryOperatorWithIgnoreCaseOption(GreaterIgnoreCaseHandler, GreaterCaseSensitiveHandler)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
|
||||
|
||||
object GreaterOrEqualsHandler : BinaryOperatorWithIgnoreCaseOption(GreaterIgnoreCaseHandler, GreaterCaseSensitiveHandler)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions.operators.handlers.binary
|
||||
|
||||
object IsHandler : BinaryOperatorWithIgnoreCaseOption(IsIgnoreCaseHandler, IsCaseSensitiveHandler)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user