diff --git a/platform/lang-api/src/com/intellij/codeInsight/editorActions/TypedHandlerDelegate.java b/platform/lang-api/src/com/intellij/codeInsight/editorActions/TypedHandlerDelegate.java index 825e94bbecf4..247496d2a25d 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/editorActions/TypedHandlerDelegate.java +++ b/platform/lang-api/src/com/intellij/codeInsight/editorActions/TypedHandlerDelegate.java @@ -23,6 +23,8 @@ import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; /** + * Handler, extending IDE behaviour on typing in editor. + * * @author yole */ public abstract class TypedHandlerDelegate { @@ -30,17 +32,22 @@ public abstract class TypedHandlerDelegate { /** * If the specified character triggers auto-popup, schedules the auto-popup appearance. This method is called even - * in overwrite mode, when the rest of typed handler delegate methods are not called. + * in overwrite mode, when the rest of typed handler delegate methods are not called. It is invoked only for the primary caret. */ public Result checkAutoPopup(char charTyped, final Project project, final Editor editor, final PsiFile file) { return Result.CONTINUE; } + /** + * Called before selected text is deleted. + * This method is supposed to be overridden by handlers having custom behaviour with respect to selection. + */ + public Result beforeSelectionRemoved(char c, final Project project, final Editor editor, final PsiFile file) { + return Result.CONTINUE; + } + /** * Called before the specified character typed by the user is inserted in the editor. - * - * @return true if the typing has been processed (in this case, no further delegates are called and the character is not inserted), - * false otherwise. */ public Result beforeCharTyped(char c, final Project project, final Editor editor, final PsiFile file, final FileType fileType) { return Result.CONTINUE; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectionQuotingTypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectionQuotingTypedHandler.java index 76dc70fe7116..932c36717e68 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectionQuotingTypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectionQuotingTypedHandler.java @@ -23,10 +23,10 @@ import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; @@ -37,52 +37,59 @@ import org.jetbrains.annotations.NotNull; public class SelectionQuotingTypedHandler extends TypedHandlerDelegate { public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.selectionDequotingFilter"); - private TextRange myReplacedTextRange; - private boolean myRestoreStickySelection; - private boolean myLtrSelection; @Override - public Result checkAutoPopup(char c, Project project, Editor editor, PsiFile psiFile) { - // TODO[oleg] provide adequate API not to use this hack - // beforeCharTyped always works with removed selection + public Result beforeSelectionRemoved(char c, Project project, Editor editor, PsiFile file) { SelectionModel selectionModel = editor.getSelectionModel(); - if(CodeInsightSettings.getInstance().SURROUND_SELECTION_ON_QUOTE_TYPED && selectionModel.hasSelection() && isDelimiter(c)) { + if(CodeInsightSettings.getInstance().SURROUND_SELECTION_ON_QUOTE_TYPED && selectionModel.hasSelection() && isDelimiter(c)) { String selectedText = selectionModel.getSelectedText(); - if (selectedText.length() < 1) { - return super.checkAutoPopup(c, project, editor, psiFile); - } - - final int selectionStart = selectionModel.getSelectionStart(); - final int selectionEnd = selectionModel.getSelectionEnd(); - if (selectedText.length() > 1) { - final char firstChar = selectedText.charAt(0); - final char lastChar = selectedText.charAt(selectedText.length() - 1); - if (isSimilarDelimiters(firstChar, c) && lastChar == getMatchingDelimiter(firstChar) && - (isQuote(firstChar) || firstChar != c) && !shouldSkipReplacementOfQuotesOrBraces(psiFile, editor, selectedText, c) && - selectedText.indexOf(lastChar, 1) == selectedText.length() - 1) { - selectedText = selectedText.substring(1, selectedText.length() - 1); + if (!StringUtil.isEmpty(selectedText)) { + final int selectionStart = selectionModel.getSelectionStart(); + final int selectionEnd = selectionModel.getSelectionEnd(); + if (selectedText.length() > 1) { + final char firstChar = selectedText.charAt(0); + final char lastChar = selectedText.charAt(selectedText.length() - 1); + if (isSimilarDelimiters(firstChar, c) && lastChar == getMatchingDelimiter(firstChar) && + (isQuote(firstChar) || firstChar != c) && !shouldSkipReplacementOfQuotesOrBraces(file, editor, selectedText, c) && + selectedText.indexOf(lastChar, 1) == selectedText.length() - 1) { + selectedText = selectedText.substring(1, selectedText.length() - 1); + } } + final int caretOffset = selectionModel.getSelectionStart(); + final char c2 = getMatchingDelimiter(c); + final String newText = String.valueOf(c) + selectedText + c2; + boolean ltrSelection = selectionModel.getLeadSelectionOffset() != selectionModel.getSelectionEnd(); + boolean restoreStickySelection = editor instanceof EditorEx && ((EditorEx)editor).isStickySelection(); + selectionModel.removeSelection(); + editor.getDocument().replaceString(selectionStart, selectionEnd, newText); + TextRange replacedTextRange = Registry.is("editor.smarterSelectionQuoting") + ? new TextRange(caretOffset + 1, caretOffset + newText.length() - 1) + : new TextRange(caretOffset, caretOffset + newText.length()); + // selection is removed here + if (replacedTextRange.getEndOffset() <= editor.getDocument().getTextLength()) { + if (restoreStickySelection) { + EditorEx editorEx = (EditorEx)editor; + CaretModel caretModel = editorEx.getCaretModel(); + caretModel.moveToOffset(ltrSelection ? replacedTextRange.getStartOffset() : replacedTextRange.getEndOffset()); + editorEx.setStickySelection(true); + caretModel.moveToOffset(ltrSelection ? replacedTextRange.getEndOffset() : replacedTextRange.getStartOffset()); + } + else { + if (ltrSelection || editor instanceof EditorWindow) { + editor.getSelectionModel().setSelection(replacedTextRange.getStartOffset(), replacedTextRange.getEndOffset()); + } + else { + editor.getSelectionModel().setSelection(replacedTextRange.getEndOffset(), replacedTextRange.getStartOffset()); + } + if (Registry.is("editor.smarterSelectionQuoting")) { + editor.getCaretModel().moveToOffset(ltrSelection ? replacedTextRange.getEndOffset() : replacedTextRange.getStartOffset()); + } + } + } + return Result.STOP; } - final int caretOffset = selectionModel.getSelectionStart(); - final char c2 = getMatchingDelimiter(c); - final String newText = String.valueOf(c) + selectedText + c2; - myLtrSelection = selectionModel.getLeadSelectionOffset() != selectionModel.getSelectionEnd(); - if (editor instanceof EditorEx) { - myRestoreStickySelection = ((EditorEx)editor).isStickySelection(); - } - else { - myRestoreStickySelection = false; - } - selectionModel.removeSelection(); - editor.getDocument().replaceString(selectionStart, selectionEnd, newText); - if (Registry.is("editor.smarterSelectionQuoting")) { - myReplacedTextRange = new TextRange(caretOffset + 1, caretOffset + newText.length() - 1); - } else { - myReplacedTextRange = new TextRange(caretOffset, caretOffset + newText.length()); - } - return Result.STOP; } - return super.checkAutoPopup(c, project, editor, psiFile); + return super.beforeSelectionRemoved(c, project, editor, file); } private static boolean shouldSkipReplacementOfQuotesOrBraces(PsiFile psiFile, Editor editor, String selectedText, char c) { @@ -116,36 +123,6 @@ public class SelectionQuotingTypedHandler extends TypedHandlerDelegate { return (isBracket(c1) && isBracket(c2)) || (isQuote(c1) && isQuote(c2)); } - @Override - public Result beforeCharTyped(final char charTyped, final Project project, final Editor editor, final PsiFile file, final FileType fileType) { - // TODO[oleg] remove this hack when API changes - if (myReplacedTextRange != null) { - if (myReplacedTextRange.getEndOffset() <= editor.getDocument().getTextLength()) { - if (myRestoreStickySelection && editor instanceof EditorEx) { - EditorEx editorEx = (EditorEx)editor; - CaretModel caretModel = editorEx.getCaretModel(); - caretModel.moveToOffset(myLtrSelection ? myReplacedTextRange.getStartOffset() : myReplacedTextRange.getEndOffset()); - editorEx.setStickySelection(true); - caretModel.moveToOffset(myLtrSelection ? myReplacedTextRange.getEndOffset() : myReplacedTextRange.getStartOffset()); - } - else { - if (myLtrSelection || editor instanceof EditorWindow) { - editor.getSelectionModel().setSelection(myReplacedTextRange.getStartOffset(), myReplacedTextRange.getEndOffset()); - } - else { - editor.getSelectionModel().setSelection(myReplacedTextRange.getEndOffset(), myReplacedTextRange.getStartOffset()); - } - if (Registry.is("editor.smarterSelectionQuoting")) { - editor.getCaretModel().moveToOffset(myLtrSelection ? myReplacedTextRange.getEndOffset() : myReplacedTextRange.getStartOffset()); - } - } - } - myReplacedTextRange = null; - return Result.STOP; - } - return Result.CONTINUE; - } - public static abstract class DequotingFilter { public abstract boolean skipReplacementQuotesOrBraces(@NotNull PsiFile file, @NotNull Editor editor, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java index 1b25d4b2c829..aea226c91831 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java @@ -162,18 +162,20 @@ public class TypedHandler extends TypedActionHandlerBase { final TypedHandlerDelegate[] delegates = Extensions.getExtensions(TypedHandlerDelegate.EP_NAME); - boolean handled = false; - for (TypedHandlerDelegate delegate : delegates) { - final TypedHandlerDelegate.Result result = delegate.checkAutoPopup(charTyped, project, editor, file); - handled = result == TypedHandlerDelegate.Result.STOP; - if (result != TypedHandlerDelegate.Result.CONTINUE) { - break; + if (caret == originalEditor.getCaretModel().getPrimaryCaret()) { + boolean handled = false; + for (TypedHandlerDelegate delegate : delegates) { + final TypedHandlerDelegate.Result result = delegate.checkAutoPopup(charTyped, project, editor, file); + handled = result == TypedHandlerDelegate.Result.STOP; + if (result != TypedHandlerDelegate.Result.CONTINUE) { + break; + } } - } - if (!handled) { - autoPopupCompletion(editor, charTyped, project, file); - autoPopupParameterInfo(editor, charTyped, project, file); + if (!handled) { + autoPopupCompletion(editor, charTyped, project, file); + autoPopupParameterInfo(editor, charTyped, project, file); + } } if (!editor.isInsertMode()) { @@ -181,6 +183,16 @@ public class TypedHandler extends TypedActionHandlerBase { return; } + for (TypedHandlerDelegate delegate : delegates) { + final TypedHandlerDelegate.Result result = delegate.beforeSelectionRemoved(charTyped, project, editor, file); + if (result == TypedHandlerDelegate.Result.STOP) { + return; + } + if (result == TypedHandlerDelegate.Result.DEFAULT) { + break; + } + } + EditorModificationUtil.deleteSelectedText(editor); FileType fileType = getFileType(file, editor);