2 * Copyright 2000-2013 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.editorActions.enter;
19 import com.intellij.codeInsight.CodeInsightSettings;
20 import com.intellij.codeInsight.highlighting.BraceMatcher;
21 import com.intellij.codeInsight.highlighting.BraceMatchingUtil;
22 import com.intellij.lang.ASTNode;
23 import com.intellij.lang.Language;
24 import com.intellij.openapi.actionSystem.DataContext;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
29 import com.intellij.openapi.editor.ex.EditorEx;
30 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
31 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
32 import com.intellij.openapi.fileTypes.FileType;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Ref;
35 import com.intellij.openapi.util.TextRange;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.psi.PsiDocumentManager;
38 import com.intellij.psi.PsiElement;
39 import com.intellij.psi.PsiFile;
40 import com.intellij.psi.TokenType;
41 import com.intellij.psi.codeStyle.CodeStyleManager;
42 import com.intellij.psi.tree.IElementType;
43 import com.intellij.psi.util.PsiUtilCore;
44 import com.intellij.util.IncorrectOperationException;
45 import com.intellij.util.text.CharArrayUtil;
46 import org.jetbrains.annotations.NotNull;
48 public class EnterAfterUnmatchedBraceHandler extends EnterHandlerDelegateAdapter {
49 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler");
52 public Result preprocessEnter(@NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Ref<Integer> caretOffsetRef, @NotNull final Ref<Integer> caretAdvance,
53 @NotNull final DataContext dataContext, final EditorActionHandler originalHandler) {
54 Document document = editor.getDocument();
55 CharSequence text = document.getCharsSequence();
56 Project project = file.getProject();
57 int caretOffset = caretOffsetRef.get().intValue();
58 int unmatchedLBracesNumber = getUnmatchedLBracesNumberBefore(editor, caretOffset, file.getFileType());
59 if (!CodeInsightSettings.getInstance().INSERT_BRACE_ON_ENTER || unmatchedLBracesNumber <= 0) {
60 return Result.Continue;
63 int offset = CharArrayUtil.shiftForward(text, caretOffset, " \t");
64 if (offset < document.getTextLength()) {
65 char c = text.charAt(offset);
66 if (c != ')' && c != ']' && c != ';' && c != ',' && c != '%' && c != '<' && c != '?') {
67 offset = calculateOffsetToInsertClosingBrace(file, text, offset);
68 //offset = CharArrayUtil.shiftForwardUntil(text, caretOffset, "\n");
71 offset = Math.min(offset, document.getTextLength());
73 // We need to adjust indents of the text that will be moved, hence, need to insert preliminary line feed.
76 // } else {<caret> if (test2()) {
79 // We insert here '\n}' after 'foo();' and have the following:
81 // } else { if (test2()) {
85 // That is formatted incorrectly because line feed between 'else' and 'if' is not inserted yet (whole 'if' block is indent anchor
86 // to 'if' code block('{}')). So, we insert temporary line feed between 'if' and 'else', correct indent and remove that temporary
88 int bracesToInsert = 0;
90 for (int i = caretOffset - 1; unmatchedLBracesNumber > 0 && i >= 0 && bracesToInsert < unmatchedLBracesNumber; i--) {
91 char c = text.charAt(i);
97 case '{': bracesToInsert++; break;
101 bracesToInsert = Math.max(bracesToInsert, 1);
102 document.insertString(offset, "\n" + StringUtil.repeatSymbol('}', bracesToInsert));
103 document.insertString(caretOffset, "\n");
104 PsiDocumentManager.getInstance(project).commitDocument(document);
105 long stamp = document.getModificationStamp();
106 boolean closingBraceIndentAdjusted;
108 CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(caretOffset, offset + 2));
110 catch (IncorrectOperationException e) {
114 closingBraceIndentAdjusted = stamp != document.getModificationStamp();
115 document.deleteString(caretOffset, caretOffset + 1);
118 // There is a possible case that formatter was unable to adjust line indent for the closing brace (that is the case for plain text
119 // document for example). Hence, we're trying to do the manually.
120 if (!closingBraceIndentAdjusted) {
121 int line = document.getLineNumber(offset);
122 StringBuilder buffer = new StringBuilder();
123 int start = document.getLineStartOffset(line);
124 int end = document.getLineEndOffset(line);
125 for (int i = start; i < end; i++) {
126 char c = text.charAt(i);
127 if (c != ' ' && c != '\t') {
134 if (buffer.length() > 0) {
135 document.insertString(offset + 1, buffer);
139 return Result.DefaultForceIndent;
143 * Current handler inserts closing curly brace (right brace) if necessary. There is a possible case that it should be located
144 * more than one line forward.
149 * } else {<caret> if (test2()) {
154 * We want to get this after the processing:
163 * I.e. closing brace should be inserted two lines below current caret line. Hence, we need to calculate correct offset
164 * to use for brace inserting. This method is responsible for that.
166 * In essence it inspects PSI structure and finds PSE elements with the max length that starts at caret offset. End offset
167 * of that element is used as an insertion point.
169 * @param file target PSI file
170 * @param text text from the given file
171 * @param offset target offset where line feed will be inserted
172 * @return offset to use for inserting closing brace
174 protected int calculateOffsetToInsertClosingBrace(PsiFile file, CharSequence text, final int offset) {
175 PsiElement element = PsiUtilCore.getElementAtOffset(file, offset);
176 ASTNode node = element.getNode();
177 if (node != null && node.getElementType() == TokenType.WHITE_SPACE) {
178 return CharArrayUtil.shiftForwardUntil(text, offset, "\n");
180 for (PsiElement parent = element.getParent(); parent != null; parent = parent.getParent()) {
181 ASTNode parentNode = parent.getNode();
182 if (parentNode == null || parentNode.getStartOffset() != offset) {
187 if (element.getTextOffset() != offset) {
188 return CharArrayUtil.shiftForwardUntil(text, offset, "\n");
191 return element.getTextRange().getEndOffset();
195 public static boolean isAfterUnmatchedLBrace(Editor editor, int offset, FileType fileType) {
196 return getUnmatchedLBracesNumberBefore(editor, offset, fileType) > 0;
200 * Calculates number of unmatched left braces before the given offset.
202 * @param editor target editor
203 * @param offset target offset
204 * @param fileType target file type
205 * @return number of unmatched braces before the given offset;
206 * negative value if it's not possible to perform the calculation or if there are no unmatched left braces before
209 protected static int getUnmatchedLBracesNumberBefore(Editor editor, int offset, FileType fileType) {
213 CharSequence chars = editor.getDocument().getCharsSequence();
214 if (chars.charAt(offset - 1) != '{') {
218 EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter();
219 HighlighterIterator iterator = highlighter.createIterator(offset - 1);
220 BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator);
222 if (!braceMatcher.isLBraceToken(iterator, chars, fileType) || !braceMatcher.isStructuralBrace(iterator, chars, fileType)) {
226 Language language = iterator.getTokenType().getLanguage();
228 iterator = highlighter.createIterator(0);
229 int lBracesBeforeOffset = 0;
230 int lBracesAfterOffset = 0;
231 int rBracesBeforeOffset = 0;
232 int rBracesAfterOffset = 0;
233 for (; !iterator.atEnd(); iterator.advance()) {
234 IElementType tokenType = iterator.getTokenType();
235 if (!tokenType.getLanguage().equals(language) || !braceMatcher.isStructuralBrace(iterator, chars, fileType)) {
239 boolean beforeOffset = iterator.getStart() < offset;
241 if (braceMatcher.isLBraceToken(iterator, chars, fileType)) {
243 lBracesBeforeOffset++;
246 lBracesAfterOffset++;
249 else if (braceMatcher.isRBraceToken(iterator, chars, fileType)) {
251 rBracesBeforeOffset++;
254 rBracesAfterOffset++;
259 return lBracesBeforeOffset - rBracesBeforeOffset - (rBracesAfterOffset - lBracesAfterOffset);