Many times I encounter the situation, where I want to reorder a list: enum values,
DataSet test cases, parameters of a function, elements of a list, etc. Cut and paste is just too much effort, and extra care has to be taken to keep formatting. So I've written a small code snippet to rearrange these lists. I use IntelliJ's
LivePlugin (
on github) to use it quickly (but it could be converted to a full Java plugin). But unfortunatelly it handles Groovy script. Testing groovy script I find it pain in the ass, so I've created a Java barebone with test, and than converted it to Groovyscript (I myself find this approach a bit stupid, though).
How it works? If your carret is sitting in between any type of brackets, it finds the innermost it sits in. It finds the list element (text, that can contain spaces, other closed brackets, comas inside those brackets). With Ctrl+Alt+L it swaps it to the right, with Ctrl+Alt+K, it swaps it to the left. The carret moves with the item.
Known limitations. If there is an unclosed backet (syntactically incorret code), it doesn't handle it well. If the carret is not inside a backet, the behaviour is unpredicted.
The rest of the post is just source code. The Java fragment:
int start;
int end;
int mid;
int midLeft;
int midRight;
public String doTheFlop(final String input, final int carretPos) {
start = carretPos - 1;
mid = carretPos;
travelToBeginning(input);
travelToMid(input);
if (isRightLimiter(input.charAt(mid))) {
return null;
}
midLeft = mid ;
midRight = mid ;
travelToMidLeft(input);
travelToMidRight(input);
end = midRight;
travelToEnd(input);
return input.substring(midRight, end) + input.substring(midLeft, midRight) + input.substring(start, midLeft);
}
private void travelToBeginning(final String input) {
int groupDepth = 0;
while (!((input.charAt(start) == ',' && groupDepth == 0)
|| (isLeftLimiter(input.charAt(start)) && groupDepth == 0))) {
if (isRightLimiter(input.charAt(start))) {
groupDepth++;
}
if (isLeftLimiter(input.charAt(start))) {
groupDepth--;
}
start--;
}
start++;
while (Character.isWhitespace(input.charAt(start))) {
start++;
}
}
private void travelToMid(final String input) {
int groupDepth = 0;
while (!((input.charAt(mid) == ',' && groupDepth == 0)
|| (isRightLimiter(input.charAt(mid)) && groupDepth == 0))) {
if (isLeftLimiter(input.charAt(mid))) {
groupDepth++;
}
if (isRightLimiter(input.charAt(mid))) {
groupDepth--;
}
mid++;
}
}
private void travelToMidLeft(final String input) {
// this currently points to a coma or bracket
midLeft--;
while (Character.isWhitespace(input.charAt(midLeft))) {
midLeft--;
}
midLeft++;
}
private void travelToMidRight(final String input) {
// this currently points to a coma or bracket
midRight++;
while (Character.isWhitespace(input.charAt(midRight))) {
midRight++;
}
}
private void travelToEnd(final String input) {
int groupDepth = 0;
while (!((input.charAt(end) == ',' && groupDepth == 0)
|| (isRightLimiter(input.charAt(end)) && groupDepth == 0))) {
if (isLeftLimiter(input.charAt(end))) {
groupDepth++;
}
if (isRightLimiter(input.charAt(end))) {
groupDepth--;
}
end++;
}
end--;
while (Character.isWhitespace(input.charAt(end))) {
end--;
}
end++;
}
private boolean isLeftLimiter(final char c) {
return c == '(' || c == '<' || c == '[' || c == '{';
}
private boolean isRightLimiter(final char c) {
return c == ')' || c == '>' || c == ']' || c == '}';
}
Test for this:
public static class Stuff extends SimpleTestVectors {
@Override
protected Object[][] generateTestVectors() {
return new Object[][] {
//"( final Broad broadcast, final Frame frame,final Logger logger)"
//(Logger logger, Frame< String, Integer > frame, final @Named ( "quickDepositStatusIntentTranslator" ) Broad broadcast)
{"( ", "final Broad broadcast, final Frame frame,final Logger logger)", "final Frame frame, final Broad broadcast"}, //, "( final Frame frame, final Broad broadcast,final Logger logger)"
{"( final Broad br", "oadcast, final Frame frame,final Logger logger)", "final Frame frame, final Broad broadcast"}, //, "( final Frame frame, final Broad broadcast,final Logger logger)"
{"( final Broad br", "oadcast, final Frame frame,final Logger logger)", "final Frame frame, final Broad broadcast"}, //, "( final Frame frame, final Broad broadcast,final Logger logger)"
{"( final Broad broadcast", ", final Frame frame,final Logger logger)", "final Frame frame, final Broad broadcast"}, //, "( final Frame frame, final Broad broadcast,final Logger logger)"
{"( final Broad broadcast,", " final Frame frame,final Logger logger)", "final Logger logger,final Frame frame"}, //, "( final Broad broadcast, final Logger logger,final Frame frame)"
{"( final Broad broadcast, final Fr", "ame frame,final Logger logger)", "final Logger logger,final Frame frame"}, //, "( final Broad broadcast, final Logger logger,final Frame frame)"
{"( final Broad broadcast, final Frame frame,final ", "Logger logger)", null}, //, "( final Broad broadcast, final Frame frame,final Logger logger)"
{"(Broad broadcast, Fr", "ame frame , Logger logger )", "Logger logger , Frame frame"}, //, "(final Broad broadcast, final Logger logger , final Frame frame )"
{"(final @", "Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Frame< String, Integer > frame, Logger logger)", "Frame< String, Integer > frame, final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast"}, //"(Frame< String, Integer > frame, final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Logger logger)"},
{"(final @Named ( \"quickDepositS", "tatusIntentTranslator\" ) Broad broadcast, Frame< String, Integer > frame, Logger logger)", null}, //"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Frame< String, Integer > frame, Logger logger)"},
{"(final @Named ( \"quickDepositStatusIntentTranslator\" ", ") Broad broadcast, Frame< String, Integer > frame, Logger logger)", null}, //"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Frame< String, Integer > frame, Logger logger)"},
{"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad", " broadcast, Frame< String, Integer > frame, Logger logger)", "Frame< String, Integer > frame, final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast"}, //"(Frame< String, Integer > frame, final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Logger logger)"},
{"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, ", "Frame< String, Integer > frame, Logger logger)", "Logger logger, Frame< String, Integer > frame"}, //"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Logger logger, Frame< String, Integer > frame)"},
{"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Frame< S", "tring, Integer > frame, Logger logger)", "Integer, String"}, //"(final @Named ( \"quickDepositStatusIntentTranslator\" ) Broad broadcast, Frame< Integer, String > frame, Logger logger)"},
{"(final @Named ( \"", " foo () \" , \" bar () \" ) Broad broadcast, Frame< String, Integer > frame, Logger logger)", "\" bar () \" , \" foo () \""}, //"(final @Named( \" bar () \" , \" foo () \" ) Broad broadcast, Frame< String, Integer > frame, Logger logger)""},
};
}
}
@Test
@DataSet(testData = Stuff.class)
public void dummy_FIXME() throws InvalidDataSetException {
// init
// run
final String result = sut.doTheFlop(rule.getString(0) + rule.getString(1), rule.getString(0).length());
// verify
assertThat(result, equalTo(rule.getString(2)));
}
And the final Groovyscript refined:
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.util.TextRange
import static liveplugin.PluginUtil.*
import static liveplugin.PluginUtil.showInConsole
import static liveplugin.PluginUtil.showInConsole
import static liveplugin.PluginUtil.showInConsole
registerAction("swap to right", "alt ctrl L") { AnActionEvent event ->
def editor = currentEditorIn(event.project)
def input = editor.getDocument().getChars()
def caretModel = editor.getCaretModel()
def selectionModel = editor.getSelectionModel()
int start = caretModel.offset - 1;
if (start < 0) return;
int mid = start + 1;
int end;
int midLeft;
int midRight;
//travelToBeginning
int groupDepth = 0;
while (!((input[start] == ',' && groupDepth == 0)
|| (isLeftLimiter(input[start]) && groupDepth == 0))) {
if (isRightLimiter(input[start])) {
groupDepth++;
}
if (isLeftLimiter(input[start])) {
groupDepth--;
}
start--;
}
// fallback to first non-whitespace char
start++;
while (isWhitespace(input[start])) {
start++;
}
//travelToMid
groupDepth = 0;
while (!((input[mid] == ',' && groupDepth == 0)
|| (isRightLimiter(input[mid]) && groupDepth == 0))) {
if (isLeftLimiter(input[mid])) {
groupDepth++;
}
if (isRightLimiter(input[mid])) {
groupDepth--;
}
mid++;
}
// this is already the last element
if (isRightLimiter(input[mid])) {
return;
}
midLeft = mid;
midRight = mid;
//travelToMidLeft
// this currently points to a coma or bracket
midLeft--;
while (isWhitespace(input[midLeft])) {
midLeft--;
}
midLeft++;
//travelToMidRight
// this currently points to a coma or bracket
midRight++;
while (isWhitespace(input[midRight])) {
midRight++;
}
//travelToEnd
end = midRight;
groupDepth = 0;
while (!((input[end] == ',' && groupDepth == 0)
|| (isRightLimiter(input[end]) && groupDepth == 0))) {
if (isLeftLimiter(input[end])) {
groupDepth++;
}
if (isRightLimiter(input[end])) {
groupDepth--;
}
end++;
}
// fallback to last non-whitespace char
end--;
while (isWhitespace(input[end])) {
end--;
}
end++;
// do the swap
String text = editor.document.getText(new TextRange(start, end))
String swappedText = text.substring(midRight - start, end - start) + text.substring(midLeft - start, midRight - start) + text.substring(0, midLeft - start)
runDocumentWriteAction(event.project, editor.document, "Swap to right a list element", "Permute plugin (mnw)") {
editor.document.replaceString(start, end, swappedText)
}
caretModel.moveToOffset(end)
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
}
registerAction("swap to left", "alt ctrl K") { AnActionEvent event ->
def editor = currentEditorIn(event.project)
def input = editor.getDocument().getChars()
def caretModel = editor.getCaretModel()
def selectionModel = editor.getSelectionModel()
int start = caretModel.offset - 1;
if (start < 0) return;
int carret = start;
int lastComa;
int mid;
int end;
int midLeft;
int midRight;
//travelToBeginning
int groupDepth = 0;
while (!(isLeftLimiter(input[start]) && groupDepth == 0)) {
if (isRightLimiter(input[start])) {
groupDepth++;
}
if (isLeftLimiter(input[start])) {
groupDepth--;
}
start--;
}
lastComa = start;
//travel backwards ToMid from start keeping track if we find coma (only the last is important)
groupDepth = 0;
mid = start + 1;
while (mid <= carret) {
if (isLeftLimiter(input[mid])) {
groupDepth++;
}
if (isRightLimiter(input[mid])) {
groupDepth--;
}
if (input[mid] == ',' && groupDepth == 0) {
start = lastComa;
lastComa = mid;
}
mid++;
}
// this is already the first element
if (lastComa == start) {
return;
}
// skip whitespaces from start
start++;
while (isWhitespace(input[start])) {
start++;
}
midLeft = lastComa;
midRight = lastComa;
// skipp whitespaces around the coma
// this currently points to a coma or bracket
midLeft--;
while (isWhitespace(input[midLeft])) {
midLeft--;
}
midLeft++;
// this currently points to a coma or bracket
midRight++;
while (isWhitespace(input[midRight])) {
midRight++;
}
//travelToEnd
end = midRight;
groupDepth = 0;
while (!((input[end] == ',' && groupDepth == 0)
|| (isRightLimiter(input[end]) && groupDepth == 0))) {
if (isLeftLimiter(input[end])) {
groupDepth++;
}
if (isRightLimiter(input[end])) {
groupDepth--;
}
end++;
}
// skip ending whitespaces from end
end--;
while (isWhitespace(input[end])) {
end--;
}
end++;
// do the swap
String text = editor.document.getText(new TextRange(start, end))
String swappedText = text.substring(midRight - start, end - start) + text.substring(midLeft - start, midRight - start) + text.substring(0, midLeft - start)
runDocumentWriteAction(event.project, editor.document, "Swap to left a list element", "Permute plugin (mnw)") {
editor.document.replaceString(start, end, swappedText)
}
caretModel.moveToOffset(start + end - midRight)
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
}
private boolean isWhitespace(char charAt) {
return charAt == " " || charAt == "\t" || charAt == "\n" || charAt == "\r" || charAt == "\f";
}
private boolean isLeftLimiter(final char c) {
return c == '(' || c == '<' || c == '[' || c == '{';
}
private boolean isRightLimiter(final char c) {
return c == ')' || c == '>' || c == ']' || c == '}';
}
show("permute Loaded")