1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-04-23 13:15:45 +02:00

Avoid using deprecated RegExp in VimRegexService

This commit is contained in:
filipp 2024-06-28 16:58:04 +03:00
parent a6aa26b5d9
commit 8de2b8976b
4 changed files with 309 additions and 60 deletions
src/main/java/com/maddyhome/idea/vim
newapi
vimscript/services
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -44,6 +44,7 @@ import com.maddyhome.idea.vim.api.VimOptionGroup
import com.maddyhome.idea.vim.api.VimProcessGroup
import com.maddyhome.idea.vim.api.VimPsiService
import com.maddyhome.idea.vim.api.VimRedrawService
import com.maddyhome.idea.vim.api.VimRegexServiceBase
import com.maddyhome.idea.vim.api.VimRegexpService
import com.maddyhome.idea.vim.api.VimScrollGroup
import com.maddyhome.idea.vim.api.VimSearchGroup
@ -88,7 +89,6 @@ import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.ui.VimRcFileState
import com.maddyhome.idea.vim.undo.VimUndoRedo
import com.maddyhome.idea.vim.vimscript.Executor
import com.maddyhome.idea.vim.vimscript.services.PatternService
import com.maddyhome.idea.vim.vimscript.services.VariableService
import com.maddyhome.idea.vim.yank.VimYankGroup
import com.maddyhome.idea.vim.yank.YankGroupBase
@ -118,7 +118,7 @@ internal class IjVimInjector : VimInjectorBase() {
override val tabService: TabService
get() = service()
override val regexpService: VimRegexpService
get() = PatternService
get() = VimRegexServiceBase()
override val clipboardManager: VimClipboardManager
get() = service<IjClipboardManager>()
override val searchHelper: VimSearchHelper

View File

@ -1,58 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.vimscript.services
import com.maddyhome.idea.vim.api.VimRegexpService
import com.maddyhome.idea.vim.regexp.RegExp
import com.maddyhome.idea.vim.regexp.RegExp.regmmatch_T
internal object PatternService : VimRegexpService {
override fun matches(pattern: String, text: String?, ignoreCase: Boolean): Boolean {
if (text == null) {
return false
}
val regExp = RegExp()
val regMatch = regmmatch_T()
regMatch.rmm_ic = ignoreCase
regMatch.regprog = regExp.vim_regcomp(pattern, 1)
regMatch.regprog
if (regMatch.regprog == null) {
return false
}
// todo optimize me senpai :(
for (i in 0..text.length) {
if (regExp.vim_string_contains_regexp(regMatch, text.substring(i))) return true
}
return false
}
override fun getAllMatches(text: String, pattern: String): List<Pair<Int, Int>> {
val regExp = RegExp()
val regMatch = regmmatch_T()
regMatch.regprog = regExp.vim_regcomp(pattern, 1)
if (regMatch.regprog == null) {
return emptyList()
}
val result = mutableListOf<Pair<Int, Int>>()
// FIXME I feel pain just looking at it
for (i in text.indices) {
if (regExp.vim_string_contains_regexp(regMatch, text.substring(i))) {
val matchStart = regMatch.startpos[0]?.col ?: continue
val matchEnd = regMatch.endpos[0]?.col ?: continue
result.add(Pair(matchStart + i, matchEnd + i))
}
}
return result
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.VimRegexOptions
public class VimRegexServiceBase : VimRegexpService {
override fun matches(pattern: String, text: String?, ignoreCase: Boolean): Boolean {
if (text == null) {
return false
}
val options = if (ignoreCase) enumSetOf(VimRegexOptions.IGNORE_CASE) else noneOfEnum()
return VimRegex(pattern).containsMatchIn(text, options)
}
override fun getAllMatches(text: String, pattern: String): List<Pair<Int, Int>> {
val matches = VimRegex(pattern).findAll(text)
return matches.map { it.range.startOffset to it.range.endOffset }
}
}

View File

@ -8,7 +8,23 @@
package com.maddyhome.idea.vim.regexp
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.LineDeleteShift
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretListener
import com.maddyhome.idea.vim.api.VimDocument
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimFoldRegion
import com.maddyhome.idea.vim.api.VimScrollingModel
import com.maddyhome.idea.vim.api.VimSelectionModel
import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.VirtualFile
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.regexp.engine.VimRegexEngine
import com.maddyhome.idea.vim.regexp.engine.nfa.NFA
@ -18,6 +34,8 @@ import com.maddyhome.idea.vim.regexp.parser.CaseSensitivitySettings
import com.maddyhome.idea.vim.regexp.parser.VimRegexParser
import com.maddyhome.idea.vim.regexp.parser.VimRegexParserResult
import com.maddyhome.idea.vim.regexp.parser.visitors.PatternVisitor
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.EnumSet
/**
@ -94,6 +112,13 @@ public class VimRegex(pattern: String) {
return false
}
internal fun containsMatchIn(
text: String,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): Boolean {
return containsMatchIn(VimEditorWrapper(text), options)
}
/**
* Returns the first match of a pattern in the editor, that comes after the startIndex
*
@ -149,6 +174,14 @@ public class VimRegex(pattern: String) {
return VimMatchResult.Failure(VimRegexErrors.E486)
}
internal fun findNext(
text: String,
startIndex: Int = 0,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): VimMatchResult {
return findNext(VimEditorWrapper(text), startIndex, options)
}
/**
* Returns the first match of a pattern in the editor, that comes before the startIndex
*
@ -180,6 +213,14 @@ public class VimRegex(pattern: String) {
}
}
internal fun findPrevious(
text: String,
startIndex: Int = 0,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): VimMatchResult {
return findPrevious(VimEditorWrapper(text), startIndex, options)
}
/**
* Finds the last match that starts at line, before maxIndex
*
@ -260,6 +301,15 @@ public class VimRegex(pattern: String) {
return foundMatches
}
internal fun findAll(
text: String,
startIndex: Int = 0,
maxIndex: Int = text.length,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): List<VimMatchResult.Success> {
return findAll(VimEditorWrapper(text), startIndex, maxIndex, options)
}
/**
* Searches for a match of a pattern on a give line, starting at a certain column.
*
@ -276,6 +326,15 @@ public class VimRegex(pattern: String) {
return simulateNonExactNFA(editor, editor.getLineStartOffset(line) + column, options)
}
internal fun findInLine(
text: String,
line: Int,
column: Int = 0,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): VimMatchResult {
return findInLine(VimEditorWrapper(text), line, column, options)
}
/**
* "Simulates" the substitution of the match of a pattern with a substitution string.
*
@ -406,6 +465,14 @@ public class VimRegex(pattern: String) {
return simulateNFA(editor, index, options)
}
internal fun matchAt(
text: String,
index: Int,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): VimMatchResult {
return matchAt(VimEditorWrapper(text), index, options)
}
/**
* Attempts to match the entire editor against the pattern.
*
@ -427,6 +494,13 @@ public class VimRegex(pattern: String) {
}
}
internal fun matchEntire(
text: String,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): VimMatchResult {
return matchEntire(VimEditorWrapper(text), options)
}
/**
* Indicates whether the pattern matches the entire editor.
*
@ -445,6 +519,13 @@ public class VimRegex(pattern: String) {
}
}
public fun matches(
text: String,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): Boolean {
return matches(VimEditorWrapper(text), options)
}
/**
* Checks if a pattern matches a part of the editor
* starting exactly at the specified index.
@ -464,6 +545,14 @@ public class VimRegex(pattern: String) {
}
}
internal fun matchesAt(
text: String,
index: Int,
options: EnumSet<VimRegexOptions> = noneOfEnum()
): Boolean {
return matchesAt(VimEditorWrapper(text), index, options)
}
/**
* Simulates the internal NFA with the determined flags,
* started on a given index.
@ -501,4 +590,193 @@ public class VimRegex(pattern: String) {
CaseSensitivitySettings.DEFAULT -> options.contains(VimRegexOptions.IGNORE_CASE) && !(options.contains(VimRegexOptions.SMART_CASE) && hasUpperCase)
}
}
private class VimEditorWrapper(private val text: String): VimEditor {
override val lfMakesNewLine: Boolean = true
override var vimChangeActionSwitchMode: Mode? = null
override fun fileSize(): Long {
return text.length.toLong()
}
override fun text(): CharSequence = text
override fun nativeLineCount(): Int {
return text.count { it == '\n' } + 1
}
override fun getLineRange(line: Int): Pair<Int, Int> {
return getLineStartOffset(line) to getLineEndOffset(line)
}
override fun carets(): List<VimCaret> = emptyList()
override fun nativeCarets(): List<VimCaret> = emptyList()
override fun forEachCaret(action: (VimCaret) -> Unit) {}
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {}
override fun isInForEachCaretScope(): Boolean = false
override fun primaryCaret(): VimCaret {
throw ExException("No caret present")
}
override fun currentCaret(): VimCaret {
throw ExException("No caret present")
}
override fun isWritable(): Boolean = false
override fun isDocumentWritable(): Boolean = false
override fun isOneLineMode(): Boolean = false
override fun search(
pair: Pair<Int, Int>,
editor: VimEditor,
shiftType: LineDeleteShift,
): Pair<Pair<Int, Int>, LineDeleteShift>? {
TODO("Not yet implemented")
}
override fun offsetToBufferPosition(offset: Int): BufferPosition {
if (offset < 0 || offset > text.length) return BufferPosition(-1, -1)
var line = 0
var lastLineStart = 0
for (i in 0 until offset) {
if (text[i] == '\n') {
line++
lastLineStart = i + 1
}
}
val column = offset - lastLineStart
return BufferPosition(line, column)
}
override fun bufferPositionToOffset(position: BufferPosition): Int {
val lines = text.lines()
var offset = 0
for (i in 0 until position.line) {
offset += lines[i].length + 1
}
offset += position.column
return offset
}
override fun offsetToVisualPosition(offset: Int): VimVisualPosition {
return bufferPositionToVisualPosition(offsetToBufferPosition(offset))
}
override fun visualPositionToOffset(position: VimVisualPosition): Int {
return bufferPositionToOffset(visualPositionToBufferPosition(position))
}
override fun visualPositionToBufferPosition(position: VimVisualPosition): BufferPosition {
return BufferPosition(position.line, position.column, position.leansRight)
}
override fun bufferPositionToVisualPosition(position: BufferPosition): VimVisualPosition {
return VimVisualPosition(position.line, position.column, position.leansForward)
}
override fun getVirtualFile(): VirtualFile? = null
override fun deleteString(range: TextRange) {}
override fun getSelectionModel(): VimSelectionModel {
TODO("Not yet implemented")
}
override fun getScrollingModel(): VimScrollingModel {
TODO("Not yet implemented")
}
override fun removeCaret(caret: VimCaret) {
}
override fun removeSecondaryCarets() {
}
override fun vimSetSystemBlockSelectionSilently(start: BufferPosition, end: BufferPosition) {
}
override fun getLineStartOffset(line: Int): Int {
if (line < 0) return -1
var currentLine = 0
for (index in text.indices) {
if (currentLine == line) return index
if (text[index] == '\n') currentLine++
}
return if (line == 0) 0 else -1
}
override fun getLineEndOffset(line: Int): Int {
if (line < 0) return -1
var currentLine = 0
for (index in text.indices) {
if (text[index] == '\n') {
if (currentLine == line) return index - 1
currentLine++
}
}
return if (line == currentLine) text.length - 1 else -1
}
override fun addCaretListener(listener: VimCaretListener) {}
override fun removeCaretListener(listener: VimCaretListener) {}
override fun isDisposed(): Boolean = false
override fun removeSelection() {}
override fun getPath(): String? = null
override fun extractProtocol(): String? = null
override val projectId: String = "no project, I am just a piece of text wrapped into an Enditor for Regexp to work"
override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) {}
override fun exitSelectModeNative(adjustCaret: Boolean) {}
override var vimLastSelectionType: SelectionType? = null
override fun isTemplateActive(): Boolean = false
override fun startGuardedBlockChecking() {}
override fun stopGuardedBlockChecking() {}
override fun hasUnsavedChanges(): Boolean = false
override fun getLastVisualLineColumnNumber(line: Int): Int {
TODO("Not yet implemented")
}
override fun createLiveMarker(start: Int, end: Int): LiveRange {
TODO("Not yet implemented")
}
override var insertMode: Boolean = false
override val document: VimDocument
get() = TODO("Not yet implemented")
override fun createIndentBySize(size: Int): String {
TODO("Not yet implemented")
}
override fun getFoldRegionAtOffset(offset: Int): VimFoldRegion? {
return null
}
override fun <T : ImmutableVimCaret> findLastVersionOfCaret(caret: T): T? {
return null
}
}
}