1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-18 19:24:55 +02:00
Files
.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
IntelliJ-IdeaVim/src/com/maddyhome/idea/vim/ex/vimscript/VimScriptParser.kt
2021-04-25 15:52:45 +03:00

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
}
}