mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-06 21:34:02 +02:00
feat: improve any brackets behavior
This commit is contained in:
parent
ffee3ccbeb
commit
491a96825f
src
main/java/com/maddyhome/idea/vim/extension/miniai
test/java/org/jetbrains/plugins/ideavim/extension/miniai
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
* Copyright 2003-2025 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
|
||||
@ -21,36 +21,31 @@ import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||
import com.maddyhome.idea.vim.extension.VimExtension
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||
import com.maddyhome.idea.vim.group.findBlockRange
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState
|
||||
import java.util.*
|
||||
import java.util.EnumSet
|
||||
|
||||
/**
|
||||
* Extend and create a/i textobjects
|
||||
* A simplified imitation of mini.ai approach for motions "aq", "iq", "ab", "ib".
|
||||
* Instead of "closest" text object, we apply a "cover-or-next" logic:
|
||||
*
|
||||
* <p>
|
||||
* mini ai provides the next motions:
|
||||
* <ul>
|
||||
* <li>aq around any quotes.</li>
|
||||
* <li>iq inside any quotes.</li>
|
||||
* <li>ab around any parentheses, curly braces and square brackets.</li>
|
||||
* <li>ib inside any parentheses, curly braces and square brackets.</li>
|
||||
* </ul>
|
||||
* 1) Among all candidate pairs, pick any that cover the caret (start <= caret < end).
|
||||
* Among those, pick the smallest range.
|
||||
* 2) If none cover the caret, pick the "next" pair (start >= caret) that is closest.
|
||||
* 3) If none are next, pick the "previous" pair (end <= caret) that is closest.
|
||||
*
|
||||
* @author Osvaldo Cordova Aburto (@oca159)
|
||||
* For "i" text object, we shrink the boundaries inward by one character on each side.
|
||||
*/
|
||||
internal class MiniAI : VimExtension {
|
||||
class MiniAI : VimExtension {
|
||||
|
||||
companion object {
|
||||
// Constants for key mappings
|
||||
// <Plug> mappings
|
||||
private const val PLUG_AQ = "<Plug>mini-ai-aq"
|
||||
private const val PLUG_IQ = "<Plug>mini-ai-iq"
|
||||
private const val PLUG_AB = "<Plug>mini-ai-ab"
|
||||
private const val PLUG_IB = "<Plug>mini-ai-ib"
|
||||
|
||||
// Constants for key sequences
|
||||
// Actual user key sequences
|
||||
private const val KEY_AQ = "aq"
|
||||
private const val KEY_IQ = "iq"
|
||||
private const val KEY_AB = "ab"
|
||||
@ -64,118 +59,46 @@ internal class MiniAI : VimExtension {
|
||||
}
|
||||
|
||||
private fun registerMappings() {
|
||||
val mappings = listOf(
|
||||
PLUG_AQ to AroundAnyQuotesHandler(),
|
||||
PLUG_IQ to InsideAnyQuotesHandler(),
|
||||
PLUG_AB to AroundAnyBracketsHandler(),
|
||||
PLUG_IB to InsideAnyBracketsHandler()
|
||||
)
|
||||
|
||||
mappings.forEach { (key, handler) ->
|
||||
putExtensionHandlerMapping(MappingMode.XO, injector.parser.parseKeys(key), owner, handler, false)
|
||||
fun createHandler(
|
||||
rangeFunc: (VimEditor, ImmutableVimCaret, Boolean) -> TextRange?
|
||||
): ExtensionHandler = object : ExtensionHandler {
|
||||
override val isRepeatable = true
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
addAction(PortedMiniAiAction(rangeFunc))
|
||||
}
|
||||
}
|
||||
|
||||
val keyMappings = listOf(
|
||||
listOf(
|
||||
// Outer quotes
|
||||
PLUG_AQ to createHandler { e, c, _ -> findQuoteRange(e, c, isOuter = true) },
|
||||
// Inner quotes
|
||||
PLUG_IQ to createHandler { e, c, _ -> findQuoteRange(e, c, isOuter = false) },
|
||||
// Outer brackets
|
||||
PLUG_AB to createHandler { e, c, _ -> findBracketRange(e, c, isOuter = true) },
|
||||
// Inner brackets
|
||||
PLUG_IB to createHandler { e, c, _ -> findBracketRange(e, c, isOuter = false) }
|
||||
).forEach { (plug, handler) ->
|
||||
putExtensionHandlerMapping(MappingMode.XO, injector.parser.parseKeys(plug), owner, handler, false)
|
||||
}
|
||||
|
||||
// Map user keys -> <Plug> keys
|
||||
listOf(
|
||||
KEY_AQ to PLUG_AQ,
|
||||
KEY_IQ to PLUG_IQ,
|
||||
KEY_AB to PLUG_AB,
|
||||
KEY_IB to PLUG_IB
|
||||
)
|
||||
|
||||
keyMappings.forEach { (key, plug) ->
|
||||
).forEach { (key, plug) ->
|
||||
putKeyMapping(MappingMode.XO, injector.parser.parseKeys(key), owner, injector.parser.parseKeys(plug), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class InsideAnyQuotesHandler : ExtensionHandler {
|
||||
override val isRepeatable = true
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
addAction(MotionInnerAnyQuoteProximityAction())
|
||||
}
|
||||
}
|
||||
|
||||
private class AroundAnyQuotesHandler : ExtensionHandler {
|
||||
override val isRepeatable = true
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
addAction(MotionOuterAnyQuoteProximityAction())
|
||||
}
|
||||
}
|
||||
|
||||
private class InsideAnyBracketsHandler : ExtensionHandler {
|
||||
override val isRepeatable = true
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
addAction(MotionInnerAnyBracketProximityAction())
|
||||
}
|
||||
}
|
||||
|
||||
private class AroundAnyBracketsHandler : ExtensionHandler {
|
||||
override val isRepeatable = true
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
addAction(MotionOuterAnyBracketProximityAction())
|
||||
}
|
||||
}
|
||||
|
||||
class MotionInnerAnyQuoteProximityAction : TextObjectActionHandler() {
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_TEXT_BLOCK)
|
||||
|
||||
override val visualType: TextObjectVisualType = TextObjectVisualType.CHARACTER_WISE
|
||||
|
||||
override fun getRange(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
rawCount: Int,
|
||||
): TextRange? {
|
||||
return findClosestQuoteRange(editor, caret, false)
|
||||
}
|
||||
}
|
||||
|
||||
class MotionOuterAnyQuoteProximityAction : TextObjectActionHandler() {
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_TEXT_BLOCK)
|
||||
|
||||
override val visualType: TextObjectVisualType = TextObjectVisualType.CHARACTER_WISE
|
||||
|
||||
override fun getRange(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
rawCount: Int,
|
||||
): TextRange? {
|
||||
return findClosestQuoteRange(editor, caret, true)
|
||||
}
|
||||
}
|
||||
|
||||
class MotionInnerAnyBracketProximityAction : TextObjectActionHandler() {
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_TEXT_BLOCK)
|
||||
|
||||
override val visualType: TextObjectVisualType = TextObjectVisualType.CHARACTER_WISE
|
||||
|
||||
override fun getRange(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
rawCount: Int,
|
||||
): TextRange? {
|
||||
return findClosestBracketRange(editor, caret, count, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MotionOuterAnyBracketProximityAction : TextObjectActionHandler() {
|
||||
// A text object action that uses the "mini.ai-like" picking strategy.
|
||||
private class PortedMiniAiAction(
|
||||
private val rangeFunc: (VimEditor, ImmutableVimCaret, Boolean) -> TextRange?
|
||||
) : TextObjectActionHandler() {
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_TEXT_BLOCK)
|
||||
|
||||
override val visualType: TextObjectVisualType = TextObjectVisualType.CHARACTER_WISE
|
||||
|
||||
override fun getRange(
|
||||
@ -183,71 +106,185 @@ class MotionOuterAnyBracketProximityAction : TextObjectActionHandler() {
|
||||
caret: ImmutableVimCaret,
|
||||
context: ExecutionContext,
|
||||
count: Int,
|
||||
rawCount: Int,
|
||||
): TextRange? {
|
||||
return findClosestBracketRange(editor, caret, count, true)
|
||||
rawCount: Int
|
||||
): TextRange? = rangeFunc(editor, caret, true).also {
|
||||
// We don't do 'count' expansions here. If you wanted to replicate
|
||||
// mini.ai's multi-level expansions, you'd call the "rangeFunc" multiple
|
||||
// times re-feeding the last output as reference, etc.
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to register action in KeyHandler
|
||||
private fun addAction(action: TextObjectActionHandler) {
|
||||
val keyHandlerState: KeyHandlerState = KeyHandler.getInstance().keyHandlerState
|
||||
keyHandlerState.commandBuilder.addAction(action)
|
||||
KeyHandler.getInstance().keyHandlerState.commandBuilder.addAction(action)
|
||||
}
|
||||
|
||||
private fun findClosestDelimitedRange(
|
||||
caret: ImmutableVimCaret,
|
||||
delimiters: List<Char>,
|
||||
findRange: (Char) -> TextRange?
|
||||
): TextRange? {
|
||||
val allRanges = delimiters.mapNotNull { char ->
|
||||
findRange(char)?.let { range ->
|
||||
DelimitedRange(range, char)
|
||||
/* -------------------------------------------------------------------------
|
||||
* Core mini.ai-like logic
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Find a text range for quotes (", ', `) around the caret using a "cover-or-next" approach.
|
||||
* - If one or more pairs **cover** the caret, pick the *smallest* covering pair.
|
||||
* - Else pick the "next" pair whose start >= caret offset (closest).
|
||||
* - Else pick the "previous" pair whose end <= caret offset (closest).
|
||||
*
|
||||
* If [isOuter] == false (i.e. 'iq'), shrink the final range by 1 char on each side.
|
||||
*/
|
||||
private fun findQuoteRange(editor: VimEditor, caret: ImmutableVimCaret, isOuter: Boolean): TextRange? {
|
||||
val text = editor.text()
|
||||
val caretOffset = caret.offset
|
||||
val caretLine = caret.getLine()
|
||||
|
||||
// 1) Gather quotes in *this caret's line*
|
||||
val lineStart = editor.getLineStartOffset(caretLine)
|
||||
val lineEnd = editor.getLineEndOffset(caretLine)
|
||||
val lineText = text.substring(lineStart, lineEnd)
|
||||
val lineRanges = gatherAllQuoteRanges(lineText).map {
|
||||
TextRange(it.startOffset + lineStart, it.endOffset + lineStart)
|
||||
}
|
||||
|
||||
val localBest = pickBestRange(lineRanges, caretOffset)
|
||||
if (localBest != null) {
|
||||
return adjustRangeForInnerOuter(localBest, isOuter)
|
||||
}
|
||||
|
||||
// 2) Fallback: entire buffer
|
||||
val allRanges = gatherAllQuoteRanges(text)
|
||||
val bestOverall = pickBestRange(allRanges, caretOffset) ?: return null
|
||||
return adjustRangeForInnerOuter(bestOverall, isOuter)
|
||||
}
|
||||
|
||||
/** Adjust final range if user requested 'inner' (i.e. skip bounding chars). */
|
||||
private fun adjustRangeForInnerOuter(range: TextRange, isOuter: Boolean): TextRange? {
|
||||
if (isOuter) return range
|
||||
// For 'inner', skip bounding chars if possible
|
||||
if (range.endOffset - range.startOffset < 2) return null
|
||||
return TextRange(range.startOffset + 1, range.endOffset - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all "balanced" pairs for single/double/backtick quotes in the entire text.
|
||||
* For simplicity, we treat each ["]...["] or [']...['] or [`]...[`] as one range,
|
||||
* ignoring complicated cases of escaping, multi-line, etc.
|
||||
*/
|
||||
private fun gatherAllQuoteRanges(text: CharSequence): List<TextRange> {
|
||||
val results = mutableListOf<TextRange>()
|
||||
val patterns = listOf(
|
||||
"\"([^\"]*)\"",
|
||||
"'([^']*)'",
|
||||
"`([^`]*)`"
|
||||
)
|
||||
for (p in patterns) {
|
||||
Regex(p).findAll(text).forEach {
|
||||
results.add(TextRange(it.range.first, it.range.last + 1))
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a text range for brackets using a "cover-or-next" approach.
|
||||
* We treat bracket pairs ( (), [], {}, <> ) in a naive balanced scanning way.
|
||||
* If [isOuter] is false, we shrink boundaries to skip the bracket chars.
|
||||
*/
|
||||
private fun findBracketRange(editor: VimEditor, caret: ImmutableVimCaret, isOuter: Boolean): TextRange? {
|
||||
val text = editor.text()
|
||||
val caretOffset = caret.offset
|
||||
val caretLine = caret.getLine()
|
||||
|
||||
// 1) Gather bracket pairs in *this caret's line*
|
||||
val lineStart = editor.getLineStartOffset(caretLine)
|
||||
val lineEnd = editor.getLineEndOffset(caretLine)
|
||||
val bracketChars = listOf('(', ')', '[', ']', '{', '}', '<', '>')
|
||||
// Gather local line bracket pairs
|
||||
val lineText = text.substring(lineStart, lineEnd)
|
||||
val lineRanges = gatherAllBracketRanges(lineText, bracketChars).map {
|
||||
// Shift each range's offsets to the global text
|
||||
TextRange(it.startOffset + lineStart, it.endOffset + lineStart)
|
||||
}
|
||||
|
||||
// Pick the best match on this line
|
||||
val localBest = pickBestRange(lineRanges, caretOffset)
|
||||
if (localBest != null) {
|
||||
return adjustRangeForInnerOuter(localBest, isOuter)
|
||||
}
|
||||
|
||||
// 2) Fallback: gather bracket pairs in the entire file
|
||||
val allRanges = gatherAllBracketRanges(text, bracketChars)
|
||||
val bestOverall = pickBestRange(allRanges, caretOffset) ?: return null
|
||||
|
||||
return adjustRangeForInnerOuter(bestOverall, isOuter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers naive "balanced bracket" ranges for the given bracket pairs.
|
||||
* This is a simplified stack-based approach scanning the entire file text.
|
||||
*/
|
||||
private fun gatherAllBracketRanges(
|
||||
text: CharSequence,
|
||||
brackets: List<Char>
|
||||
): List<TextRange> {
|
||||
val pairs = mapOf('(' to ')', '[' to ']', '{' to '}', '<' to '>')
|
||||
val results = mutableListOf<TextRange>()
|
||||
val stack = ArrayDeque<Int>() // offsets of open bracket
|
||||
val bracketTypeStack = ArrayDeque<Char>() // store which bracket
|
||||
|
||||
text.forEachIndexed { i, ch ->
|
||||
if (pairs.containsKey(ch)) {
|
||||
// Opening bracket
|
||||
stack.addLast(i)
|
||||
bracketTypeStack.addLast(ch)
|
||||
} else {
|
||||
// Maybe a closing bracket?
|
||||
val top = bracketTypeStack.lastOrNull() ?: '\u0000'
|
||||
if (pairs[top] == ch) {
|
||||
// Balanced pair
|
||||
val openPos = stack.removeLast()
|
||||
bracketTypeStack.removeLast()
|
||||
results.add(TextRange(openPos, i + 1)) // i+1 for endOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks best range among [candidates] in a “cover-or-next” approach:
|
||||
* 1) Among those covering [caretOffset], pick the narrowest.
|
||||
* 2) Else pick the "next" bracket whose start >= caret, if any (closest).
|
||||
* 3) Else pick the "previous" bracket whose end <= caret, if any (closest).
|
||||
*/
|
||||
private fun pickBestRange(candidates: List<TextRange>, caretOffset: Int): TextRange? {
|
||||
if (candidates.isEmpty()) return null
|
||||
val covering = mutableListOf<TextRange>()
|
||||
val nextOnes = mutableListOf<TextRange>()
|
||||
val prevOnes = mutableListOf<TextRange>()
|
||||
|
||||
for (r in candidates) {
|
||||
if (r.startOffset <= caretOffset && caretOffset < r.endOffset) {
|
||||
covering.add(r)
|
||||
} else if (r.startOffset >= caretOffset) {
|
||||
nextOnes.add(r)
|
||||
} else if (r.endOffset <= caretOffset) {
|
||||
prevOnes.add(r)
|
||||
}
|
||||
}
|
||||
|
||||
// First, find all ranges that contain the caret
|
||||
val containingRanges = allRanges.filter {
|
||||
caret.offset in it.range.startOffset..it.range.endOffset
|
||||
// 1) Covering, smallest width
|
||||
if (covering.isNotEmpty()) {
|
||||
return covering.minByOrNull { it.endOffset - it.startOffset }
|
||||
}
|
||||
|
||||
// If we have containing ranges, return the smallest one
|
||||
if (containingRanges.isNotEmpty()) {
|
||||
return containingRanges.minBy {
|
||||
it.range.endOffset - it.range.startOffset
|
||||
}.range
|
||||
// 2) Next (closest by startOffset)
|
||||
if (nextOnes.isNotEmpty()) {
|
||||
return nextOnes.minByOrNull { kotlin.math.abs(it.startOffset - caretOffset) }
|
||||
}
|
||||
|
||||
// If no containing ranges, find the closest one
|
||||
return allRanges
|
||||
.minByOrNull { range ->
|
||||
kotlin.math.abs(caret.offset - range.range.startOffset)
|
||||
}?.range
|
||||
// 3) Previous (closest by endOffset)
|
||||
if (prevOnes.isNotEmpty()) {
|
||||
return prevOnes.minByOrNull { kotlin.math.abs(it.endOffset - caretOffset) }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findClosestBracketRange(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
count: Int,
|
||||
isOuter: Boolean
|
||||
): TextRange? {
|
||||
val brackets = listOf('(', '[', '{')
|
||||
return findClosestDelimitedRange(caret, brackets) { char ->
|
||||
findBlockRange(editor, caret, char, count, isOuter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findClosestQuoteRange(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
isOuter: Boolean
|
||||
): TextRange? {
|
||||
val quotes = listOf('`', '"', '\'')
|
||||
return findClosestDelimitedRange(caret, quotes) { char ->
|
||||
injector.searchHelper.findBlockQuoteInLineRange(editor, caret, char, isOuter)
|
||||
}
|
||||
}
|
||||
|
||||
private data class DelimitedRange(
|
||||
val range: TextRange,
|
||||
val char: Char
|
||||
)
|
||||
|
@ -37,6 +37,157 @@ class MiniAIExtensionTest : VimTestCase() {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testFalseSingleQuoteInTheMiddle() {
|
||||
doTest(
|
||||
"ciq",
|
||||
"'balanced'false <caret>string'balanced'",
|
||||
"'balanced'false string'<caret>'",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testFalseDoubleQuoteInTheMiddle() {
|
||||
doTest(
|
||||
"ciq",
|
||||
"\"balanced\"false <caret>string\"balanced\"",
|
||||
"\"balanced\"false string\"<caret>\"",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testFalseBackQuoteInTheMiddle() {
|
||||
doTest(
|
||||
"ciq",
|
||||
"`balanced`false <caret>string`balanced`",
|
||||
"`balanced`false string`<caret>`",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testInsideMultilineSingleQuote() {
|
||||
doTest(
|
||||
"ciq",
|
||||
"""'
|
||||
something
|
||||
<caret>
|
||||
something
|
||||
'""",
|
||||
"''",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsideMultilineDoubleQuote() {
|
||||
doTest(
|
||||
"ciq",
|
||||
""""
|
||||
something
|
||||
<caret>
|
||||
something
|
||||
"""",
|
||||
"\"\"",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testInsideMultilineBackQuote() {
|
||||
doTest(
|
||||
"ciq",
|
||||
"""`
|
||||
something
|
||||
<caret>
|
||||
something
|
||||
`""",
|
||||
"``",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNextInsideMultilineSingleQuote() {
|
||||
doTest(
|
||||
"ciq",
|
||||
"""
|
||||
<caret>
|
||||
|
||||
'
|
||||
something
|
||||
'
|
||||
""",
|
||||
"""
|
||||
|
||||
|
||||
'<caret>'
|
||||
""",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNextInsideMultilineCurlyBracket() {
|
||||
doTest(
|
||||
"cib",
|
||||
"""
|
||||
{
|
||||
<caret>
|
||||
print(something)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
{}
|
||||
""",
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testNextBracketInCurrentLineHasPriority() {
|
||||
// Test case 1: Next bracket in same line has priority
|
||||
doTest(
|
||||
"cib",
|
||||
"""
|
||||
{
|
||||
<caret> print(something) {nested}
|
||||
}
|
||||
""".trimIndent(),
|
||||
"""
|
||||
{
|
||||
print(<caret>) {nested}
|
||||
}
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
JavaFileType.INSTANCE,
|
||||
)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
// To make sure the order in list is not relevant
|
||||
@Test
|
||||
fun testChangeInsideBackQuoteWithNestedSingleQuote() {
|
||||
|
Loading…
Reference in New Issue
Block a user