mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-18 19:24:55 +02:00
.github
.idea
.teamcity
assets
doc
gradle
resources
src
com
maddyhome
idea
vim
action
command
common
config
ex
handler
ranges
vimscript
VimScriptCommandHandler.java
VimScriptGlobalEnvironment.java
VimScriptParser.kt
CommandDefinition.kt
CommandHandler.kt
CommandNode.kt
CommandParser.kt
ExBeanClass.kt
ExCommand.kt
ExExceptions.kt
ExOutputModel.kt
extension
group
handler
helper
key
listener
option
regexp
ui
DynamicLoaderStopper.kt
EventFacade.java
KeyHandler.java
PluginStartup.kt
RegisterActions.java
VimBundledDictionaryProvider.kt
VimPlugin.java
VimProjectService.kt
VimTypedActionHandler.kt
package-info.java
icons
test
.editorconfig
.gitignore
.gitmodules
AUTHORS.md
CHANGES.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
LICENSE.txt
README.md
build.gradle.kts
gradle.properties
gradlew
gradlew.bat
qodana.yaml
settings.gradle
214 lines
6.7 KiB
Kotlin
214 lines
6.7 KiB
Kotlin
/*
|
|
* 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
|
|
}
|
|
}
|