/* * Copyright 2000-2012 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.enter; import com.intellij.lang.Language; import com.intellij.lang.LanguageFormatting; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorModificationUtil; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. * * @author oleg * @date 11/17/10 */ public class BaseIndentEnterHandler extends EnterHandlerDelegateAdapter { private final Language myLanguage; private final TokenSet myIndentTokens; private final IElementType myLineCommentType; private final String myLineCommentPrefix; private final TokenSet myWhitespaceTokens; private final boolean myWorksWithFormatter; public BaseIndentEnterHandler( final Language language, final TokenSet indentTokens, final IElementType lineCommentType, final String lineCommentPrefix, final TokenSet whitespaceTokens) { this(language, indentTokens, lineCommentType, lineCommentPrefix, whitespaceTokens, false); } public BaseIndentEnterHandler( final Language language, final TokenSet indentTokens, final IElementType lineCommentType, final String lineCommentPrefix, final TokenSet whitespaceTokens, final boolean worksWithFormatter) { myLanguage = language; myIndentTokens = indentTokens; myLineCommentType = lineCommentType; myLineCommentPrefix = lineCommentPrefix; myWhitespaceTokens = whitespaceTokens; myWorksWithFormatter = worksWithFormatter; } protected Result shouldSkipWithResult(@NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final DataContext dataContext) { final Project project = CommonDataKeys.PROJECT.getData(dataContext); if (project == null) { return Result.Continue; } if (!file.getViewProvider().getLanguages().contains(myLanguage)) { return Result.Continue; } if (editor.isViewer()) { return Result.Continue; } final Document document = editor.getDocument(); if (!document.isWritable()) { return Result.Continue; } PsiDocumentManager.getInstance(project).commitDocument(document); int caret = editor.getCaretModel().getOffset(); if (caret == 0) { return Result.DefaultSkipIndent; } if (caret <= 0) { return Result.Continue; } return null; } @Override public Result preprocessEnter( @NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Ref<Integer> caretOffset, @NotNull final Ref<Integer> caretAdvance, @NotNull final DataContext dataContext, final EditorActionHandler originalHandler) { Result res = shouldSkipWithResult(file, editor, dataContext); if (res != null) { return res; } final Document document = editor.getDocument(); int caret = editor.getCaretModel().getOffset(); final int lineNumber = document.getLineNumber(caret); final int lineStartOffset = document.getLineStartOffset(lineNumber); final int previousLineStartOffset = lineNumber > 0 ? document.getLineStartOffset(lineNumber - 1) : lineStartOffset; final EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); final HighlighterIterator iterator = highlighter.createIterator(caret - 1); final IElementType type = getNonWhitespaceElementType(iterator, lineStartOffset, previousLineStartOffset); final CharSequence editorCharSequence = document.getCharsSequence(); final CharSequence lineIndent = editorCharSequence.subSequence(lineStartOffset, EditorActionUtil.findFirstNonSpaceOffsetOnTheLine(document, lineNumber)); // Enter in line comment if (type == myLineCommentType) { final String restString = editorCharSequence.subSequence(caret, document.getLineEndOffset(lineNumber)).toString(); if (!StringUtil.isEmptyOrSpaces(restString)) { final String linePrefix = lineIndent + myLineCommentPrefix; EditorModificationUtil.insertStringAtCaret(editor, "\n" + linePrefix); editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(lineNumber + 1, linePrefix.length())); return Result.Stop; } else if (iterator.getStart() < lineStartOffset) { EditorModificationUtil.insertStringAtCaret(editor, "\n" + lineIndent); return Result.Stop; } } if (!myWorksWithFormatter && LanguageFormatting.INSTANCE.forLanguage(myLanguage) != null) { return Result.Continue; } else { if (myIndentTokens.contains(type)) { final String newIndent = getNewIndent(file, document, lineIndent); EditorModificationUtil.insertStringAtCaret(editor, "\n" + newIndent); return Result.Stop; } EditorModificationUtil.insertStringAtCaret(editor, "\n" + lineIndent); editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(lineNumber + 1, calcLogicalLength(editor, lineIndent))); return Result.Stop; } } protected String getNewIndent( @NotNull final PsiFile file, @NotNull final Document document, @NotNull final CharSequence oldIndent) { CharSequence nonEmptyIndent = oldIndent; final CharSequence editorCharSequence = document.getCharsSequence(); final int nLines = document.getLineCount(); for (int line = 0; line < nLines && nonEmptyIndent.length() == 0; ++line) { final int lineStart = document.getLineStartOffset(line); final int indentEnd = EditorActionUtil.findFirstNonSpaceOffsetOnTheLine(document, line); if (lineStart < indentEnd) { nonEmptyIndent = editorCharSequence.subSequence(lineStart, indentEnd); } } final boolean usesSpacesForIndentation = nonEmptyIndent.length() > 0 && nonEmptyIndent.charAt(nonEmptyIndent.length() - 1) == ' '; final boolean firstIndent = nonEmptyIndent.length() == 0; final CodeStyleSettings currentSettings = CodeStyleSettingsManager.getSettings(file.getProject()); final CommonCodeStyleSettings.IndentOptions indentOptions = currentSettings.getIndentOptions(file.getFileType()); if (firstIndent && indentOptions.USE_TAB_CHARACTER || !firstIndent && !usesSpacesForIndentation) { int nTabsToIndent = indentOptions.INDENT_SIZE / indentOptions.TAB_SIZE; if (indentOptions.INDENT_SIZE % indentOptions.TAB_SIZE != 0) { ++nTabsToIndent; } return oldIndent + StringUtil.repeatSymbol('\t', nTabsToIndent); } return oldIndent + StringUtil.repeatSymbol(' ', indentOptions.INDENT_SIZE); } private static int calcLogicalLength(Editor editor, CharSequence lineIndent) { int result = 0; for (int i = 0; i < lineIndent.length(); i++) { if (lineIndent.charAt(i) == '\t') { result += EditorUtil.getTabSize(editor); } else { result++; } } return result; } @Nullable protected IElementType getNonWhitespaceElementType(final HighlighterIterator iterator, int currentLineStartOffset, final int prevLineStartOffset) { while (!iterator.atEnd() && iterator.getEnd() >= currentLineStartOffset && iterator.getStart() >= prevLineStartOffset) { final IElementType tokenType = iterator.getTokenType(); if (!myWhitespaceTokens.contains(tokenType)) { return tokenType; } iterator.retreat(); } return null; } }