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