diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt index 203e3633c..bf07ea171 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt @@ -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 diff --git a/src/main/java/com/maddyhome/idea/vim/vimscript/services/PatternService.kt b/src/main/java/com/maddyhome/idea/vim/vimscript/services/PatternService.kt deleted file mode 100644 index 9efe32679..000000000 --- a/src/main/java/com/maddyhome/idea/vim/vimscript/services/PatternService.kt +++ /dev/null @@ -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 - } -} diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimRegexServiceBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimRegexServiceBase.kt new file mode 100644 index 000000000..74ea0cac4 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimRegexServiceBase.kt @@ -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 } + } +} \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt index 32bec5d96..8aae4045a 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt @@ -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 + } + } } +