| /* |
| * 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. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.codeInsight.editorActions; |
| |
| import com.intellij.codeInsight.AutoPopupController; |
| import com.intellij.codeInsight.CodeInsightSettings; |
| import com.intellij.codeInsight.CodeInsightUtilBase; |
| import com.intellij.codeInsight.completion.CompletionContributor; |
| import com.intellij.codeInsight.highlighting.BraceMatcher; |
| import com.intellij.codeInsight.highlighting.BraceMatchingUtil; |
| import com.intellij.codeInsight.highlighting.NontrivialBraceMatcher; |
| import com.intellij.codeInsight.template.impl.editorActions.TypedActionHandlerBase; |
| import com.intellij.injected.editor.DocumentWindow; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.LanguageParserDefinitions; |
| import com.intellij.lang.ParserDefinition; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.*; |
| import com.intellij.openapi.editor.actionSystem.TypedActionHandler; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.editor.highlighter.EditorHighlighter; |
| import com.intellij.openapi.editor.highlighter.HighlighterIterator; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.fileTypes.FileTypes; |
| import com.intellij.openapi.fileTypes.LanguageFileType; |
| import com.intellij.openapi.fileTypes.PlainTextLanguage; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.tree.TokenSet; |
| import com.intellij.psi.util.PsiUtilBase; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.text.CharArrayUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class TypedHandler extends TypedActionHandlerBase { |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.TypedHandler"); |
| |
| private static final Map<FileType,QuoteHandler> quoteHandlers = new HashMap<FileType, QuoteHandler>(); |
| |
| private static final Map<Class<? extends Language>, QuoteHandler> ourBaseLanguageQuoteHandlers = new HashMap<Class<? extends Language>, QuoteHandler>(); |
| |
| public TypedHandler(TypedActionHandler originalHandler){ |
| super(originalHandler); |
| } |
| |
| @Nullable |
| public static QuoteHandler getQuoteHandler(@NotNull PsiFile file, @NotNull Editor editor) { |
| FileType fileType = getFileType(file, editor); |
| QuoteHandler quoteHandler = getQuoteHandlerForType(fileType); |
| if (quoteHandler == null) { |
| FileType fileFileType = file.getFileType(); |
| if (fileFileType != fileType) { |
| quoteHandler = getQuoteHandlerForType(fileFileType); |
| } |
| } |
| if (quoteHandler == null) { |
| final Language baseLanguage = file.getViewProvider().getBaseLanguage(); |
| for (Map.Entry<Class<? extends Language>, QuoteHandler> entry : ourBaseLanguageQuoteHandlers.entrySet()) { |
| if (entry.getKey().isInstance(baseLanguage)) { |
| return entry.getValue(); |
| } |
| } |
| } |
| return quoteHandler; |
| } |
| |
| private static FileType getFileType(@NotNull PsiFile file, @NotNull Editor editor) { |
| FileType fileType = file.getFileType(); |
| Language language = PsiUtilBase.getLanguageInEditor(editor, file.getProject()); |
| if (language != null && language != PlainTextLanguage.INSTANCE) { |
| LanguageFileType associatedFileType = language.getAssociatedFileType(); |
| if (associatedFileType != null) fileType = associatedFileType; |
| } |
| return fileType; |
| } |
| |
| public static void registerBaseLanguageQuoteHandler(@NotNull Class<? extends Language> languageClass, @NotNull QuoteHandler quoteHandler) { |
| ourBaseLanguageQuoteHandlers.put(languageClass, quoteHandler); |
| } |
| |
| public static QuoteHandler getQuoteHandlerForType(@NotNull FileType fileType) { |
| if (!quoteHandlers.containsKey(fileType)) { |
| QuoteHandler handler = null; |
| final QuoteHandlerEP[] handlerEPs = Extensions.getExtensions(QuoteHandlerEP.EP_NAME); |
| for(QuoteHandlerEP ep: handlerEPs) { |
| if (ep.fileType.equals(fileType.getName())) { |
| handler = ep.getHandler(); |
| break; |
| } |
| } |
| quoteHandlers.put(fileType, handler); |
| } |
| return quoteHandlers.get(fileType); |
| } |
| |
| /** @see QuoteHandlerEP */ |
| @Deprecated |
| public static void registerQuoteHandler(@NotNull FileType fileType, @NotNull QuoteHandler quoteHandler) { |
| quoteHandlers.put(fileType, quoteHandler); |
| } |
| |
| @Override |
| public void execute(@NotNull final Editor originalEditor, final char charTyped, @NotNull final DataContext dataContext) { |
| final Project project = CommonDataKeys.PROJECT.getData(dataContext); |
| final PsiFile originalFile; |
| |
| if (project == null || (originalFile = PsiUtilBase.getPsiFileInEditor(originalEditor, project)) == null) { |
| if (myOriginalHandler != null){ |
| myOriginalHandler.execute(originalEditor, charTyped, dataContext); |
| } |
| return; |
| } |
| |
| if (!CodeInsightUtilBase.prepareEditorForWrite(originalEditor)) return; |
| if (!FileDocumentManager.getInstance().requestWriting(originalEditor.getDocument(), project)) { |
| return; |
| } |
| |
| final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); |
| final Document originalDocument = originalEditor.getDocument(); |
| originalEditor.getCaretModel().runForEachCaret(new CaretAction() { |
| @Override |
| public void perform(Caret caret) { |
| if (psiDocumentManager.isDocumentBlockedByPsi(originalDocument)) { |
| psiDocumentManager.doPostponedOperationsAndUnblockDocument(originalDocument); // to clean up after previous caret processing |
| } |
| |
| Editor editor = injectedEditorIfCharTypedIsSignificant(charTyped, originalEditor, originalFile); |
| PsiFile file = editor == originalEditor ? originalFile : psiDocumentManager.getPsiFile(editor.getDocument()); |
| |
| |
| 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 (!handled) { |
| autoPopupCompletion(editor, charTyped, project, file); |
| autoPopupParameterInfo(editor, charTyped, project, file); |
| } |
| |
| if (!editor.isInsertMode()) { |
| type(originalEditor, charTyped); |
| return; |
| } |
| |
| EditorModificationUtil.deleteSelectedText(editor); |
| |
| FileType fileType = getFileType(file, editor); |
| |
| for (TypedHandlerDelegate delegate : delegates) { |
| final TypedHandlerDelegate.Result result = delegate.beforeCharTyped(charTyped, project, editor, file, fileType); |
| if (result == TypedHandlerDelegate.Result.STOP) { |
| return; |
| } |
| if (result == TypedHandlerDelegate.Result.DEFAULT) { |
| break; |
| } |
| } |
| |
| if (!editor.getSelectionModel().hasBlockSelection()) { |
| if (')' == charTyped || ']' == charTyped || '}' == charTyped) { |
| if (FileTypes.PLAIN_TEXT != fileType) { |
| if (handleRParen(editor, fileType, charTyped)) return; |
| } |
| } |
| else if ('"' == charTyped || '\'' == charTyped || '`' == charTyped/* || '/' == charTyped*/) { |
| if (handleQuote(editor, charTyped, dataContext, file)) return; |
| } |
| } |
| |
| long modificationStampBeforeTyping = editor.getDocument().getModificationStamp(); |
| type(originalEditor, charTyped); |
| AutoHardWrapHandler.getInstance().wrapLineIfNecessary(editor, dataContext, modificationStampBeforeTyping); |
| |
| if (('(' == charTyped || '[' == charTyped || '{' == charTyped) && |
| CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && |
| !editor.getSelectionModel().hasBlockSelection() && fileType != FileTypes.PLAIN_TEXT) { |
| handleAfterLParen(editor, fileType, charTyped); |
| } |
| else if ('}' == charTyped) { |
| indentClosingBrace(project, editor); |
| } |
| else if (')' == charTyped) { |
| indentClosingParenth(project, editor); |
| } |
| |
| for (TypedHandlerDelegate delegate : delegates) { |
| final TypedHandlerDelegate.Result result = delegate.charTyped(charTyped, project, editor, file); |
| if (result == TypedHandlerDelegate.Result.STOP) { |
| return; |
| } |
| if (result == TypedHandlerDelegate.Result.DEFAULT) { |
| break; |
| } |
| } |
| if ('{' == charTyped) { |
| indentOpenedBrace(project, editor); |
| } |
| else if ('(' == charTyped) { |
| indentOpenedParenth(project, editor); |
| } |
| } |
| }, true); |
| } |
| |
| private static void type(Editor editor, char charTyped) { |
| CommandProcessor.getInstance().setCurrentCommandName(EditorBundle.message("typing.in.editor.command.name")); |
| EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, String.valueOf(charTyped), true); |
| } |
| |
| private static void autoPopupParameterInfo(@NotNull Editor editor, char charTyped, @NotNull Project project, @NotNull PsiFile file) { |
| if ((charTyped == '(' || charTyped == ',') && !isInsideStringLiteral(editor, file)) { |
| AutoPopupController.getInstance(project).autoPopupParameterInfo(editor, null); |
| } |
| } |
| |
| public static void autoPopupCompletion(@NotNull Editor editor, char charTyped, @NotNull Project project, @NotNull PsiFile file) { |
| if (charTyped == '.' || isAutoPopup(editor, file, charTyped)) { |
| AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, null); |
| } |
| } |
| |
| private static boolean isAutoPopup(@NotNull Editor editor, @NotNull PsiFile file, char charTyped) { |
| final int offset = editor.getCaretModel().getOffset() - 1; |
| if (offset >= 0) { |
| final PsiElement element = file.findElementAt(offset); |
| if (element != null) { |
| final List<CompletionContributor> list = CompletionContributor.forLanguage(element.getLanguage()); |
| for (CompletionContributor contributor : list) { |
| if (contributor.invokeAutoPopup(element, charTyped)) return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isInsideStringLiteral(@NotNull Editor editor, @NotNull PsiFile file) { |
| int offset = editor.getCaretModel().getOffset(); |
| PsiElement element = file.findElementAt(offset); |
| if (element == null) return false; |
| final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage()); |
| if (definition != null) { |
| final TokenSet stringLiteralElements = definition.getStringLiteralElements(); |
| final ASTNode node = element.getNode(); |
| if (node == null) return false; |
| final IElementType elementType = node.getElementType(); |
| if (stringLiteralElements.contains(elementType)) { |
| return true; |
| } |
| PsiElement parent = element.getParent(); |
| if (parent != null) { |
| ASTNode parentNode = parent.getNode(); |
| if (parentNode != null && stringLiteralElements.contains(parentNode.getElementType())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @NotNull |
| public static Editor injectedEditorIfCharTypedIsSignificant(final char charTyped, @NotNull Editor editor, @NotNull PsiFile oldFile) { |
| int offset = editor.getCaretModel().getOffset(); |
| // even for uncommitted document try to retrieve injected fragment that has been there recently |
| // we are assuming here that when user is (even furiously) typing, injected language would not change |
| // and thus we can use its lexer to insert closing braces etc |
| for (DocumentWindow documentWindow : InjectedLanguageUtil.getCachedInjectedDocuments(oldFile)) { |
| if (documentWindow.isValid() && documentWindow.containsRange(offset, offset)) { |
| PsiFile injectedFile = PsiDocumentManager.getInstance(oldFile.getProject()).getPsiFile(documentWindow); |
| final Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile); |
| // IDEA-52375 fix: last quote sign should be handled by outer language quote handler |
| final CharSequence charsSequence = editor.getDocument().getCharsSequence(); |
| if (injectedEditor.getCaretModel().getOffset() == injectedEditor.getDocument().getTextLength() && |
| offset < charsSequence.length() && charTyped == charsSequence.charAt(offset)) { |
| return editor; |
| } |
| return injectedEditor; |
| } |
| } |
| |
| return editor; |
| } |
| |
| private static void handleAfterLParen(@NotNull Editor editor, @NotNull FileType fileType, char lparenChar){ |
| int offset = editor.getCaretModel().getOffset(); |
| HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); |
| boolean atEndOfDocument = offset == editor.getDocument().getTextLength(); |
| |
| if (!atEndOfDocument) iterator.retreat(); |
| if (iterator.atEnd()) return; |
| BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); |
| if (iterator.atEnd()) return; |
| IElementType braceTokenType = iterator.getTokenType(); |
| final CharSequence fileText = editor.getDocument().getCharsSequence(); |
| if (!braceMatcher.isLBraceToken(iterator, fileText, fileType)) return; |
| |
| if (!iterator.atEnd()) { |
| iterator.advance(); |
| |
| if (!iterator.atEnd()) { |
| if (!BraceMatchingUtil.isPairedBracesAllowedBeforeTypeInFileType(braceTokenType, iterator.getTokenType(), fileType)) { |
| return; |
| } |
| if (BraceMatchingUtil.isLBraceToken(iterator, fileText, fileType)) { |
| return; |
| } |
| } |
| |
| iterator.retreat(); |
| } |
| |
| int lparenOffset = BraceMatchingUtil.findLeftmostLParen(iterator, braceTokenType, fileText,fileType); |
| if (lparenOffset < 0) lparenOffset = 0; |
| |
| iterator = ((EditorEx)editor).getHighlighter().createIterator(lparenOffset); |
| boolean matched = BraceMatchingUtil.matchBrace(fileText, fileType, iterator, true, true); |
| |
| if (!matched) { |
| String text; |
| if (lparenChar == '(') { |
| text = ")"; |
| } |
| else if (lparenChar == '[') { |
| text = "]"; |
| } |
| else if (lparenChar == '<') { |
| text = ">"; |
| } |
| else if (lparenChar == '{') { |
| text = "}"; |
| } |
| else { |
| throw new AssertionError("Unknown char "+lparenChar); |
| } |
| editor.getDocument().insertString(offset, text); |
| } |
| } |
| |
| public static boolean handleRParen(@NotNull Editor editor, @NotNull FileType fileType, char charTyped) { |
| if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return false; |
| |
| int offset = editor.getCaretModel().getOffset(); |
| |
| if (offset == editor.getDocument().getTextLength()) return false; |
| |
| HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); |
| if (iterator.atEnd()) return false; |
| |
| if (iterator.getEnd() - iterator.getStart() != 1 || editor.getDocument().getCharsSequence().charAt(iterator.getStart()) != charTyped) { |
| return false; |
| } |
| |
| BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); |
| CharSequence text = editor.getDocument().getCharsSequence(); |
| if (!braceMatcher.isRBraceToken(iterator, text, fileType)) { |
| return false; |
| } |
| |
| IElementType tokenType = iterator.getTokenType(); |
| |
| iterator.retreat(); |
| |
| IElementType lparenTokenType = braceMatcher.getOppositeBraceTokenType(tokenType); |
| int lparenthOffset = BraceMatchingUtil.findLeftmostLParen( |
| iterator, |
| lparenTokenType, |
| text, |
| fileType |
| ); |
| |
| if (lparenthOffset < 0) { |
| if (braceMatcher instanceof NontrivialBraceMatcher) { |
| for(IElementType t:((NontrivialBraceMatcher)braceMatcher).getOppositeBraceTokenTypes(tokenType)) { |
| if (t == lparenTokenType) continue; |
| lparenthOffset = BraceMatchingUtil.findLeftmostLParen( |
| iterator, |
| t, text, |
| fileType |
| ); |
| if (lparenthOffset >= 0) break; |
| } |
| } |
| if (lparenthOffset < 0) return false; |
| } |
| |
| iterator = ((EditorEx) editor).getHighlighter().createIterator(lparenthOffset); |
| boolean matched = BraceMatchingUtil.matchBrace(text, fileType, iterator, true, true); |
| |
| if (!matched) return false; |
| |
| EditorModificationUtil.moveCaretRelatively(editor, 1); |
| return true; |
| } |
| |
| private boolean handleQuote(@NotNull Editor editor, char quote, @NotNull DataContext dataContext, @NotNull PsiFile file) { |
| if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE) return false; |
| final QuoteHandler quoteHandler = getQuoteHandler(file, editor); |
| if (quoteHandler == null) return false; |
| |
| int offset = editor.getCaretModel().getOffset(); |
| |
| final Document document = editor.getDocument(); |
| CharSequence chars = document.getCharsSequence(); |
| int length = document.getTextLength(); |
| if (isTypingEscapeQuote(editor, quoteHandler, offset)) return false; |
| |
| if (offset < length && chars.charAt(offset) == quote){ |
| if (isClosingQuote(editor, quoteHandler, offset)){ |
| EditorModificationUtil.moveCaretRelatively(editor, 1); |
| return true; |
| } |
| } |
| |
| HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); |
| |
| if (!iterator.atEnd()){ |
| IElementType tokenType = iterator.getTokenType(); |
| if (quoteHandler instanceof JavaLikeQuoteHandler) { |
| try { |
| if (!((JavaLikeQuoteHandler)quoteHandler).isAppropriateElementTypeForLiteral(tokenType)) return false; |
| } |
| catch (AbstractMethodError incompatiblePluginErrorThatDoesNotInterestUs) { |
| // ignore |
| } |
| } |
| } |
| |
| type(editor, quote); |
| offset = editor.getCaretModel().getOffset(); |
| |
| if (quoteHandler instanceof MultiCharQuoteHandler) { |
| CharSequence closingQuote = getClosingQuote(editor, (MultiCharQuoteHandler)quoteHandler, offset); |
| if (closingQuote != null && hasNonClosedLiterals(editor, quoteHandler, offset - 1)) { |
| if (offset == document.getTextLength() || |
| !Character.isUnicodeIdentifierPart(document.getCharsSequence().charAt(offset))) { //any better heuristic or an API? |
| document.insertString(offset, closingQuote); |
| return true; |
| } |
| } |
| } |
| |
| if (isOpeningQuote(editor, quoteHandler, offset - 1) && hasNonClosedLiterals(editor, quoteHandler, offset - 1)) { |
| if (offset == document.getTextLength() || |
| !Character.isUnicodeIdentifierPart(document.getCharsSequence().charAt(offset))) { //any better heuristic or an API? |
| document.insertString(offset, String.valueOf(quote)); |
| } |
| } |
| |
| return true; |
| } |
| |
| private static boolean isClosingQuote(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset) { |
| HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); |
| if (iterator.atEnd()){ |
| LOG.assertTrue(false); |
| return false; |
| } |
| |
| return quoteHandler.isClosingQuote(iterator,offset); |
| } |
| |
| @Nullable |
| private static CharSequence getClosingQuote(@NotNull Editor editor, @NotNull MultiCharQuoteHandler quoteHandler, int offset) { |
| HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); |
| if (iterator.atEnd()){ |
| LOG.assertTrue(false); |
| return null; |
| } |
| |
| return quoteHandler.getClosingQuote(iterator, offset); |
| } |
| |
| private static boolean isOpeningQuote(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset) { |
| HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); |
| if (iterator.atEnd()){ |
| LOG.assertTrue(false); |
| return false; |
| } |
| |
| return quoteHandler.isOpeningQuote(iterator, offset); |
| } |
| |
| private static boolean hasNonClosedLiterals(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset) { |
| HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); |
| if (iterator.atEnd()) { |
| LOG.assertTrue(false); |
| return false; |
| } |
| |
| return quoteHandler.hasNonClosedLiteral(editor, iterator, offset); |
| } |
| |
| private static boolean isTypingEscapeQuote(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset){ |
| if (offset == 0) return false; |
| CharSequence chars = editor.getDocument().getCharsSequence(); |
| int offset1 = CharArrayUtil.shiftBackward(chars, offset - 1, "\\"); |
| int slashCount = offset - 1 - offset1; |
| return slashCount % 2 != 0 && isInsideLiteral(editor, quoteHandler, offset); |
| } |
| |
| private static boolean isInsideLiteral(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset){ |
| if (offset == 0) return false; |
| |
| HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset - 1); |
| if (iterator.atEnd()){ |
| LOG.assertTrue(false); |
| return false; |
| } |
| |
| return quoteHandler.isInsideLiteral(iterator); |
| } |
| |
| private static void indentClosingBrace(@NotNull Project project, @NotNull Editor editor){ |
| indentBrace(project, editor, '}'); |
| } |
| |
| static void indentOpenedBrace(@NotNull Project project, @NotNull Editor editor){ |
| indentBrace(project, editor, '{'); |
| } |
| |
| private static void indentOpenedParenth(@NotNull Project project, @NotNull Editor editor){ |
| indentBrace(project, editor, '('); |
| } |
| |
| private static void indentClosingParenth(@NotNull Project project, @NotNull Editor editor){ |
| indentBrace(project, editor, ')'); |
| } |
| |
| private static void indentBrace(@NotNull final Project project, @NotNull final Editor editor, final char braceChar) { |
| final int offset = editor.getCaretModel().getOffset() - 1; |
| final Document document = editor.getDocument(); |
| CharSequence chars = document.getCharsSequence(); |
| if (offset < 0 || chars.charAt(offset) != braceChar) return; |
| |
| int spaceStart = CharArrayUtil.shiftBackward(chars, offset - 1, " \t"); |
| if (spaceStart < 0 || chars.charAt(spaceStart) == '\n' || chars.charAt(spaceStart) == '\r'){ |
| PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); |
| documentManager.commitDocument(document); |
| |
| final PsiFile file = documentManager.getPsiFile(document); |
| if (file == null || !file.isWritable()) return; |
| PsiElement element = file.findElementAt(offset); |
| if (element == null) return; |
| |
| EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); |
| HighlighterIterator iterator = highlighter.createIterator(offset); |
| |
| final FileType fileType = file.getFileType(); |
| BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); |
| boolean rBraceToken = braceMatcher.isRBraceToken(iterator, chars, fileType); |
| final boolean isBrace = braceMatcher.isLBraceToken(iterator, chars, fileType) || rBraceToken; |
| int lBraceOffset = -1; |
| |
| if (CodeInsightSettings.getInstance().REFORMAT_BLOCK_ON_RBRACE && |
| rBraceToken && |
| braceMatcher.isStructuralBrace(iterator, chars, fileType) && offset > 0) { |
| lBraceOffset = BraceMatchingUtil.findLeftLParen( |
| highlighter.createIterator(offset - 1), |
| braceMatcher.getOppositeBraceTokenType(iterator.getTokenType()), |
| editor.getDocument().getCharsSequence(), |
| fileType |
| ); |
| } |
| if (element.getNode() != null && isBrace) { |
| final int finalLBraceOffset = lBraceOffset; |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run(){ |
| try{ |
| int newOffset; |
| if (finalLBraceOffset != -1) { |
| RangeMarker marker = document.createRangeMarker(offset, offset + 1); |
| CodeStyleManager.getInstance(project).reformatRange(file, finalLBraceOffset, offset, true); |
| newOffset = marker.getStartOffset(); |
| marker.dispose(); |
| } else { |
| newOffset = CodeStyleManager.getInstance(project).adjustLineIndent(file, offset); |
| } |
| |
| editor.getCaretModel().moveToOffset(newOffset + 1); |
| editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); |
| editor.getSelectionModel().removeSelection(); |
| } |
| catch(IncorrectOperationException e){ |
| LOG.error(e); |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| } |
| |