Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / editorActions / TypedHandler.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.editorActions;
18
19 import com.intellij.codeInsight.AutoPopupController;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.CodeInsightUtilBase;
22 import com.intellij.codeInsight.completion.CompletionContributor;
23 import com.intellij.codeInsight.highlighting.BraceMatcher;
24 import com.intellij.codeInsight.highlighting.BraceMatchingUtil;
25 import com.intellij.codeInsight.highlighting.NontrivialBraceMatcher;
26 import com.intellij.codeInsight.template.impl.editorActions.TypedActionHandlerBase;
27 import com.intellij.injected.editor.DocumentWindow;
28 import com.intellij.lang.*;
29 import com.intellij.openapi.actionSystem.CommonDataKeys;
30 import com.intellij.openapi.actionSystem.DataContext;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.command.CommandProcessor;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.editor.*;
35 import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
36 import com.intellij.openapi.editor.ex.EditorEx;
37 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
38 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
39 import com.intellij.openapi.extensions.Extensions;
40 import com.intellij.openapi.fileEditor.FileDocumentManager;
41 import com.intellij.openapi.fileTypes.FileType;
42 import com.intellij.openapi.fileTypes.FileTypes;
43 import com.intellij.openapi.fileTypes.LanguageFileType;
44 import com.intellij.openapi.fileTypes.PlainTextLanguage;
45 import com.intellij.openapi.project.Project;
46 import com.intellij.psi.PsiDocumentManager;
47 import com.intellij.psi.PsiElement;
48 import com.intellij.psi.PsiFile;
49 import com.intellij.psi.codeStyle.CodeStyleManager;
50 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
51 import com.intellij.psi.tree.IElementType;
52 import com.intellij.psi.tree.TokenSet;
53 import com.intellij.psi.util.PsiUtilBase;
54 import com.intellij.util.IncorrectOperationException;
55 import com.intellij.util.containers.ContainerUtil;
56 import com.intellij.util.text.CharArrayUtil;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import java.util.HashMap;
61 import java.util.List;
62 import java.util.Map;
63
64 public class TypedHandler extends TypedActionHandlerBase {
65
66   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.TypedHandler");
67
68   private static final Map<FileType,QuoteHandler> quoteHandlers = new HashMap<FileType, QuoteHandler>();
69
70   private static final Map<Class<? extends Language>, QuoteHandler> ourBaseLanguageQuoteHandlers = new HashMap<Class<? extends Language>, QuoteHandler>();
71
72   public TypedHandler(TypedActionHandler originalHandler){
73     super(originalHandler);
74   }
75
76   @Nullable
77   public static QuoteHandler getQuoteHandler(@NotNull PsiFile file, @NotNull Editor editor) {
78     FileType fileType = getFileType(file, editor);
79     QuoteHandler quoteHandler = getQuoteHandlerForType(fileType);
80     if (quoteHandler == null) {
81       FileType fileFileType = file.getFileType();
82       if (fileFileType != fileType) {
83         quoteHandler = getQuoteHandlerForType(fileFileType);
84       }
85     }
86     if (quoteHandler == null) {
87       final Language baseLanguage = file.getViewProvider().getBaseLanguage();
88       for (Map.Entry<Class<? extends Language>, QuoteHandler> entry : ourBaseLanguageQuoteHandlers.entrySet()) {
89         if (entry.getKey().isInstance(baseLanguage)) {
90           return entry.getValue();
91         }
92       }
93       return LanguageQuoteHandling.INSTANCE.forLanguage(baseLanguage);
94     }
95     return quoteHandler;
96   }
97
98   private static FileType getFileType(@NotNull PsiFile file, @NotNull Editor editor) {
99     FileType fileType = file.getFileType();
100     Language language = PsiUtilBase.getLanguageInEditor(editor, file.getProject());
101     if (language != null && language != PlainTextLanguage.INSTANCE) {
102       LanguageFileType associatedFileType = language.getAssociatedFileType();
103       if (associatedFileType != null) fileType = associatedFileType;
104     }
105     return fileType;
106   }
107
108   public static void registerBaseLanguageQuoteHandler(@NotNull Class<? extends Language> languageClass, @NotNull QuoteHandler quoteHandler) {
109     ourBaseLanguageQuoteHandlers.put(languageClass, quoteHandler);
110   }
111
112   public static QuoteHandler getQuoteHandlerForType(@NotNull FileType fileType) {
113     if (!quoteHandlers.containsKey(fileType)) {
114       QuoteHandler handler = null;
115       final QuoteHandlerEP[] handlerEPs = Extensions.getExtensions(QuoteHandlerEP.EP_NAME);
116       for(QuoteHandlerEP ep: handlerEPs) {
117         if (ep.fileType.equals(fileType.getName())) {
118           handler = ep.getHandler();
119           break;
120         }
121       }
122       quoteHandlers.put(fileType, handler);
123     }
124     return quoteHandlers.get(fileType);
125   }
126
127   /** @see QuoteHandlerEP */
128   @Deprecated
129   public static void registerQuoteHandler(@NotNull FileType fileType, @NotNull QuoteHandler quoteHandler) {
130     quoteHandlers.put(fileType, quoteHandler);
131   }
132
133   @Override
134   public void execute(@NotNull final Editor originalEditor, final char charTyped, @NotNull final DataContext dataContext) {
135     final Project project = CommonDataKeys.PROJECT.getData(dataContext);
136     final PsiFile originalFile;
137
138     if (project == null || (originalFile = PsiUtilBase.getPsiFileInEditor(originalEditor, project)) == null) {
139       if (myOriginalHandler != null){
140         myOriginalHandler.execute(originalEditor, charTyped, dataContext);
141       }
142       return;
143     }
144
145     if (!CodeInsightUtilBase.prepareEditorForWrite(originalEditor)) return;
146     if (!FileDocumentManager.getInstance().requestWriting(originalEditor.getDocument(), project)) {
147        return;
148     }
149
150     final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
151     final Document originalDocument = originalEditor.getDocument();
152     originalEditor.getCaretModel().runForEachCaret(new CaretAction() {
153       @Override
154       public void perform(Caret caret) {
155         if (psiDocumentManager.isDocumentBlockedByPsi(originalDocument)) {
156           psiDocumentManager.doPostponedOperationsAndUnblockDocument(originalDocument); // to clean up after previous caret processing
157         }
158
159         Editor editor = injectedEditorIfCharTypedIsSignificant(charTyped, originalEditor, originalFile);
160         PsiFile file = editor == originalEditor ? originalFile : psiDocumentManager.getPsiFile(editor.getDocument());
161
162
163         final TypedHandlerDelegate[] delegates = Extensions.getExtensions(TypedHandlerDelegate.EP_NAME);
164
165         boolean handled = false;
166         for (TypedHandlerDelegate delegate : delegates) {
167           final TypedHandlerDelegate.Result result = delegate.checkAutoPopup(charTyped, project, editor, file);
168           handled = result == TypedHandlerDelegate.Result.STOP;
169           if (result != TypedHandlerDelegate.Result.CONTINUE) {
170             break;
171           }
172         }
173
174         if (!handled) {
175           autoPopupCompletion(editor, charTyped, project, file);
176           autoPopupParameterInfo(editor, charTyped, project, file);
177         }
178
179         if (!editor.isInsertMode()) {
180           type(originalEditor, charTyped);
181           return;
182         }
183
184         EditorModificationUtil.deleteSelectedText(editor);
185
186         FileType fileType = getFileType(file, editor);
187
188         for (TypedHandlerDelegate delegate : delegates) {
189           final TypedHandlerDelegate.Result result = delegate.beforeCharTyped(charTyped, project, editor, file, fileType);
190           if (result == TypedHandlerDelegate.Result.STOP) {
191             return;
192           }
193           if (result == TypedHandlerDelegate.Result.DEFAULT) {
194             break;
195           }
196         }
197
198         if (')' == charTyped || ']' == charTyped || '}' == charTyped) {
199           if (FileTypes.PLAIN_TEXT != fileType) {
200             if (handleRParen(editor, fileType, charTyped)) return;
201           }
202         }
203         else if ('"' == charTyped || '\'' == charTyped || '`' == charTyped/* || '/' == charTyped*/) {
204           if (handleQuote(editor, charTyped, file)) return;
205         }
206
207         long modificationStampBeforeTyping = editor.getDocument().getModificationStamp();
208         type(originalEditor, charTyped);
209         AutoHardWrapHandler.getInstance().wrapLineIfNecessary(editor, dataContext, modificationStampBeforeTyping);
210
211         if (('(' == charTyped || '[' == charTyped || '{' == charTyped) &&
212             CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
213             fileType != FileTypes.PLAIN_TEXT) {
214           handleAfterLParen(editor, fileType, charTyped);
215         }
216         else if ('}' == charTyped) {
217           indentClosingBrace(project, editor);
218         }
219         else if (')' == charTyped) {
220           indentClosingParenth(project, editor);
221         }
222
223         for (TypedHandlerDelegate delegate : delegates) {
224           final TypedHandlerDelegate.Result result = delegate.charTyped(charTyped, project, editor, file);
225           if (result == TypedHandlerDelegate.Result.STOP) {
226             return;
227           }
228           if (result == TypedHandlerDelegate.Result.DEFAULT) {
229             break;
230           }
231         }
232         if ('{' == charTyped) {
233           indentOpenedBrace(project, editor);
234         }
235         else if ('(' == charTyped) {
236           indentOpenedParenth(project, editor);
237         }
238       }
239     });
240   }
241
242   private static void type(Editor editor, char charTyped) {
243     CommandProcessor.getInstance().setCurrentCommandName(EditorBundle.message("typing.in.editor.command.name"));
244     EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, String.valueOf(charTyped), true);
245   }
246
247   private static void autoPopupParameterInfo(@NotNull Editor editor, char charTyped, @NotNull Project project, @NotNull PsiFile file) {
248     if ((charTyped == '(' || charTyped == ',') && !isInsideStringLiteral(editor, file)) {
249       AutoPopupController.getInstance(project).autoPopupParameterInfo(editor, null);
250     }
251   }
252
253   public static void autoPopupCompletion(@NotNull Editor editor, char charTyped, @NotNull Project project, @NotNull PsiFile file) {
254     if (charTyped == '.' || isAutoPopup(editor, file, charTyped)) {
255       AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, null);
256     }
257   }
258   
259   public static void commitDocumentIfCurrentCaretIsNotTheFirstOne(@NotNull Editor editor, @NotNull Project project) {
260     if (ContainerUtil.getFirstItem(editor.getCaretModel().getAllCarets()) != editor.getCaretModel().getCurrentCaret()) {
261       PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
262     }
263   }
264
265   private static boolean isAutoPopup(@NotNull Editor editor, @NotNull PsiFile file, char charTyped) {
266     final int offset = editor.getCaretModel().getOffset() - 1;
267     if (offset >= 0) {
268       final PsiElement element = file.findElementAt(offset);
269       if (element != null) {
270         final List<CompletionContributor> list = CompletionContributor.forLanguage(element.getLanguage());
271         for (CompletionContributor contributor : list) {
272           if (contributor.invokeAutoPopup(element, charTyped)) return true;
273         }
274       }
275     }
276     return false;
277   }
278
279   private static boolean isInsideStringLiteral(@NotNull Editor editor, @NotNull PsiFile file) {
280     int offset = editor.getCaretModel().getOffset();
281     PsiElement element = file.findElementAt(offset);
282     if (element == null) return false;
283     final ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage());
284     if (definition != null) {
285       final TokenSet stringLiteralElements = definition.getStringLiteralElements();
286       final ASTNode node = element.getNode();
287       if (node == null) return false;
288       final IElementType elementType = node.getElementType();
289       if (stringLiteralElements.contains(elementType)) {
290         return true;
291       }
292       PsiElement parent = element.getParent();
293       if (parent != null) {
294         ASTNode parentNode = parent.getNode();
295         if (parentNode != null && stringLiteralElements.contains(parentNode.getElementType())) {
296           return true;
297         }
298       }
299     }
300     return false;
301   }
302
303   @NotNull
304   public static Editor injectedEditorIfCharTypedIsSignificant(final char charTyped, @NotNull Editor editor, @NotNull PsiFile oldFile) {
305     int offset = editor.getCaretModel().getOffset();
306     // even for uncommitted document try to retrieve injected fragment that has been there recently
307     // we are assuming here that when user is (even furiously) typing, injected language would not change
308     // and thus we can use its lexer to insert closing braces etc
309     for (DocumentWindow documentWindow : InjectedLanguageUtil.getCachedInjectedDocuments(oldFile)) {
310       if (documentWindow.isValid() && documentWindow.containsRange(offset, offset)) {
311         PsiFile injectedFile = PsiDocumentManager.getInstance(oldFile.getProject()).getPsiFile(documentWindow);
312         final Editor injectedEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injectedFile);
313         // IDEA-52375 fix: last quote sign should be handled by outer language quote handler
314         final CharSequence charsSequence = editor.getDocument().getCharsSequence();
315         if (injectedEditor.getCaretModel().getOffset() == injectedEditor.getDocument().getTextLength() &&
316             offset < charsSequence.length() && charTyped == charsSequence.charAt(offset)) {
317           return editor;
318         }
319         return injectedEditor;
320       }
321     }
322
323     return editor;
324   }
325
326   private static void handleAfterLParen(@NotNull Editor editor, @NotNull FileType fileType, char lparenChar){
327     int offset = editor.getCaretModel().getOffset();
328     HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
329     boolean atEndOfDocument = offset == editor.getDocument().getTextLength();
330
331     if (!atEndOfDocument) iterator.retreat();
332     if (iterator.atEnd()) return;
333     BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator);
334     if (iterator.atEnd()) return;
335     IElementType braceTokenType = iterator.getTokenType();
336     final CharSequence fileText = editor.getDocument().getCharsSequence();
337     if (!braceMatcher.isLBraceToken(iterator, fileText, fileType)) return;
338
339     if (!iterator.atEnd()) {
340       iterator.advance();
341
342       if (!iterator.atEnd()) {
343         if (!BraceMatchingUtil.isPairedBracesAllowedBeforeTypeInFileType(braceTokenType, iterator.getTokenType(), fileType)) {
344           return;
345         }
346         if (BraceMatchingUtil.isLBraceToken(iterator, fileText, fileType)) {
347           return;
348         }
349       }
350
351       iterator.retreat();
352     }
353
354     int lparenOffset = BraceMatchingUtil.findLeftmostLParen(iterator, braceTokenType, fileText,fileType);
355     if (lparenOffset < 0) lparenOffset = 0;
356
357     iterator = ((EditorEx)editor).getHighlighter().createIterator(lparenOffset);
358     boolean matched = BraceMatchingUtil.matchBrace(fileText, fileType, iterator, true, true);
359
360     if (!matched) {
361       String text;
362       if (lparenChar == '(') {
363         text = ")";
364       }
365       else if (lparenChar == '[') {
366         text = "]";
367       }
368       else if (lparenChar == '<') {
369         text = ">";
370       }
371       else if (lparenChar == '{') {
372         text = "}";
373       }
374       else {
375         throw new AssertionError("Unknown char "+lparenChar);
376       }
377       editor.getDocument().insertString(offset, text);
378     }
379   }
380
381   public static boolean handleRParen(@NotNull Editor editor, @NotNull FileType fileType, char charTyped) {
382     if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return false;
383
384     int offset = editor.getCaretModel().getOffset();
385
386     if (offset == editor.getDocument().getTextLength()) return false;
387
388     HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
389     if (iterator.atEnd()) return false;
390
391     if (iterator.getEnd() - iterator.getStart() != 1 || editor.getDocument().getCharsSequence().charAt(iterator.getStart()) != charTyped) {
392       return false;
393     }
394
395     BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator);
396     CharSequence text = editor.getDocument().getCharsSequence();
397     if (!braceMatcher.isRBraceToken(iterator, text, fileType)) {
398       return false;
399     }
400
401     IElementType tokenType = iterator.getTokenType();
402
403     iterator.retreat();
404
405     IElementType lparenTokenType = braceMatcher.getOppositeBraceTokenType(tokenType);
406     int lparenthOffset = BraceMatchingUtil.findLeftmostLParen(
407       iterator,
408       lparenTokenType,
409       text,
410       fileType
411     );
412
413     if (lparenthOffset < 0) {
414       if (braceMatcher instanceof NontrivialBraceMatcher) {
415         for(IElementType t:((NontrivialBraceMatcher)braceMatcher).getOppositeBraceTokenTypes(tokenType)) {
416           if (t == lparenTokenType) continue;
417           lparenthOffset = BraceMatchingUtil.findLeftmostLParen(
418             iterator,
419             t, text,
420             fileType
421           );
422           if (lparenthOffset >= 0) break;
423         }
424       }
425       if (lparenthOffset < 0) return false;
426     }
427
428     iterator = ((EditorEx) editor).getHighlighter().createIterator(lparenthOffset);
429     boolean matched = BraceMatchingUtil.matchBrace(text, fileType, iterator, true, true);
430
431     if (!matched) return false;
432
433     EditorModificationUtil.moveCaretRelatively(editor, 1);
434     return true;
435   }
436
437   private static boolean handleQuote(@NotNull Editor editor, char quote, @NotNull PsiFile file) {
438     if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE) return false;
439     final QuoteHandler quoteHandler = getQuoteHandler(file, editor);
440     if (quoteHandler == null) return false;
441
442     int offset = editor.getCaretModel().getOffset();
443
444     final Document document = editor.getDocument();
445     CharSequence chars = document.getCharsSequence();
446     int length = document.getTextLength();
447     if (isTypingEscapeQuote(editor, quoteHandler, offset)) return false;
448
449     if (offset < length && chars.charAt(offset) == quote){
450       if (isClosingQuote(editor, quoteHandler, offset)){
451         EditorModificationUtil.moveCaretRelatively(editor, 1);
452         return true;
453       }
454     }
455
456     HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset);
457
458     if (!iterator.atEnd()){
459       IElementType tokenType = iterator.getTokenType();
460       if (quoteHandler instanceof JavaLikeQuoteHandler) {
461         try {
462           if (!((JavaLikeQuoteHandler)quoteHandler).isAppropriateElementTypeForLiteral(tokenType)) return false;
463         }
464         catch (AbstractMethodError incompatiblePluginErrorThatDoesNotInterestUs) {
465           // ignore
466         }
467       }
468     }
469
470     type(editor, quote);
471     offset = editor.getCaretModel().getOffset();
472
473     if (quoteHandler instanceof MultiCharQuoteHandler) {
474       CharSequence closingQuote = getClosingQuote(editor, (MultiCharQuoteHandler)quoteHandler, offset);
475       if (closingQuote != null && hasNonClosedLiterals(editor, quoteHandler, offset - 1)) {
476         if (offset == document.getTextLength() ||
477             !Character.isUnicodeIdentifierPart(document.getCharsSequence().charAt(offset))) { //any better heuristic or an API?
478           document.insertString(offset, closingQuote);
479           return true;
480         }
481       }
482     }
483
484     if (isOpeningQuote(editor, quoteHandler, offset - 1) && hasNonClosedLiterals(editor, quoteHandler, offset - 1)) {
485       if (offset == document.getTextLength() ||
486           !Character.isUnicodeIdentifierPart(document.getCharsSequence().charAt(offset))) { //any better heuristic or an API?
487         document.insertString(offset, String.valueOf(quote));
488       }
489     }
490
491     return true;
492   }
493
494   private static boolean isClosingQuote(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset) {
495     HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset);
496     if (iterator.atEnd()){
497       LOG.assertTrue(false);
498       return false;
499     }
500
501     return quoteHandler.isClosingQuote(iterator,offset);
502   }
503
504   @Nullable
505   private static CharSequence getClosingQuote(@NotNull Editor editor, @NotNull MultiCharQuoteHandler quoteHandler, int offset) {
506     HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset);
507     if (iterator.atEnd()){
508       LOG.assertTrue(false);
509       return null;
510     }
511
512     return quoteHandler.getClosingQuote(iterator, offset);
513   }
514
515   private static boolean isOpeningQuote(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset) {
516     HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset);
517     if (iterator.atEnd()){
518       LOG.assertTrue(false);
519       return false;
520     }
521
522     return quoteHandler.isOpeningQuote(iterator, offset);
523   }
524
525   private static boolean hasNonClosedLiterals(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset) {
526     HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
527     if (iterator.atEnd()) {
528       LOG.assertTrue(false);
529       return false;
530     }
531
532     return quoteHandler.hasNonClosedLiteral(editor, iterator, offset);
533   }
534
535   private static boolean isTypingEscapeQuote(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset){
536     if (offset == 0) return false;
537     CharSequence chars = editor.getDocument().getCharsSequence();
538     int offset1 = CharArrayUtil.shiftBackward(chars, offset - 1, "\\");
539     int slashCount = offset - 1 - offset1;
540     return slashCount % 2 != 0 && isInsideLiteral(editor, quoteHandler, offset);
541   }
542
543   private static boolean isInsideLiteral(@NotNull Editor editor, @NotNull QuoteHandler quoteHandler, int offset){
544     if (offset == 0) return false;
545
546     HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset - 1);
547     if (iterator.atEnd()){
548       LOG.assertTrue(false);
549       return false;
550     }
551
552     return quoteHandler.isInsideLiteral(iterator);
553   }
554
555   private static void indentClosingBrace(@NotNull Project project, @NotNull Editor editor){
556     indentBrace(project, editor, '}');
557   }
558
559   static void indentOpenedBrace(@NotNull Project project, @NotNull Editor editor){
560     indentBrace(project, editor, '{');
561   }
562
563   private static void indentOpenedParenth(@NotNull Project project, @NotNull Editor editor){
564     indentBrace(project, editor, '(');
565   }
566
567   private static void indentClosingParenth(@NotNull Project project, @NotNull Editor editor){
568     indentBrace(project, editor, ')');
569   }
570
571   private static void indentBrace(@NotNull final Project project, @NotNull final Editor editor, final char braceChar) {
572     final int offset = editor.getCaretModel().getOffset() - 1;
573     final Document document = editor.getDocument();
574     CharSequence chars = document.getCharsSequence();
575     if (offset < 0 || chars.charAt(offset) != braceChar) return;
576
577     int spaceStart = CharArrayUtil.shiftBackward(chars, offset - 1, " \t");
578     if (spaceStart < 0 || chars.charAt(spaceStart) == '\n' || chars.charAt(spaceStart) == '\r'){
579       PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
580       documentManager.commitDocument(document);
581
582       final PsiFile file = documentManager.getPsiFile(document);
583       if (file == null || !file.isWritable()) return;
584       PsiElement element = file.findElementAt(offset);
585       if (element == null) return;
586
587       EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter();
588       HighlighterIterator iterator = highlighter.createIterator(offset);
589
590       final FileType fileType = file.getFileType();
591       BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator);
592       boolean rBraceToken = braceMatcher.isRBraceToken(iterator, chars, fileType);
593       final boolean isBrace = braceMatcher.isLBraceToken(iterator, chars, fileType) || rBraceToken;
594       int lBraceOffset = -1;
595
596       if (CodeInsightSettings.getInstance().REFORMAT_BLOCK_ON_RBRACE &&
597           rBraceToken &&
598           braceMatcher.isStructuralBrace(iterator, chars, fileType) && offset > 0) {
599         lBraceOffset = BraceMatchingUtil.findLeftLParen(
600           highlighter.createIterator(offset - 1),
601           braceMatcher.getOppositeBraceTokenType(iterator.getTokenType()),
602           editor.getDocument().getCharsSequence(),
603           fileType
604         );
605       }
606       if (element.getNode() != null && isBrace) {
607         final int finalLBraceOffset = lBraceOffset;
608         ApplicationManager.getApplication().runWriteAction(new Runnable() {
609           @Override
610           public void run(){
611             try{
612               int newOffset;
613               if (finalLBraceOffset != -1) {
614                 RangeMarker marker = document.createRangeMarker(offset, offset + 1);
615                 CodeStyleManager.getInstance(project).reformatRange(file, finalLBraceOffset, offset, true);
616                 newOffset = marker.getStartOffset();
617                 marker.dispose();
618               } else {
619                 newOffset = CodeStyleManager.getInstance(project).adjustLineIndent(file, offset);
620               }
621
622               editor.getCaretModel().moveToOffset(newOffset + 1);
623               editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
624               editor.getSelectionModel().removeSelection();
625             }
626             catch(IncorrectOperationException e){
627               LOG.error(e);
628             }
629           }
630         });
631       }
632     }
633   }
634
635 }
636