[java-rd] IDEA-322563 Improve editing experience in Remote Dev for Java

- editor actions

GitOrigin-RevId: e774f42e72be12b613a9c300dd988589048bd483
This commit is contained in:
Mikhail Pyltsin
2023-09-13 09:09:01 +00:00
committed by intellij-monorepo-bot
parent e23e60a0eb
commit d2af2f9736
124 changed files with 4490 additions and 3699 deletions
@@ -0,0 +1,348 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.EditorModificationUtilEx;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public abstract class AbstractBasicJavaTypedHandler extends TypedHandlerDelegate {
private boolean myJavaLTTyped;
protected AbstractBasicJavaTypedHandler() {
}
protected abstract boolean isJavaFile(@NotNull PsiFile file);
protected abstract boolean isJspFile(@NotNull PsiFile file);
protected abstract void autoPopupMemberLookup(@NotNull Project project, @NotNull Editor editor);
protected abstract void autoPopupJavadocLookup(@NotNull final Project project, @NotNull final Editor editor);
protected abstract boolean isLanguageLevel5OrHigher(@NotNull PsiFile file);
@NotNull
protected abstract Result processWhileAndIfStatementBody(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file);
/**
* Automatically inserts parentheses if != or == was typed after a&b, a|b or a^b where a and b are numbers.
*
* @return true if the '=' char was processed
*/
public abstract boolean handleEquality(Project project, Editor editor, PsiFile file, int offsetBefore);
/**
* Automatically insert parentheses around the ?: when necessary.
*
* @return true if question mark was handled
*/
protected abstract boolean handleQuestionMark(Project project, Editor editor, PsiFile file, int offsetBefore);
protected abstract boolean handleAnnotationParameter(Project project, @NotNull Editor editor, @NotNull PsiFile file);
@NotNull
@Override
public Result beforeCharTyped(final char c,
@NotNull final Project project,
@NotNull final Editor editor,
@NotNull final PsiFile file,
@NotNull final FileType fileType) {
if (!isJavaFile(file)) return Result.CONTINUE;
if (c == '@') {
autoPopupJavadocLookup(project, editor);
}
else if (c == '#' || c == '.') {
autoPopupMemberLookup(project, editor);
}
int offsetBefore = editor.getCaretModel().getOffset();
//important to calculate before inserting charTyped
myJavaLTTyped = '<' == c &&
!isJspFile(file) &&
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
isLanguageLevel5OrHigher(file) &&
TypedHandlerUtil.isAfterClassLikeIdentifierOrDot(offsetBefore, editor, JavaTokenType.DOT, JavaTokenType.IDENTIFIER,
true);
if ('>' == c) {
if (!isJspFile(file) && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && isLanguageLevel5OrHigher(file)) {
if (TypedHandlerUtil.handleGenericGT(editor, JavaTokenType.LT, JavaTokenType.GT, JavaTypingTokenSets.INVALID_INSIDE_REFERENCE)) {
return Result.STOP;
}
}
}
if (c == '?') {
if (handleQuestionMark(project, editor, file, offsetBefore)) {
return Result.STOP;
}
}
if (c == '=') {
if (handleEquality(project, editor, file, offsetBefore)) {
return Result.STOP;
}
}
if (c == ';') {
if (handleSemicolon(project, editor, file, fileType)) return Result.STOP;
}
if (fileType instanceof JavaFileType && c == '{') {
int offset = editor.getCaretModel().getOffset();
if (offset == 0) {
return Result.CONTINUE;
}
HighlighterIterator iterator = editor.getHighlighter().createIterator(offset - 1);
while (!iterator.atEnd() && iterator.getTokenType() == TokenType.WHITE_SPACE) {
iterator.retreat();
}
if (iterator.atEnd() || iterator.getTokenType() == JavaTokenType.RBRACKET || iterator.getTokenType() == JavaTokenType.EQ) {
return Result.CONTINUE;
}
Document doc = editor.getDocument();
PsiDocumentManager.getInstance(project).commitDocument(doc);
final PsiElement leaf = file.findElementAt(offset);
if (BasicJavaAstTreeUtil.getParentOfType(leaf, BASIC_ARRAY_INITIALIZER_EXPRESSION, false,
BasicJavaTokenSet.orSet(BasicJavaTokenSet.create(BASIC_CODE_BLOCK), MEMBER_SET)) != null) {
return Result.CONTINUE;
}
PsiElement st = leaf != null ? leaf.getParent() : null;
PsiElement prev = offset > 1 ? file.findElementAt(offset - 1) : null;
if (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && isRparenth(leaf) &&
st != null &&
(BasicJavaAstTreeUtil.is(st.getNode(), BASIC_WHILE_STATEMENT) ||
BasicJavaAstTreeUtil.is(st.getNode(), BASIC_IF_STATEMENT)) &&
shouldInsertStatementBody(st, doc, prev)) {
return processWhileAndIfStatementBody(project, editor, file);
}
if (BasicJavaAstTreeUtil.getParentOfType(leaf, BASIC_CODE_BLOCK, false, MEMBER_SET) != null &&
!shouldInsertPairedBrace(leaf)) {
EditorModificationUtilEx.insertStringAtCaret(editor, "{");
TypedHandler.indentOpenedBrace(project, editor);
return Result.STOP; // use case: manually wrapping part of method's code in 'if', 'while', etc
}
}
return Result.CONTINUE;
}
private static boolean shouldInsertPairedBrace(@NotNull PsiElement leaf) {
PsiElement prevLeaf = PsiTreeUtil.prevVisibleLeaf(leaf);
// lambda
if (prevLeaf != null && prevLeaf.getNode().getElementType() == JavaTokenType.ARROW) return true;
// anonymous class
BasicJavaTokenSet stopAt = BasicJavaTokenSet.orSet(MEMBER_SET, BasicJavaTokenSet.create(BASIC_CODE_BLOCK));
if (BasicJavaAstTreeUtil.getParentOfType(prevLeaf, BASIC_NEW_EXPRESSION, true, stopAt) != null) return true;
// local class
if (prevLeaf != null && prevLeaf.getParent() != null && BasicJavaAstTreeUtil.is(prevLeaf.getNode(), JavaTokenType.IDENTIFIER) &&
BasicJavaAstTreeUtil.is(prevLeaf.getParent().getNode(), CLASS_SET)) {
return true;
}
// local record
if (prevLeaf != null && prevLeaf.getParent() != null && prevLeaf.getNode().getElementType() == JavaTokenType.RPARENTH &&
BasicJavaAstTreeUtil.is(prevLeaf.getParent().getNode(), BASIC_RECORD_HEADER)) {
return true;
}
return false;
}
private static boolean shouldInsertStatementBody(@NotNull PsiElement statement, @NotNull Document doc, @Nullable PsiElement prev) {
ASTNode block;
ASTNode astNodeStatement = statement.getNode();
if (BasicJavaAstTreeUtil.is(astNodeStatement, BASIC_WHILE_STATEMENT)) {
block = BasicJavaAstTreeUtil.getBlock(astNodeStatement);
}
else {
block = BasicJavaAstTreeUtil.getThenBranch(astNodeStatement);
}
ASTNode condition = BasicJavaAstTreeUtil.findChildByType(astNodeStatement, EXPRESSION_SET);
ASTNode latestExpression = BasicJavaAstTreeUtil.getParentOfType(BasicJavaAstTreeUtil.toNode(prev), EXPRESSION_SET);
if (BasicJavaAstTreeUtil.is(latestExpression, BASIC_NEW_EXPRESSION) &&
BasicJavaAstTreeUtil.getAnonymousClass(latestExpression) == null) {
return false;
}
return !(BasicJavaAstTreeUtil.is(block, BASIC_BLOCK_STATEMENT)) &&
(block == null || startLine(doc, block) != startLine(doc, astNodeStatement) || condition == null);
}
private static boolean isRparenth(@Nullable PsiElement leaf) {
if (leaf == null) return false;
if (leaf.getNode().getElementType() == JavaTokenType.RPARENTH) return true;
PsiElement next = PsiTreeUtil.nextVisibleLeaf(leaf);
if (next == null) return false;
return next.getNode().getElementType() == JavaTokenType.RPARENTH;
}
private static int startLine(@NotNull Document doc, @NotNull ASTNode astNode) {
return doc.getLineNumber(astNode.getTextRange().getStartOffset());
}
@NotNull
@Override
public Result charTyped(final char c, @NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
if (!(isJavaFile(file))) return Result.CONTINUE;
if (myJavaLTTyped) {
myJavaLTTyped = false;
TypedHandlerUtil.handleAfterGenericLT(editor, JavaTokenType.LT, JavaTokenType.GT, JavaTypingTokenSets.INVALID_INSIDE_REFERENCE);
return Result.STOP;
}
else if (c == ':') {
if (autoIndentCase(editor, project, file)) {
return Result.STOP;
}
}
else if (c == ',' && handleAnnotationParameter(project, editor, file)) {
return Result.STOP;
}
else if (c == '.') {
if (handleDotTyped(project, editor, file)) {
return Result.STOP;
}
}
return Result.CONTINUE;
}
private static boolean handleDotTyped(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
int offset = editor.getCaretModel().getOffset() - 1;
if (offset >= 0) {
Document document = editor.getDocument();
int line = document.getLineNumber(offset);
int lineStart = document.getLineStartOffset(line);
if (StringUtil.isEmptyOrSpaces(document.getCharsSequence().subSequence(lineStart, offset))) {
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
CodeStyleManager.getInstance(project).adjustLineIndent(file, offset);
return true;
}
}
return false;
}
private static boolean handleSemicolon(@NotNull Project project,
@NotNull Editor editor,
@NotNull PsiFile file,
@NotNull FileType fileType) {
if (!(fileType instanceof JavaFileType)) return false;
int offset = editor.getCaretModel().getOffset();
if (offset == editor.getDocument().getTextLength()) return false;
if (moveSemicolonAtRParen(project, editor, file, offset)) return true;
char charAt = editor.getDocument().getCharsSequence().charAt(offset);
if (charAt != ';') return false;
HighlighterIterator hi = editor.getHighlighter().createIterator(offset);
if (hi.atEnd() || hi.getTokenType() != JavaTokenType.SEMICOLON) return false;
EditorModificationUtil.moveCaretRelatively(editor, 1);
return true;
}
private static boolean moveSemicolonAtRParen(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, int caretOffset) {
if (!Registry.is("editor.move.semicolon.after.paren")) {
return false;
}
ApplicationManager.getApplication().assertWriteAccessAllowed();
// Note, this feature may be rewritten using only lexer if needed.
// In that case accuracy will not be 100%, but good enough.
HighlighterIterator it = editor.getHighlighter().createIterator(caretOffset);
int afterLastParenOffset = -1;
while (!it.atEnd()) {
if (isAtLineEnd(it)) {
break;
}
else if (it.getTokenType() == JavaTokenType.RBRACE) {
break;
}
else if (it.getTokenType() == JavaTokenType.RPARENTH) {
afterLastParenOffset = it.getEnd();
}
else if (it.getTokenType() != TokenType.WHITE_SPACE) {
// Other tokens are not permitted
return false;
}
it.advance();
}
if (!it.atEnd() && afterLastParenOffset >= 0 && afterLastParenOffset >= caretOffset) {
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiElement curElement = file.findElementAt(caretOffset);
ASTNode curStmt = BasicJavaAstTreeUtil.getParentOfType(BasicJavaAstTreeUtil.toNode(curElement), STATEMENT_SET);
if (curStmt != null) {
if (BasicJavaAstTreeUtil.is(curStmt, BASIC_TRY_STATEMENT)) {
// try-with-resources can contain semicolons inside
return false;
}
if (BasicJavaAstTreeUtil.is(curStmt, BASIC_FOR_STATEMENT)) {
// for loop can have semicolons inside
return false;
}
// It may worth to check if the error element is about expecting semicolon
PsiElement curPsiElement = BasicJavaAstTreeUtil.toPsi(curStmt);
if (curPsiElement != null && PsiTreeUtil.getDeepestLast(curPsiElement) instanceof PsiErrorElement) {
int stmtEndOffset = curStmt.getTextRange().getEndOffset();
if (stmtEndOffset == afterLastParenOffset || stmtEndOffset == it.getStart()) {
editor.getDocument().insertString(stmtEndOffset, ";");
editor.getCaretModel().moveToOffset(stmtEndOffset + 1);
return true;
}
}
}
}
return false;
}
private static boolean isAtLineEnd(HighlighterIterator it) {
if (it.getTokenType() == TokenType.WHITE_SPACE) {
CharSequence tokenText = it.getDocument().getImmutableCharSequence().subSequence(it.getStart(), it.getEnd());
return CharArrayUtil.containLineBreaks(tokenText);
}
return false;
}
private static boolean autoIndentCase(Editor editor, Project project, PsiFile file) {
int offset = editor.getCaretModel().getOffset();
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiElement currElement = file.findElementAt(offset - 1);
if (currElement != null) {
PsiElement parent = currElement.getParent();
if (BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(parent), BASIC_SWITCH_LABEL_STATEMENT)) {
CodeStyleManager.getInstance(project).adjustLineIndent(file, parent.getTextOffset());
return true;
}
}
return false;
}
}
@@ -0,0 +1,207 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.*;
import static com.intellij.util.text.CharArrayUtil.containsOnlyWhiteSpaces;
/**
* Advises typing in javadoc if necessary.
*/
public abstract class AbstractBasicJavadocTypedHandler extends TypedHandlerDelegate {
private static final char START_TAG_SYMBOL = '<';
private static final char CLOSE_TAG_SYMBOL = '>';
private static final char SLASH = '/';
private static final String COMMENT_PREFIX = "!--";
protected AbstractBasicJavadocTypedHandler() {
}
@NotNull
@Override
public Result charTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (isJavaFile(file)) {
if (!insertClosingTagIfNecessary(c, project, editor, file)) {
adjustStartTagIndent(c, editor, file);
}
}
return Result.CONTINUE;
}
public abstract boolean isJavaFile(@Nullable PsiFile file);
private static void adjustStartTagIndent(char c, @NotNull Editor editor, @NotNull PsiFile file) {
if (c == '@') {
final int offset = editor.getCaretModel().getOffset();
PsiElement currElement = file.findElementAt(offset);
if (currElement instanceof PsiWhiteSpace) {
PsiElement prev = currElement.getPrevSibling();
if (prev != null && prev.getNode().getElementType() == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
editor.getDocument().replaceString(currElement.getTextRange().getStartOffset(), offset - 1, " ");
}
}
}
}
/**
* Checks if it's necessary to insert closing tag on typed character.
*
* @param c typed symbol
* @param project current project
* @param editor current editor
* @param file current file
* @return {@code true} if closing tag is inserted; {@code false} otherwise
*/
private boolean insertClosingTagIfNecessary(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (c != CLOSE_TAG_SYMBOL || !CodeInsightSettings.getInstance().JAVADOC_GENERATE_CLOSING_TAG) {
return false;
}
PsiDocumentManager.getInstance(project).commitAllDocuments();
if (!isAppropriatePlace(editor, file)) {
return false;
}
// Inspect symbols to the left of the current caret position, insert closing tag only if valid tag is just typed
// (e.g. don't insert anything on single '>' symbol typing).
int offset = editor.getCaretModel().getOffset();
Document document = editor.getDocument();
String tagName = getTagName(document.getText(), offset);
if (tagName == null || isSingleHtmlTag(tagName) || tagName.startsWith(COMMENT_PREFIX)) {
return false;
}
document.insertString(offset, String.valueOf(START_TAG_SYMBOL) + SLASH + tagName + CLOSE_TAG_SYMBOL);
return true;
}
public abstract boolean isSingleHtmlTag(@NotNull String tagName);
/**
* Tries to derive start tag name assuming that given offset points to position just after {@code '>'} symbol.
* <p/>
* Is expected to return {@code null} when offset is not located just after start tag, e.g. the following situations:
* <pre>
* <ul>
* <li>standalone {@code '>'} symbol (surrounded by white spaces);</li>
* <li>after end tag {@code <mytag><mytag>[caret]};</li>
* <li>after empty element tag {@code <p/>[caret]};</li>
* </ul>
* </pre>
*
* @param text target text
* @param afterTagOffset offset that points after
* @return tag name if the one is parsed; {@code null} otherwise
*/
@Nullable
public static String getTagName(@NotNull CharSequence text, int afterTagOffset) {
if (afterTagOffset > text.length()) {
return null;
}
int endOffset = afterTagOffset - 1;
// Check empty element like <p/>
if (endOffset > 0 && text.charAt(endOffset - 1) == SLASH) {
return null;
}
for (int i = endOffset - 1; i >= 0; i--) {
char c = text.charAt(i);
switch (c) {
case '\n' -> {
return null;
}
case CLOSE_TAG_SYMBOL -> {
return null;
}
case START_TAG_SYMBOL -> {
if (text.charAt(i + 1) == SLASH) {
// Handle situation like <tag></tag>[offset].
return null;
}
return text.subSequence(i + 1, endOffset).toString();
}
// There is a possible case that opening tag has attributes, e.g. <a href='bla-bla-bla'>[offset]. We want to extract
// only tag name then.
case ' ', '\t' -> endOffset = i;
}
}
return null;
}
private static boolean isAppropriatePlace(Editor editor, PsiFile file) {
FileViewProvider provider = file.getViewProvider();
int offset = editor.getCaretModel().getOffset();
final PsiElement elementAtCaret;
if (offset < editor.getDocument().getTextLength()) {
elementAtCaret = provider.findElementAt(offset);
}
else {
elementAtCaret = provider.findElementAt(editor.getDocument().getTextLength() - 1);
}
PsiElement element = elementAtCaret;
while (element instanceof PsiWhiteSpace || element != null && containsOnlyWhiteSpaces(element.getText())) {
element = element.getPrevSibling();
}
if (element == null) {
return false;
}
ASTNode astNode = BasicJavaAstTreeUtil.toNode(element);
if (BasicJavaAstTreeUtil.is(astNode, DOC_PARAMETER_REF)) {
astNode = astNode.getTreeParent();
}
if (BasicJavaAstTreeUtil.is(astNode, DOC_TAG, DOC_SNIPPET_TAG, DOC_INLINE_TAG) &&
"param".equals(BasicJavaAstTreeUtil.getTagName(astNode)) &&
isTypeParamBracketClosedAfterParamTag(astNode, offset)) {
return false;
}
// The contents of inline tags is not HTML, so the paired tag completion isn't appropriate there.
if (BasicJavaAstTreeUtil.is(astNode, DOC_INLINE_TAG, DOC_SNIPPET_TAG) ||
BasicJavaAstTreeUtil.getParentOfType(astNode, BasicJavaTokenSet.create(DOC_INLINE_TAG, DOC_SNIPPET_TAG)) != null) {
return false;
}
ASTNode node = element.getNode();
return node != null
&& (JavaDocTokenType.ALL_JAVADOC_TOKENS.contains(node.getElementType())
|| ALL_JAVADOC_ELEMENTS.contains(node.getElementType()));
}
private static boolean isTypeParamBracketClosedAfterParamTag(ASTNode tag, int bracketOffset) {
ASTNode paramToDocument = getDocumentingParameter(tag);
if (paramToDocument == null) return false;
TextRange paramRange = paramToDocument.getTextRange();
return paramRange.getEndOffset() == bracketOffset;
}
@Nullable
private static ASTNode getDocumentingParameter(@NotNull ASTNode tag) {
for (ASTNode element = tag.getFirstChildNode(); element != null; element = element.getTreeNext()) {
if (BasicJavaAstTreeUtil.is(element, DOC_PARAMETER_REF)) {
return element;
}
}
return null;
}
}
@@ -0,0 +1,163 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions;
import com.intellij.application.options.CodeStyle;
import com.intellij.lang.Language;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtilEx;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtilBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.util.List;
public class AutoFormatTypedHandler extends TypedHandlerDelegate {
private static boolean myIsEnabledInTests;
private static final char[] NO_SPACE_AFTER = {
'+', '-', '*', '/', '%', '&', '^', '|', '<', '>', '!', '=', ' '
};
private static final List<IElementType> COMPLEX_ASSIGNMENTS = List.of(JavaTokenType.PLUSEQ, JavaTokenType.MINUSEQ,
JavaTokenType.ASTERISKEQ, JavaTokenType.DIVEQ,
JavaTokenType.PERCEQ,
JavaTokenType.ANDEQ, JavaTokenType.XOREQ, JavaTokenType.OREQ,
JavaTokenType.LTLTEQ, JavaTokenType.GTGTEQ);
private static boolean isEnabled(Editor editor) {
boolean isEnabled = myIsEnabledInTests && ApplicationManager.getApplication().isUnitTestMode()
|| Registry.is("editor.reformat.on.typing");
if (!isEnabled) {
return false;
}
Project project = editor.getProject();
Language language = null;
if (project != null) {
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file != null) {
language = file.getLanguage();
}
}
return language == JavaLanguage.INSTANCE;
}
@TestOnly
public static void setEnabledInTests(boolean value) {
myIsEnabledInTests = value;
}
@Override
public @NotNull Result beforeCharTyped(char c,
@NotNull Project project,
@NotNull Editor editor,
@NotNull PsiFile file,
@NotNull FileType fileType) {
if (!isEnabled(editor)) {
return Result.CONTINUE;
}
if (isInsertSpaceAtCaret(editor, c, project)) {
EditorModificationUtilEx.insertStringAtCaret(editor, " ");
}
return Result.CONTINUE;
}
private static boolean isInsertSpaceAtCaret(@NotNull Editor editor, char charTyped, @NotNull Project project) {
if (!isSpaceAroundAssignment(editor, project)) {
return false;
}
int caretOffset = editor.getCaretModel().getOffset();
CharSequence text = editor.getDocument().getImmutableCharSequence();
HighlighterIterator lexerIterator = createLexerIterator(editor, caretOffset);
if (lexerIterator == null || lexerIterator.getTokenType() == JavaTokenType.STRING_LITERAL) {
return false;
}
boolean insertBeforeEq = charTyped == '=' && isInsertSpaceBeforeEq(caretOffset, text);
boolean insertAfterEq = caretOffset > 0 && caretOffset - 1 < text.length() && text.charAt(caretOffset - 1) == '='
&& isAssignmentOperator(lexerIterator) && isInsertSpaceAfterEq(charTyped);
return (insertBeforeEq || insertAfterEq);
}
private static boolean isAssignmentOperator(HighlighterIterator iterator) {
IElementType type = iterator.getTokenType();
if (type == TokenType.WHITE_SPACE) {
iterator.retreat();
type = iterator.getTokenType();
}
if (COMPLEX_ASSIGNMENTS.contains(type)) {
return true;
}
if (type == JavaTokenType.EQ) {
iterator.retreat();
type = iterator.getTokenType();
if (type == JavaTokenType.GT) {
iterator.retreat();
type = iterator.getTokenType();
if (type == JavaTokenType.GT) {
return true;
}
}
else if (type == TokenType.WHITE_SPACE || type == JavaTokenType.IDENTIFIER) {
return true;
}
}
return false;
}
private static boolean isInsertSpaceAfterEq(char charTyped) {
return charTyped != '=' && charTyped != ' ';
}
private static HighlighterIterator createLexerIterator(Editor editor, int offset) {
if (editor.getDocument().getTextLength() == 0) return null;
return editor.getHighlighter().createIterator(offset);
}
private static boolean isInsertSpaceBeforeEq(int caretOffset, CharSequence text) {
if (caretOffset == 0) return false;
char charBefore = text.charAt(caretOffset - 1);
for (char c : NO_SPACE_AFTER) {
if (c == charBefore) {
return false;
}
}
return true;
}
private static boolean isSpaceAroundAssignment(Editor editor, Project project) {
PsiFile file = project == null ? null : PsiUtilBase.getPsiFileInEditor(editor, project);
if (file != null) {
Language language = file.getLanguage();
CodeStyleSettings settings = CodeStyle.getSettings(editor);
CommonCodeStyleSettings common = settings.getCommonSettings(language);
return common.SPACE_AROUND_ASSIGNMENT_OPERATORS;
}
return false;
}
}
@@ -0,0 +1,43 @@
// Copyright 2000-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.definition.AbstractBasicJavaDefinitionService;
import com.intellij.openapi.editor.Editor;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.AbstractBasicJavaFile;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaBackspaceHandler extends BackspaceHandlerDelegate {
private boolean myToDeleteGt;
@Override
public void beforeCharDeleted(char c, @NotNull PsiFile file, @NotNull Editor editor) {
myToDeleteGt = c == '<' &&
isHigherThan50r(file) &&
TypedHandlerUtil.isAfterClassLikeIdentifierOrDot(editor.getCaretModel().getOffset() - 1,
editor, JavaTokenType.DOT, JavaTokenType.IDENTIFIER, true);
}
private boolean isHigherThan50r(@Nullable PsiFile file){
return file instanceof AbstractBasicJavaFile &&
AbstractBasicJavaDefinitionService.getJavaDefinitionService().getLanguageLevel(file).isAtLeast(LanguageLevel.JDK_1_5);
}
@Override
public boolean charDeleted(final char c, @NotNull final PsiFile file, @NotNull final Editor editor) {
if (c == '<' && myToDeleteGt) {
int offset = editor.getCaretModel().getOffset();
final CharSequence chars = editor.getDocument().getCharsSequence();
if (editor.getDocument().getTextLength() <= offset) return false; //virtual space after end of file
char c1 = chars.charAt(offset);
if (c1 != '>') return true;
TypedHandlerUtil.handleGenericLTDeletion(editor, offset, JavaTokenType.LT, JavaTokenType.GT, JavaTypingTokenSets.INVALID_INSIDE_REFERENCE);
return true;
}
return false;
}
}
@@ -0,0 +1,45 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler;
import com.intellij.core.JavaPsiBundle;
import com.intellij.lang.ASTNode;
import com.intellij.psi.AbstractBasicJavaFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_EXPRESSION_LIST_STATEMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.EXPRESSION_SET;
public class JavaEnterAfterUnmatchedBraceHandler extends EnterAfterUnmatchedBraceHandler {
protected JavaEnterAfterUnmatchedBraceHandler() {
}
@Override
public boolean isApplicable(@NotNull PsiFile file, int caretOffset) {
return file instanceof AbstractBasicJavaFile;
}
@Override
protected int calculateOffsetToInsertClosingBraceInsideElement(PsiElement element) {
if (element instanceof PsiErrorElement &&
((PsiErrorElement)element).getErrorDescription().equals(JavaPsiBundle.message("else.without.if"))) {
return element.getTextRange().getStartOffset();
}
ASTNode node = BasicJavaAstTreeUtil.toNode(element);
if (BasicJavaAstTreeUtil.is(node, BASIC_EXPRESSION_LIST_STATEMENT)) {
final ASTNode list = BasicJavaAstTreeUtil.getExpressionList(node);
if (list != null) {
final ASTNode firstExpression = BasicJavaAstTreeUtil.findChildByType(list, EXPRESSION_SET);
if (firstExpression != null) {
return firstExpression.getTextRange().getEndOffset();
}
}
}
return super.calculateOffsetToInsertClosingBraceInsideElement(element);
}
}
@@ -0,0 +1,96 @@
// Copyright 2000-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.editorActions.enter.EnterInStringLiteralHandler;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
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.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_LITERAL_EXPRESSION;
public class JavaEnterInTextBlockHandler extends EnterInStringLiteralHandler {
@Override
public Result preprocessEnter(@NotNull PsiFile file,
@NotNull Editor editor,
@NotNull Ref<Integer> caretOffsetRef,
@NotNull Ref<Integer> caretAdvanceRef,
@NotNull DataContext dataContext,
EditorActionHandler originalHandler) {
int offset = editor.getCaretModel().getOffset();
PsiElement textBlock = getTextBlockAt(file, offset);
if (textBlock == null) return Result.Continue;
int textBlockOffset = textBlock.getTextOffset();
String text = textBlock.getText();
int offsetInTextBlock = offset - textBlockOffset;
boolean isAtFirstLine = !text.substring(0, offsetInTextBlock).contains("\n");
if (!isAtFirstLine) return Result.Continue;
Document document = editor.getDocument();
Project project = textBlock.getProject();
int secondLineStart = text.indexOf('\n');
if (secondLineStart == -1) {
document.insertString(offset, "\n");
PsiDocumentManager.getInstance(project).commitDocument(document);
CodeStyleManager.getInstance(project).reformat(textBlock);
text = textBlock.getText();
int indent = getIndent(text, offsetInTextBlock + 1);
if (indent == -1) return Result.Continue;
editor.getCaretModel().moveToOffset(offset + 1 + indent);
}
else {
int indent = getIndent(text, secondLineStart + 1);
if (indent == -1) return Result.Continue;
String newLine = '\n' + StringUtil.repeatSymbol(' ', indent);
document.insertString(offset, newLine);
PsiDocumentManager.getInstance(project).commitDocument(document);
editor.getCaretModel().moveToOffset(offset + newLine.length());
}
return Result.Stop;
}
@Contract("null, _ -> null")
private static PsiElement getTextBlockAt(PsiFile file, int offset) {
if (!isJavaFile(file)) return null;
PsiElement token = file.findElementAt(offset);
if (token == null || token.getNode() == null || !BasicJavaAstTreeUtil.is(token.getNode(), JavaTokenType.TEXT_BLOCK_LITERAL)) return null;
PsiElement parent = token.getParent();
if (!BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(parent), BASIC_LITERAL_EXPRESSION)) {
return null;
}
return parent;
}
private static boolean isJavaFile(@Nullable PsiFile file) {
return file != null && file.getLanguage() == JavaLanguage.INSTANCE;
}
private static int getIndent(@NotNull String text, int start) {
int indent = 0;
for (int i = start; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\n') {
indent = 0;
continue;
}
if (Character.isWhitespace(c)) {
indent++;
continue;
}
return indent;
}
return -1;
}
}
@@ -0,0 +1,132 @@
/*
* Copyright 2000-2016 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.application.options.CodeStyle;
import com.intellij.formatting.Indent;
import com.intellij.lang.Language;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.TokenType;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition;
import com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import static com.intellij.formatting.Indent.Type.CONTINUATION;
import static com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider.JavaLikeElement.*;
public class JavaLineIndentProvider extends JavaLikeLangLineIndentProvider {
private final static HashMap<IElementType, SemanticEditorPosition.SyntaxElement> SYNTAX_MAP = new HashMap<>();
static {
SYNTAX_MAP.put(TokenType.WHITE_SPACE, Whitespace);
SYNTAX_MAP.put(JavaTokenType.SEMICOLON, Semicolon);
SYNTAX_MAP.put(JavaTokenType.LBRACE, BlockOpeningBrace);
SYNTAX_MAP.put(JavaTokenType.RBRACE, BlockClosingBrace);
SYNTAX_MAP.put(JavaTokenType.LBRACKET, ArrayOpeningBracket);
SYNTAX_MAP.put(JavaTokenType.RBRACKET, ArrayClosingBracket);
SYNTAX_MAP.put(JavaTokenType.RPARENTH, RightParenthesis);
SYNTAX_MAP.put(JavaTokenType.LPARENTH, LeftParenthesis);
SYNTAX_MAP.put(JavaTokenType.COLON, Colon);
SYNTAX_MAP.put(JavaTokenType.CASE_KEYWORD, SwitchCase);
SYNTAX_MAP.put(JavaTokenType.DEFAULT_KEYWORD, SwitchDefault);
SYNTAX_MAP.put(JavaTokenType.IF_KEYWORD, IfKeyword);
SYNTAX_MAP.put(JavaTokenType.WHILE_KEYWORD, IfKeyword);
SYNTAX_MAP.put(JavaTokenType.ELSE_KEYWORD, ElseKeyword);
SYNTAX_MAP.put(JavaTokenType.FOR_KEYWORD, ForKeyword);
SYNTAX_MAP.put(JavaTokenType.DO_KEYWORD, DoKeyword);
SYNTAX_MAP.put(JavaTokenType.C_STYLE_COMMENT, BlockComment);
SYNTAX_MAP.put(JavaDocTokenType.DOC_COMMENT_START, DocBlockStart);
SYNTAX_MAP.put(JavaDocTokenType.DOC_COMMENT_END, DocBlockEnd);
SYNTAX_MAP.put(JavaTokenType.COMMA, Comma);
SYNTAX_MAP.put(JavaTokenType.END_OF_LINE_COMMENT, LineComment);
SYNTAX_MAP.put(JavaTokenType.TRY_KEYWORD, TryKeyword);
}
@Nullable
@Override
protected SemanticEditorPosition.SyntaxElement mapType(@NotNull IElementType tokenType) {
return SYNTAX_MAP.get(tokenType);
}
@Override
public boolean isSuitableForLanguage(@NotNull Language language) {
return language.isKindOf(JavaLanguage.INSTANCE);
}
@Nullable
@Override
protected Indent getIndentInBlock(@NotNull Project project,
@Nullable Language language,
@NotNull SemanticEditorPosition blockStartPosition) {
SemanticEditorPosition beforeStart = blockStartPosition.before().beforeOptional(Whitespace);
if (beforeStart.isAt(JavaTokenType.EQ) ||
beforeStart.isAt(JavaTokenType.RBRACKET) ||
beforeStart.isAt(JavaTokenType.LPARENTH)
) {
// For arrays like int x = {<caret>0, 1, 2}
return getDefaultIndentFromType(CONTINUATION);
}
else if (beforeStart.isAt(JavaTokenType.IDENTIFIER)) {
moveBeforeExtendsImplementsAndIdentifier(beforeStart);
if (beforeStart.isAt(JavaTokenType.CLASS_KEYWORD) && doNotIndentClassMembers(beforeStart)) {
return Indent.getNoneIndent();
}
}
return super.getIndentInBlock(project, language, blockStartPosition);
}
private static void moveBeforeExtendsImplementsAndIdentifier(@NotNull SemanticEditorPosition position) {
while (position.isAt(JavaTokenType.IDENTIFIER) || position.isAtAnyOf(Whitespace, Comma) ||
position.isAt(JavaTokenType.EXTENDS_KEYWORD) || position.isAt(JavaTokenType.IMPLEMENTS_KEYWORD)) {
position.moveBefore();
}
}
private static boolean doNotIndentClassMembers(@NotNull SemanticEditorPosition position) {
Editor editor = position.getEditor();
CommonCodeStyleSettings javaSettings = CodeStyle.getSettings(editor).getCommonSettings(JavaLanguage.INSTANCE);
return javaSettings.DO_NOT_INDENT_TOP_LEVEL_CLASS_MEMBERS;
}
@Override
protected boolean isInsideForLikeConstruction(SemanticEditorPosition position) {
return position.isAfterOnSameLine(ForKeyword, TryKeyword);
}
@Override
protected boolean isInArray(@NotNull Editor editor, int offset) {
SemanticEditorPosition position = getPosition(editor, offset);
position.moveBefore();
if (position.isAt(JavaTokenType.LBRACE)) {
if (position.before().beforeOptional(Whitespace).isAt(JavaTokenType.RBRACKET)) return true;
}
return super.isInArray(editor, offset);
}
@Override
protected boolean isIndentProvider(@NotNull SemanticEditorPosition position, boolean ignoreLabels) {
return !(position.afterOptionalMix(Whitespace, BlockComment).after().isAt(Colon)
&& position.isAt(JavaTokenType.IDENTIFIER));
}
}
@@ -0,0 +1,146 @@
// Copyright 2000-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.definition.AbstractBasicJavaDefinitionService;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_OR_WHITESPACE_BIT_SET;
import static com.intellij.psi.impl.source.BasicElementTypes.TEXT_LITERALS;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_LITERAL_EXPRESSION;
import static com.intellij.psi.impl.source.BasicJavaElementType.REFERENCE_EXPRESSION_SET;
public class JavaQuoteHandler extends SimpleTokenSetQuoteHandler implements JavaLikeQuoteHandler, MultiCharQuoteHandler {
private final BasicJavaTokenSet myConcatenableStrings = BasicJavaTokenSet.create(JavaTokenType.STRING_LITERAL);
private final BasicJavaTokenSet myAppropriateElementTypeForLiteral = BasicJavaTokenSet.orSet(
BasicJavaTokenSet.create(JavaDocTokenType.ALL_JAVADOC_TOKENS),
JAVA_COMMENT_OR_WHITESPACE_BIT_SET, TEXT_LITERALS,
BasicJavaTokenSet.create(JavaTokenType.SEMICOLON, JavaTokenType.COMMA, JavaTokenType.RPARENTH, JavaTokenType.RBRACKET,
JavaTokenType.RBRACE));
public JavaQuoteHandler() {
super(BasicJavaTokenSet.orSet(TEXT_LITERALS, BasicJavaTokenSet.create(JavaDocTokenType.DOC_TAG_VALUE_QUOTE)).toTokenSet());
}
@Override
public boolean isOpeningQuote(HighlighterIterator iterator, int offset) {
boolean openingQuote = super.isOpeningQuote(iterator, offset);
if (openingQuote) {
// check escape next
if (!iterator.atEnd()) {
iterator.retreat();
if (!iterator.atEnd() && StringEscapesTokenTypes.STRING_LITERAL_ESCAPES.contains(iterator.getTokenType())) {
openingQuote = false;
}
iterator.advance();
}
}
return openingQuote;
}
@Override
public boolean isClosingQuote(HighlighterIterator iterator, int offset) {
if (iterator.getTokenType() == JavaTokenType.TEXT_BLOCK_LITERAL) {
int start = iterator.getStart(), end = iterator.getEnd();
return end - start >= 5 && offset >= end - 3;
}
boolean closingQuote = super.isClosingQuote(iterator, offset);
if (closingQuote) {
// check escape next
if (!iterator.atEnd()) {
iterator.advance();
if (!iterator.atEnd() && StringEscapesTokenTypes.STRING_LITERAL_ESCAPES.contains(iterator.getTokenType())) {
closingQuote = false;
}
iterator.retreat();
}
}
return closingQuote;
}
@NotNull
@Override
public TokenSet getConcatenatableStringTokenTypes() {
return myConcatenableStrings.toTokenSet();
}
@Override
public String getStringConcatenationOperatorRepresentation() {
return "+";
}
@Override
public TokenSet getStringTokenTypes() {
return myLiteralTokenSet;
}
@Override
public boolean isAppropriateElementTypeForLiteral(@NotNull IElementType tokenType) {
return myAppropriateElementTypeForLiteral.contains(tokenType);
}
@Override
public boolean needParenthesesAroundConcatenation(PsiElement element) {
// example code: "some string".length() must become ("some" + " string").length()
return element != null && element.getParent() != null && element.getParent().getParent() != null &&
BasicJavaAstTreeUtil.is(element.getParent().getNode(), BASIC_LITERAL_EXPRESSION) &&
BasicJavaAstTreeUtil.is(element.getParent().getParent().getNode(), REFERENCE_EXPRESSION_SET);
}
@Nullable
@Override
public CharSequence getClosingQuote(@NotNull HighlighterIterator iterator, int offset) {
return iterator.getTokenType() == JavaTokenType.TEXT_BLOCK_LITERAL && offset == iterator.getStart() + 3 ? "\"\"\"" : null;
}
@Override
public boolean hasNonClosedLiteral(Editor editor, HighlighterIterator iterator, int offset) {
if (iterator.getTokenType() == JavaTokenType.TEXT_BLOCK_LITERAL) {
Document document = editor.getDocument();
Project project = editor.getProject();
PsiFile file = project == null ? null : PsiDocumentManager.getInstance(project).getPsiFile(document);
if (file == null || !testBlocksIsAvailable(file)) return false;
String text = document.getText();
boolean hasOpenQuotes = StringUtil.equals(text.substring(iterator.getStart(), offset + 1), "\"\"\"");
if (hasOpenQuotes) {
boolean hasCloseQuotes = StringUtil.contains(text.substring(offset + 1, iterator.getEnd()), "\"\"\"");
if (!hasCloseQuotes) return true;
// check if parser interpreted next text block start quotes as end quotes for the current one
int nTextBlockQuotes = StringUtil.getOccurrenceCount(text.substring(iterator.getEnd()), "\"\"\"");
return nTextBlockQuotes % 2 != 0;
}
}
return super.hasNonClosedLiteral(editor, iterator, offset);
}
private static boolean testBlocksIsAvailable(@NotNull PsiFile file){
return AbstractBasicJavaDefinitionService.getJavaDefinitionService()
.getLanguageLevel(file).isAtLeast(LanguageLevel.JDK_15);
}
@Override
public void insertClosingQuote(@NotNull Editor editor, int offset, @NotNull PsiFile file, @NotNull CharSequence closingQuote) {
editor.getDocument().insertString(offset, "\n\"\"\"");
Project project = file.getProject();
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiElement token = file.findElementAt(offset);
if (token == null) return;
PsiElement parent = token.getParent();
if (parent != null && BasicJavaAstTreeUtil.is(parent.getNode(), BASIC_LITERAL_EXPRESSION)) {
CodeStyleManager.getInstance(project).reformat(parent);
editor.getCaretModel().moveToOffset(parent.getTextRange().getEndOffset() - 3);
}
}
}
@@ -0,0 +1,26 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions;
import com.intellij.lang.java.parser.BasicExpressionParser;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.tree.TokenSet;
public final class JavaTypingTokenSets {
private JavaTypingTokenSets() {
}
static final TokenSet INVALID_INSIDE_REFERENCE = TokenSet.create(JavaTokenType.SEMICOLON, JavaTokenType.LBRACE, JavaTokenType.RBRACE);
public static final TokenSet UNWANTED_TOKEN_AT_QUESTION =
TokenSet.create(JavaTokenType.C_STYLE_COMMENT, JavaTokenType.END_OF_LINE_COMMENT, JavaTokenType.CHARACTER_LITERAL,
JavaTokenType.STRING_LITERAL, JavaTokenType.TEXT_BLOCK_LITERAL);
public static final TokenSet UNWANTED_TOKEN_BEFORE_QUESTION =
TokenSet.orSet(BasicExpressionParser.ASSIGNMENT_OPS, TokenSet.create(JavaTokenType.QUEST, JavaTokenType.COLON));
public static final TokenSet WANTED_TOKEN_BEFORE_QUESTION =
// Tokens that may appear before ?: in polyadic expression that may have non-boolean result
TokenSet.orSet(
TokenSet.create(JavaTokenType.OR, JavaTokenType.XOR, JavaTokenType.AND),
BasicExpressionParser.SHIFT_OPS, BasicExpressionParser.ADDITIVE_OPS, BasicExpressionParser.MULTIPLICATIVE_OPS);
}
@@ -0,0 +1,15 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions;
import com.intellij.openapi.editor.actions.WordBoundaryFilter;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
public class JavaWordBoundaryFilter extends WordBoundaryFilter {
@Override
public boolean isWordBoundary(@NotNull IElementType previousTokenType, @NotNull IElementType tokenType) {
if (previousTokenType == JavaTokenType.GT && tokenType == JavaTokenType.EQ) return false;
return super.isWordBoundary(previousTokenType, tokenType);
}
}
@@ -0,0 +1,75 @@
// Copyright 2000-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavadocLineStartHandler extends EditorActionHandler.ForEachCaret {
private static final String WHITESPACE = " \t";
private final EditorActionHandler myOriginalHandler;
private final boolean myWithSelection;
public JavadocLineStartHandler(@NotNull EditorActionHandler originalHandler) {
this(originalHandler, false);
}
public JavadocLineStartHandler(@NotNull EditorActionHandler originalHandler,
boolean withSelection) {
myOriginalHandler = originalHandler;
myWithSelection = withSelection;
}
@Override
protected void doExecute(@NotNull Editor editor, @NotNull Caret caret, DataContext dataContext) {
Project project = editor.getProject();
if (project != null && EditorSettingsExternalizable.getInstance().isSmartHome()) {
Document document = editor.getDocument();
CharSequence text = document.getImmutableCharSequence();
int lineStartOffset = document.getLineStartOffset(caret.getLogicalPosition().line);
int nonWsStartOffset = CharArrayUtil.shiftForward(text, lineStartOffset, WHITESPACE);
if (CharArrayUtil.regionMatches(text, nonWsStartOffset, "/**") || CharArrayUtil.regionMatches(text, nonWsStartOffset, "*")) {
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
PsiFile file = psiDocumentManager.getPsiFile(document);
if (file != null && isJavaFile(file)) {
psiDocumentManager.commitDocument(document);
PsiElement startElement = file.findElementAt(nonWsStartOffset);
if (startElement == null || startElement.getNode() == null) {
return;
}
IElementType type = startElement.getNode().getElementType();
if (type == JavaDocTokenType.DOC_COMMENT_START || type == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
int targetOffset = CharArrayUtil.shiftForward(text, startElement.getTextRange().getEndOffset(), WHITESPACE);
if (caret.getOffset() == targetOffset) targetOffset = lineStartOffset;
int selectionStartOffset = caret.getLeadSelectionOffset();
caret.moveToOffset(targetOffset);
if (myWithSelection) {
caret.setSelection(selectionStartOffset, caret.getVisualPosition(), caret.getOffset());
}
else {
caret.removeSelection();
}
EditorModificationUtil.scrollToCaret(editor);
return;
}
}
}
}
myOriginalHandler.execute(editor, caret, dataContext);
}
private boolean isJavaFile(@Nullable PsiFile file){
return file instanceof AbstractBasicJavaFile;
}
}
@@ -0,0 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
public class JavadocLineStartWithSelectionHandler extends JavadocLineStartHandler {
public JavadocLineStartWithSelectionHandler(EditorActionHandler originalHandler) {
super(originalHandler, true);
}
}
@@ -0,0 +1,22 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
public interface ASTNodeEnterProcessor extends EnterProcessor {
boolean doEnter(@NotNull Editor editor, @NotNull ASTNode psiElement, boolean isModified);
@Override
default boolean doEnter(Editor editor, PsiElement psiElement, boolean isModified) {
ASTNode node = BasicJavaAstTreeUtil.toNode(psiElement);
if (node == null) {
return false;
}
return doEnter(editor, node, isModified);
}
}
@@ -0,0 +1,398 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SyntaxTraverser;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_BIT_SET;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public abstract class AbstractBasicJavaSmartEnterProcessor extends SmartEnterProcessor {
private static final Logger LOG = Logger.getInstance(AbstractBasicJavaSmartEnterProcessor.class);
private final List<Fixer> ourFixers;
private final EnterProcessor[] ourEnterProcessors;
private final EnterProcessor[] ourAfterCompletionEnterProcessors;
protected int myFirstErrorOffset = Integer.MAX_VALUE;
protected boolean mySkipEnter;
private static final int MAX_ATTEMPTS = 20;
private static final Key<Long> SMART_ENTER_TIMESTAMP = Key.create("smartEnterOriginalTimestamp");
private final EnterProcessor myBreakerEnterProcessor;
protected void insertBraces(@NotNull Editor editor, int offset) {
Document document = editor.getDocument();
document.insertString(offset, "{");
insertCloseBrace(editor, offset + 1);
}
protected void insertCloseBrace(@NotNull Editor editor, int offset) {
Document document = editor.getDocument();
document.insertString(offset, "}");
}
protected void insertBracesWithNewLine(Editor editor, int offset) {
Document document = editor.getDocument();
document.insertString(offset, "{\n");
insertCloseBrace(editor, offset + 2);
}
private static class TooManyAttemptsException extends Exception {
}
private final AbstractBasicJavadocFixer myJavadocFixer;
protected AbstractBasicJavaSmartEnterProcessor(@NotNull List<Fixer> fixers,
EnterProcessor @NotNull [] enterProcessors,
EnterProcessor @NotNull [] afterCompletionEnterProcessors,
@NotNull AbstractBasicJavadocFixer thinJavadocFixer,
@NotNull EnterProcessor breakerEnterProcessor) {
myBreakerEnterProcessor = breakerEnterProcessor;
ourFixers = fixers;
ourEnterProcessors = enterProcessors;
ourAfterCompletionEnterProcessors = afterCompletionEnterProcessors;
myJavadocFixer = thinJavadocFixer;
}
@Override
public boolean process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile psiFile) {
return invokeProcessor(editor, psiFile, false);
}
@Override
public boolean processAfterCompletion(@NotNull Editor editor, @NotNull PsiFile psiFile) {
return invokeProcessor(editor, psiFile, true);
}
private boolean invokeProcessor(Editor editor, PsiFile psiFile, boolean afterCompletion) {
final Document document = editor.getDocument();
final CharSequence textForRollback = document.getImmutableCharSequence();
try {
editor.putUserData(SMART_ENTER_TIMESTAMP, editor.getDocument().getModificationStamp());
myFirstErrorOffset = Integer.MAX_VALUE;
mySkipEnter = false;
process(editor, psiFile, 0, afterCompletion);
}
catch (TooManyAttemptsException e) {
document.replaceString(0, document.getTextLength(), textForRollback);
}
catch (Exception e) {
e.printStackTrace();
}
finally {
editor.putUserData(SMART_ENTER_TIMESTAMP, null);
}
return true;
}
private void process(@NotNull final Editor editor, @NotNull final PsiFile file, final int attempt, boolean afterCompletion)
throws TooManyAttemptsException {
if (attempt > MAX_ATTEMPTS) throw new TooManyAttemptsException();
try {
commit(editor);
if (myFirstErrorOffset != Integer.MAX_VALUE) {
editor.getCaretModel().moveToOffset(myFirstErrorOffset);
}
myFirstErrorOffset = Integer.MAX_VALUE;
PsiElement atCaret = getStatementAtCaret(editor, file);
if (atCaret == null) {
if (myJavadocFixer.process(editor, file)) {
return;
}
if (!(myBreakerEnterProcessor).doEnter(editor, file, false)) {
plainEnter(editor);
}
return;
}
List<ASTNode> queue = new ArrayList<>();
ASTNode caretNode = atCaret.getNode();
collectAllElements(caretNode, queue, true);
queue.add(caretNode);
for (ASTNode astNode : queue) {
for (Fixer fixer : ourFixers) {
Document document = editor.getDocument();
int offset = myFirstErrorOffset;
long stamp = document.getModificationStamp();
fixer.apply(editor, this, astNode);
Project project = file.getProject();
if (document.getModificationStamp() != stamp || offset != myFirstErrorOffset) {
log(fixer, project);
}
if (LookupManager.getInstance(project).getActiveLookup() != null) {
return;
}
PsiElement psi = BasicJavaAstTreeUtil.toPsi(astNode);
if (isUncommited(project) || !(psi != null && psi.isValid())) {
moveCaretInsideBracesIfAny(editor, file);
process(editor, file, attempt + 1, afterCompletion);
return;
}
}
}
doEnter(atCaret, editor, afterCompletion);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
protected abstract void log(@NotNull Fixer fixer, @NotNull Project project);
@Override
public void reformat(PsiElement atCaretElement) throws IncorrectOperationException {
if (atCaretElement == null) {
return;
}
ASTNode atCaret = atCaretElement.getNode();
ASTNode parent = atCaret.getTreeParent();
if (BasicJavaAstTreeUtil.is(parent, BASIC_FOR_STATEMENT)) {
atCaret = parent;
}
if (BasicJavaAstTreeUtil.is(parent, BASIC_IF_STATEMENT) &&
atCaret == BasicJavaAstTreeUtil.getElseBranch(parent)) {
PsiFile file = atCaretElement.getContainingFile();
Document document = file.getViewProvider().getDocument();
if (document != null) {
TextRange elseIfRange = atCaret.getTextRange();
int lineStart = document.getLineStartOffset(document.getLineNumber(elseIfRange.getStartOffset()));
CodeStyleManager.getInstance(atCaretElement.getProject()).reformatText(file, lineStart, elseIfRange.getEndOffset());
return;
}
}
super.reformat(atCaretElement);
}
private void doEnter(PsiElement atCaret, Editor editor, boolean afterCompletion) throws IncorrectOperationException {
final PsiFile psiFile = atCaret.getContainingFile();
if (myFirstErrorOffset != Integer.MAX_VALUE) {
editor.getCaretModel().moveToOffset(myFirstErrorOffset);
reformat(editor, atCaret);
return;
}
final RangeMarker rangeMarker = createRangeMarker(atCaret);
reformat(editor, atCaret);
commit(editor);
if (!mySkipEnter) {
ASTNode atCaretNode = BasicJavaAstTreeUtil.findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(),
atCaret.getNode().getElementType());
for (EnterProcessor processor : afterCompletion ? ourAfterCompletionEnterProcessors : ourEnterProcessors) {
if (atCaretNode == null) {
// Can't restore element at caret after enter processor execution!
break;
}
if (processor.doEnter(editor, BasicJavaAstTreeUtil.toPsi(atCaretNode), isModified(editor))) {
rangeMarker.dispose();
return;
}
}
if (!isModified(editor) && !afterCompletion) {
plainEnter(editor);
}
else {
if (myFirstErrorOffset == Integer.MAX_VALUE) {
editor.getCaretModel().moveToOffset(rangeMarker.getEndOffset());
}
else {
editor.getCaretModel().moveToOffset(myFirstErrorOffset);
}
}
}
rangeMarker.dispose();
}
private static void collectAllElements(ASTNode atCaret, List<? super ASTNode> res, boolean recurse) {
res.add(0, atCaret);
if (doNotStepInto(atCaret)) {
if (!recurse) return;
recurse = false;
}
final List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(atCaret);
for (ASTNode child : children) {
if (BasicJavaAstTreeUtil.is(atCaret, STATEMENT_SET) &&
BasicJavaAstTreeUtil.is(child, STATEMENT_SET) &&
!(BasicJavaAstTreeUtil.is(atCaret, BASIC_FOR_STATEMENT)
&& child == (BasicJavaAstTreeUtil.getForInitialization(atCaret)))) {
continue;
}
collectAllElements(child, res, recurse);
}
}
private static boolean doNotStepInto(ASTNode element) {
return BasicJavaAstTreeUtil.is(element, CLASS_SET) ||
BasicJavaAstTreeUtil.is(element, BASIC_CODE_BLOCK) ||
BasicJavaAstTreeUtil.is(element, STATEMENT_SET) ||
BasicJavaAstTreeUtil.is(element, BASIC_METHOD);
}
@Override
@Nullable
protected PsiElement getStatementAtCaret(Editor editor, PsiFile psiFile) {
PsiElement atCaretElement = super.getStatementAtCaret(editor, psiFile);
if (atCaretElement == null) {
return null;
}
ASTNode atCaret = atCaretElement.getNode();
if (BasicJavaAstTreeUtil.isWhiteSpace(atCaret)) return null;
if (BasicJavaAstTreeUtil.is(atCaret, JavaTokenType.RBRACE)) {
atCaret = atCaret.getTreeParent();
boolean expressionEndingWithBrace = BasicJavaAstTreeUtil.is(atCaret, BASIC_ANONYMOUS_CLASS) ||
BasicJavaAstTreeUtil.is(atCaret, BASIC_ARRAY_INITIALIZER_EXPRESSION) ||
BasicJavaAstTreeUtil.is(atCaret, BASIC_CODE_BLOCK) && (
BasicJavaAstTreeUtil.is(atCaret.getTreeParent(), BASIC_LAMBDA_EXPRESSION) ||
BasicJavaAstTreeUtil.is(atCaret.getTreeParent(), BASIC_SWITCH_EXPRESSION));
if (!expressionEndingWithBrace) return null;
}
for (ASTNode each : SyntaxTraverser.astApi().parents(atCaret).skip(1)) {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(each);
if (BasicJavaAstTreeUtil.is(each, MEMBER_SET) ||
isImportStatementBase(psiElement) ||
BasicJavaAstTreeUtil.is(each, BASIC_PACKAGE_STATEMENT) ||
BasicJavaAstTreeUtil.is(each, BASIC_ANNOTATION) &&
psiElement != null &&
PsiTreeUtil.hasErrorElements(psiElement)) {
return each.getPsi();
}
if (BasicJavaAstTreeUtil.is(each, BASIC_CODE_BLOCK) ||
BasicJavaAstTreeUtil.is(each, JAVA_COMMENT_BIT_SET)) {
return null;
}
if (BasicJavaAstTreeUtil.is(each, STATEMENT_SET)) {
return BasicJavaAstTreeUtil.is(each.getTreeParent(), BASIC_FOR_STATEMENT) &&
!PsiTreeUtil.hasErrorElements(each.getPsi()) ? each.getPsi().getParent() : each.getPsi();
}
if (BasicJavaAstTreeUtil.is(each, BASIC_CONDITIONAL_EXPRESSION) &&
PsiUtilCore.hasErrorElementChild(each.getPsi())) {
return each.getPsi();
}
}
return null;
}
protected abstract boolean isImportStatementBase(PsiElement el);
protected void moveCaretInsideBracesIfAny(@NotNull final Editor editor, @NotNull final PsiFile file) throws IncorrectOperationException {
int caretOffset = editor.getCaretModel().getOffset();
final CharSequence chars = editor.getDocument().getCharsSequence();
if (CharArrayUtil.regionMatches(chars, caretOffset, "{}")) {
caretOffset += 2;
}
else if (CharArrayUtil.regionMatches(chars, caretOffset, "{\n}")) {
caretOffset += 3;
}
caretOffset = CharArrayUtil.shiftBackward(chars, caretOffset - 1, " \t") + 1;
if (CharArrayUtil.regionMatches(chars, caretOffset - "{}".length(), "{}") ||
CharArrayUtil.regionMatches(chars, caretOffset - "{\n}".length(), "{\n}")) {
commit(editor);
final CommonCodeStyleSettings settings = CodeStyle.getSettings(file).getCommonSettings(JavaLanguage.INSTANCE);
final boolean old = settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE;
settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = false;
PsiElement leaf = file.findElementAt(caretOffset - 1);
PsiElement elt = BasicJavaAstTreeUtil.toPsi(
BasicJavaAstTreeUtil.getParentOfType(BasicJavaAstTreeUtil.toNode(leaf), BASIC_CODE_BLOCK));
if (elt == null &&
leaf != null &&
leaf.getParent() != null &&
BasicJavaAstTreeUtil.is(leaf.getParent().getNode(), CLASS_SET)) {
elt = leaf.getParent();
}
reformatAndMove(editor, elt, caretOffset);
settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = old;
reformatBlockParentIfNeeded(editor, file);
}
}
protected void reformatAndMove(@NotNull Editor editor, @Nullable PsiElement elt, int caretOffset) {
reformat(elt);
editor.getCaretModel().moveToOffset(caretOffset - 1);
}
protected void reformat(@NotNull Editor editor, @Nullable PsiElement elt) {
reformat(elt);
}
private void reformatBlockParentIfNeeded(@NotNull Editor editor, @NotNull PsiFile file) {
commit(editor);
ASTNode block =
BasicJavaAstTreeUtil.findElementOfClassAtOffset(file, editor.getCaretModel().getOffset(), BASIC_CODE_BLOCK, false);
if (block != null &&
BasicJavaAstTreeUtil.is(block.getTreeParent(), BASIC_BLOCK_STATEMENT) &&
BasicJavaAstTreeUtil.is(block.getTreeParent().getTreeParent(), BASIC_FOR_STATEMENT)) {
reformat(block.getTreeParent().getTreeParent().getPsi());
}
}
public void registerUnresolvedError(int offset) {
if (myFirstErrorOffset > offset) {
myFirstErrorOffset = offset;
}
}
public void setSkipEnter(boolean skipEnter) {
mySkipEnter = skipEnter;
}
protected static void plainEnter(@NotNull final Editor editor) {
getEnterHandler().execute(editor, editor.getCaretModel().getCurrentCaret(), EditorUtil.getEditorDataContext(editor));
}
protected static EditorActionHandler getEnterHandler() {
return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE);
}
protected static boolean isModified(@NotNull final Editor editor) {
final Long timestamp = editor.getUserData(SMART_ENTER_TIMESTAMP);
return timestamp != null && editor.getDocument().getModificationStamp() != timestamp.longValue();
}
}
@@ -0,0 +1,113 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.javadoc.AbstractBasicJavadocHelper;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Serves as a facade for javadoc smart completion.
* <p/>
* Thread-safe.
*/
public abstract class AbstractBasicJavadocFixer {
private final AbstractBasicJavadocHelper myHelper;;
public AbstractBasicJavadocFixer(@NotNull AbstractBasicJavadocHelper helper) { myHelper = helper; }
/**
* Checks if caret of the given editor is located inside javadoc and tries to perform smart completion there in case of the positive
* answer.
*
* @param editor target editor
* @param psiFile PSI file for the document exposed via the given editor
* @return {@code true} if smart completion was performed; {@code false} otherwise
*/
public boolean process(@NotNull Editor editor, @NotNull PsiFile psiFile) {
// Check parameter description completion.
final CaretModel caretModel = editor.getCaretModel();
final Pair<AbstractBasicJavadocHelper.JavadocParameterInfo,List<AbstractBasicJavadocHelper.JavadocParameterInfo>> pair =
myHelper.parse(psiFile, editor, caretModel.getOffset());
if (pair.first == null) {
return false;
}
final AbstractBasicJavadocHelper.JavadocParameterInfo next = findNext(pair.second, pair.first);
if (next == null) {
final int line = pair.first.lastLine + 1;
final Document document = editor.getDocument();
if (line < document.getLineCount()) {
StringBuilder indent = new StringBuilder();
boolean insertIndent = true;
final CharSequence text = document.getCharsSequence();
for (int i = document.getLineStartOffset(line), max = document.getLineEndOffset(line); i < max; i++) {
final char c = text.charAt(i);
if (c == ' ' || c == '\t') {
indent.append(c);
continue;
}
else if (c == '*') {
indent.append("* ");
if (i < max - 1 && text.charAt(i + 1) != '/') {
insertIndent = false;
}
}
indent.append("\n");
break;
}
if (insertIndent) {
document.insertString(document.getLineStartOffset(line), indent);
}
}
moveCaretToTheLineEndIfPossible(editor, line);
return true;
}
if (next.parameterDescriptionStartPosition != null) {
myHelper.navigate(next.parameterDescriptionStartPosition, editor, psiFile.getProject());
}
else {
final LogicalPosition position = myHelper.calculateDescriptionStartPosition(psiFile, pair.second, next);
myHelper.navigate(position, editor, psiFile.getProject());
}
return true;
}
private static void moveCaretToTheLineEndIfPossible(@NotNull Editor editor, int line) {
final Document document = editor.getDocument();
final CaretModel caretModel = editor.getCaretModel();
int offset;
if (line >= document.getLineCount()) {
offset = document.getTextLength();
}
else {
offset = document.getLineEndOffset(line);
}
caretModel.moveToOffset(offset);
}
@Nullable
private static AbstractBasicJavadocHelper.JavadocParameterInfo findNext(@NotNull Collection<? extends AbstractBasicJavadocHelper.JavadocParameterInfo> data,
@NotNull AbstractBasicJavadocHelper.JavadocParameterInfo anchor)
{
boolean returnNow = false;
for (AbstractBasicJavadocHelper.JavadocParameterInfo info : data) {
if (returnNow) {
return info;
}
returnNow = info == anchor;
}
return null;
}
}
@@ -0,0 +1,173 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_OR_WHITESPACE_BIT_SET;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public abstract class AbstractBasicSemicolonFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (fixReturn(editor, psiElement)) return;
if (fixForUpdate(editor, astNode)) return;
fixAfterLastValidElement(editor, astNode);
}
protected abstract boolean fixReturn(@NotNull Editor editor, @Nullable PsiElement astNode);
protected abstract boolean getSpaceAfterSemicolon(@NotNull PsiElement psiElement);
private boolean fixForUpdate(@NotNull Editor editor, @Nullable ASTNode astNode) {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_FOR_STATEMENT))) {
return false;
}
ASTNode condition = BasicJavaAstTreeUtil.getForCondition(astNode);
if (BasicJavaAstTreeUtil.getForUpdate(astNode) != null || condition == null) {
return false;
}
TextRange range = condition.getTextRange();
Document document = editor.getDocument();
CharSequence text = document.getCharsSequence();
for (int i = range.getEndOffset() - 1, max = astNode.getTextRange().getEndOffset(); i < max; i++) {
if (text.charAt(i) == ';') {
return false;
}
}
String toInsert = ";";
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (psiElement != null && getSpaceAfterSemicolon(psiElement)) {
toInsert += " ";
}
document.insertString(range.getEndOffset(), toInsert);
return true;
}
private void fixAfterLastValidElement(@NotNull Editor editor, @Nullable ASTNode astNode) {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (astNode == null || psiElement == null) {
return;
}
if (
BasicJavaAstTreeUtil.is(astNode, BASIC_EXPRESSION_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_DECLARATION_STATEMENT) ||
isImportStatementBase(psiElement) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_DO_WHILE_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_RETURN_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_THROW_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_BREAK_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_CONTINUE_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_YIELD_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_ASSERT_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_PACKAGE_STATEMENT) ||
isStandaloneField(psiElement) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_METHOD) &&
BasicJavaAstTreeUtil.getCodeBlock(astNode) == null &&
!isMethodShouldHaveBody(psiElement) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_REQUIRES_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_OPENS_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_EXPORTS_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_USES_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_PROVIDES_STATEMENT)) {
String text = astNode.getText();
int tailLength = 0;
ASTNode leaf = TreeUtil.findLastLeaf(astNode);
while (leaf != null && JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(leaf.getElementType())) {
tailLength += leaf.getTextLength();
leaf = TreeUtil.prevLeaf(leaf);
}
if (leaf == null) {
return;
}
if (tailLength > 0) {
text = text.substring(0, text.length() - tailLength);
}
int insertionOffset = leaf.getTextRange().getEndOffset();
Document doc = editor.getDocument();
if (BasicJavaAstTreeUtil.is(astNode, BASIC_FIELD) &&
(BasicJavaAstTreeUtil.hasModifierProperty(astNode, JavaTokenType.ABSTRACT_KEYWORD))) {
// abstract rarely seem to be field. It is rather incomplete method.
doc.insertString(insertionOffset, "()");
insertionOffset += "()".length();
}
// Like:
// assert x instanceof Type
// String s = "hello";
// Here, String is parsed as name of the pattern variable, and we have an assignment, instead of declaration
ASTNode error = astNode.getLastChildNode();
if (BasicJavaAstTreeUtil.is(error, TokenType.ERROR_ELEMENT) &&
BasicJavaAstTreeUtil.is(error.getTreePrev(), BASIC_INSTANCE_OF_EXPRESSION) &&
BasicJavaAstTreeUtil.is(error.getTreePrev().getLastChildNode(), BASIC_TYPE_TEST_PATTERN)) {
ASTNode variable = BasicJavaAstTreeUtil.getPatternVariable(error.getTreePrev().getLastChildNode());
PsiElement skipWhitespacesForward = PsiTreeUtil.skipWhitespacesForward(psiElement);
ASTNode assignmentExpr = BasicJavaAstTreeUtil.getExpression(BasicJavaAstTreeUtil.toNode(skipWhitespacesForward));
if (variable != null &&
BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(skipWhitespacesForward), BASIC_EXPRESSION_STATEMENT) &&
BasicJavaAstTreeUtil.is(assignmentExpr, BASIC_ASSIGNMENT_EXPRESSION) &&
JavaTokenType.EQ.equals(BasicJavaAstTreeUtil.getAssignmentOperationTokenType(assignmentExpr))) {
ASTNode identifier = BasicJavaAstTreeUtil.getNameIdentifier(variable);
if (identifier != null &&
BasicJavaAstTreeUtil.toPsi(identifier.getTreePrev()) instanceof PsiWhiteSpace ws &&
ws.getText().contains("\n") &&
editor.getCaretModel().getOffset() < identifier.getTextRange().getStartOffset()) {
insertionOffset = ws.getTextRange().getStartOffset();
}
}
}
if (!StringUtil.endsWithChar(text, ';')) {
ASTNode parent = astNode.getTreeParent();
String toInsert = ";";
if (BasicJavaAstTreeUtil.is(parent, BASIC_FOR_STATEMENT)) {
if (BasicJavaAstTreeUtil.getForUpdate(parent) == astNode) {
return;
}
if (getSpaceAfterSemicolon(psiElement)) {
toInsert += " ";
}
}
doc.insertString(insertionOffset, toInsert);
}
}
}
private static boolean isMethodShouldHaveBody(@Nullable PsiElement psiElement){
return AfterSemicolonEnterProcessor.shouldHaveBody(BasicJavaAstTreeUtil.toNode(psiElement));
}
protected abstract boolean isImportStatementBase(@Nullable PsiElement psiElement);
private static boolean isStandaloneField(@Nullable PsiElement psiElement) {
if (psiElement == null || !BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(psiElement), BASIC_FIELD)) return false;
PsiElement node = PsiTreeUtil.nextLeaf(psiElement, true);
if (node == null) {
return false;
}
return !",".equals(node.getText());
}
}
@@ -0,0 +1,105 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaElementType;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class AfterSemicolonEnterProcessor implements ASTNodeEnterProcessor {
@Override
public boolean doEnter(@NotNull Editor editor, @NotNull ASTNode astNode, boolean isModified) {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (psiElement == null) {
return false;
}
if (BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_EXPRESSION_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_DECLARATION_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_DO_WHILE_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_RETURN_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_THROW_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_BREAK_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_CONTINUE_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_YIELD_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_ASSERT_STATEMENT) ||
BasicJavaAstTreeUtil.is(astNode, Set.of(BasicJavaElementType.BASIC_FIELD, BasicJavaElementType.BASIC_ENUM_CONSTANT)) ||
isImportStatementBase(psiElement) ||
isMethodWithoutBody(psiElement)) {
int errorOffset = getErrorElementOffset(psiElement);
int elementEndOffset = astNode.getTextRange().getEndOffset();
if (BasicJavaAstTreeUtil.is(astNode, BasicJavaElementType.BASIC_ENUM_CONSTANT)) {
final CharSequence text = editor.getDocument().getCharsSequence();
final int commaOffset = CharArrayUtil.shiftForwardUntil(text, elementEndOffset, ",");
if (commaOffset < text.length()) {
elementEndOffset = commaOffset + 1;
}
}
if (errorOffset >= 0 && errorOffset < elementEndOffset) {
final CharSequence text = editor.getDocument().getCharsSequence();
if (text.charAt(errorOffset) == ' ' && text.charAt(errorOffset + 1) == ';') {
errorOffset++;
}
}
editor.getCaretModel().moveToOffset(errorOffset >= 0 ? errorOffset : elementEndOffset);
return isModified;
}
return false;
}
static boolean shouldHaveBody(@Nullable ASTNode element) {
if (element == null) {
return false;
}
ASTNode containingClass = BasicJavaAstTreeUtil.getParentOfType(element, CLASS_SET);
if (containingClass == null) return false;
if (BasicJavaAstTreeUtil.hasModifierProperty(element, JavaTokenType.ABSTRACT_KEYWORD) ||
BasicJavaAstTreeUtil.hasModifierProperty(element, JavaTokenType.NATIVE_KEYWORD)) {
return false;
}
if (BasicJavaAstTreeUtil.hasModifierProperty(element, JavaTokenType.PRIVATE_KEYWORD)) return true;
if (BasicJavaAstTreeUtil.isInterfaceEnumClassOrRecord(containingClass, JavaTokenType.INTERFACE_KEYWORD) &&
!BasicJavaAstTreeUtil.hasModifierProperty(element, JavaTokenType.DEFAULT_KEYWORD) &&
!BasicJavaAstTreeUtil.hasModifierProperty(element, JavaTokenType.STATIC_KEYWORD)) {
return false;
}
return true;
}
private boolean isMethodWithoutBody(@Nullable PsiElement psiElement){
ASTNode node = BasicJavaAstTreeUtil.toNode(psiElement);
return BasicJavaAstTreeUtil.is(node, BASIC_METHOD) &&
!shouldHaveBody(node);
}
private boolean isImportStatementBase(@Nullable PsiElement psiElement){
ASTNode node = BasicJavaAstTreeUtil.toNode(psiElement);
return
BasicJavaAstTreeUtil.is(node, BASIC_IMPORT_STATEMENT) ||
BasicJavaAstTreeUtil.is(node, BASIC_IMPORT_STATIC_STATEMENT);
}
private static int getErrorElementOffset(PsiElement elt) {
final int[] offset = {-1};
elt.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitErrorElement(@NotNull PsiErrorElement element) {
if (offset[0] == -1) offset[0] = element.getTextRange().getStartOffset();
}
});
return offset[0];
}
}
@@ -0,0 +1,40 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_CODE_BLOCK;
import static com.intellij.psi.impl.source.BasicJavaElementType.STATEMENT_SET;
public class BlockBraceFixer implements Fixer{
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (psiElement == null) {
return;
}
if (BasicJavaAstTreeUtil.is(astNode, BASIC_CODE_BLOCK) && afterUnmatchedBrace(editor, psiElement.getContainingFile().getFileType())) {
int stopOffset = astNode.getTextRange().getEndOffset();
List<ASTNode> statements = BasicJavaAstTreeUtil.getChildren(astNode).stream().filter(node->
BasicJavaAstTreeUtil.is(node, STATEMENT_SET)
).toList();
if (!statements.isEmpty()) {
stopOffset = statements.get(0).getTextRange().getEndOffset();
}
editor.getDocument().insertString(stopOffset, "}");
}
}
static boolean afterUnmatchedBrace(Editor editor, FileType fileType) {
return EnterAfterUnmatchedBraceHandler.isAfterUnmatchedLBrace(editor, editor.getCaretModel().getOffset(), fileType);
}
}
@@ -0,0 +1,43 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_CATCH_SECTION;
public class CatchDeclarationFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_CATCH_SECTION)) {
final Document doc = editor.getDocument();
final int catchStart = astNode.getTextRange().getStartOffset();
int stopOffset = doc.getLineEndOffset(doc.getLineNumber(catchStart));
final ASTNode catchBlock = BasicJavaAstTreeUtil.getCatchBlock(astNode);
if (catchBlock != null) {
stopOffset = Math.min(stopOffset, catchBlock.getTextRange().getStartOffset());
}
stopOffset = Math.min(stopOffset, astNode.getTextRange().getEndOffset());
final ASTNode lParenth = BasicJavaAstTreeUtil.getLParenth(astNode);
if (lParenth == null) {
doc.replaceString(catchStart, stopOffset, "catch ()");
processor.registerUnresolvedError(catchStart + "catch (".length());
}
else {
if (BasicJavaAstTreeUtil.getParameter(astNode) == null) {
processor.registerUnresolvedError(lParenth.getTextRange().getEndOffset());
}
if (BasicJavaAstTreeUtil.getRParenth(astNode) == null) {
doc.insertString(stopOffset, ")");
}
}
}
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2000-2009 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.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtilEx;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicElementTypes;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.DOC_COMMENT;
public class CommentBreakerEnterProcessor implements ASTNodeEnterProcessor {
private final BasicJavaTokenSet myCommentTypes = BasicJavaTokenSet.orSet(
BasicElementTypes.JAVA_PLAIN_COMMENT_BIT_SET, BasicJavaTokenSet.create(DOC_COMMENT)
);
@Override
public boolean doEnter(@NotNull Editor editor, @NotNull ASTNode astNode, boolean isModified) {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (isModified || psiElement == null) return false;
final PsiElement atCaret = psiElement.getContainingFile().findElementAt(editor.getCaretModel().getOffset());
if (atCaret == null) return false;
final ASTNode comment = BasicJavaAstTreeUtil.getParentOfType(atCaret.getNode(), myCommentTypes, false);
if (comment != null) {
plainEnter(editor);
if (BasicJavaAstTreeUtil.is(comment, JavaTokenType.END_OF_LINE_COMMENT)) {
EditorModificationUtilEx.insertStringAtCaret(editor, "// ");
}
return true;
}
return false;
}
private static void plainEnter(Editor editor) {
getEnterHandler().execute(editor, editor.getCaretModel().getCurrentCaret(), EditorUtil.getEditorDataContext(editor));
}
private static EditorActionHandler getEnterHandler() {
return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE);
}
}
@@ -0,0 +1,47 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_BLOCK_STATEMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_DO_WHILE_STATEMENT;
public class DoWhileConditionFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_DO_WHILE_STATEMENT)) {
final Document doc = editor.getDocument();
ASTNode whileKeyword = BasicJavaAstTreeUtil.getWhileKeyword(astNode);
ASTNode doWhileBody = BasicJavaAstTreeUtil.getDoWhileBody(astNode);
if (doWhileBody == null || !(BasicJavaAstTreeUtil.is(doWhileBody, BASIC_BLOCK_STATEMENT)) && whileKeyword == null) {
final int startOffset = astNode.getTextRange().getStartOffset();
doc.replaceString(startOffset, startOffset + "do".length(), "do {} while()");
return;
}
if (BasicJavaAstTreeUtil.getWhileCondition(astNode) == null) {
if (whileKeyword == null) {
final int endOffset = astNode.getTextRange().getEndOffset();
doc.insertString(endOffset, "while()");
}
else if (BasicJavaAstTreeUtil.getLParenth(astNode) == null || BasicJavaAstTreeUtil.getRParenth(astNode) == null) {
final TextRange whileRange = whileKeyword.getTextRange();
doc.replaceString(whileRange.getStartOffset(), whileRange.getEndOffset(), "while()");
}
else {
ASTNode lParenth = BasicJavaAstTreeUtil.getLParenth(astNode);
if (lParenth != null) {
processor.registerUnresolvedError(lParenth.getTextRange().getEndOffset());
}
}
}
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2000-2009 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.smartEnter;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
public interface EnterProcessor {
boolean doEnter(Editor editor, PsiElement psiElement, boolean isModified);
}
@@ -0,0 +1,41 @@
/*
* Copyright 2000-2009 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.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_ENUM_CONSTANT;
public class EnumFieldFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_ENUM_CONSTANT)) {
int insertionOffset = astNode.getTextRange().getEndOffset();
Document doc = editor.getDocument();
final CharSequence text = doc.getCharsSequence();
final int probableCommaOffset = CharArrayUtil.shiftForward(text, insertionOffset, " \t");
if (probableCommaOffset >= text.length() || text.charAt(probableCommaOffset) != ',') {
doc.insertString(insertionOffset, ",");
}
}
}
}
@@ -0,0 +1,11 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
public interface Fixer {
void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException;
}
@@ -0,0 +1,107 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.BaseJavaJspElementType;
import com.intellij.application.options.CodeStyle;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_EMPTY_STATEMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_FOR_STATEMENT;
/**
* {@link Fixer} that handles use-cases like below:
* <b>before:</b>
* <pre>
* void foo() {
* for[caret]
* }
* </pre>
*
* <b>after:</b>
* <pre>
* void foo() {
* for ([caret]) {
*
* }
* }
* </pre>
*/
public class ForStatementFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_FOR_STATEMENT))) {
return;
}
final ASTNode lParenth = BasicJavaAstTreeUtil.getLParenth(astNode);
final ASTNode rParenth = BasicJavaAstTreeUtil.getRParenth(astNode);
if (lParenth == null || rParenth == null) {
final TextRange textRange = astNode.getTextRange();
editor.getDocument().replaceString(textRange.getStartOffset(), textRange.getEndOffset(), "for () {\n}");
processor.registerUnresolvedError(textRange.getStartOffset() + "for (".length());
return;
}
final ASTNode initialization = BasicJavaAstTreeUtil.getForInitialization(astNode);
if (initialization == null) {
processor.registerUnresolvedError(lParenth.getTextRange().getEndOffset());
return;
}
final ASTNode condition = BasicJavaAstTreeUtil.getForCondition(astNode);
if (condition == null) {
boolean endlessLoop = BasicJavaAstTreeUtil.is(initialization, BASIC_EMPTY_STATEMENT) &&
BasicJavaAstTreeUtil.getForUpdate(astNode) == null;
if (!endlessLoop) {
registerErrorOffset(editor, processor, initialization, astNode);
}
return;
}
if (BasicJavaAstTreeUtil.getForUpdate(astNode) == null) {
registerErrorOffset(editor, processor, condition, astNode);
}
}
/**
* {@link JavaSmartEnterProcessor#registerUnresolvedError(int) registers target offset} taking care of the situation when
* current code style implies white space after 'for' part's semicolon.
*
* @param editor target editor
* @param processor target smart enter processor
* @param lastValidForPart last valid element of the target 'for' loop
* @param forStatement PSI element for the target 'for' loop
*/
private void registerErrorOffset(@NotNull Editor editor, @NotNull AbstractBasicJavaSmartEnterProcessor processor,
@NotNull ASTNode lastValidForPart, @NotNull ASTNode forStatement) {
final Project project = editor.getProject();
int offset = lastValidForPart.getTextRange().getEndOffset();
if (project != null && CodeStyle.getSettings(editor).getCommonSettings(JavaLanguage.INSTANCE).SPACE_AFTER_COMMA) {
if (editor.getDocument().getCharsSequence().charAt(lastValidForPart.getTextRange().getEndOffset() - 1) != ';') {
offset++;
}
for (ASTNode element = lastValidForPart.getTreeNext();
element != null && element != BasicJavaAstTreeUtil.getRParenth(forStatement) && element.getTreeParent() == forStatement;
element = element.getTreeNext()) {
if (isWhiteSpaceIncludingJsp(element) && element.getTextLength() > 0) {
offset++;
break;
}
}
}
processor.registerUnresolvedError(offset);
}
private static boolean isWhiteSpaceIncludingJsp(@NotNull ASTNode node) {
return BaseJavaJspElementType.WHITE_SPACE_BIT_SET.contains(node.getElementType());
}
}
@@ -0,0 +1,77 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_PLAIN_COMMENT_BIT_SET;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.DOC_COMMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class IfConditionFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_IF_STATEMENT)) {
final Document doc = editor.getDocument();
final ASTNode rParen = BasicJavaAstTreeUtil.getRParenth(astNode);
final ASTNode lParen = BasicJavaAstTreeUtil.getLParenth(astNode);
final ASTNode condition = BasicJavaAstTreeUtil.getIfCondition(astNode);
if (condition == null) {
if (lParen == null || rParen == null) {
int stopOffset = doc.getLineEndOffset(doc.getLineNumber(astNode.getTextRange().getStartOffset()));
final ASTNode then = BasicJavaAstTreeUtil.getThenBranch(astNode);
if (then != null) {
stopOffset = Math.min(stopOffset, then.getTextRange().getStartOffset());
}
stopOffset = Math.min(stopOffset, astNode.getTextRange().getEndOffset());
ASTNode lastChild = astNode.getLastChildNode();
String innerComment = "";
String lastComment = "";
if (lParen != null && PsiUtilCore.getElementType(lastChild) == JavaTokenType.C_STYLE_COMMENT) {
innerComment = lastChild.getText();
}
else if (BasicJavaAstTreeUtil.is(lastChild, DOC_COMMENT) ||
BasicJavaAstTreeUtil.is(lastChild, JAVA_PLAIN_COMMENT_BIT_SET)
) {
lastComment = lastChild.getText();
}
String prefix = "if (" + innerComment;
doc.replaceString(astNode.getTextRange().getStartOffset(), stopOffset, prefix + ")" + lastComment);
processor.registerUnresolvedError(astNode.getTextRange().getStartOffset() + prefix.length());
}
else {
processor.registerUnresolvedError(lParen.getTextRange().getEndOffset());
}
}
else if (rParen == null) {
doc.insertString(condition.getTextRange().getEndOffset(), ")");
}
}
else if (BasicJavaAstTreeUtil.is(astNode, EXPRESSION_SET) &&
BasicJavaAstTreeUtil.is(astNode.getTreeParent(), BASIC_EXPRESSION_STATEMENT)) {
PsiElement psi = BasicJavaAstTreeUtil.toPsi(astNode);
if (psi != null) {
PsiElement prevLeaf = PsiTreeUtil.prevVisibleLeaf(psi);
if (prevLeaf != null && prevLeaf.textMatches(PsiKeyword.IF)) {
Document doc = editor.getDocument();
doc.insertString(astNode.getTextRange().getEndOffset(), ")");
doc.insertString(astNode.getTextRange().getStartOffset(), "(");
}
}
}
}
}
@@ -0,0 +1,85 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.ide.DataManager;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class LeaveCodeBlockEnterProcessor implements ASTNodeEnterProcessor {
private final BasicJavaTokenSet CONTROL_FLOW_ELEMENT_TYPES =
BasicJavaTokenSet.create(BASIC_IF_STATEMENT, BASIC_WHILE_STATEMENT, BASIC_DO_WHILE_STATEMENT, BASIC_FOR_STATEMENT,
BASIC_FOREACH_STATEMENT);
@Override
public boolean doEnter(@NotNull Editor editor, @NotNull ASTNode astNode, boolean isModified) {
ASTNode parent = astNode.getTreeParent();
if (!(BasicJavaAstTreeUtil.is(parent, BASIC_CODE_BLOCK))) {
return false;
}
if (CONTROL_FLOW_ELEMENT_TYPES.contains(astNode.getElementType())) {
return false;
}
boolean leaveCodeBlock = isControlFlowBreak(astNode);
if (!leaveCodeBlock) {
return false;
}
final int offset = parent.getTextRange().getEndOffset();
// Check if there is empty line after the code block. Just move caret there in the case of the positive answer.
final CharSequence text = editor.getDocument().getCharsSequence();
if (offset < text.length() - 1) {
final int i = CharArrayUtil.shiftForward(text, offset + 1, " \t");
if (i < text.length() && text.charAt(i) == '\n') {
editor.getCaretModel().moveToOffset(offset + 1);
EditorActionManager actionManager = EditorActionManager.getInstance();
EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_LINE_END);
final DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent());
actionHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext);
return true;
}
}
editor.getCaretModel().moveToOffset(offset);
return false;
}
/**
* Handles situations like the one below:
* <pre>
* void foo(int i) {
* if (i < 0) {
* return;[caret]
* }
* }
* </pre>
*
* <b>Output:</b>
* <pre>
* void foo(int i) {
* if (i < 0) {
* return;
* }
* [caret]
* }
* </pre>
*/
private static boolean isControlFlowBreak(@Nullable ASTNode element) {
return BasicJavaAstTreeUtil.is(element, BASIC_RETURN_STATEMENT) ||
BasicJavaAstTreeUtil.is(element, BASIC_THROW_STATEMENT);
}
}
@@ -0,0 +1,24 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
public class LiteralFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode)
throws IncorrectOperationException {
if (astNode.getElementType() == JavaTokenType.STRING_LITERAL &&
!StringUtil.endsWithChar(astNode.getText(), '\"')) {
editor.getDocument().insertString(astNode.getTextRange().getEndOffset(), "\"");
}
else if (astNode.getElementType() == JavaTokenType.CHARACTER_LITERAL &&
!StringUtil.endsWithChar(astNode.getText(), '\'')) {
editor.getDocument().insertString(astNode.getTextRange().getEndOffset(), "'");
}
}
}
@@ -0,0 +1,31 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_NEW_EXPRESSION;
public class MissingArrayConstructorBracketFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_NEW_EXPRESSION))) return;
int count = 0;
for (ASTNode element = astNode.getFirstChildNode(); element != null; element = element.getTreeNext()) {
if (BasicJavaAstTreeUtil.is(element, JavaTokenType.LBRACKET)) {
count++;
}
else if (BasicJavaAstTreeUtil.is(element, JavaTokenType.RBRACKET)) {
count--;
}
}
if (count > 0) {
editor.getDocument().insertString(astNode.getTextRange().getEndOffset(), "]");
}
}
}
@@ -0,0 +1,50 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_ANNOTATION_ARRAY_INITIALIZER;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_ARRAY_INITIALIZER_EXPRESSION;
public class MissingArrayInitializerBraceFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(
BasicJavaAstTreeUtil.is(astNode, BASIC_ARRAY_INITIALIZER_EXPRESSION) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_ANNOTATION_ARRAY_INITIALIZER))) {
return;
}
ASTNode child = astNode.getFirstChildNode();
if (!child.getElementType().equals(JavaTokenType.LBRACE)) return;
PsiElement psi = BasicJavaAstTreeUtil.toPsi(astNode);
if (psi == null) {
return;
}
if (!EnterAfterUnmatchedBraceHandler.isAfterUnmatchedLBrace(editor, child.getTextRange().getEndOffset(),
psi.getContainingFile().getFileType())) {
return;
}
ASTNode anchor = BasicJavaAstTreeUtil.findChildByType(astNode, TokenType.ERROR_ELEMENT);
if (anchor == null) {
PsiElement last = PsiTreeUtil.getDeepestVisibleLast(psi);
while (last != null && last.getNode().getElementType().equals(JavaTokenType.RBRACE)) {
last = PsiTreeUtil.prevCodeLeaf(last);
}
if (last != null && PsiTreeUtil.isAncestor(psi, last, true)) {
anchor = last.getNode();
}
}
int endOffset = (anchor != null ? anchor : astNode).getTextRange().getEndOffset();
editor.getDocument().insertString(endOffset, "}");
}
}
@@ -0,0 +1,26 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_CATCH_SECTION;
public class MissingCatchBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_CATCH_SECTION))) return;
ASTNode body = BasicJavaAstTreeUtil.getCatchBlock(astNode);
if (body != null && BasicJavaAstTreeUtil.getLBrace(body) != null && BasicJavaAstTreeUtil.getRBrace(body) != null) return;
final ASTNode rParenth = BasicJavaAstTreeUtil.getRParenth(astNode);
if (rParenth == null) return;
processor.insertBraces(editor, rParenth.getTextRange().getEndOffset());
}
}
@@ -0,0 +1,50 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class MissingClassBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_RECORD_COMPONENT)) {
astNode = BasicJavaAstTreeUtil.getRecordComponentContainingClass(astNode);
}
if (!(BasicJavaAstTreeUtil.is(astNode, CLASS_SET)) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_TYPE_PARAMETER)) {
return;
}
if (BasicJavaAstTreeUtil.getLBrace(astNode) == null && astNode != null) {
ASTNode lastChild = astNode.getLastChildNode();
int offset = astNode.getTextRange().getEndOffset();
if (BasicJavaAstTreeUtil.is(lastChild, TokenType.ERROR_ELEMENT)) {
ASTNode previous = lastChild.getTreePrev();
if (BasicJavaAstTreeUtil.isWhiteSpace(previous)) {
offset = previous.getTextRange().getStartOffset();
}
else {
offset = lastChild.getTextRange().getStartOffset();
}
}
if (BasicJavaAstTreeUtil.isInterfaceEnumClassOrRecord(astNode, JavaTokenType.RECORD_KEYWORD) &&
BasicJavaAstTreeUtil.getRecordHeader(astNode) == null) {
editor.getDocument().insertString(offset, "() {}");
editor.getCaretModel().moveToOffset(offset + 1);
processor.setSkipEnter(true);
}
else {
processor.insertBracesWithNewLine(editor, offset);
editor.getCaretModel().moveToOffset(offset + 1);
}
}
}
}
@@ -0,0 +1,60 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_BLOCK_STATEMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_IF_STATEMENT;
public class MissingIfBranchesFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_IF_STATEMENT))) return;
final Document doc = editor.getDocument();
final ASTNode elseElement = BasicJavaAstTreeUtil.getElseElement(astNode);
if (elseElement != null) {
handleBranch(doc, astNode, elseElement, BasicJavaAstTreeUtil.getElseBranch(astNode));
}
ASTNode rParenth = BasicJavaAstTreeUtil.getRParenth(astNode);
assert rParenth != null;
handleBranch(doc, astNode, rParenth, BasicJavaAstTreeUtil.getThenBranch(astNode));
}
private static void handleBranch(@NotNull Document doc,
@NotNull ASTNode ifStatement,
@NotNull ASTNode beforeBranch,
@Nullable ASTNode branch) {
if (BasicJavaAstTreeUtil.is(branch, BASIC_BLOCK_STATEMENT) ||
PsiKeyword.ELSE.equals(beforeBranch.getText()) && BasicJavaAstTreeUtil.is(branch, BASIC_IF_STATEMENT)) {
return;
}
boolean transformingOneLiner = branch != null && (startLine(doc, beforeBranch) == startLine(doc, branch) ||
startCol(doc, ifStatement) < startCol(doc, branch));
if (!transformingOneLiner) {
doc.insertString(beforeBranch.getTextRange().getEndOffset(), "{}");
}
else {
doc.insertString(beforeBranch.getTextRange().getEndOffset(), "{");
doc.insertString(branch.getTextRange().getEndOffset() + 1, "}");
}
}
private static int startLine(Document doc, @NotNull ASTNode astNode) {
return doc.getLineNumber(astNode.getTextRange().getStartOffset());
}
private static int startCol(Document doc, @NotNull ASTNode astNode) {
int offset = astNode.getTextRange().getStartOffset();
return offset - doc.getLineStartOffset(doc.getLineNumber(offset));
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2000-2009 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.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class MissingLambdaBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
ASTNode body = null;
if (BasicJavaAstTreeUtil.is(astNode, BASIC_LAMBDA_EXPRESSION)) {
final ASTNode lastChild = astNode.getLastChildNode();
if (BasicJavaAstTreeUtil.is(lastChild, EXPRESSION_SET) ||
BasicJavaAstTreeUtil.is(lastChild, BASIC_CODE_BLOCK)) {
body = lastChild;
}
}
else if (BasicJavaAstTreeUtil.is(astNode, BASIC_SWITCH_LABELED_RULE)) {
body = BasicJavaAstTreeUtil.getRuleBody(astNode);
}
else {
return;
}
if (body != null) return;
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(astNode);
if (psiElement == null) {
return;
}
PsiElement arrow = PsiTreeUtil.getDeepestVisibleLast(psiElement);
if (arrow == null || !arrow.getNode().getElementType().equals(JavaTokenType.ARROW)) return;
int offset = arrow.getTextRange().getEndOffset();
processor.insertBracesWithNewLine(editor, offset);
editor.getCaretModel().moveToOffset(offset + 1);
processor.commit(editor);
processor.reformat(editor, psiElement);
processor.setSkipEnter(BasicJavaAstTreeUtil.is(astNode, BASIC_LAMBDA_EXPRESSION));
}
}
@@ -0,0 +1,162 @@
/*
* Copyright 2000-2009 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.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class MissingLoopBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
ASTNode loopStatement = getLoopParent(astNode);
if (loopStatement == null) return;
final Document doc = editor.getDocument();
ASTNode body;
if (BasicJavaAstTreeUtil.is(loopStatement, BASIC_FOR_STATEMENT)) {
body = BasicJavaAstTreeUtil.getForBody(loopStatement);
}
else if (BasicJavaAstTreeUtil.is(loopStatement, BASIC_FOREACH_STATEMENT)) {
body = BasicJavaAstTreeUtil.getForeachBody(loopStatement);
}
else if (BasicJavaAstTreeUtil.is(loopStatement, BASIC_WHILE_STATEMENT)) {
body = BasicJavaAstTreeUtil.getWhileBody(loopStatement);
}
else {
return;
}
if (BasicJavaAstTreeUtil.is(body, BASIC_BLOCK_STATEMENT)) return;
if (body != null && startLine(doc, body) == startLine(doc, loopStatement)) return;
ASTNode eltToInsertAfter = BasicJavaAstTreeUtil.getRParenth(loopStatement);
fixLoopBody(editor, processor, loopStatement, doc, body, eltToInsertAfter);
}
private static ASTNode getLoopParent(@NotNull ASTNode element) {
ASTNode statement = BasicJavaAstTreeUtil.getParentOfType(element, BasicJavaTokenSet.create(BASIC_FOREACH_STATEMENT,
BASIC_FOR_STATEMENT,
BASIC_WHILE_STATEMENT));
if (statement == null) return null;
if (BasicJavaAstTreeUtil.is(statement, BASIC_FOREACH_STATEMENT)) {
return isForEachApplicable(statement, element) ? statement : null;
}
if (BasicJavaAstTreeUtil.is(statement, BASIC_FOR_STATEMENT)) {
return isForApplicable(statement, element) ? statement : null;
}
if (BasicJavaAstTreeUtil.is(statement, BASIC_WHILE_STATEMENT)) {
return statement;
}
return null;
}
private static boolean isForApplicable(ASTNode statement, ASTNode astNode) {
ASTNode init = BasicJavaAstTreeUtil.getForInitialization(statement);
ASTNode update = BasicJavaAstTreeUtil.getForUpdate(statement);
ASTNode check = BasicJavaAstTreeUtil.getForCondition(statement);
return isValidChild(init, astNode) || isValidChild(update, astNode) || isValidChild(check, astNode);
}
private static boolean isValidChild(ASTNode ancestorNode, ASTNode node) {
PsiElement ancestor = BasicJavaAstTreeUtil.toPsi(ancestorNode);
PsiElement element = BasicJavaAstTreeUtil.toPsi(node);
if (ancestor != null && element != null) {
if (PsiTreeUtil.isAncestor(ancestor, element, false)) {
if (PsiTreeUtil.hasErrorElements(ancestor)) return false;
return true;
}
}
return false;
}
private static boolean isForEachApplicable(ASTNode statement, ASTNode astNode) {
ASTNode iterated = BasicJavaAstTreeUtil.getForEachIteratedValue(statement);
ASTNode parameter = BasicJavaAstTreeUtil.getForEachIterationParameter(statement);
PsiElement iteratedElement = BasicJavaAstTreeUtil.toPsi(iterated);
PsiElement parameterElement = BasicJavaAstTreeUtil.toPsi(parameter);
PsiElement element = BasicJavaAstTreeUtil.toPsi(astNode);
return element != null &&
(PsiTreeUtil.isAncestor(iteratedElement, element, false) ||
PsiTreeUtil.isAncestor(parameterElement, element, false));
}
private static int startLine(Document doc, ASTNode astNode) {
return doc.getLineNumber(astNode.getTextRange().getStartOffset());
}
private static void fixLoopBody(@NotNull Editor editor,
@NotNull AbstractBasicJavaSmartEnterProcessor processor,
@NotNull ASTNode loop,
@NotNull Document doc,
@Nullable ASTNode body,
@Nullable ASTNode eltToInsertAfter) {
PsiElement loopElement = BasicJavaAstTreeUtil.toPsi(loop);
if (body != null && eltToInsertAfter != null) {
PsiElement bodyElement = BasicJavaAstTreeUtil.toPsi(body);
if (loopElement != null && bodyElement != null && bodyIsIndented(loopElement, bodyElement)) {
int endOffset = body.getTextRange().getEndOffset();
doc.insertString(endOffset, "\n");
processor.insertCloseBrace(editor, endOffset + 1);
int offset = eltToInsertAfter.getTextRange().getEndOffset();
doc.insertString(offset, "{");
editor.getCaretModel().moveToOffset(endOffset + "{".length());
processor.setSkipEnter(true);
processor.reformat(loopElement);
return;
}
}
boolean needToClose = false;
if (eltToInsertAfter == null) {
eltToInsertAfter = loop;
needToClose = true;
}
int offset = eltToInsertAfter.getTextRange().getEndOffset();
if (needToClose) {
doc.insertString(offset, ")");
offset++;
}
processor.insertBraces(editor, offset);
editor.getCaretModel().moveToOffset(offset);
}
private static boolean bodyIsIndented(@NotNull PsiElement loop, @NotNull PsiElement body) {
PsiWhiteSpace beforeBody = ObjectUtils.tryCast(body.getPrevSibling(), PsiWhiteSpace.class);
if (beforeBody == null) return false;
PsiWhiteSpace beforeLoop = ObjectUtils.tryCast(loop.getPrevSibling(), PsiWhiteSpace.class);
if (beforeLoop == null) return false;
String beforeBodyText = beforeBody.getText();
String beforeLoopText = beforeLoop.getText();
int beforeBodyLineBreak = beforeBodyText.lastIndexOf('\n');
if (beforeBodyLineBreak == -1) return false;
int beforeLoopLineBreak = beforeLoopText.lastIndexOf('\n');
if (beforeLoopLineBreak == -1) return false;
return beforeBodyText.length() - beforeBodyLineBreak > beforeLoopText.length() - beforeLoopLineBreak;
}
}
@@ -0,0 +1,85 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.core.JavaPsiBundle;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class MissingMethodBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_FIELD)) {
// replace something like `void x` with `void x() {...}`
// while it's ambiguous whether user wants a field or a method, declaring a field is easier (just append a semicolon),
// so completing a method looks more useful
if (BasicJavaAstTreeUtil.getInitializer(astNode) != null) return;
ASTNode lastChild = astNode.getLastChildNode();
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(lastChild);
if (!(psiElement instanceof PsiErrorElement)) return;
if (!((PsiErrorElement)psiElement).getErrorDescription().equals(JavaPsiBundle.message("expected.semicolon"))) return;
// Impossible modifiers for a method
if (BasicJavaAstTreeUtil.hasModifierProperty(astNode, JavaTokenType.TRANSIENT_KEYWORD) ||
BasicJavaAstTreeUtil.hasModifierProperty(astNode, JavaTokenType.VOLATILE_KEYWORD)) {
return;
}
ASTNode typeElement = BasicJavaAstTreeUtil.getTypeElement(astNode);
if (typeElement == null || !typeElement.getText().equals("void")) return;
int endOffset = astNode.getTextRange().getEndOffset();
editor.getDocument().insertString(endOffset, "()");
editor.getDocument().insertString(endOffset + 2, "{}");
editor.getCaretModel().moveToOffset(endOffset + 1);
processor.registerUnresolvedError(endOffset + 1);
processor.setSkipEnter(true);
return;
}
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_METHOD))) return;
if (!shouldMethodHaveBody(BasicJavaAstTreeUtil.toPsi(astNode))) return;
final ASTNode body = BasicJavaAstTreeUtil.getCodeBlock(astNode);
final Document doc = editor.getDocument();
if (body != null) {
// See IDEADEV-1093. This is quite hacky heuristic but it seem to be best we can do.
String bodyText = body.getText();
if (bodyText.startsWith("{")) {
final ASTNode statement = BasicJavaAstTreeUtil.findChildByType(body, STATEMENT_SET);
if (statement != null) {
if (BasicJavaAstTreeUtil.is(statement, BASIC_DECLARATION_STATEMENT)) {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(statement);
if (psiElement != null && PsiTreeUtil.getDeepestLast(psiElement) instanceof PsiErrorElement) {
ASTNode containingClass = BasicJavaAstTreeUtil.getParentOfType(astNode, CLASS_SET);
if (containingClass != null && BasicJavaAstTreeUtil.getRBrace(containingClass) == null) {
doc.insertString(body.getTextRange().getStartOffset() + 1, "\n}");
}
}
}
}
}
return;
}
ASTNode throwList = BasicJavaAstTreeUtil.findChildByType(astNode, BASIC_THROWS_LIST);
if (throwList != null) {
int endOffset = throwList.getTextRange().getEndOffset();
if (endOffset < doc.getTextLength() && doc.getCharsSequence().charAt(endOffset) == ';') {
doc.deleteString(endOffset, endOffset + 1);
}
processor.insertBracesWithNewLine(editor, endOffset);
}
}
private static boolean shouldMethodHaveBody(@Nullable PsiElement method){
return AfterSemicolonEnterProcessor.shouldHaveBody(
BasicJavaAstTreeUtil.toNode(method));
}
}
@@ -0,0 +1,103 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaToken;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class MissingReturnExpressionFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_RETURN_STATEMENT))) {
return;
}
if (!BasicJavaAstTreeUtil.hasErrorElements(astNode)) {
return;
}
if (fixMethodCallWithoutTrailingSemicolon(astNode, editor, processor)) {
return;
}
ASTNode returnValue = BasicJavaAstTreeUtil.getReturnValue(astNode);
if (returnValue != null
&& lineNumber(editor, editor.getCaretModel().getOffset()) == lineNumber(editor, returnValue.getTextRange().getStartOffset())) {
return;
}
ASTNode parent = BasicJavaAstTreeUtil.getParentOfType(astNode, BasicJavaTokenSet.create(BASIC_CLASS_INITIALIZER, BASIC_METHOD));
if (BasicJavaAstTreeUtil.is(parent, BASIC_METHOD)) {
ASTNode type = BasicJavaAstTreeUtil.findChildByType(parent, BASIC_TYPE);
if (type != null && !type.getText().equals("void")) {
final int startOffset = astNode.getTextRange().getStartOffset();
if (returnValue != null) {
editor.getDocument().insertString(startOffset + "return".length(), ";");
}
processor.registerUnresolvedError(startOffset + "return".length());
}
}
}
private static boolean fixMethodCallWithoutTrailingSemicolon(@Nullable ASTNode returnStatement, @NotNull Editor editor,
@NotNull AbstractBasicJavaSmartEnterProcessor processor) {
if (returnStatement == null) {
return false;
}
final ASTNode lastChild = returnStatement.getLastChildNode();
if (!(BasicJavaAstTreeUtil.is(lastChild, TokenType.ERROR_ELEMENT))) {
return false;
}
ASTNode prev = lastChild.getTreePrev();
if (BasicJavaAstTreeUtil.isWhiteSpace(prev)) {
prev = prev.getTreePrev();
}
if (!(prev instanceof PsiJavaToken prevToken)) {
int offset = returnStatement.getTextRange().getEndOffset();
final PsiElement psiMethod =
BasicJavaAstTreeUtil.getParentOfType(BasicJavaAstTreeUtil.toPsi(returnStatement), BASIC_METHOD, true,
BasicJavaTokenSet.create(BASIC_LAMBDA_EXPRESSION));
ASTNode method = BasicJavaAstTreeUtil.toNode(psiMethod);
ASTNode type = BasicJavaAstTreeUtil.findChildByType(method, BASIC_TYPE);
if (method != null && type != null && type.getText().equals("void")) {
offset = returnStatement.getTextRange().getStartOffset() + "return".length();
}
editor.getDocument().insertString(offset, ";");
//processor.setSkipEnter(true);
return true;
}
if (prevToken.getTokenType() == JavaTokenType.SEMICOLON) {
return false;
}
final int offset = returnStatement.getTextRange().getEndOffset();
editor.getDocument().insertString(offset, ";");
if (prevToken.getTokenType() == JavaTokenType.RETURN_KEYWORD) {
final ASTNode method = BasicJavaAstTreeUtil.getParentOfType(returnStatement, BASIC_METHOD);
ASTNode type = BasicJavaAstTreeUtil.findChildByType(method, BASIC_TYPE);
if (method != null && type != null && !type.getText().equals("void")) {
editor.getCaretModel().moveToOffset(offset);
processor.setSkipEnter(true);
}
}
return true;
}
private static int lineNumber(Editor editor, int offset) {
return editor.getDocument().getLineNumber(offset);
}
}
@@ -0,0 +1,28 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_SWITCH_EXPRESSION;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_SWITCH_STATEMENT;
public class MissingSwitchBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_SWITCH_EXPRESSION, BASIC_SWITCH_STATEMENT))) return;
final ASTNode body = BasicJavaAstTreeUtil.getCodeBlock(astNode);
if (body != null) return;
final ASTNode rParenth = BasicJavaAstTreeUtil.getRParenth(astNode);
assert rParenth != null;
int offset = rParenth.getTextRange().getEndOffset();
processor.insertBraces(editor, offset);
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_SYNCHRONIZED_STATEMENT;
public class MissingSynchronizedBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_SYNCHRONIZED_STATEMENT))) return;
ASTNode body = BasicJavaAstTreeUtil.getCodeBlock(astNode);
if (body != null) return;
processor.insertBraces(editor, astNode.getTextRange().getEndOffset());
}
}
@@ -0,0 +1,34 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_THROW_STATEMENT;
public class MissingThrowExpressionFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode)
throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_THROW_STATEMENT)) {
ASTNode expression = BasicJavaAstTreeUtil.getExpression(astNode);
if (expression != null &&
startLine(editor, astNode) == startLine(editor, expression)) {
return;
}
final int startOffset = astNode.getTextRange().getStartOffset();
if (expression != null) {
editor.getDocument().insertString(startOffset + "throw".length(), ";");
}
processor.registerUnresolvedError(startOffset + "throw".length());
}
}
private static int startLine(Editor editor, @NotNull ASTNode psiElement) {
return editor.getDocument().getLineNumber(psiElement.getTextRange().getStartOffset());
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_TRY_STATEMENT;
public class MissingTryBodyFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_TRY_STATEMENT))) return;
ASTNode body = BasicJavaAstTreeUtil.getCodeBlock(astNode);
if (body != null) return;
processor.insertBraces(editor, astNode.getTextRange().getEndOffset());
}
}
@@ -0,0 +1,36 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_ANNOTATION_PARAMETER_LIST;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_PARAMETER_LIST;
public class ParameterListFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_PARAMETER_LIST) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_ANNOTATION_PARAMETER_LIST)) {
String text = astNode.getText();
if (StringUtil.startsWithChar(text, '(') && !StringUtil.endsWithChar(text, ')')) {
ASTNode[] params = BasicJavaAstTreeUtil.is(astNode, BASIC_PARAMETER_LIST) ?
BasicJavaAstTreeUtil.getParameterListParameters(astNode) :
BasicJavaAstTreeUtil.getAnnotationParameterListAttributes(astNode);
int offset;
if (params.length == 0) {
offset = astNode.getTextRange().getStartOffset() + 1;
}
else {
offset = params[params.length - 1].getTextRange().getEndOffset();
}
editor.getDocument().insertString(offset, ")");
}
}
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_PARENTH_EXPRESSION;
public class ParenthesizedFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_PARENTH_EXPRESSION)) {
final ASTNode lastChild = astNode.getLastChildNode();
if (lastChild != null && !")".equals(lastChild.getText())) {
editor.getDocument().insertString(astNode.getTextRange().getEndOffset(), ")");
}
}
}
}
@@ -0,0 +1,193 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.ide.DataManager;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class PlainEnterProcessor implements ASTNodeEnterProcessor {
@Override
public boolean doEnter(@NotNull Editor editor, @NotNull ASTNode astNode, boolean isModified) {
if (expandCodeBlock(editor, astNode)) return true;
getEnterHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE).execute(editor, editor.getCaretModel().getCurrentCaret(),
EditorUtil.getEditorDataContext(editor));
return true;
}
public static boolean expandCodeBlock(@NotNull Editor editor, @Nullable ASTNode astNode) {
ASTNode block = getControlStatementBlock(editor.getCaretModel().getOffset(), astNode);
PsiElement psiBlock = BasicJavaAstTreeUtil.toPsi(block);
if (processExistingBlankLine(editor, psiBlock, astNode)) {
return true;
}
if (block == null) {
return false;
}
EditorActionHandler enterHandler = getEnterHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE);
ASTNode firstElement = BasicJavaAstTreeUtil.getFirstBodyElement(block);
if (firstElement == null) {
firstElement = BasicJavaAstTreeUtil.getRBrace(block);
// Plain enter processor inserts enter after the end of line, hence, we don't want to use it here because the line ends with
// the empty braces block. So, we get the following in case of default handler usage:
// Before:
// if (condition[caret]) {}
// After:
// if (condition) {}
// [caret]
enterHandler = getEnterHandler(IdeActions.ACTION_EDITOR_ENTER);
}
editor.getCaretModel().moveToOffset(firstElement != null ?
firstElement.getTextRange().getStartOffset() :
block.getTextRange().getEndOffset());
enterHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), EditorUtil.getEditorDataContext(editor));
return true;
}
private static EditorActionHandler getEnterHandler(String actionId) {
return EditorActionManager.getInstance().getActionHandler(actionId);
}
@Nullable
private static ASTNode getControlStatementBlock(int caret, ASTNode astNode) {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_TRY_STATEMENT)) {
ASTNode tryBlock = BasicJavaAstTreeUtil.getCodeBlock(astNode);
if (tryBlock != null && caret < tryBlock.getTextRange().getEndOffset()) return tryBlock;
for (ASTNode catchBlock : BasicJavaAstTreeUtil.getCatchBlocks(astNode)) {
if (catchBlock != null && caret < catchBlock.getTextRange().getEndOffset()) return catchBlock;
}
return BasicJavaAstTreeUtil.getFinallyBlock(astNode);
}
if (BasicJavaAstTreeUtil.is(astNode, BASIC_SYNCHRONIZED_STATEMENT)) {
return BasicJavaAstTreeUtil.getCodeBlock(astNode);
}
if (BasicJavaAstTreeUtil.is(astNode, BASIC_METHOD)) {
ASTNode methodBody = BasicJavaAstTreeUtil.getCodeBlock(astNode);
if (methodBody != null) return methodBody;
}
if (BasicJavaAstTreeUtil.is(astNode, BASIC_SWITCH_STATEMENT)) {
return BasicJavaAstTreeUtil.getCodeBlock(astNode);
}
ASTNode body = null;
if (BasicJavaAstTreeUtil.is(astNode, BASIC_IF_STATEMENT)) {
body = BasicJavaAstTreeUtil.getThenBranch(astNode);
if (body != null && caret > body.getTextRange().getEndOffset()) {
body = BasicJavaAstTreeUtil.getElseBranch(astNode);
}
}
else if (BasicJavaAstTreeUtil.is(astNode, BASIC_WHILE_STATEMENT)) {
body = BasicJavaAstTreeUtil.getBlock(astNode);
}
else if (BasicJavaAstTreeUtil.is(astNode, BASIC_FOR_STATEMENT)) {
body = BasicJavaAstTreeUtil.getForBody(astNode);
}
else if (BasicJavaAstTreeUtil.is(astNode, BASIC_FOREACH_STATEMENT)) {
body = BasicJavaAstTreeUtil.getBlock(astNode);
}
else if (BasicJavaAstTreeUtil.is(astNode, BASIC_DO_WHILE_STATEMENT)) {
body = BasicJavaAstTreeUtil.getDoWhileBody(astNode);
}
return BasicJavaAstTreeUtil.is(body, BASIC_BLOCK_STATEMENT) ?
BasicJavaAstTreeUtil.getCodeBlock(body) : null;
}
/**
* There is a possible case that target code block already starts with the empty line:
* <pre>
* void test(int i) {
* if (i > 1[caret]) {
*
* }
* }
* </pre>
* We want just move caret to correct position at that empty line without creating additional empty line then.
*
* @param editor target editor
* @param codeBlock target code block to which new empty line is going to be inserted
* @param element target element under caret
* @return {@code true} if it was found out that the given code block starts with the empty line and caret
* is pointed to correct position there, i.e. no additional processing is required;
* {@code false} otherwise
*/
private static boolean processExistingBlankLine(@NotNull Editor editor, @Nullable PsiElement codeBlock, @Nullable ASTNode element) {
PsiWhiteSpace whiteSpace = null;
if (codeBlock == null) {
PsiElement psiElement = BasicJavaAstTreeUtil.toPsi(element);
if (psiElement != null && !(BasicJavaAstTreeUtil.is(element, MEMBER_SET))) {
final PsiElement next = PsiTreeUtil.nextLeaf(psiElement);
if (next instanceof PsiWhiteSpace) {
whiteSpace = (PsiWhiteSpace)next;
}
}
}
else {
whiteSpace = PsiTreeUtil.findChildOfType(codeBlock, PsiWhiteSpace.class);
if (whiteSpace == null) {
return false;
}
PsiElement lbraceCandidate = whiteSpace.getPrevSibling();
if (lbraceCandidate == null) {
return false;
}
ASTNode node = lbraceCandidate.getNode();
if (node == null || node.getElementType() != JavaTokenType.LBRACE) {
return false;
}
}
if (whiteSpace == null) {
return false;
}
final TextRange textRange = whiteSpace.getTextRange();
final Document document = editor.getDocument();
final CharSequence whiteSpaceText = document.getCharsSequence().subSequence(textRange.getStartOffset(), textRange.getEndOffset());
if (StringUtil.countNewLines(whiteSpaceText) < 2) {
return false;
}
int i = CharArrayUtil.shiftForward(whiteSpaceText, 0, " \t");
if (i >= whiteSpaceText.length() - 1) {
assert false : String.format("code block: %s, white space: %s",
codeBlock == null ? "undefined" : codeBlock.getTextRange(),
whiteSpace.getTextRange());
return false;
}
editor.getCaretModel().moveToOffset(i + 1 + textRange.getStartOffset());
EditorActionManager actionManager = EditorActionManager.getInstance();
EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_LINE_END);
final DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent());
actionHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext);
return true;
}
}
@@ -0,0 +1,44 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_SWITCH_EXPRESSION;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_SWITCH_STATEMENT;
public class SwitchExpressionFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_SWITCH_EXPRESSION) ||
BasicJavaAstTreeUtil.is(astNode, BASIC_SWITCH_STATEMENT)
) {
final Document doc = editor.getDocument();
final ASTNode rParenth = BasicJavaAstTreeUtil.getRParenth(astNode);
final ASTNode lParenth = BasicJavaAstTreeUtil.getLParenth(astNode);
final ASTNode condition = BasicJavaAstTreeUtil.getExpression(astNode);
if (condition == null) {
if (lParenth == null || rParenth == null) {
int stopOffset = doc.getLineEndOffset(doc.getLineNumber(astNode.getTextRange().getStartOffset()));
final ASTNode block = BasicJavaAstTreeUtil.getCodeBlock(astNode);
if (block != null) {
stopOffset = Math.min(stopOffset, block.getTextRange().getStartOffset());
}
doc.replaceString(astNode.getTextRange().getStartOffset(), stopOffset, "switch ()");
}
else {
processor.registerUnresolvedError(lParenth.getTextRange().getEndOffset());
}
}
else if (rParenth == null) {
doc.insertString(condition.getTextRange().getEndOffset(), ")");
}
}
}
}
@@ -0,0 +1,32 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtilEx;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_CONDITIONAL_EXPRESSION;
public class TernaryColonFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (!(BasicJavaAstTreeUtil.is(astNode, BASIC_CONDITIONAL_EXPRESSION))) {
return;
}
if (BasicJavaAstTreeUtil.getConditionalExpressionThenExpression(astNode) == null ||
astNode.findChildByType(JavaTokenType.COLON) != null) {
return;
}
editor.getCaretModel().moveToOffset(astNode.getTextRange().getEndOffset());
EditorModificationUtilEx.insertStringAtCaret(editor, ": ");
processor.setSkipEnter(true);
}
}
@@ -0,0 +1,41 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.smartEnter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_WHILE_STATEMENT;
public class WhileConditionFixer implements Fixer {
@Override
public void apply(Editor editor, AbstractBasicJavaSmartEnterProcessor processor, @NotNull ASTNode astNode) throws IncorrectOperationException {
if (BasicJavaAstTreeUtil.is(astNode, BASIC_WHILE_STATEMENT)) {
final Document doc = editor.getDocument();
final ASTNode rParenth = BasicJavaAstTreeUtil.getRParenth(astNode);
final ASTNode lParenth = BasicJavaAstTreeUtil.getLParenth(astNode);
final ASTNode condition = BasicJavaAstTreeUtil.getWhileCondition(astNode);
if (condition == null) {
if (lParenth == null || rParenth == null) {
int stopOffset = doc.getLineEndOffset(doc.getLineNumber(astNode.getTextRange().getStartOffset()));
final ASTNode block = BasicJavaAstTreeUtil.getWhileBody(astNode);
if (block != null) {
stopOffset = Math.min(stopOffset, block.getTextRange().getStartOffset());
}
stopOffset = Math.min(stopOffset, astNode.getTextRange().getEndOffset());
doc.replaceString(astNode.getTextRange().getStartOffset(), stopOffset, "while ()");
processor.registerUnresolvedError(astNode.getTextRange().getStartOffset() + "while (".length());
} else {
processor.registerUnresolvedError(lParenth.getTextRange().getEndOffset());
}
} else if (rParenth == null) {
doc.insertString(condition.getTextRange().getEndOffset(), ")");
}
}
}
}
@@ -0,0 +1,45 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandlerBase;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.psi.PsiElement;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.function.Predicate;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_BIT_SET;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.*;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public abstract class AbstractBasicBackBasicSelectioner extends ExtendWordSelectionHandlerBase {
private static Predicate<PsiElement> getElementPredicate() {
return (e) -> {
Language language = e.getLanguage();
return !(language instanceof XMLLanguage || language.isKindOf(XMLLanguage.INSTANCE));
};
}
@Override
public boolean canSelect(@NotNull final PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return
!BasicJavaAstTreeUtil.is(node, TokenType.WHITE_SPACE) &&
!BasicJavaAstTreeUtil.is(node, JAVA_COMMENT_BIT_SET) &&
!BasicJavaAstTreeUtil.is(node, BASIC_CODE_BLOCK) &&
!BasicJavaAstTreeUtil.is(node, BASIC_ARRAY_INITIALIZER_EXPRESSION) &&
!BasicJavaAstTreeUtil.is(node, BASIC_PARAMETER_LIST) &&
!BasicJavaAstTreeUtil.is(node, BASIC_EXPRESSION_LIST) &&
!BasicJavaAstTreeUtil.is(node, BASIC_BLOCK_STATEMENT) &&
!BasicJavaAstTreeUtil.is(node, JAVA_CODE_REFERENCE_ELEMENT_SET) &&
!(BasicJavaAstTreeUtil.isJavaToken(node) &&
!BasicJavaAstTreeUtil.isKeyword(node)) &&
!BasicJavaAstTreeUtil.is(node, DOC_TAG, DOC_SNIPPET_TAG, DOC_INLINE_TAG) &&
getElementPredicate().test(e);
}
}
@@ -0,0 +1,57 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandlerBase;
import com.intellij.lang.Language;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_BIT_SET;
public class AntLikePropertySelectionHandler extends ExtendWordSelectionHandlerBase {
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
TextRange range = e.getTextRange();
char prevLeftChar = ' ';
for (int left = Math.min(cursorOffset, editor.getDocument().getTextLength() - 1); left >= range.getStartOffset(); left--) {
char leftChar = editorText.charAt(left);
if (leftChar == '}') return Collections.emptyList();
if (leftChar == '$' && prevLeftChar == '{') {
for (int right = cursorOffset; right < range.getEndOffset(); right++) {
char rightChar = editorText.charAt(right);
if (rightChar == '{') return Collections.emptyList();
if (rightChar == '}') {
return Arrays.asList(new TextRange(left + 2, right), new TextRange(left, right + 1));
}
}
}
prevLeftChar = leftChar;
}
return Collections.emptyList();
}
@Override
public boolean canSelect(@NotNull PsiElement e) {
Language l = e.getLanguage();
if (!(l.equals(JavaLanguage.INSTANCE)
|| l.equals(XMLLanguage.INSTANCE))) {
return false;
}
if (BasicJavaAstTreeUtil.getParentOfType(BasicJavaAstTreeUtil.toNode(e), JAVA_COMMENT_BIT_SET) == null) {
return true;
}
return PsiTreeUtil.getParentOfType(e, PsiComment.class) == null;
}
}
@@ -0,0 +1,92 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.DocumentUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class CaseStatementsSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return node != null &&
BasicJavaAstTreeUtil.is(node.getTreeParent(), BASIC_CODE_BLOCK) &&
BasicJavaAstTreeUtil.is(node.getTreeParent().getTreeParent(), BASIC_SWITCH_STATEMENT);
}
@Override
public List<TextRange> select(@NotNull PsiElement elementStatement,
@NotNull CharSequence editorText,
int cursorOffset,
@NotNull Editor editor) {
ASTNode statement = BasicJavaAstTreeUtil.toNode(elementStatement);
List<TextRange> result = new ArrayList<>();
ASTNode caseStart = statement;
ASTNode caseEnd = statement;
if (statement == null ||
BasicJavaAstTreeUtil.is(statement, BASIC_SWITCH_STATEMENT)) {
return result;
}
ASTNode labelStatement = BasicJavaAstTreeUtil.is(statement, BASIC_SWITCH_LABEL_STATEMENT) ? statement : null;
ASTNode sibling;
if (labelStatement == null) {
sibling = statement.getTreePrev();
while (sibling != null && !BasicJavaAstTreeUtil.is(sibling, BASIC_SWITCH_LABEL_STATEMENT)) {
if (!BasicJavaAstTreeUtil.isWhiteSpace(sibling)) caseStart = sibling;
sibling = sibling.getTreePrev();
}
labelStatement = sibling;
}
if (labelStatement != null) {
ASTNode nextLabel = BasicJavaAstTreeUtil.skipSiblingsBackward(labelStatement, TokenType.WHITE_SPACE);
while (BasicJavaAstTreeUtil.is(nextLabel, BASIC_SWITCH_LABEL_STATEMENT)) {
labelStatement = nextLabel;
nextLabel = BasicJavaAstTreeUtil.skipSiblingsBackward(labelStatement, TokenType.WHITE_SPACE);
}
}
sibling = BasicJavaAstTreeUtil.isWhiteSpace(statement) ? statement.getTreeNext() : statement;
while (BasicJavaAstTreeUtil.is(sibling, BASIC_SWITCH_LABEL_STATEMENT)) {
sibling = BasicJavaAstTreeUtil.skipSiblingsForward(sibling, TokenType.WHITE_SPACE);
}
while (sibling != null && !BasicJavaAstTreeUtil.is(sibling, BASIC_SWITCH_LABEL_STATEMENT)) {
if (!BasicJavaAstTreeUtil.isWhiteSpace(sibling) &&
!BasicJavaAstTreeUtil.isJavaToken(sibling) // end of switch
) {
caseEnd = sibling;
}
sibling = sibling.getTreeNext();
}
Document document = editor.getDocument();
int endOffset =
DocumentUtil.getLineEndOffset(BasicJavaAstTreeUtil.getTextOffset(caseEnd) + caseEnd.getTextLength(), document) + 1;
if (!BasicJavaAstTreeUtil.is(caseStart, BASIC_SWITCH_LABEL_STATEMENT)) {
result.add(new TextRange(DocumentUtil.getLineStartOffset(BasicJavaAstTreeUtil.getTextOffset(caseStart), document),
endOffset));
}
if (labelStatement != null) {
result.add(
new TextRange(DocumentUtil.getLineStartOffset(BasicJavaAstTreeUtil.getTextOffset(labelStatement), document),
endOffset));
}
return result;
}
}
@@ -0,0 +1,98 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class CodeBlockOrInitializerSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return BasicJavaAstTreeUtil.is(node, BASIC_CODE_BLOCK) ||
BasicJavaAstTreeUtil.is(node, BASIC_ARRAY_INITIALIZER_EXPRESSION) ||
BasicJavaAstTreeUtil.is(node, CLASS_SET) &&
!BasicJavaAstTreeUtil.is(node, BASIC_TYPE_PARAMETER);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = new ArrayList<>();
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
result.add(getElementRange(node));
List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(node);
if (!children.isEmpty()) {
int start = findOpeningBrace(children);
// in non-Java PsiClasses, there can be no opening brace
if (start != 0) {
int end = findClosingBrace(children, start);
result.addAll(expandToWholeLine(editorText, new TextRange(start, end)));
}
}
return result;
}
public TextRange getElementRange(@NotNull ASTNode astNode) {
if (BasicJavaAstTreeUtil.is(astNode, CLASS_SET)) {
ASTNode lBrace = BasicJavaAstTreeUtil.getLBrace(astNode);
ASTNode rBrace = BasicJavaAstTreeUtil.getRBrace(astNode);
if (lBrace != null && rBrace != null) {
return new TextRange(BasicJavaAstTreeUtil.getTextOffset(lBrace), rBrace.getTextRange().getEndOffset());
}
}
return astNode.getTextRange();
}
public static int findOpeningBrace(List<ASTNode> children) {
int start = 0;
for (int i = 0; i < children.size(); i++) {
ASTNode child = children.get(i);
if (BasicJavaAstTreeUtil.is(child, JavaTokenType.LBRACE)) {
int j = i + 1;
while (BasicJavaAstTreeUtil.isWhiteSpace(children.get(j))) {
j++;
}
start = children.get(j).getTextRange().getStartOffset();
}
}
return start;
}
public static int findClosingBrace(List<ASTNode> children, int startOffset) {
int end = children.get(children.size() - 1).getTextRange().getEndOffset();
for (int i = 0; i < children.size(); i++) {
ASTNode child = children.get(i);
if (BasicJavaAstTreeUtil.is(child, JavaTokenType.RBRACE)) {
int j = i - 1;
while (BasicJavaAstTreeUtil.isWhiteSpace(children.get(j)) && children.get(j).getTextRange().getStartOffset() > startOffset) {
j--;
}
end = children.get(j).getTextRange().getEndOffset();
}
}
return end;
}
}
@@ -0,0 +1,68 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.DOC_COMMENT;
public class DocCommentSelectioner extends LineCommentSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), DOC_COMMENT);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = super.select(e, editorText, cursorOffset, editor);
if (result == null) {
return null;
}
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(node);
int startOffset = e.getTextRange().getStartOffset();
int endOffset = e.getTextRange().getEndOffset();
for (ASTNode child : children) {
if (BasicJavaAstTreeUtil.is(child, JavaDocTokenType.DOC_COMMENT_DATA)) {
char[] chars = child.getText().toCharArray();
if (CharArrayUtil.shiftForward(chars, 0, " *\n\t\r") != chars.length) {
break;
}
}
startOffset = child.getTextRange().getEndOffset();
}
for (ASTNode child : children) {
if (BasicJavaAstTreeUtil.is(child, JavaDocTokenType.DOC_COMMENT_DATA)) {
char[] chars = child.getText().toCharArray();
if (CharArrayUtil.shiftForward(chars, 0, " *\n\t\r") != chars.length) {
endOffset = child.getTextRange().getEndOffset();
}
}
}
startOffset = CharArrayUtil.shiftBackward(editorText, startOffset - 1, "* \t") + 1;
result.add(new TextRange(startOffset, endOffset));
return result;
}
}
@@ -0,0 +1,75 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.*;
public class DocTagSelectioner extends WordSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return BasicJavaAstTreeUtil.is(node, DOC_TAG, DOC_SNIPPET_TAG, DOC_INLINE_TAG);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = super.select(e, editorText, cursorOffset, editor);
if (result == null) {
return null;
}
result.add(getDocTagRange(e, editorText, cursorOffset));
return result;
}
public static TextRange getDocTagRange(@NotNull PsiElement e, @NotNull CharSequence documentText, int minOffset) {
TextRange range = e.getTextRange();
int endOffset = range.getEndOffset();
int startOffset = range.getStartOffset();
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(node);
for (int i = children.size() - 1; i >= 0; i--) {
ASTNode child = children.get(i);
int childStartOffset = child.getTextRange().getStartOffset();
if (childStartOffset <= minOffset) {
break;
}
if (BasicJavaAstTreeUtil.isDocToken(child)) {
CharSequence chars = child.getChars();
int shift = CharArrayUtil.shiftForward(chars, 0, " \t\n\r");
if (shift != chars.length() && !BasicJavaAstTreeUtil.is(child, JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS)) {
break;
}
}
else if (!(BasicJavaAstTreeUtil.isWhiteSpace(child))) {
break;
}
endOffset = Math.min(childStartOffset, endOffset);
}
startOffset = CharArrayUtil.shiftBackward(documentText, startOffset - 1, "* \t") + 1;
return new TextRange(startOffset, endOffset);
}
}
@@ -0,0 +1,64 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_ENUM_CONSTANT;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_FIELD;
public class FieldSelectioner extends WordSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), BASIC_FIELD, BASIC_ENUM_CONSTANT) &&
e.getLanguage() == JavaLanguage.INSTANCE;
}
private static void addRangeElem(final List<? super TextRange> result,
CharSequence editorText,
final ASTNode first,
final int end) {
if (first != null) {
result.addAll(expandToWholeLine(editorText,
new TextRange(first.getTextRange().getStartOffset(), end)));
}
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = super.select(e, editorText, cursorOffset, editor);
if (result == null) {
return null;
}
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
TextRange fieldRange = node.getTextRange();
ASTNode nameId = BasicJavaAstTreeUtil.getNameIdentifier(node);
if (nameId == null) return new ArrayList<>();
TextRange nameRange = nameId.getTextRange();
ASTNode last = BasicJavaAstTreeUtil.getInitializer(node);
int end = last == null ? nameRange.getEndOffset() : last.getTextRange().getEndOffset();
ASTNode comment = BasicJavaAstTreeUtil.getDocComment(node);
if (comment != null) {
TextRange commentTextRange = comment.getTextRange();
addRangeElem(result, editorText, comment, commentTextRange.getEndOffset());
}
addRangeElem(result, editorText, nameId, end);
addRangeElem(result, editorText, BasicJavaAstTreeUtil.getTypeElement(node), nameRange.getEndOffset());
addRangeElem(result, editorText, BasicJavaAstTreeUtil.getModifierList(node), fieldRange.getEndOffset());
result.addAll(expandToWholeLine(editorText, fieldRange));
return result;
}
}
@@ -0,0 +1,41 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_TRY_STATEMENT;
public class FinallyBlockSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), JavaTokenType.FINALLY_KEYWORD);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = new ArrayList<>();
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
final ASTNode parent = node.getTreeParent();
if (BasicJavaAstTreeUtil.is(parent, BASIC_TRY_STATEMENT)) {
final ASTNode finallyBlock = BasicJavaAstTreeUtil.getFinallyBlock(parent);
if (finallyBlock != null) {
result.add(new TextRange(e.getTextRange().getStartOffset(), finallyBlock.getTextRange().getEndOffset()));
}
}
return result;
}
}
@@ -0,0 +1,40 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandler;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_FOREACH_STATEMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_FOR_STATEMENT;
public class ForStatementHeaderSelectioner implements ExtendWordSelectionHandler {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return
BasicJavaAstTreeUtil.is(node, BASIC_FOR_STATEMENT) ||
BasicJavaAstTreeUtil.is(node, BASIC_FOREACH_STATEMENT);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
ASTNode lParen = BasicJavaAstTreeUtil.getLParenth(node);
ASTNode rParen = BasicJavaAstTreeUtil.getRParenth(node);
if (lParen == null || rParen == null) return null;
TextRange result = new TextRange(lParen.getTextRange().getEndOffset(), rParen.getTextRange().getStartOffset());
return result.containsOffset(cursorOffset) ? Collections.singletonList(result) : null;
}
}
@@ -0,0 +1,62 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_IF_STATEMENT;
public class IfStatementSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), BASIC_IF_STATEMENT);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = new ArrayList<>(expandToWholeLine(editorText, e.getTextRange(), false));
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
final ASTNode elseKeyword = BasicJavaAstTreeUtil.getElseElement(node);
if (elseKeyword != null) {
final ASTNode then = BasicJavaAstTreeUtil.getThenBranch(node);
if (then != null) {
final TextRange thenRange = new TextRange(e.getTextRange().getStartOffset(), then.getTextRange().getEndOffset());
if (thenRange.contains(cursorOffset)) {
result.addAll(expandToWholeLine(editorText, thenRange, false));
}
}
result.addAll(expandToWholeLine(editorText,
new TextRange(elseKeyword.getTextRange().getStartOffset(),
node.getTextRange().getEndOffset()),
false));
final ASTNode branch = BasicJavaAstTreeUtil.getElseBranch(node);
if (BasicJavaAstTreeUtil.is(branch, BASIC_IF_STATEMENT)) {
final ASTNode element = BasicJavaAstTreeUtil.getElseElement(branch);
if (element != null) {
final ASTNode elseThen = BasicJavaAstTreeUtil.getThenBranch(branch);
if (elseThen != null) {
result.addAll(expandToWholeLine(editorText,
new TextRange(elseKeyword.getTextRange().getStartOffset(),
elseThen.getTextRange().getEndOffset()),
false));
}
}
}
}
return result;
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.*;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class JavaBasicWordSelectionFilter implements Condition<PsiElement> {
public JavaBasicWordSelectionFilter() {
}
@Override
public boolean value(final PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return !BasicJavaAstTreeUtil.is(node, BASIC_CODE_BLOCK) &&
!BasicJavaAstTreeUtil.is(node, BASIC_ARRAY_INITIALIZER_EXPRESSION) &&
!BasicJavaAstTreeUtil.is(node, BASIC_PARAMETER_LIST) &&
!BasicJavaAstTreeUtil.is(node, BASIC_EXPRESSION_LIST) &&
!BasicJavaAstTreeUtil.is(node, BASIC_BLOCK_STATEMENT) &&
!BasicJavaAstTreeUtil.is(node, JAVA_CODE_REFERENCE_ELEMENT_SET) &&
!BasicJavaAstTreeUtil.isJavaToken(node) &&
!BasicJavaAstTreeUtil.is(node, DOC_TAG, DOC_SNIPPET_TAG, DOC_INLINE_TAG) &&
!(BasicJavaAstTreeUtil.isDocToken(node) &&
BasicJavaAstTreeUtil.is(node, JavaDocTokenType.DOC_COMMENT_DATA));
}
}
@@ -0,0 +1,42 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_CODE_BLOCK;
import static com.intellij.psi.impl.source.BasicJavaElementType.CLASS_SET;
public class JavaTokenSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return BasicJavaAstTreeUtil.isJavaToken(node) && !BasicJavaAstTreeUtil.isKeyword(node) &&
!BasicJavaAstTreeUtil.is(node.getTreeParent(), BASIC_CODE_BLOCK) &&
!BasicJavaAstTreeUtil.is(node.getTreeParent(), CLASS_SET);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
if (!BasicJavaAstTreeUtil.is(node, JavaTokenType.SEMICOLON) &&
!BasicJavaAstTreeUtil.is(node, JavaTokenType.LPARENTH)) {
return super.select(e, editorText, cursorOffset, editor);
}
else {
return null;
}
}
}
@@ -0,0 +1,50 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.List;
public class JavaWordSelectioner extends AbstractWordSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (BasicJavaAstTreeUtil.isKeyword(node)) {
return true;
}
if (BasicJavaAstTreeUtil.isJavaToken(node)) {
return BasicJavaAstTreeUtil.is(node, JavaTokenType.IDENTIFIER) || BasicJavaAstTreeUtil.is(node, JavaTokenType.STRING_LITERAL);
}
return false;
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> ranges = super.select(e, editorText, cursorOffset, editor);
if (ranges == null) {
return null;
}
if (BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), JavaTokenType.STRING_LITERAL)) {
killRangesBreakingEscapes(e, ranges, e.getTextRange());
}
return ranges;
}
private static void killRangesBreakingEscapes(PsiElement e, List<TextRange> ranges, TextRange literalRange) {
for (Iterator<TextRange> iterator = ranges.iterator(); iterator.hasNext(); ) {
TextRange each = iterator.next();
if (literalRange.contains(each) &&
literalRange.getStartOffset() < each.getStartOffset() &&
e.getText().charAt(each.getStartOffset() - literalRange.getStartOffset() - 1) == '\\') {
iterator.remove();
}
}
}
}
@@ -0,0 +1,55 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class ListSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return BasicJavaAstTreeUtil.is(node, BASIC_PARAMETER_LIST) ||
BasicJavaAstTreeUtil.is(node, BASIC_EXPRESSION_LIST) ||
BasicJavaAstTreeUtil.is(node, BASIC_RECORD_HEADER);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(node);
int start = 0;
int end = 0;
for (ASTNode child : children) {
if (BasicJavaAstTreeUtil.isJavaToken(child)) {
if (BasicJavaAstTreeUtil.is(child, JavaTokenType.LPARENTH)) {
start = BasicJavaAstTreeUtil.getTextOffset(child) + 1;
}
if (BasicJavaAstTreeUtil.is(child, JavaTokenType.RPARENTH)) {
end = BasicJavaAstTreeUtil.getTextOffset(child);
}
}
}
List<TextRange> result = new ArrayList<>();
if (start != 0 && end != 0) {
result.add(new TextRange(start, end));
}
return result;
}
}
@@ -0,0 +1,87 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.codeInsight.editorActions.SelectWordUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lexer.StringLiteralLexer;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.BasicLiteralUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.psi.impl.source.BasicElementTypes.STRING_LITERALS;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_LITERAL_EXPRESSION;
public class LiteralSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
PsiElement parent = e.getParent();
return isStringLiteral(e) || isStringLiteral(parent);
}
private static boolean isStringLiteral(PsiElement element) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(element), STRING_LITERALS)
&& element.getText().startsWith("\"")
&& element.getText().endsWith("\"");
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = super.select(e, editorText, cursorOffset, editor);
if (result == null) {
return null;
}
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
TextRange range = node.getTextRange();
SelectWordUtil.addWordHonoringEscapeSequences(editorText, range, cursorOffset,
new StringLiteralLexer('\"', JavaTokenType.STRING_LITERAL),
result);
ASTNode literalExpression = null;
if (BasicJavaAstTreeUtil.is(node, BASIC_LITERAL_EXPRESSION)) {
literalExpression = node;
}
if (literalExpression == null) {
ASTNode parent = node.getTreeParent();
if (BasicJavaAstTreeUtil.is(parent, BASIC_LITERAL_EXPRESSION)) {
literalExpression = parent;
}
}
PsiElement literalPsiExpression = BasicJavaAstTreeUtil.toPsi(literalExpression);
if (literalExpression != null && literalPsiExpression != null && BasicJavaAstTreeUtil.isTextBlock(literalExpression)) {
int contentStart = StringUtil.indexOf(editorText, '\n', range.getStartOffset());
if (contentStart == -1) return result;
contentStart += 1;
int indent = BasicLiteralUtil.getTextBlockIndent(literalPsiExpression);
if (indent == -1) return result;
for (int i = 0; i < indent; i++) {
if (editorText.charAt(contentStart + i) == '\n') return result;
}
int start = contentStart + indent;
int end = range.getEndOffset() - 4;
for (; end >= start; end--) {
char c = editorText.charAt(end);
if (c == '\n') break;
if (!Character.isWhitespace(c)) {
end += 1;
break;
}
}
if (start < end) result.add(new TextRange(start, end));
}
else {
result.add(new TextRange(range.getStartOffset() + 1, range.getEndOffset() - 1));
}
return result;
}
}
@@ -0,0 +1,43 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandler;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_METHOD_CALL_EXPRESSION;
public class MethodCallSelectioner implements ExtendWordSelectionHandler {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), BASIC_METHOD_CALL_EXPRESSION);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
ASTNode methodExpression = BasicJavaAstTreeUtil.getMethodExpression(node);
if (methodExpression == null) {
return null;
}
ASTNode referenceNameElement = BasicJavaAstTreeUtil.getReferenceNameElement(methodExpression);
if (referenceNameElement == null) {
return null;
}
else {
return Arrays.asList(new TextRange(referenceNameElement.getTextRange().getStartOffset(), e.getTextRange().getEndOffset()),
e.getTextRange());
}
}
}
@@ -0,0 +1,118 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_BIT_SET;
import static com.intellij.psi.impl.source.BasicElementTypes.JAVA_COMMENT_OR_WHITESPACE_BIT_SET;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.DOC_COMMENT;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class MethodOrClassSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return (
BasicJavaAstTreeUtil.is(node, CLASS_SET) &&
!BasicJavaAstTreeUtil.is(node, BASIC_TYPE_PARAMETER) ||
BasicJavaAstTreeUtil.is(node, BASIC_METHOD)) &&
e.getLanguage() == JavaLanguage.INSTANCE;
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = new ArrayList<>();
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return result;
}
ASTNode firstChild = node.getFirstChildNode();
List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(node);
int i = 1;
if (BasicJavaAstTreeUtil.is(firstChild, DOC_COMMENT)) {
while (BasicJavaAstTreeUtil.isWhiteSpace(children.get(i))) {
i++;
}
TextRange range = new TextRange(children.get(i).getTextRange().getStartOffset(), e.getTextRange().getEndOffset());
result.add(range);
result.addAll(expandToWholeLinesWithBlanks(editorText, range));
range = firstChild.getTextRange();
result.addAll(expandToWholeLinesWithBlanks(editorText, range));
firstChild = children.get(i++);
}
if (BasicJavaAstTreeUtil.is(firstChild, JAVA_COMMENT_BIT_SET)) {
while (BasicJavaAstTreeUtil.is(children.get(i), JAVA_COMMENT_OR_WHITESPACE_BIT_SET)) {
i++;
}
ASTNode last = BasicJavaAstTreeUtil.isWhiteSpace(children.get(i - 1)) ? children.get(i - 2) : children.get(i - 1);
TextRange range = new TextRange(firstChild.getTextRange().getStartOffset(), last.getTextRange().getEndOffset());
if (range.contains(cursorOffset)) {
result.addAll(expandToWholeLinesWithBlanks(editorText, range));
}
range = new TextRange(children.get(i).getTextRange().getStartOffset(), e.getTextRange().getEndOffset());
result.add(range);
result.addAll(expandToWholeLinesWithBlanks(editorText, range));
}
result.add(node.getTextRange());
result.addAll(expandToWholeLinesWithBlanks(editorText, node.getTextRange()));
if (BasicJavaAstTreeUtil.is(node, CLASS_SET)) {
result.addAll(selectWithTypeParameters(node));
result.addAll(selectBetweenBracesLines(children, editorText));
}
if (BasicJavaAstTreeUtil.is(node, BASIC_ANONYMOUS_CLASS)) {
result.addAll(selectWholeBlock(node));
}
return result;
}
private static Collection<TextRange> selectWithTypeParameters(@NotNull ASTNode astClass) {
final ASTNode identifier = BasicJavaAstTreeUtil.getNameIdentifier(astClass);
final ASTNode list = BasicJavaAstTreeUtil.getTypeParameterList(astClass);
if (identifier != null && list != null) {
return Collections.singletonList(new TextRange(identifier.getTextRange().getStartOffset(), list.getTextRange().getEndOffset()));
}
return Collections.emptyList();
}
private static Collection<TextRange> selectBetweenBracesLines(List<ASTNode> children,
@NotNull CharSequence editorText) {
int start = CodeBlockOrInitializerSelectioner.findOpeningBrace(children);
// in non-Java PsiClasses, there can be no opening brace
if (start != 0) {
int end = CodeBlockOrInitializerSelectioner.findClosingBrace(children, start);
return expandToWholeLinesWithBlanks(editorText, new TextRange(start, end));
}
return Collections.emptyList();
}
private static Collection<TextRange> selectWholeBlock(ASTNode clazz) {
ASTNode lBrace = BasicJavaAstTreeUtil.getLBrace(clazz);
ASTNode rBrace = BasicJavaAstTreeUtil.getRBrace(clazz);
if (lBrace != null && rBrace != null) {
return Collections.singleton(new TextRange(lBrace.getTextRange().getStartOffset(), rBrace.getTextRange().getEndOffset()));
}
return Collections.emptyList();
}
}
@@ -0,0 +1,83 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class ReferenceSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
return BasicJavaAstTreeUtil.is(node, JAVA_CODE_REFERENCE_ELEMENT_SET);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
ASTNode endElement = node;
if (endElement == null) {
return null;
}
while (BasicJavaAstTreeUtil.is(endElement, JAVA_CODE_REFERENCE_ELEMENT_SET) &&
endElement.getTreeNext() != null) {
endElement = endElement.getTreeNext();
}
if (!(BasicJavaAstTreeUtil.is(endElement, JAVA_CODE_REFERENCE_ELEMENT_SET)) &&
!(BasicJavaAstTreeUtil.is(endElement.getTreePrev(), REFERENCE_EXPRESSION_SET) &&
BasicJavaAstTreeUtil.is(endElement, BASIC_EXPRESSION_LIST))) {
endElement = endElement.getTreePrev();
}
ASTNode element = node;
List<TextRange> result = new ArrayList<>();
while (BasicJavaAstTreeUtil.is(element, JAVA_CODE_REFERENCE_ELEMENT_SET)) {
ASTNode firstChild = element.getFirstChildNode();
ASTNode referenceName = BasicJavaAstTreeUtil.getReferenceNameElement(element);
if (referenceName != null) {
result.addAll(expandToWholeLine(editorText, new TextRange(referenceName.getTextRange().getStartOffset(),
endElement.getTextRange().getEndOffset())));
if (BasicJavaAstTreeUtil.is(endElement, JAVA_CODE_REFERENCE_ELEMENT_SET)) {
final ASTNode endReferenceName = BasicJavaAstTreeUtil.getReferenceNameElement(endElement);
if (endReferenceName != null) {
result.addAll(expandToWholeLine(editorText, new TextRange(referenceName.getTextRange().getStartOffset(),
endReferenceName.getTextRange().getEndOffset())));
}
}
}
if (firstChild == null) break;
element = firstChild;
}
TextRange range = new TextRange(element.getTextRange().getStartOffset(),
endElement.getTextRange().getEndOffset());
result.add(range);
result.addAll(expandToWholeLine(editorText, range));
if (!(BasicJavaAstTreeUtil.is(node.getTreeParent(), JAVA_CODE_REFERENCE_ELEMENT_SET))) {
if (BasicJavaAstTreeUtil.isJavaToken(node.getTreeNext()) ||
BasicJavaAstTreeUtil.isWhiteSpace(node.getTreeNext()) ||
BasicJavaAstTreeUtil.is(node.getTreeNext(), BASIC_EXPRESSION_LIST)) {
List<TextRange> superSelect = super.select(e, editorText, cursorOffset, editor);
if (superSelect != null) {
result.addAll(superSelect);
}
}
}
return result;
}
}
@@ -0,0 +1,92 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.*;
public class StatementGroupSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), STATEMENT_SET);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = new ArrayList<>();
PsiElement parentElement = e.getParent();
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return result;
}
ASTNode parentNode = BasicJavaAstTreeUtil.toNode(parentElement);
if (!BasicJavaAstTreeUtil.is(parentNode, BASIC_CODE_BLOCK) &&
!BasicJavaAstTreeUtil.is(parentNode, BASIC_BLOCK_STATEMENT) ||
BasicJavaAstTreeUtil.is(node, BASIC_SWITCH_LABEL_STATEMENT)) {
return result;
}
ASTNode startElement = node;
ASTNode endElement = node;
while (startElement.getTreePrev() != null) {
ASTNode sibling = startElement.getTreePrev();
if (BasicJavaAstTreeUtil.is(sibling, JavaTokenType.LBRACE)) break;
if (BasicJavaAstTreeUtil.isWhiteSpace(sibling)) {
String[] strings = LineTokenizer.tokenize(sibling.getText().toCharArray(), false);
if (strings.length > 2) {
break;
}
}
if (BasicJavaAstTreeUtil.is(sibling, BASIC_SWITCH_LABEL_STATEMENT)) break;
startElement = sibling;
}
while (BasicJavaAstTreeUtil.isWhiteSpace(startElement)) {
startElement = startElement.getTreeNext();
}
while (endElement.getTreeNext() != null) {
ASTNode sibling = endElement.getTreeNext();
if (BasicJavaAstTreeUtil.is(sibling, JavaTokenType.RBRACE)) break;
if (BasicJavaAstTreeUtil.isWhiteSpace(sibling)) {
String[] strings = LineTokenizer.tokenize(sibling.getText().toCharArray(), false);
if (strings.length > 2) {
break;
}
}
if (BasicJavaAstTreeUtil.is(sibling, BASIC_SWITCH_LABEL_STATEMENT)) break;
endElement = sibling;
}
while (BasicJavaAstTreeUtil.isWhiteSpace(endElement)) {
endElement = endElement.getTreePrev();
}
result.addAll(expandToWholeLine(editorText, new TextRange(startElement.getTextRange().getStartOffset(),
endElement.getTextRange().getEndOffset())));
return result;
}
}
@@ -0,0 +1,45 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.editorActions.wordSelection;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaElementType.BASIC_TYPE_CAST_EXPRESSION;
public class TypeCastSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), BASIC_TYPE_CAST_EXPRESSION);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = new ArrayList<>(expandToWholeLine(editorText, e.getTextRange(), false));
List<ASTNode> children = BasicJavaAstTreeUtil.getChildren(BasicJavaAstTreeUtil.toNode(e));
ASTNode lParen = null;
ASTNode rParen = null;
for (ASTNode child : children) {
if (BasicJavaAstTreeUtil.is(child, JavaTokenType.LPARENTH)) lParen = child;
if (BasicJavaAstTreeUtil.is(child, JavaTokenType.RPARENTH)) rParen = child;
}
if (lParen != null && rParen != null) {
result.addAll(expandToWholeLine(editorText,
new TextRange(lParen.getTextRange().getStartOffset(),
rParen.getTextRange().getEndOffset()),
false));
}
return result;
}
}
@@ -0,0 +1,82 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.highlighting;
import com.intellij.codeInsight.hint.DeclarationRangeUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.BracePair;
import com.intellij.lang.PairedBraceMatcher;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.java.IJavaElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.BasicElementTypes.*;
public class JavaBraceMatcher implements PairedBraceMatcher {
private final BracePair[] pairs = new BracePair[] {
new BracePair(JavaTokenType.LPARENTH, JavaTokenType.RPARENTH, false),
new BracePair(JavaTokenType.LBRACE, JavaTokenType.RBRACE, true),
new BracePair(JavaTokenType.LBRACKET, JavaTokenType.RBRACKET, false),
new BracePair(JavaDocTokenType.DOC_INLINE_TAG_START, JavaDocTokenType.DOC_INLINE_TAG_END, false),
new BracePair(JavaTokenType.LT, JavaTokenType.GT, false)
};
public JavaBraceMatcher() {
}
@Override
public BracePair @NotNull [] getPairs() {
return pairs;
}
@Override
public boolean isPairedBracesAllowedBeforeType(@NotNull final IElementType lbraceType, @Nullable final IElementType contextType) {
if (contextType instanceof IJavaElementType) return isPairedBracesAllowedBeforeTypeInJava(contextType);
return true;
}
private static boolean isPairedBracesAllowedBeforeTypeInJava(final IElementType tokenType) {
return JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(tokenType)
|| tokenType == JavaTokenType.SEMICOLON
|| tokenType == JavaTokenType.COMMA
|| tokenType == JavaTokenType.RPARENTH
|| tokenType == JavaTokenType.RBRACKET
|| tokenType == JavaTokenType.RBRACE
|| tokenType == JavaTokenType.LBRACE
|| tokenType == JavaTokenType.DOT;
}
@Override
public int getCodeConstructStart(final PsiFile file, int openingBraceOffset) {
PsiElement element = file.findElementAt(openingBraceOffset);
if (element == null || element instanceof PsiFile) return openingBraceOffset;
PsiElement parent = element.getParent();
if(parent==null) return openingBraceOffset;
ASTNode parentNode = parent.getNode();
if (BasicJavaAstTreeUtil.is(parentNode, BASIC_CODE_BLOCK)) {
parentNode = parentNode.getTreeParent();
if (BasicJavaAstTreeUtil.is(parentNode, BASIC_METHOD) ||
BasicJavaAstTreeUtil.is(parentNode, BASIC_CLASS_INITIALIZER)) {
TextRange range = DeclarationRangeUtil.getDeclarationRange(parentNode.getPsi());
return range.getStartOffset();
}
else if (BasicJavaAstTreeUtil.is(parentNode, JAVA_STATEMENT_BIT_SET)) {
if (BasicJavaAstTreeUtil.is(parentNode, BASIC_BLOCK_STATEMENT) &&
BasicJavaAstTreeUtil.is(parentNode.getTreeParent(), JAVA_STATEMENT_BIT_SET)) {
parentNode = parentNode.getTreeParent();
}
return parentNode.getTextRange().getStartOffset();
}
}
else if (BasicJavaAstTreeUtil.is(parentNode, CLASS_KEYWORD_BIT_SET)) {
TextRange range = DeclarationRangeUtil.getDeclarationRange(parent);
return range.getStartOffset();
}
return openingBraceOffset;
}
}
@@ -0,0 +1,37 @@
package com.intellij.codeInsight.highlighting;
import com.intellij.BaseJavaJspElementType;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.impl.source.BasicElementTypes;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
public class JavaPairedBraceMatcher extends PairedBraceAndAnglesMatcher {
private static class Holder {
private static final BasicJavaTokenSet TYPE_TOKENS =
BasicJavaTokenSet.orSet(BaseJavaJspElementType.WHITE_SPACE_BIT_SET,
BasicElementTypes.JAVA_COMMENT_BIT_SET,
BasicJavaTokenSet.create(JavaTokenType.IDENTIFIER, JavaTokenType.COMMA,
JavaTokenType.AT,//anno
JavaTokenType.RBRACKET, JavaTokenType.LBRACKET, //arrays
JavaTokenType.QUEST, JavaTokenType.EXTENDS_KEYWORD, JavaTokenType.SUPER_KEYWORD));//wildcards
}
public JavaPairedBraceMatcher() {
super(new JavaBraceMatcher(), JavaLanguage.INSTANCE, JavaFileType.INSTANCE, Holder.TYPE_TOKENS.toTokenSet());
}
@Override
public @NotNull IElementType lt() {
return JavaTokenType.LT;
}
@Override
public @NotNull IElementType gt() {
return JavaTokenType.GT;
}
}
@@ -0,0 +1,95 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.highlighting;
import com.intellij.lang.BracePair;
import com.intellij.lang.Language;
import com.intellij.lang.PairedBraceMatcher;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
public abstract class PairedBraceAndAnglesMatcher extends PairedBraceMatcherAdapter {
private final TokenSet myTokenSetAllowedInsideAngleBrackets;
private final LanguageFileType myFileType;
public PairedBraceAndAnglesMatcher(@NotNull PairedBraceMatcher matcher,
@NotNull Language language,
@NotNull LanguageFileType fileType,
@NotNull TokenSet tokenSetAllowedInsideAngleBrackets) {
super(matcher, language);
myTokenSetAllowedInsideAngleBrackets = tokenSetAllowedInsideAngleBrackets;
myFileType = fileType;
}
@Override
public boolean isLBraceToken(@NotNull HighlighterIterator iterator, @NotNull CharSequence fileText, @NotNull FileType fileType) {
return isBrace(iterator, fileText, fileType, true);
}
@Override
public boolean isRBraceToken(@NotNull HighlighterIterator iterator, @NotNull CharSequence fileText, @NotNull FileType fileType) {
return isBrace(iterator, fileText, fileType, false);
}
public abstract @NotNull IElementType lt();
public abstract @NotNull IElementType gt();
private boolean isBrace(HighlighterIterator iterator,
CharSequence fileText,
FileType fileType,
boolean left) {
final BracePair pair = findPair(left, iterator, fileText, fileType);
if (pair == null) return false;
final IElementType opposite = left ? gt() : lt();
if ((left ? pair.getRightBraceType() : pair.getLeftBraceType()) != opposite) return true;
if (fileType != myFileType) return false;
final IElementType braceElementType = left ? lt() : gt();
int count = 0;
try {
int paired = 1;
while (true) {
count++;
if (left) {
iterator.advance();
}
else {
iterator.retreat();
}
if (iterator.atEnd()) break;
final IElementType tokenType = iterator.getTokenType();
if (tokenType == opposite) {
paired--;
if (paired == 0) return true;
continue;
}
if (tokenType == braceElementType) {
paired++;
continue;
}
if (!myTokenSetAllowedInsideAngleBrackets.contains(tokenType)) {
return false;
}
}
return false;
}
finally {
while (count-- > 0) {
if (left) {
iterator.retreat();
}
else {
iterator.advance();
}
}
}
}
}
@@ -0,0 +1,64 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.highlighter;
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
/**
* Highlighting text attributes for Java language.
*/
public final class JavaHighlightingColors {
public static final TextAttributesKey LINE_COMMENT = TextAttributesKey.createTextAttributesKey("JAVA_LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT);
public static final TextAttributesKey JAVA_BLOCK_COMMENT = TextAttributesKey.createTextAttributesKey("JAVA_BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT);
public static final TextAttributesKey DOC_COMMENT = TextAttributesKey.createTextAttributesKey("JAVA_DOC_COMMENT", DefaultLanguageHighlighterColors.DOC_COMMENT);
public static final TextAttributesKey KEYWORD = TextAttributesKey.createTextAttributesKey("JAVA_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD);
public static final TextAttributesKey NUMBER = TextAttributesKey.createTextAttributesKey("JAVA_NUMBER", DefaultLanguageHighlighterColors.NUMBER);
public static final TextAttributesKey STRING = TextAttributesKey.createTextAttributesKey("JAVA_STRING", DefaultLanguageHighlighterColors.STRING);
public static final TextAttributesKey OPERATION_SIGN = TextAttributesKey.createTextAttributesKey("JAVA_OPERATION_SIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN);
public static final TextAttributesKey PARENTHESES = TextAttributesKey.createTextAttributesKey("JAVA_PARENTH", DefaultLanguageHighlighterColors.PARENTHESES);
public static final TextAttributesKey BRACKETS = TextAttributesKey.createTextAttributesKey("JAVA_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS);
public static final TextAttributesKey BRACES = TextAttributesKey.createTextAttributesKey("JAVA_BRACES", DefaultLanguageHighlighterColors.BRACES);
public static final TextAttributesKey COMMA = TextAttributesKey.createTextAttributesKey("JAVA_COMMA", DefaultLanguageHighlighterColors.COMMA);
public static final TextAttributesKey DOT = TextAttributesKey.createTextAttributesKey("JAVA_DOT", DefaultLanguageHighlighterColors.DOT);
public static final TextAttributesKey JAVA_SEMICOLON = TextAttributesKey.createTextAttributesKey("JAVA_SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON);
public static final TextAttributesKey DOC_COMMENT_TAG = TextAttributesKey.createTextAttributesKey("JAVA_DOC_TAG", DefaultLanguageHighlighterColors.DOC_COMMENT_TAG);
public static final TextAttributesKey DOC_COMMENT_MARKUP = TextAttributesKey.createTextAttributesKey("JAVA_DOC_MARKUP", DefaultLanguageHighlighterColors.DOC_COMMENT_MARKUP);
public static final TextAttributesKey DOC_COMMENT_TAG_VALUE = TextAttributesKey.createTextAttributesKey("DOC_COMMENT_TAG_VALUE", DefaultLanguageHighlighterColors.DOC_COMMENT_TAG_VALUE);
public static final TextAttributesKey VALID_STRING_ESCAPE = TextAttributesKey.createTextAttributesKey("JAVA_VALID_STRING_ESCAPE", DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE);
public static final TextAttributesKey INVALID_STRING_ESCAPE = TextAttributesKey.createTextAttributesKey("JAVA_INVALID_STRING_ESCAPE", DefaultLanguageHighlighterColors.INVALID_STRING_ESCAPE);
public static final TextAttributesKey LOCAL_VARIABLE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("LOCAL_VARIABLE_ATTRIBUTES", DefaultLanguageHighlighterColors.LOCAL_VARIABLE);
public static final TextAttributesKey PARAMETER_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("PARAMETER_ATTRIBUTES", DefaultLanguageHighlighterColors.PARAMETER);
public static final TextAttributesKey LAMBDA_PARAMETER_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("LAMBDA_PARAMETER_ATTRIBUTES", PARAMETER_ATTRIBUTES);
public static final TextAttributesKey REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES", DefaultLanguageHighlighterColors.REASSIGNED_LOCAL_VARIABLE);
public static final TextAttributesKey REASSIGNED_PARAMETER_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("REASSIGNED_PARAMETER_ATTRIBUTES", DefaultLanguageHighlighterColors.REASSIGNED_PARAMETER);
public static final TextAttributesKey INSTANCE_FIELD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("INSTANCE_FIELD_ATTRIBUTES", DefaultLanguageHighlighterColors.INSTANCE_FIELD);
public static final TextAttributesKey INSTANCE_FINAL_FIELD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("INSTANCE_FINAL_FIELD_ATTRIBUTES", INSTANCE_FIELD_ATTRIBUTES);
public static final TextAttributesKey STATIC_FIELD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("STATIC_FIELD_ATTRIBUTES", DefaultLanguageHighlighterColors.STATIC_FIELD);
public static final TextAttributesKey STATIC_FIELD_IMPORTED_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("STATIC_FIELD_IMPORTED_ATTRIBUTES", STATIC_FIELD_ATTRIBUTES);
public static final TextAttributesKey STATIC_FINAL_FIELD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("STATIC_FINAL_FIELD_ATTRIBUTES", STATIC_FIELD_ATTRIBUTES);
public static final TextAttributesKey STATIC_FINAL_FIELD_IMPORTED_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("STATIC_FINAL_FIELD_IMPORTED_ATTRIBUTES", STATIC_FINAL_FIELD_ATTRIBUTES);
public static final TextAttributesKey CLASS_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("CLASS_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.CLASS_NAME);
public static final TextAttributesKey ANONYMOUS_CLASS_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ANONYMOUS_CLASS_NAME_ATTRIBUTES", CLASS_NAME_ATTRIBUTES);
public static final TextAttributesKey IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES", CLASS_NAME_ATTRIBUTES);
public static final TextAttributesKey TYPE_PARAMETER_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("TYPE_PARAMETER_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.PARAMETER);
public static final TextAttributesKey INTERFACE_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("INTERFACE_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.INTERFACE_NAME);
public static final TextAttributesKey ENUM_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ENUM_NAME_ATTRIBUTES", CLASS_NAME_ATTRIBUTES);
public static final TextAttributesKey ABSTRACT_CLASS_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ABSTRACT_CLASS_NAME_ATTRIBUTES", CLASS_NAME_ATTRIBUTES);
public static final TextAttributesKey METHOD_CALL_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("METHOD_CALL_ATTRIBUTES", DefaultLanguageHighlighterColors.FUNCTION_CALL);
public static final TextAttributesKey METHOD_DECLARATION_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("METHOD_DECLARATION_ATTRIBUTES", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION);
public static final TextAttributesKey STATIC_METHOD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("STATIC_METHOD_ATTRIBUTES", DefaultLanguageHighlighterColors.STATIC_METHOD);
public static final TextAttributesKey STATIC_METHOD_CALL_IMPORTED_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("STATIC_METHOD_IMPORTED_ATTRIBUTES", STATIC_METHOD_ATTRIBUTES);
public static final TextAttributesKey ABSTRACT_METHOD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ABSTRACT_METHOD_ATTRIBUTES", METHOD_CALL_ATTRIBUTES);
public static final TextAttributesKey INHERITED_METHOD_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("INHERITED_METHOD_ATTRIBUTES", METHOD_CALL_ATTRIBUTES);
public static final TextAttributesKey CONSTRUCTOR_CALL_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("CONSTRUCTOR_CALL_ATTRIBUTES", DefaultLanguageHighlighterColors.FUNCTION_CALL);
public static final TextAttributesKey CONSTRUCTOR_DECLARATION_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("CONSTRUCTOR_DECLARATION_ATTRIBUTES", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION);
public static final TextAttributesKey ANNOTATION_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ANNOTATION_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.METADATA);
public static final TextAttributesKey ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.METADATA);
public static final TextAttributesKey ANNOTATION_ATTRIBUTE_VALUE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("ANNOTATION_ATTRIBUTE_VALUE_ATTRIBUTES", DefaultLanguageHighlighterColors.METADATA);
//visibility
public static final TextAttributesKey PUBLIC_REFERENCE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("PUBLIC_REFERENCE", (TextAttributesKey)null);
public static final TextAttributesKey PROTECTED_REFERENCE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("PROTECTED_REFERENCE", (TextAttributesKey)null);
public static final TextAttributesKey PACKAGE_PRIVATE_REFERENCE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("PACKAGE_PRIVATE_REFERENCE", (TextAttributesKey)null);
public static final TextAttributesKey PRIVATE_REFERENCE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("PRIVATE_REFERENCE", (TextAttributesKey)null);
}
@@ -0,0 +1,230 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.javadoc;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicJavaTokenSet;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.intellij.psi.impl.source.BasicJavaDocElementType.*;
public abstract class AbstractBasicJavadocHelper {
private static final String PARAM_TEXT = "param";
public static final Pair<JavadocParameterInfo, List<JavadocParameterInfo>> EMPTY
= new Pair<>(null, Collections.emptyList());
private static final @NotNull BasicJavaTokenSet TAG_TOKEN_SET = BasicJavaTokenSet.create(DOC_TAG,
DOC_SNIPPET_TAG,
DOC_INLINE_TAG);
protected AbstractBasicJavadocHelper() {
}
/**
* Tries to navigate caret at the given editor to the target position inserting missing white spaces if necessary.
*
* @param position target caret position
* @param editor target editor
* @param project target project
*/
public void navigate(@NotNull LogicalPosition position, @NotNull Editor editor, final @NotNull Project project) {
final Document document = editor.getDocument();
final CaretModel caretModel = editor.getCaretModel();
final int endLineOffset = document.getLineEndOffset(position.line);
final LogicalPosition endLinePosition = editor.offsetToLogicalPosition(endLineOffset);
if (endLinePosition.column < position.column && !editor.getSettings().isVirtualSpace() && !editor.isViewer()) {
final String toInsert = StringUtil.repeat(" ", position.column - endLinePosition.column);
ApplicationManager.getApplication().runWriteAction(() -> {
document.insertString(endLineOffset, toInsert);
PsiDocumentManager.getInstance(project).commitDocument(document);
});
}
caretModel.moveToLogicalPosition(position);
}
/**
* Calculates desired position of target javadoc parameter's description start.
*
* @param psiFile PSI holder
* @param data parsed adjacent javadoc parameters
* @param anchor descriptor for the target parameter
* @return logical position that points to the desired parameter description start location
*/
public @NotNull LogicalPosition calculateDescriptionStartPosition(@NotNull PsiFile psiFile,
@NotNull Collection<? extends JavadocParameterInfo> data,
@NotNull JavadocParameterInfo anchor) {
int descriptionStartColumn = -1;
int parameterNameEndColumn = -1;
for (JavadocParameterInfo parameterInfo : data) {
parameterNameEndColumn = Math.max(parameterNameEndColumn, parameterInfo.parameterNameEndPosition.column);
if (parameterInfo.parameterDescriptionStartPosition != null) {
descriptionStartColumn = Math.max(descriptionStartColumn, parameterInfo.parameterDescriptionStartPosition.column);
}
}
int column;
if (getJdAlignParamComments(psiFile)) {
column = Math.max(descriptionStartColumn, parameterNameEndColumn);
if (column <= parameterNameEndColumn) {
column = parameterNameEndColumn + 1;
}
}
else {
column = anchor.parameterNameEndPosition.column + 1;
}
return new LogicalPosition(anchor.parameterNameEndPosition.line, column);
}
protected abstract boolean getJdAlignParamComments(@NotNull PsiFile psiFile);
/**
* Returns information about all lines that contain javadoc parameters and are adjacent to the one that holds given offset.
*
* @param psiFile PSI holder for the document exposed the given editor
* @param editor target editor
* @param offset target offset that identifies anchor line to check
* @return pair like (javadoc info for the line identified by the given offset; list of javadoc parameter infos for
* adjacent lines if any
*/
public @NotNull Pair<JavadocParameterInfo, List<JavadocParameterInfo>> parse(@NotNull PsiFile psiFile,
@NotNull Editor editor,
int offset) {
List<JavadocParameterInfo> result = new ArrayList<>();
PsiDocumentManager.getInstance(psiFile.getProject()).commitDocument(editor.getDocument());
final PsiElement elementAtCaret = psiFile.findElementAt(offset);
if (elementAtCaret == null) {
return EMPTY;
}
ASTNode nodeAtCaret = BasicJavaAstTreeUtil.toNode(elementAtCaret);
ASTNode tag = BasicJavaAstTreeUtil.getParentOfType(nodeAtCaret, TAG_TOKEN_SET);
if (tag == null) {
// Due to javadoc PSI specifics.
if (BasicJavaAstTreeUtil.isWhiteSpace(nodeAtCaret)) {
for (ASTNode e = nodeAtCaret.getTreePrev(); e != null && tag == null; e = e.getTreePrev()) {
tag = BasicJavaAstTreeUtil.getParentOfType(e, TAG_TOKEN_SET, false);
if (e instanceof PsiWhiteSpace
|| (e.getElementType() == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS)) {
continue;
}
break;
}
}
}
if (tag == null) {
return EMPTY;
}
JavadocParameterInfo anchorInfo = parse(tag, editor);
if (anchorInfo == null) {
return EMPTY;
}
// Parse previous parameters.
for (ASTNode n = tag.getTreePrev(); n != null; n = n.getTreePrev()) {
JavadocParameterInfo info = parse(n, editor);
if (info == null) {
break;
}
result.add(0, info);
}
result.add(anchorInfo);
// Parse subsequent parameters.
for (ASTNode n = tag.getTreeNext(); n != null; n = n.getTreeNext()) {
JavadocParameterInfo info = parse(n, editor);
if (info == null) {
break;
}
result.add(info);
}
return Pair.create(anchorInfo, result);
}
private static @Nullable JavadocParameterInfo parse(@NotNull ASTNode astNode, @NotNull Editor editor) {
final ASTNode tag = BasicJavaAstTreeUtil.getParentOfType(astNode, TAG_TOKEN_SET, false);
if (tag == null || !PARAM_TEXT.equals(BasicJavaAstTreeUtil.getTagName(tag))) {
return null;
}
final ASTNode paramRef = BasicJavaAstTreeUtil.findChildByType(tag, DOC_TAG_VALUE_ELEMENT,
DOC_METHOD_OR_FIELD_REF,
DOC_PARAMETER_REF,
DOC_SNIPPET_TAG_VALUE);
if (paramRef == null) {
return null;
}
for (ASTNode node = paramRef.getTreeNext(); node != null; node = node.getTreeNext()) {
final IElementType elementType = node.getElementType();
if (elementType == JavaDocTokenType.DOC_COMMENT_DATA) {
return new JavadocParameterInfo(
editor.offsetToLogicalPosition(paramRef.getTextRange().getEndOffset()),
editor.offsetToLogicalPosition(node.getTextRange().getStartOffset()),
editor.getDocument().getLineNumber(node.getTextRange().getEndOffset())
);
}
else if (elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
break;
}
}
return new JavadocParameterInfo(
editor.offsetToLogicalPosition(paramRef.getTextRange().getEndOffset()),
null,
editor.getDocument().getLineNumber(paramRef.getTextRange().getEndOffset())
);
}
/**
* Encapsulates information about source code line that holds javadoc parameter.
*/
public static class JavadocParameterInfo {
/**
* Logical position that points to location just after javadoc parameter name.
* <p/>
* Example:
* <pre>
* /**
* * @param i[X] description
* *&#47;
* </pre>
*/
public final @NotNull LogicalPosition parameterNameEndPosition;
public final @Nullable LogicalPosition parameterDescriptionStartPosition;
/** Last logical line occupied by the current javadoc parameter. */
public final int lastLine;
public JavadocParameterInfo(@NotNull LogicalPosition parameterNameEndPosition,
@Nullable LogicalPosition parameterDescriptionStartPosition,
int lastLine) {
this.parameterNameEndPosition = parameterNameEndPosition;
this.parameterDescriptionStartPosition = parameterDescriptionStartPosition;
this.lastLine = lastLine;
}
@Override
public String toString() {
return "name end: " + parameterNameEndPosition + ", description start: " + parameterDescriptionStartPosition;
}
}
}
@@ -0,0 +1,199 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.options.colors.pages;
import com.intellij.application.options.colors.InspectionColorSettingsPage;
import com.intellij.codeHighlighting.RainbowHighlighter;
import com.intellij.core.JavaOptionBundle;
import com.intellij.ide.highlighter.JavaHighlightingColors;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.options.OptionsBundle;
import com.intellij.openapi.options.colors.AttributesDescriptor;
import com.intellij.openapi.options.colors.ColorDescriptor;
import com.intellij.openapi.options.colors.ColorSettingsPage;
import com.intellij.psi.codeStyle.DisplayPriority;
import com.intellij.psi.codeStyle.DisplayPrioritySortable;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.Map;
public abstract class AbstractBasicJavaColorSettingsPage
implements ColorSettingsPage, InspectionColorSettingsPage, DisplayPrioritySortable {
private static final AttributesDescriptor[] ourDescriptors = {
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.keyword"), JavaHighlightingColors.KEYWORD),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.number"), JavaHighlightingColors.NUMBER),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.string"), JavaHighlightingColors.STRING),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.valid.escape.in.string"), JavaHighlightingColors.VALID_STRING_ESCAPE),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.invalid.escape.in.string"), JavaHighlightingColors.INVALID_STRING_ESCAPE),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.operator.sign"), JavaHighlightingColors.OPERATION_SIGN),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.parentheses"), JavaHighlightingColors.PARENTHESES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.braces"), JavaHighlightingColors.BRACES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.brackets"), JavaHighlightingColors.BRACKETS),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.comma"), JavaHighlightingColors.COMMA),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.semicolon"), JavaHighlightingColors.JAVA_SEMICOLON),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.dot"), JavaHighlightingColors.DOT),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.line.comment"), JavaHighlightingColors.LINE_COMMENT),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.block.comment"), JavaHighlightingColors.JAVA_BLOCK_COMMENT),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.javadoc.comment"), JavaHighlightingColors.DOC_COMMENT),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.javadoc.tag"), JavaHighlightingColors.DOC_COMMENT_TAG),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.javadoc.tag.value"), JavaHighlightingColors.DOC_COMMENT_TAG_VALUE),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.javadoc.markup"), JavaHighlightingColors.DOC_COMMENT_MARKUP),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.class"), JavaHighlightingColors.CLASS_NAME_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.anonymous.class"), JavaHighlightingColors.ANONYMOUS_CLASS_NAME_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.type.parameter"), JavaHighlightingColors.TYPE_PARAMETER_NAME_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.abstract.class"), JavaHighlightingColors.ABSTRACT_CLASS_NAME_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.interface"), JavaHighlightingColors.INTERFACE_NAME_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.enum"), JavaHighlightingColors.ENUM_NAME_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.local.variable"), JavaHighlightingColors.LOCAL_VARIABLE_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.reassigned.local.variable"), JavaHighlightingColors.REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.reassigned.parameter"), JavaHighlightingColors.REASSIGNED_PARAMETER_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.implicit.anonymous.parameter"), JavaHighlightingColors.IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.instance.field"), JavaHighlightingColors.INSTANCE_FIELD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.instance.final.field"), JavaHighlightingColors.INSTANCE_FINAL_FIELD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.static.field"), JavaHighlightingColors.STATIC_FIELD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.static.imported.field"), JavaHighlightingColors.STATIC_FIELD_IMPORTED_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.static.final.field"), JavaHighlightingColors.STATIC_FINAL_FIELD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.static.final.imported.field"), JavaHighlightingColors.STATIC_FINAL_FIELD_IMPORTED_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.parameter"), JavaHighlightingColors.PARAMETER_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.lambda.parameter"), JavaHighlightingColors.LAMBDA_PARAMETER_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.method.call"), JavaHighlightingColors.METHOD_CALL_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.method.imported.call"), JavaHighlightingColors.STATIC_METHOD_CALL_IMPORTED_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.method.declaration"), JavaHighlightingColors.METHOD_DECLARATION_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.constructor.call"), JavaHighlightingColors.CONSTRUCTOR_CALL_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.constructor.declaration"), JavaHighlightingColors.CONSTRUCTOR_DECLARATION_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.static.method"), JavaHighlightingColors.STATIC_METHOD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.abstract.method"), JavaHighlightingColors.ABSTRACT_METHOD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.inherited.method"), JavaHighlightingColors.INHERITED_METHOD_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.public"), JavaHighlightingColors.PUBLIC_REFERENCE_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.protected"), JavaHighlightingColors.PROTECTED_REFERENCE_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.package.private"), JavaHighlightingColors.PACKAGE_PRIVATE_REFERENCE_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.private"), JavaHighlightingColors.PRIVATE_REFERENCE_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.annotation.name"), JavaHighlightingColors.ANNOTATION_NAME_ATTRIBUTES),
new AttributesDescriptor(JavaOptionBundle.message("options.java.attribute.descriptor.annotation.attribute.name"), JavaHighlightingColors.ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES)
};
@NonNls private static final Map<String, TextAttributesKey> ourTags = RainbowHighlighter.createRainbowHLM();
static {
ourTags.put("field", JavaHighlightingColors.INSTANCE_FIELD_ATTRIBUTES);
ourTags.put("unusedField", CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES);
ourTags.put("error", CodeInsightColors.ERRORS_ATTRIBUTES);
ourTags.put("warning", CodeInsightColors.WARNINGS_ATTRIBUTES);
ourTags.put("weak_warning", CodeInsightColors.WEAK_WARNING_ATTRIBUTES);
ourTags.put("server_problems", CodeInsightColors.GENERIC_SERVER_ERROR_OR_WARNING);
ourTags.put("server_duplicate", CodeInsightColors.DUPLICATE_FROM_SERVER);
ourTags.put("unknownType", CodeInsightColors.WRONG_REFERENCES_ATTRIBUTES);
ourTags.put("localVar", JavaHighlightingColors.LOCAL_VARIABLE_ATTRIBUTES);
ourTags.put("reassignedLocalVar", JavaHighlightingColors.REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES);
ourTags.put("implicitAnonymousParameter", JavaHighlightingColors.IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES);
ourTags.put("static", JavaHighlightingColors.STATIC_FIELD_ATTRIBUTES);
ourTags.put("static_final", JavaHighlightingColors.STATIC_FINAL_FIELD_ATTRIBUTES);
ourTags.put("deprecated", CodeInsightColors.DEPRECATED_ATTRIBUTES);
ourTags.put("for_removal", CodeInsightColors.MARKED_FOR_REMOVAL_ATTRIBUTES);
ourTags.put("constructorCall", JavaHighlightingColors.CONSTRUCTOR_CALL_ATTRIBUTES);
ourTags.put("constructorDeclaration", JavaHighlightingColors.CONSTRUCTOR_DECLARATION_ATTRIBUTES);
ourTags.put("methodCall", JavaHighlightingColors.METHOD_CALL_ATTRIBUTES);
ourTags.put("methodDeclaration", JavaHighlightingColors.METHOD_DECLARATION_ATTRIBUTES);
ourTags.put("static_method", JavaHighlightingColors.STATIC_METHOD_ATTRIBUTES);
ourTags.put("abstract_method", JavaHighlightingColors.ABSTRACT_METHOD_ATTRIBUTES);
ourTags.put("inherited_method", JavaHighlightingColors.INHERITED_METHOD_ATTRIBUTES);
ourTags.put("param", JavaHighlightingColors.PARAMETER_ATTRIBUTES);
ourTags.put("lambda_param", JavaHighlightingColors.LAMBDA_PARAMETER_ATTRIBUTES);
ourTags.put("class", JavaHighlightingColors.CLASS_NAME_ATTRIBUTES);
ourTags.put("anonymousClass", JavaHighlightingColors.ANONYMOUS_CLASS_NAME_ATTRIBUTES);
ourTags.put("typeParameter", JavaHighlightingColors.TYPE_PARAMETER_NAME_ATTRIBUTES);
ourTags.put("abstractClass", JavaHighlightingColors.ABSTRACT_CLASS_NAME_ATTRIBUTES);
ourTags.put("interface", JavaHighlightingColors.INTERFACE_NAME_ATTRIBUTES);
ourTags.put("enum", JavaHighlightingColors.ENUM_NAME_ATTRIBUTES);
ourTags.put("annotationName", JavaHighlightingColors.ANNOTATION_NAME_ATTRIBUTES);
ourTags.put("annotationAttributeName", JavaHighlightingColors.ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES);
ourTags.put("javadocTagValue", JavaHighlightingColors.DOC_COMMENT_TAG_VALUE);
ourTags.put("instanceFinalField", JavaHighlightingColors.INSTANCE_FINAL_FIELD_ATTRIBUTES);
ourTags.put("staticallyConstImported", JavaHighlightingColors.STATIC_FINAL_FIELD_IMPORTED_ATTRIBUTES);
ourTags.put("staticallyImported", JavaHighlightingColors.STATIC_FIELD_IMPORTED_ATTRIBUTES);
ourTags.put("static_imported_method", JavaHighlightingColors.STATIC_METHOD_CALL_IMPORTED_ATTRIBUTES);
ourTags.put("public", JavaHighlightingColors.PUBLIC_REFERENCE_ATTRIBUTES);
ourTags.put("protected", JavaHighlightingColors.PROTECTED_REFERENCE_ATTRIBUTES);
ourTags.put("package_private", JavaHighlightingColors.PACKAGE_PRIVATE_REFERENCE_ATTRIBUTES);
ourTags.put("private", JavaHighlightingColors.PRIVATE_REFERENCE_ATTRIBUTES);
}
@Override
@NotNull
public String getDisplayName() {
return JavaOptionBundle.message("options.java.display.name");
}
@Override
public abstract Icon getIcon();
@Override
public AttributesDescriptor @NotNull [] getAttributeDescriptors() {
return ourDescriptors;
}
@Override
public ColorDescriptor @NotNull [] getColorDescriptors() {
return ColorDescriptor.EMPTY_ARRAY;
}
@Override
@NotNull
public String getDemoText() {
return
"""
/* Block comment */
import <class>java.util.Date</class>;
/**
* Doc comment here for <code>SomeClass</code>
* @param <javadocTagValue>T</javadocTagValue> type parameter
* @see <class>Math</class>#<methodCall>sin</methodCall>(double)
*/
<annotationName>@Annotation</annotationName> (<annotationAttributeName>name</annotationAttributeName>=value)
public class <class>SomeClass</class><<typeParameter>T</typeParameter> extends <interface>Runnable</interface>> { // some comment
private <typeParameter>T</typeParameter> <field>field</field> = null;
private double <unusedField>unusedField</unusedField> = 12345.67890;
private <unknownType>UnknownType</unknownType> <field>anotherString</field> = "Another\\nStrin\\g";
public static int <static>staticField</static> = 0;
public final int <instanceFinalField>instanceFinalField</instanceFinalField> = 0;
public <constructorDeclaration>SomeClass</constructorDeclaration>(<interface>AnInterface</interface> <param>param</param>, int[] <reassignedParameter>reassignedParam</reassignedParameter>) {
<error>int <localVar>localVar</localVar> = "IntelliJ"</error>; // Error, incompatible types
<class>System</class>.<static>out</static>.<methodCall>println</methodCall>(<field>anotherString</field> + <inherited_method>toString</inherited_method>() + <localVar>localVar</localVar>);
long <localVar>time</localVar> = <class>Date</class>.<static_method><deprecated>parse</deprecated></static_method>("1.2.3"); // Method is deprecated
int <reassignedLocalVar>reassignedValue</reassignedLocalVar> = this.<warning>staticField</warning>;\s
<reassignedLocalVar>reassignedValue</reassignedLocalVar> ++;\s
<field>field</field>.<abstract_method>run</abstract_method>();\s
new <anonymousClass>SomeClass</anonymousClass>() {
{
int <localVar>a</localVar> = <implicitAnonymousParameter>localVar</implicitAnonymousParameter>;
}
};
<reassignedParameter>reassignedParam</reassignedParameter> = new <constructorCall>ArrayList</constructorCall><<class>String</class>>().toArray(new int[0]);
}
}
enum <enum>AnEnum</enum> { <static_final>CONST1</static_final>, <static_final>CONST2</static_final> }
interface <interface>AnInterface</interface> {
int <static_final>CONSTANT</static_final> = 2;
void <methodDeclaration>method</methodDeclaration>();
}
abstract class <abstractClass>SomeAbstractClass</abstractClass> {
}""";
}
@Override
public Map<String,TextAttributesKey> getAdditionalHighlightingTagToDescriptorMap() {
return ourTags;
}
@Override
public DisplayPriority getPriority() {
return DisplayPriority.KEY_LANGUAGE_SETTINGS;
}
}