blob: 524a2b2f286ae79cbfb88a78b02e49f3e3ce91a8 [file] [log] [blame]
/*
* 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;
}
}