From dfe4e010823e821fe2194405311f569f6d288dda Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 13 Mar 2014 14:38:42 +0400 Subject: [PATCH] undo should restore primary caret properly (IDEA-80056) --- .../intellij/openapi/editor/CaretModel.java | 16 ++++++ .../injected/editor/CaretModelWindow.java | 13 +++++ .../openapi/editor/impl/CaretModelImpl.java | 14 +++++ .../textarea/TextComponentCaretModel.java | 6 +++ .../impl/text/TextEditorProvider.java | 54 +++++++++++++------ .../editor/EditorMultiCaretUndoRedoTest.java | 9 ++++ 6 files changed, 97 insertions(+), 15 deletions(-) diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java index 1d26b77128b3..02a849a19eb7 100644 --- a/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java +++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java @@ -224,9 +224,25 @@ public interface CaretModel { * If multiple carets are not supported, the behaviour is unspecified. * * @see #supportsMultipleCarets() + * @see #getCaretsAndSelections() */ void setCaretsAndSelections(@NotNull List caretStates); + /** + * Returns the current positions of all carets and their selections. The order of entries in the returned list does not necessarily + * correspond to the order of {@link #getAllCarets()} method results. Passing the result of this method to + * {@link #setCaretsAndSelections(java.util.List)} will restore the state of carets, including the internal caret order, in particular, + * the caret, that was primary when this method was called, will be the primary one after corresponding + * {@link #setCaretsAndSelections(java.util.List)} invocation. + *

+ * If multiple carets are not supported, the behaviour is unspecified. + * + * @see #supportsMultipleCarets() + * @see #setCaretsAndSelections(java.util.List) + */ + @NotNull + List getCaretsAndSelections(); + /** * Executes the given task for each existing caret. Carets are iterated in their position order. Set of carets to iterate over is * determined in the beginning and is not affected by the potential carets addition or removal by the task being executed. diff --git a/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java index 33b417de0af7..5c098fa90cfb 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java @@ -221,6 +221,19 @@ public class CaretModelWindow implements CaretModel { myDelegate.setCaretsAndSelections(convertedStates); } + @NotNull + @Override + public List getCaretsAndSelections() { + List caretsAndSelections = myDelegate.getCaretsAndSelections(); + List convertedStates = new ArrayList(caretsAndSelections.size()); + for (CaretState state : caretsAndSelections) { + convertedStates.add(new CaretState(state.getCaretPosition() == null ? null : myEditorWindow.hostToInjected(state.getCaretPosition()), + state.getSelectionStart() == null ? null : myEditorWindow.hostToInjected(state.getSelectionStart()), + state.getSelectionEnd() == null ? null : myEditorWindow.hostToInjected(state.getSelectionEnd()))); + } + return convertedStates; + } + private InjectedCaret createInjectedCaret(Caret caret) { if (caret == null) { return null; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index a6bebe64f370..aa548af56b05 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -488,6 +488,20 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, }); } + @NotNull + @Override + public List getCaretsAndSelections() { + synchronized (myCarets) { + List states = new ArrayList(myCarets.size()); + for (CaretImpl caret : myCarets) { + states.add(new CaretState(caret.getLogicalPosition(), + myEditor.visualToLogicalPosition(caret.getSelectionStartPosition()), + myEditor.visualToLogicalPosition(caret.getSelectionEndPosition()))); + } + return states; + } + } + void fireCaretPositionChanged(CaretEvent caretEvent) { myCaretListeners.getMulticaster().caretPositionChanged(caretEvent); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java index ef86c6f3ae3d..4b3ba40849ca 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java @@ -187,6 +187,12 @@ public class TextComponentCaretModel implements CaretModel { throw new UnsupportedOperationException("Multiple carets are not supported"); } + @NotNull + @Override + public List getCaretsAndSelections() { + throw new UnsupportedOperationException("Multiple carets are not supported"); + } + @Override public void runForEachCaret(@NotNull CaretAction action) { action.perform(myCaret); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorProvider.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorProvider.java index 47c0088fa2ce..018503dcf7d0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorProvider.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorProvider.java @@ -29,7 +29,9 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; @@ -42,7 +44,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.beans.PropertyChangeListener; import java.util.ArrayList; -import java.util.Collection; import java.util.List; /** @@ -226,19 +227,23 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware { protected TextEditorState getStateImpl(final Project project, @NotNull Editor editor, @NotNull FileEditorStateLevel level){ TextEditorState state = new TextEditorState(); CaretModel caretModel = editor.getCaretModel(); - Collection allCarets = caretModel.getAllCarets(); - state.CARETS = new TextEditorState.CaretState[allCarets.size()]; - int i = 0; - for (Caret caret : allCarets) { - state.CARETS[i] = new TextEditorState.CaretState(); - state.CARETS[i].LINE = caret.getLogicalPosition().line; - state.CARETS[i].COLUMN = caret.getLogicalPosition().column; - LogicalPosition selectionStartPosition = editor.visualToLogicalPosition(caret.getSelectionStartPosition()); - LogicalPosition selectionEndPosition = editor.visualToLogicalPosition(caret.getSelectionEndPosition()); - state.CARETS[i].SELECTION_START_LINE = selectionStartPosition.line; - state.CARETS[i].SELECTION_START_COLUMN = selectionStartPosition.column; - state.CARETS[i].SELECTION_END_LINE = selectionEndPosition.line; - state.CARETS[i++].SELECTION_END_COLUMN = selectionEndPosition.column; + if (caretModel.supportsMultipleCarets()) { + List caretsAndSelections = caretModel.getCaretsAndSelections(); + state.CARETS = new TextEditorState.CaretState[caretsAndSelections.size()]; + for (int i = 0; i < caretsAndSelections.size(); i++) { + CaretState caretState = caretsAndSelections.get(i); + LogicalPosition caretPosition = caretState.getCaretPosition(); + LogicalPosition selectionStartPosition = caretState.getSelectionStart(); + LogicalPosition selectionEndPosition = caretState.getSelectionEnd(); + state.CARETS[i] = createCaretState(caretPosition, selectionStartPosition, selectionEndPosition); + } + } + else { + LogicalPosition caretPosition = caretModel.getLogicalPosition(); + LogicalPosition selectionStartPosition = editor.offsetToLogicalPosition(editor.getSelectionModel().getSelectionStart()); + LogicalPosition selectionEndPosition = editor.offsetToLogicalPosition(editor.getSelectionModel().getSelectionEnd()); + state.CARETS = new TextEditorState.CaretState[1]; + state.CARETS[0] = createCaretState(caretPosition, selectionStartPosition, selectionEndPosition); } // Saving scrolling proportion on UNDO may cause undesirable results of undo action fails to perform since @@ -253,6 +258,25 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware { return state; } + private static TextEditorState.CaretState createCaretState(LogicalPosition caretPosition, LogicalPosition selectionStartPosition, LogicalPosition selectionEndPosition) { + TextEditorState.CaretState caretState = new TextEditorState.CaretState(); + caretState.LINE = getLine(caretPosition); + caretState.COLUMN = getColumn(caretPosition); + caretState.SELECTION_START_LINE = getLine(selectionStartPosition); + caretState.SELECTION_START_COLUMN = getColumn(selectionStartPosition); + caretState.SELECTION_END_LINE = getLine(selectionEndPosition); + caretState.SELECTION_END_COLUMN = getColumn(selectionEndPosition); + return caretState; + } + + private static int getLine(@Nullable LogicalPosition pos) { + return pos == null ? 0 : pos.line; + } + + private static int getColumn(@Nullable LogicalPosition pos) { + return pos == null ? 0 : pos.column; + } + protected void setStateImpl(final Project project, final Editor editor, final TextEditorState state){ if (editor.getCaretModel().supportsMultipleCarets()) { CaretModel caretModel = editor.getCaretModel(); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java index c2578fc732dc..bd3f5464a21b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java @@ -97,6 +97,15 @@ public class EditorMultiCaretUndoRedoTest extends AbstractEditorTest { verifyCaretsAndSelections(0, 2, 0, 0, 0, 2); } + public void testPrimaryCaretPositionAfterUndo() throws Exception { + init("line1\n" + + "line2"); + mouse().alt().clickAt(1, 1).dragTo(0, 0).release(); + type(' '); + undo(); + assertEquals(new LogicalPosition(0, 0), myEditor.getCaretModel().getPrimaryCaret().getLogicalPosition()); + } + private void checkResult(final String text) { CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { @Override