mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-03-06 00:32:52 +01:00
Add argtextobj.vim plugin emulation
This commit is contained in:
parent
767b3c4a39
commit
5ee0a93675
resources/META-INF/includes
src/com/maddyhome/idea/vim/extension/argtextobj
test/org/jetbrains/plugins/ideavim/extesion/argtextobj
@ -3,5 +3,6 @@
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.surround.VimSurroundExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.multiplecursors.VimMultipleCursorsExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.commentary.CommentaryExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.argtextobj.VimArgTextObjExtension"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
@ -0,0 +1,611 @@
|
||||
package com.maddyhome.idea.vim.extension.argtextobj;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.*;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension;
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler;
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Stack;
|
||||
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
||||
import static com.maddyhome.idea.vim.group.visual.VisualGroupKt.vimSetSelection;
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public class VimArgTextObjExtension extends VimNonDisposableExtension {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "argtextobj";
|
||||
}
|
||||
|
||||
/**
|
||||
* A text object for an argument to a function definition or a call.
|
||||
*/
|
||||
static class ArgumentHandler implements VimExtensionHandler {
|
||||
final boolean isInner;
|
||||
|
||||
ArgumentHandler(boolean isInner) {
|
||||
super();
|
||||
this.isInner = isInner;
|
||||
}
|
||||
|
||||
static class ArgumentTextObjectHandler extends TextObjectActionHandler {
|
||||
private final boolean isInner;
|
||||
|
||||
ArgumentTextObjectHandler(boolean isInner) {
|
||||
this.isInner = isInner;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TextRange getRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count, int rawCount, @Nullable Argument argument) {
|
||||
final ArgBoundsFinder finder = new ArgBoundsFinder(editor.getDocument());
|
||||
int pos = caret.getOffset();
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (!finder.findBoundsAt(pos)) {
|
||||
VimPlugin.showMessage(finder.errorMessage());
|
||||
VimPlugin.indicateError();
|
||||
return null;
|
||||
}
|
||||
if (i + 1 < count) {
|
||||
finder.extendTillNext();
|
||||
}
|
||||
|
||||
pos = finder.getRightBound();
|
||||
}
|
||||
|
||||
if (isInner) {
|
||||
finder.adjustForInner();
|
||||
} else {
|
||||
finder.adjustForOuter();
|
||||
}
|
||||
return new TextRange(finder.getLeftBound(), finder.getRightBound());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
|
||||
@NotNull CommandState commandState = CommandState.getInstance(editor);
|
||||
int count = Math.max(1, commandState.getCommandBuilder().getCount());
|
||||
|
||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||
if (!commandState.isOperatorPending()) {
|
||||
editor.getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0, null);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (commandState.getMode() == CommandState.Mode.VISUAL) {
|
||||
vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||
} else {
|
||||
caret.moveToOffset(range.getStartOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION, EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE),
|
||||
emptyList()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initOnce() {
|
||||
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>InnerArgument"), getOwner(), new VimArgTextObjExtension.ArgumentHandler(true), false);
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>OuterArgument"), getOwner(), new VimArgTextObjExtension.ArgumentHandler(false), false);
|
||||
|
||||
putKeyMapping(MappingMode.XO, parseKeys("ia"), getOwner(), parseKeys("<Plug>InnerArgument"), true);
|
||||
putKeyMapping(MappingMode.XO, parseKeys("aa"), getOwner(), parseKeys("<Plug>OuterArgument"), true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to find argument boundaries starting at the specified
|
||||
* position
|
||||
*/
|
||||
private static class ArgBoundsFinder {
|
||||
final private CharSequence text;
|
||||
final private Document document;
|
||||
private int leftBound = Integer.MAX_VALUE;
|
||||
private int rightBound = Integer.MIN_VALUE;
|
||||
private int leftBracket;
|
||||
private int rightBracket;
|
||||
private String error = null;
|
||||
final private static String QUOTES = "\"\'";
|
||||
// NOTE: brackets must match by the position, and ordered by rank.
|
||||
final private static String OPEN_BRACKETS = "[{(<";
|
||||
final private static String CLOSE_BRACKETS = "]})>";
|
||||
final private static int MAX_SEARCH_LINES = 10;
|
||||
final private static int MAX_SEARCH_OFFSET = MAX_SEARCH_LINES * 80;
|
||||
|
||||
ArgBoundsFinder(@NotNull Document document) {
|
||||
this.text = document.getImmutableCharSequence();
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds left and right boundaries of an argument at the specified
|
||||
* position. If successful @ref getLeftBound() will point to the left
|
||||
* argument delimiter and @ref getRightBound() will point to the right
|
||||
* argument delimiter. Use @ref adjustForInner or @ref adjustForOuter to
|
||||
* fix the boundaries based on the type of text object.
|
||||
*
|
||||
* @param position starting position.
|
||||
*/
|
||||
boolean findBoundsAt(int position) throws IllegalStateException {
|
||||
if (text.length() == 0) {
|
||||
error = "empty document";
|
||||
return false;
|
||||
}
|
||||
leftBound = Math.min(position, leftBound);
|
||||
rightBound = Math.max(position, rightBound);
|
||||
getOutOfQuotedText();
|
||||
if (rightBound == leftBound) {
|
||||
if (isCloseBracket(getCharAt(rightBound))) {
|
||||
--leftBound;
|
||||
} else {
|
||||
++rightBound;
|
||||
}
|
||||
}
|
||||
int nextLeft = leftBound;
|
||||
int nextRight = rightBound;
|
||||
final int leftLimit = leftLimit(position);
|
||||
final int rightLimit = rightLimit(position);
|
||||
//
|
||||
// Try to extend the bounds until one of the bounds is a comma.
|
||||
// This handles cases like: fun(a, (30 + <cursor>x) * 20, c)
|
||||
//
|
||||
boolean bothBrackets;
|
||||
do {
|
||||
leftBracket = nextLeft;
|
||||
rightBracket = nextRight;
|
||||
if (!findOuterBrackets(leftLimit, rightLimit)) {
|
||||
error = "not inside argument list";
|
||||
return false;
|
||||
}
|
||||
leftBound = nextLeft;
|
||||
findLeftBound();
|
||||
nextLeft = leftBound - 1;
|
||||
rightBound = nextRight;
|
||||
findRightBound();
|
||||
nextRight = rightBound + 1;
|
||||
//
|
||||
// If reached text boundaries or there is nothing between delimiters.
|
||||
//
|
||||
if (nextLeft < leftLimit || nextRight > rightLimit || (rightBound - leftBound) == 1) {
|
||||
error = "not an argument";
|
||||
return false;
|
||||
}
|
||||
bothBrackets = getCharAt(leftBound) != ',' && getCharAt(rightBound) != ',';
|
||||
if (bothBrackets && isIdentPreceding()) {
|
||||
// Looking at a pair of brackets preceded by an
|
||||
// identifier -- single argument function call.
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (leftBound > leftLimit && rightBound < rightLimit && bothBrackets);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip left delimiter character and any following whitespace.
|
||||
*/
|
||||
void adjustForInner() {
|
||||
++leftBound;
|
||||
while (leftBound < rightBound && Character.isWhitespace(getCharAt(leftBound))) {
|
||||
++leftBound;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude left bound character for the first argument, include the
|
||||
* right bound character and any following whitespace.
|
||||
*/
|
||||
void adjustForOuter() {
|
||||
if (getCharAt(leftBound) != ',') {
|
||||
++leftBound;
|
||||
extendTillNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the right bound to the beginning of the next argument (if any).
|
||||
*/
|
||||
void extendTillNext() {
|
||||
if (rightBound + 1 < rightBracket && getCharAt(rightBound) == ',') {
|
||||
++rightBound;
|
||||
while (rightBound + 1 < rightBracket && Character.isWhitespace(getCharAt(rightBound))) {
|
||||
++rightBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getLeftBound() {
|
||||
return leftBound;
|
||||
}
|
||||
|
||||
int getRightBound() {
|
||||
return rightBound;
|
||||
}
|
||||
|
||||
private boolean isIdentPreceding() {
|
||||
int i = leftBound - 1;
|
||||
// Skip whitespace first.
|
||||
while (i > 0 && Character.isWhitespace(getCharAt(i))) {
|
||||
--i;
|
||||
}
|
||||
final int idEnd = i;
|
||||
while (i > 0 && Character.isJavaIdentifierPart(getCharAt(i))) {
|
||||
--i;
|
||||
}
|
||||
return (idEnd - i) > 0 && Character.isJavaIdentifierStart(getCharAt(i + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if current position is inside a quoted string and adjusts
|
||||
* left and right bounds to the boundaries of the string.
|
||||
*
|
||||
* @note Does not support line continuations for quoted string ('\' at the end of line).
|
||||
*/
|
||||
private void getOutOfQuotedText() {
|
||||
final int lineNo = document.getLineNumber(leftBound);
|
||||
final int lineStartOffset = document.getLineStartOffset(lineNo);
|
||||
final int lineEndOffset = document.getLineEndOffset(lineNo);
|
||||
int i = lineStartOffset;
|
||||
while (i <= leftBound) {
|
||||
if (isQuote(i)) {
|
||||
final int endOfQuotedText = skipQuotedTextForward(i, lineEndOffset);
|
||||
if (endOfQuotedText >= leftBound) {
|
||||
leftBound = i - 1;
|
||||
rightBound = endOfQuotedText + 1;
|
||||
break;
|
||||
} else {
|
||||
i = endOfQuotedText;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
private void findRightBound() {
|
||||
while (rightBound < rightBracket) {
|
||||
final char ch = getCharAt(rightBound);
|
||||
if (ch == ',') {
|
||||
break;
|
||||
}
|
||||
if (isOpenBracket(ch)) {
|
||||
rightBound = skipSexp(rightBound, rightBracket, SexpDirection.FORWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
rightBound = skipQuotedTextForward(rightBound, rightBracket);
|
||||
}
|
||||
++rightBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private char matchingBracket(char ch) {
|
||||
int idx = CLOSE_BRACKETS.indexOf(ch);
|
||||
if (idx != -1) {
|
||||
return OPEN_BRACKETS.charAt(idx);
|
||||
} else {
|
||||
assert isOpenBracket(ch);
|
||||
idx = OPEN_BRACKETS.indexOf(ch);
|
||||
return CLOSE_BRACKETS.charAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
static private boolean isCloseBracket(final int ch) {
|
||||
return CLOSE_BRACKETS.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
static private boolean isOpenBracket(final int ch) {
|
||||
return OPEN_BRACKETS.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
private void findLeftBound() {
|
||||
while (leftBound > leftBracket) {
|
||||
final char ch = getCharAt(leftBound);
|
||||
if (ch == ',') {
|
||||
break;
|
||||
}
|
||||
if (isCloseBracket(ch)) {
|
||||
leftBound = skipSexp(leftBound, leftBracket, SexpDirection.BACKWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
leftBound = skipQuotedTextBackward(leftBound, leftBracket);
|
||||
}
|
||||
--leftBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isQuote(final int i) {
|
||||
return QUOTES.indexOf(getCharAt(i)) != -1;
|
||||
}
|
||||
|
||||
static private boolean isQuoteChar(final int ch) {
|
||||
return QUOTES.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
private char getCharAt(int logicalOffset) {
|
||||
assert logicalOffset < text.length();
|
||||
return text.charAt(logicalOffset);
|
||||
}
|
||||
|
||||
private int skipQuotedTextForward(final int start, final int end) {
|
||||
assert start < end;
|
||||
final char quoteChar = getCharAt(start);
|
||||
boolean backSlash = false;
|
||||
int i = start + 1;
|
||||
|
||||
while (i <= end) {
|
||||
final char ch = getCharAt(i);
|
||||
if (ch == quoteChar && !backSlash) {
|
||||
// Found matching quote and it's not escaped.
|
||||
break;
|
||||
} else {
|
||||
backSlash = ch == '\\' && !backSlash;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private int skipQuotedTextBackward(final int start, final int end) {
|
||||
assert start > end;
|
||||
final char quoteChar = getCharAt(start);
|
||||
int i = start - 1;
|
||||
|
||||
while (i > end) {
|
||||
final char ch = getCharAt(i);
|
||||
final char prevChar = getCharAt(i - 1);
|
||||
// NOTE: doesn't handle cases like \\"str", but they make no
|
||||
// sense anyway.
|
||||
if (ch == quoteChar && prevChar != '\\') {
|
||||
// Found matching quote and it's not escaped.
|
||||
break;
|
||||
}
|
||||
--i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private int leftLimit(final int pos) {
|
||||
final int offsetLimit = Math.max(pos - MAX_SEARCH_OFFSET, 0);
|
||||
final int lineNo = document.getLineNumber(pos);
|
||||
final int lineOffsetLimit = document.getLineStartOffset(Math.max(0, lineNo - MAX_SEARCH_LINES));
|
||||
return Math.max(offsetLimit, lineOffsetLimit);
|
||||
}
|
||||
|
||||
private int rightLimit(final int pos) {
|
||||
final int offsetLimit = Math.min(pos + MAX_SEARCH_OFFSET, text.length());
|
||||
final int lineNo = document.getLineNumber(pos);
|
||||
final int lineOffsetLimit = document.getLineEndOffset(Math.min(document.getLineCount() - 1, lineNo + MAX_SEARCH_LINES));
|
||||
return Math.min(offsetLimit, lineOffsetLimit);
|
||||
}
|
||||
|
||||
String errorMessage() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to parametrise S-expression traversal direction.
|
||||
*/
|
||||
abstract static class SexpDirection {
|
||||
abstract int delta();
|
||||
|
||||
abstract boolean isOpenBracket(char ch);
|
||||
|
||||
abstract boolean isCloseBracket(char ch);
|
||||
|
||||
abstract int skipQuotedText(int pos, int end, ArgBoundsFinder self);
|
||||
|
||||
static final SexpDirection FORWARD = new SexpDirection() {
|
||||
@Override
|
||||
int delta() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isOpenBracket(char ch) {
|
||||
return ArgBoundsFinder.isOpenBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCloseBracket(char ch) {
|
||||
return ArgBoundsFinder.isCloseBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
|
||||
return self.skipQuotedTextForward(pos, end);
|
||||
}
|
||||
};
|
||||
static final SexpDirection BACKWARD = new SexpDirection() {
|
||||
@Override
|
||||
int delta() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isOpenBracket(char ch) {
|
||||
return ArgBoundsFinder.isCloseBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCloseBracket(char ch) {
|
||||
return ArgBoundsFinder.isOpenBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
|
||||
return self.skipQuotedTextBackward(pos, end);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip over an S-expression considering priorities when unbalanced.
|
||||
*
|
||||
* @param start position of the starting bracket.
|
||||
* @param end maximum position
|
||||
* @param dir direction instance
|
||||
* @return position after the S-expression, or the next to the start position if
|
||||
* unbalanced.
|
||||
*/
|
||||
private int skipSexp(final int start, final int end, SexpDirection dir) {
|
||||
char lastChar = getCharAt(start);
|
||||
assert dir.isOpenBracket(lastChar);
|
||||
Stack<Character> bracketStack = new Stack<Character>();
|
||||
bracketStack.push(lastChar);
|
||||
int i = start + dir.delta();
|
||||
while (!bracketStack.empty() && i != end) {
|
||||
final char ch = getCharAt(i);
|
||||
if (dir.isOpenBracket(ch)) {
|
||||
bracketStack.push(ch);
|
||||
} else {
|
||||
if (dir.isCloseBracket(ch)) {
|
||||
if (bracketStack.lastElement() == matchingBracket(ch)) {
|
||||
bracketStack.pop();
|
||||
} else {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (getBracketPrio(ch) < getBracketPrio(bracketStack.lastElement())) {
|
||||
// (<...) -> (...)
|
||||
bracketStack.pop();
|
||||
// Retry the same character again for cases like (...<<...).
|
||||
continue;
|
||||
} else { // Unbalanced brackets -- check ranking.
|
||||
// Ignore lower-priority closing brackets.
|
||||
// (...> -> (....
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
i = dir.skipQuotedText(i, end, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
i += dir.delta();
|
||||
}
|
||||
if (bracketStack.empty()) {
|
||||
return i;
|
||||
} else {
|
||||
return start + dir.delta();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return rank of a bracket.
|
||||
*/
|
||||
static int getBracketPrio(char ch) {
|
||||
return Math.max(OPEN_BRACKETS.indexOf(ch), CLOSE_BRACKETS.indexOf(ch));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a pair of brackets surrounding (leftBracket..rightBracket) block.
|
||||
*
|
||||
* @param start minimum position to look for
|
||||
* @param end maximum position
|
||||
* @return true if found
|
||||
*/
|
||||
boolean findOuterBrackets(final int start, final int end) {
|
||||
boolean hasNewBracket = findPrevOpenBracket(start) && findNextCloseBracket(end);
|
||||
while (hasNewBracket) {
|
||||
final int leftPrio = getBracketPrio(getCharAt(leftBracket));
|
||||
final int rightPrio = getBracketPrio(getCharAt(rightBracket));
|
||||
if (leftPrio == rightPrio) {
|
||||
// matching brackets
|
||||
return true;
|
||||
} else {
|
||||
if (leftPrio < rightPrio) {
|
||||
if (rightBracket + 1 < end) {
|
||||
++rightBracket;
|
||||
hasNewBracket = findNextCloseBracket(end);
|
||||
} else {
|
||||
hasNewBracket = false;
|
||||
}
|
||||
} else {
|
||||
if (leftBracket > 1) {
|
||||
--leftBracket;
|
||||
hasNewBracket = findPrevOpenBracket(start);
|
||||
} else {
|
||||
hasNewBracket = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds unmatched open bracket starting at @a leftBracket.
|
||||
*
|
||||
* @param start minimum position.
|
||||
* @return true if found
|
||||
*/
|
||||
private boolean findPrevOpenBracket(final int start) {
|
||||
char ch;
|
||||
while (!isOpenBracket(ch = getCharAt(leftBracket))) {
|
||||
if (isCloseBracket(ch)) {
|
||||
leftBracket = skipSexp(leftBracket, start, SexpDirection.BACKWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
leftBracket = skipQuotedTextBackward(leftBracket, start);
|
||||
} else {
|
||||
if (leftBracket == start) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
--leftBracket;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds unmatched close bracket starting at @a rightBracket.
|
||||
*
|
||||
* @param end maximum position.
|
||||
* @return true if found
|
||||
*/
|
||||
private boolean findNextCloseBracket(final int end) {
|
||||
char ch;
|
||||
while (!isCloseBracket(ch = getCharAt(rightBracket))) {
|
||||
if (isOpenBracket(ch)) {
|
||||
rightBracket = skipSexp(rightBracket, end, SexpDirection.FORWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
rightBracket = skipQuotedTextForward(rightBracket, end);
|
||||
}
|
||||
++rightBracket;
|
||||
}
|
||||
if (rightBracket >= end) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
package org.jetbrains.plugins.ideavim.extesion.argtextobj;
|
||||
|
||||
import com.maddyhome.idea.vim.command.CommandState;
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
|
||||
public class VimArgTextObjExtensionTest extends VimTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
enableExtensions("argtextobj");
|
||||
}
|
||||
|
||||
public void testDeleteAnArgument() {
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char<caret>* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1<caret>)",
|
||||
"function(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testChangeInnerArgument() {
|
||||
doTest(parseKeys("cia"),
|
||||
"function(int arg1, char<caret>* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1, <caret>)",
|
||||
CommandState.Mode.INSERT, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testSmartArgumentRecognition() {
|
||||
doTest(parseKeys("dia"),
|
||||
"function(1, (20<caret>*30)+40, somefunc2(3, 4))",
|
||||
"function(1, <caret>, somefunc2(3, 4))",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(1, (20*30)+40, somefunc2(<caret>3, 4))",
|
||||
"function(1, (20*30)+40, somefunc2(<caret>4))",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testIgnoreQuotedArguments() {
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* arg2=a,b,c(<caret>arg,e))",
|
||||
"function(int arg1, char* arg2=a,b,c(<caret>e))",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* arg2=\"a,b,c(<caret>arg,e)\")",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* arg2=\"a,b,c(arg,e\"<caret>)",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* a<caret>rg2={\"a,b},c(arg,e\"})",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testDeleteTwoArguments() {
|
||||
doTest(parseKeys("d2aa"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\")",
|
||||
"function(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2ia"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\")",
|
||||
"function(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2aa"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<caret>bool arg3)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2ia"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<caret>, bool arg3)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2aa"),
|
||||
"function(int arg1, char* arg<caret>2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2ia"),
|
||||
"function(int arg1, char* arg<caret>2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(int arg1, <caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testSelectTwoArguments() {
|
||||
doTest(parseKeys("v2aa"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<selection>int arg1, char* arg2=\"a,b,c(d,e)\", </selection>bool arg3)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
doTest(parseKeys("v2ia"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<selection>int arg1, char* arg2=\"a,b,c(d,e)\"</selection>, bool arg3)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
}
|
||||
|
||||
public void testArgumentsInsideAngleBrackets() {
|
||||
doTest(parseKeys("dia"),
|
||||
"std::vector<int, std::unique_p<caret>tr<bool>> v{};",
|
||||
"std::vector<int, <caret>> v{};",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testBracketPriorityToHangleShiftOperators() {
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(30 << 10, 20 << <caret>3) >> 17",
|
||||
"foo(30 << 10, <caret>) >> 17",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(30 << <caret>10, 20 * 3) >> 17",
|
||||
"foo(<caret>, 20 * 3) >> 17",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(<caret>30 >> 10, 20 * 3) << 17",
|
||||
"foo(<caret>, 20 * 3) << 17",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testEmptyFile() {
|
||||
assertPluginError(false);
|
||||
doTest(parseKeys("daa"),
|
||||
"<caret>",
|
||||
"<caret>",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"<caret>",
|
||||
"<caret>",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testEmptyLine() {
|
||||
assertPluginError(false);
|
||||
doTest(parseKeys("daa"),
|
||||
"<caret>\n",
|
||||
"<caret>\n",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"<caret>\n",
|
||||
"<caret>\n",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testEmptyArg() {
|
||||
assertPluginError(false);
|
||||
doTest(parseKeys("daa"),
|
||||
"foo(<caret>)",
|
||||
"foo(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(<caret>)",
|
||||
"foo(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testWhenUnbalancedHigherPriorityPairIsUsed() {
|
||||
doTest(parseKeys("dia"),
|
||||
"{ void foo(int arg1, bool arg2<caret> { body }\n}",
|
||||
"{ void foo(int arg1, <caret>}",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testSkipCommasInsideNestedPairs() {
|
||||
final String before = "void foo(int arg1)\n{" +
|
||||
" methodCall(arg1, \"{ arg1 , 2\");\n" +
|
||||
" otherMeth<caret>odcall(arg, 3);\n" +
|
||||
"}";
|
||||
doTest(parseKeys("dia"), before, before,
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testHandleNestedPairs() {
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, arr<caret>ay[someexpr(Class{arg1 << 3, arg2})] + 3)\n{",
|
||||
"foo(arg1, <caret>)\n{",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testHandleImbalancedPairs() {
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, ba<caret>r(not-an-arg{body",
|
||||
"foo(arg1, ba<caret>r(not-an-arg{body",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, ba<caret>r ( x > 3 )",
|
||||
"foo(arg1, ba<caret>r ( x > 3 )",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, ba<caret>r + x >",
|
||||
"foo(arg1, ba<caret>r + x >",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"<arg1, ba<caret>r + x)",
|
||||
"<arg1, ba<caret>r + x)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testArgumentBoundsSearchIsLimitedByLineCount() {
|
||||
final String before = "foo(\n" +
|
||||
String.join("", Collections.nCopies(10, " arg,\n")) +
|
||||
" last<caret>Arg" +
|
||||
")";
|
||||
doTest(parseKeys("dia"), before, before,
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testExtendVisualSelection() {
|
||||
doTest(parseKeys("vllia"),
|
||||
"function(int arg1, ch<caret>ar* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1, <selection>char* arg2=\"a,b,c(d,e)\"</selection>)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
doTest(parseKeys("vhhia"),
|
||||
"function(int arg1, char<caret>* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1, <selection>char* arg2=\"a,b,c(d,e)\"</selection>)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
}
|
||||
|
||||
public void testExtendVisualSelectionUsesCaretPos() {
|
||||
doTest(parseKeys("vllia"),
|
||||
"fu<caret>n(arg)",
|
||||
"fun(<selection>arg</selection>)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user