Skip to content

Commit

Permalink
feat: add any-quotes-text-object extension
Browse files Browse the repository at this point in the history
  • Loading branch information
oca159 committed Jan 17, 2025
1 parent 7548b16 commit 5e59eb9
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 2 deletions.
11 changes: 10 additions & 1 deletion doc/IdeaVim Plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,16 @@ https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope

</details>

<details>
<summary><h2>Any Quotes Text Object</h2></summary>

Original plugin: [mini.ai](https://github.com/echasnovski/mini.ai).

### Setup:
- Add the following command to `~/.ideavimrc`: `set any-quotes-text-object`

</details>


<details>
<summary><h2>Which-Key</h2></summary>
Expand Down Expand Up @@ -484,4 +494,3 @@ or restart the IDE.

https://plugins.jetbrains.com/plugin/25899-vim-switch

</details>
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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.extension.anyquotestextobj

import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.TextRange
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.putKeyMappingIfMissing
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.KeyHandlerState
import org.jetbrains.annotations.NonNls
import java.util.*

/**
* Add motions to work with any type of quotes: single, double, and back quotes
*
* <p>
* any quotes provides two motions:
* <ul>
* <li>aq around any quotes.</li>
* <li>iq inside any quotes</li>
* </ul>
*
* @author Osvaldo Cordova Aburto (@oca159)
*/
internal class AnyQuotesTextObj : VimExtension {

override fun getName() = "any-quotes-text-object"

@NonNls
private val NO_MAPPINGS = "anyquotes_no_mappings"

override fun init() {
putExtensionHandlerMapping(
MappingMode.XO,
injector.parser.parseKeys("<Plug>any-quotes-aq"),
owner,
AroundAnyQuotesHandler(),
false
)
putExtensionHandlerMapping(
MappingMode.XO,
injector.parser.parseKeys("<Plug>any-quotes-iq"),
owner,
InsideAnyQuotesHandler(),
false
)

val noMappings = VimPlugin.getVariableService().getGlobalVariableValue(NO_MAPPINGS)?.asBoolean() ?: false
if (!noMappings) {
putKeyMappingIfMissing(
MappingMode.XO,
injector.parser.parseKeys("aq"),
owner,
injector.parser.parseKeys("<Plug>any-quotes-aq"),
true
)
putKeyMappingIfMissing(
MappingMode.XO,
injector.parser.parseKeys("iq"),
owner,
injector.parser.parseKeys("<Plug>any-quotes-iq"),
true
)
}
}

private class InsideAnyQuotesHandler : ExtensionHandler {
override val isRepeatable = true

override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyHandlerState: KeyHandlerState = KeyHandler.getInstance().keyHandlerState
keyHandlerState.commandBuilder.addAction(MotionInnerAnyQuoteProximityAction())
}
}

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 listOf('`', '"', '\'')
.mapNotNull { quote ->
injector.searchHelper.findBlockQuoteInLineRange(editor, caret, quote, false)?.let { range -> range to quote }
}
.minByOrNull { it.first.distanceTo(caret.offset) }?.first
}
}

private class AroundAnyQuotesHandler : ExtensionHandler {
override val isRepeatable = true

override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyHandlerState: KeyHandlerState = KeyHandler.getInstance().keyHandlerState
keyHandlerState.commandBuilder.addAction(MotionOuterAnyQuoteProximityAction())
}
}
}

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 listOf('`', '"', '\'')
.mapNotNull { quote ->
injector.searchHelper.findBlockQuoteInLineRange(editor, caret, quote, true)?.let { range -> range to quote }
}
.minByOrNull { it.first.distanceTo(caret.offset) }?.first
}
}

private fun TextRange.distanceTo(caretOffset: Int): Int {
return if (caretOffset < startOffset) {
startOffset - caretOffset
} else if (caretOffset > endOffset) {
caretOffset - endOffset
} else {
0 // Caret is inside the range
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ internal class PluginState : ApplicationUsagesCollector() {
"surround",
"commentary",
"matchit",
"textobj-indent"
"textobj-indent",
"any-quotes-text-object"
)
internal val enabledExtensions = HashSet<String>()
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/resources/META-INF/includes/VimExtensions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@
</aliases>
</vimExtension>


<vimExtension implementation="com.maddyhome.idea.vim.extension.anyquotestextobj.AnyQuotesTextObj"
name="any-quotes-text-object">
<aliases>
<alias name="oca159/any-quotes-text-object"/>
</aliases>
</vimExtension>

<vimExtension implementation="com.maddyhome.idea.vim.extension.argtextobj.VimArgTextObjExtension"
name="argtextobj">
<aliases>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class SetCommandTest : VimTestCase() {
| history=50 nonumber nosmartcase whichwrap=b,s
|nohlsearch operatorfunc= nosneak wrap
|noideajoin norelativenumber startofline wrapscan
|noany-quotes-text-object
| clipboard=ideaput,autoselect
| fileencoding=utf-8
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
Expand Down Expand Up @@ -255,6 +256,7 @@ class SetCommandTest : VimTestCase() {
assertCommandOutput(
"set! all", """
|--- Options ---
|noany-quotes-text-object
|noargtextobj
|nobomb
|nobreakindent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ class SetglobalCommandTest : VimTestCase() {
| history=50 operatorfunc= startofline
|nohlsearch norelativenumber nosurround
|noideajoin scroll=0 notextobj-entire
|noany-quotes-text-object
| clipboard=ideaput,autoselect
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
| ide=IntelliJ IDEA Community Edition
Expand Down Expand Up @@ -506,6 +507,7 @@ class SetglobalCommandTest : VimTestCase() {
assertCommandOutput(
"setglobal! all", """
|--- Global option values ---
|noany-quotes-text-object
|noargtextobj
|nobomb
|nobreakindent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ class SetlocalCommandTest : VimTestCase() {
| history=50 nrformats=hex sidescrolloff=-1 whichwrap=b,s
|nohlsearch nonumber nosmartcase wrap
|--ideajoin operatorfunc= nosneak wrapscan
|noany-quotes-text-object
| clipboard=ideaput,autoselect
| fileencoding=utf-8
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
Expand Down Expand Up @@ -555,6 +556,7 @@ class SetlocalCommandTest : VimTestCase() {
assertCommandOutput(
"setlocal! all", """
|--- Local option values ---
|noany-quotes-text-object
|noargtextobj
|nobomb
|nobreakindent
Expand Down

0 comments on commit 5e59eb9

Please sign in to comment.