fix multi-caret completion issues, make TypedHandlers supporting block selection work also with multiple carets (IDEA-80056)

This commit is contained in:
Dmitry Batrak
2014-02-27 19:03:00 +04:00
parent a3258e2d47
commit 399fdef08e
10 changed files with 163 additions and 37 deletions
@@ -0,0 +1,4 @@
class Foo {{
System.out.p<caret>
System.out.p<caret>
}}
@@ -0,0 +1,4 @@
class Foo {{
System.out.append(<caret>)
System.out.append(<caret>)
}}
@@ -52,6 +52,7 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.statistics.StatisticsManager
import com.intellij.psi.statistics.impl.StatisticsManagerImpl
import com.intellij.testFramework.EditorTestUtil
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.annotations.NotNull
@@ -1141,6 +1142,56 @@ class Foo {{
}}'''
}
public void testMulticaret() {
doTestMulticaret """
class Foo {{
<selection>t<caret></selection>x;
<selection>t<caret></selection>x;
}}""", '\n', '''
class Foo {{
toString()<caret>x;
toString()<caret>x;
}}'''
}
public void testMulticaretTab() {
doTestMulticaret """
class Foo {{
<selection>t<caret></selection>x;
<selection>t<caret></selection>x;
}}""", '\t', '''
class Foo {{
toString()<caret>;
toString()<caret>;
}}'''
}
public void testMulticaretBackspace() {
doTestMulticaret """
class Foo {{
<selection>t<caret></selection>;
<selection>t<caret></selection>;
}}""", '\b\t', '''
class Foo {{
toString()<caret>;
toString()<caret>;
}}'''
}
private doTestMulticaret(final String textBefore, final String toType, final String textAfter) {
EditorTestUtil.enableMultipleCarets()
try {
myFixture.configureByText "a.java", textBefore
type 'toStr'
assert lookup
type toType
myFixture.checkResult textAfter
}
finally {
EditorTestUtil.disableMultipleCarets()
}
}
private doTestBlockSelection(final String textBefore, final String toType, final String textAfter) {
myFixture.configureByText "a.java", textBefore
edt {
@@ -1403,6 +1403,21 @@ class Foo {{
}
}
public void testMulticaretTyping() {
EditorTestUtil.enableMultipleCarets()
try {
configure()
assert lookup
type('p')
assert lookup
type('\n')
checkResult()
}
finally {
EditorTestUtil.disableMultipleCarets()
}
}
public void "test complete lowercase class name"() {
myFixture.addClass("package foo; public class myClass {}")
myFixture.configureByText "a.java", """
@@ -135,10 +135,7 @@ public class TypedHandler extends TypedActionHandlerBase {
Project project = CommonDataKeys.PROJECT.getData(dataContext);
PsiFile file;
if (project == null
|| editor.isColumnMode()
|| editor.getCaretModel().getAllCarets().size() > 1
|| (file = PsiUtilBase.getPsiFileInEditor(editor, project)) == null) {
if (project == null || editor.isColumnMode() || (file = PsiUtilBase.getPsiFileInEditor(editor, project)) == null) {
if (myOriginalHandler != null){
myOriginalHandler.execute(editor, charTyped, dataContext);
}
@@ -174,13 +171,13 @@ public class TypedHandler extends TypedActionHandlerBase {
}
if (!editor.isInsertMode()){
myOriginalHandler.execute(originalEditor, charTyped, dataContext);
if (myOriginalHandler != null) {
myOriginalHandler.execute(originalEditor, charTyped, dataContext);
}
return;
}
if (editor.getSelectionModel().hasSelection()){
EditorModificationUtil.deleteSelectedText(editor);
}
EditorModificationUtil.deleteSelectedTextForAllCarets(editor);
FileType fileType = getFileType(file, editor);
@@ -194,7 +191,7 @@ public class TypedHandler extends TypedActionHandlerBase {
}
}
if (!editor.getSelectionModel().hasBlockSelection()) {
if (!editor.getSelectionModel().hasBlockSelection() && editor.getCaretModel().getAllCarets().size() == 1) {
if (')' == charTyped || ']' == charTyped || '}' == charTyped) {
if (FileTypes.PLAIN_TEXT != fileType) {
if (handleRParen(editor, fileType, charTyped)) return;
@@ -206,12 +203,14 @@ public class TypedHandler extends TypedActionHandlerBase {
}
long modificationStampBeforeTyping = editor.getDocument().getModificationStamp();
myOriginalHandler.execute(originalEditor, charTyped, dataContext);
if (myOriginalHandler != null) {
myOriginalHandler.execute(originalEditor, charTyped, dataContext);
}
AutoHardWrapHandler.getInstance().wrapLineIfNecessary(editor, dataContext, modificationStampBeforeTyping);
if (('(' == charTyped || '[' == charTyped || '{' == charTyped) &&
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
!editor.getSelectionModel().hasBlockSelection() && fileType != FileTypes.PLAIN_TEXT) {
!editor.getSelectionModel().hasBlockSelection() && editor.getCaretModel().getAllCarets().size() == 1 && fileType != FileTypes.PLAIN_TEXT) {
handleAfterLParen(editor, fileType, charTyped);
}
else if ('}' == charTyped) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,7 +80,7 @@ public class LookupTypedHandler extends TypedHandlerDelegate {
if (!lookup.performGuardedChange(new Runnable() {
@Override
public void run() {
EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, String.valueOf(charTyped), true);
EditorModificationUtil.typeInStringAtCaretHonorMultipleCarets(editor, String.valueOf(charTyped), true);
}
})) {
return Result.STOP;
@@ -25,6 +25,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.util.Producer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
@@ -51,6 +52,15 @@ public class EditorModificationUtil {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
public static void deleteSelectedTextForAllCarets(@NotNull final Editor editor) {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
deleteSelectedText(editor);
}
});
}
public static void deleteBlockSelection(Editor editor) {
SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasBlockSelection()) return;
@@ -81,11 +91,15 @@ public class EditorModificationUtil {
editor.getSelectionModel().setBlockSelection(new LogicalPosition(startLine, caretColumn), new LogicalPosition(endLine, caretColumn));
}
public static void insertStringAtCaret(Editor editor, String s) {
public static void insertStringAtCaret(Editor editor, @NotNull String s) {
insertStringAtCaret(editor, s, false, true);
}
public static int insertStringAtCaret(Editor editor, String s, boolean toProcessOverwriteMode, boolean toMoveCaret) {
public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, boolean toMoveCaret) {
return insertStringAtCaret(editor, s, toProcessOverwriteMode, toMoveCaret, s.length());
}
public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, boolean toMoveCaret, int caretShift) {
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasSelection()) {
editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart(), true);
@@ -120,7 +134,7 @@ public class EditorModificationUtil {
document.replaceString(oldOffset, Math.min(endOffset, oldOffset + s.length()), s);
}
int offset = oldOffset + s.length();
int offset = oldOffset + filler.length() + caretShift;
if (toMoveCaret){
editor.getCaretModel().moveToOffset(offset, true);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
@@ -365,6 +379,62 @@ public class EditorModificationUtil {
}
}
public static void typeInStringAtCaretHonorMultipleCarets(final Editor editor, @NotNull final String str, final boolean toProcessOverwriteMode) {
typeInStringAtCaretHonorMultipleCarets(editor, str, toProcessOverwriteMode, str.length());
}
/**
* Inserts given string at each caret's position. Effective caret shift will be equal to <code>caretShift</code> for each caret.
*/
public static void typeInStringAtCaretHonorMultipleCarets(final Editor editor, @NotNull final String str, final boolean toProcessOverwriteMode, final int caretShift)
throws ReadOnlyFragmentModificationException
{
Document doc = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasBlockSelection()) {
RangeMarker guard = selectionModel.getBlockSelectionGuard();
if (guard != null) {
DocumentEvent evt = new MockDocumentEvent(doc, editor.getCaretModel().getOffset());
ReadOnlyFragmentModificationException e = new ReadOnlyFragmentModificationException(evt, guard);
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
}
else {
final LogicalPosition start = selectionModel.getBlockStart();
final LogicalPosition end = selectionModel.getBlockEnd();
assert start != null;
assert end != null;
int column = Math.min(start.column, end.column);
int startLine = Math.min(start.line, end.line);
int endLine = Math.max(start.line, end.line);
deleteBlockSelection(editor);
for (int i = startLine; i <= endLine; i++) {
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(i, column));
insertStringAtCaret(editor, str, toProcessOverwriteMode, true, caretShift);
}
selectionModel.setBlockSelection(new LogicalPosition(startLine, column + str.length()),
new LogicalPosition(endLine, column + str.length()));
}
}
else {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
insertStringAtCaret(editor, str, toProcessOverwriteMode, true, caretShift);
}
});
}
}
public static void moveAllCaretsRelatively(@NotNull Editor editor, final int caretShift) {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
caret.moveToOffset(caret.getOffset() + caretShift);
}
});
}
/** @deprecated use {@link #pasteTransferable(Editor, Producer)} (to remove in IDEA 14) */
@SuppressWarnings("UnusedDeclaration")
public static TextRange pasteFromClipboard(Editor editor) {
@@ -64,18 +64,7 @@ public class TypedAction {
try {
final String str = String.valueOf(charTyped);
CommandProcessor.getInstance().setCurrentCommandName(EditorBundle.message("typing.in.editor.command.name"));
if (editor.getCaretModel().getAllCarets().size() == 1) { // temporary fix for completion - going forward we shouldn't use this check
EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, str, true);
}
else {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, str, true);
}
});
}
EditorModificationUtil.typeInStringAtCaretHonorMultipleCarets(editor, str, true);
}
catch (ReadOnlyFragmentModificationException e) {
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
@@ -53,12 +53,7 @@ public class CutAction extends EditorAction {
});
}
editor.getSelectionModel().copySelectionToClipboard();
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
EditorModificationUtil.deleteSelectedText(editor);
}
});
EditorModificationUtil.deleteSelectedTextForAllCarets(editor);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,8 +82,7 @@ public class PyMethodNameTypedHandler extends TypedHandlerDelegate {
if (caretOffset == chars.length() || chars.charAt(caretOffset) != ':') {
textToType += ':';
}
EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, textToType, true);
editor.getCaretModel().moveToOffset(offset + 1 + pname.length()); // right after param name
EditorModificationUtil.typeInStringAtCaretHonorMultipleCarets(editor, textToType, true, 1 + pname.length()); // right after param name
return Result.STOP;
}
}