@SuppressWarnings("ForLoopThatDoesntUseLoopVariable") private static void indentPlainTextBlock( final Document document, final int startOffset, final int endOffset, final int indentLevel) { CharSequence chars = document.getCharsSequence(); int spaceEnd = CharArrayUtil.shiftForward(chars, startOffset, " \t"); int line = document.getLineNumber(startOffset); if (spaceEnd > endOffset || indentLevel <= 0 || line >= document.getLineCount() - 1 || chars.charAt(spaceEnd) == '\n') { return; } int linesToAdjustIndent = 0; for (int i = line + 1; i < document.getLineCount(); i++) { if (document.getLineStartOffset(i) >= endOffset) { break; } linesToAdjustIndent++; } String indentString = StringUtil.repeatSymbol(' ', indentLevel); for (; linesToAdjustIndent > 0; linesToAdjustIndent--) { int lineStartOffset = document.getLineStartOffset(++line); document.insertString(lineStartOffset, indentString); } }
/** * Tries to calculate given line's indent column assuming that there might be a comment at the * given indent offset (see {@link #getCommentPrefix(IElementType)}). * * @param line target line * @param indentOffset start indent offset to use for the given line * @param lineEndOffset given line's end offset * @param fallbackColumn column to return if it's not possible to apply comment-specific indent * calculation rules * @return given line's indent column to use */ private int calcIndent(int line, int indentOffset, int lineEndOffset, int fallbackColumn) { final HighlighterIterator it = myEditor.getHighlighter().createIterator(indentOffset); IElementType tokenType = it.getTokenType(); Language language = tokenType.getLanguage(); TokenSet comments = myComments.get(language); if (comments == null) { ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language); if (definition != null) { comments = definition.getCommentTokens(); } if (comments == null) { return fallbackColumn; } else { myComments.put(language, comments); } } if (comments.contains(tokenType) && indentOffset == it.getStart()) { String prefix = COMMENT_PREFIXES.get(tokenType); if (prefix == null) { prefix = getCommentPrefix(tokenType); } if (!NO_COMMENT_INFO_MARKER.equals(prefix)) { final int indentInsideCommentOffset = CharArrayUtil.shiftForward( myChars, indentOffset + prefix.length(), lineEndOffset, " \t"); if (indentInsideCommentOffset < lineEndOffset) { int indent = myEditor.calcColumnNumber(indentInsideCommentOffset, line); indentAfterUncomment.put(line, indent - prefix.length()); return indent; } } } return fallbackColumn; }
private boolean isBlankLine(int line, CharSequence chars) { Document document = myDocument; if (document == null) { return true; } int startOffset = document.getLineStartOffset(line); int endOffset = document.getLineEndOffset(line); return CharArrayUtil.shiftForward(chars, startOffset, endOffset, " \t") >= myDocument.getLineEndOffset(line); }
private int getCommentStart(int line) { int offset = myDocument.getLineStartOffset(line); CharSequence chars = myDocument.getCharsSequence(); offset = CharArrayUtil.shiftForward(chars, offset, " \t"); final Commenter commenter = findCommenter(line); if (commenter == null) return -1; String prefix = commenter.getLineCommentPrefix(); if (prefix == null) prefix = commenter.getBlockCommentPrefix(); if (prefix == null) return -1; return CharArrayUtil.regionMatches(chars, offset, prefix) ? offset : -1; }
/** * Calculates the position for insertion of one or more '}'. * * @param file target PSI file * @param editor target editor * @param caretOffset target caret offset * @return the position between <code>caretOffset</code> and the end of file */ protected int getRBraceOffset( @NotNull final PsiFile file, @NotNull final Editor editor, int caretOffset) { CharSequence text = editor.getDocument().getCharsSequence(); int offset = CharArrayUtil.shiftForward(text, caretOffset, " \t"); final int fileLength = text.length(); if (offset < fileLength && ")];,%<?".indexOf(text.charAt(offset)) < 0) { offset = calculateOffsetToInsertClosingBrace(file, text, offset); // offset = CharArrayUtil.shiftForwardUntil(text, caretOffset, "\n"); } return Math.min(offset, fileLength); }
@Override public void handleInsert(InsertionContext context) { final Document document = context.getEditor().getDocument(); document.replaceString(context.getStartOffset(), context.getTailOffset(), ";"); final InsertionContext qualifierContext = CompletionUtil.emulateInsertion(context, context.getStartOffset(), myQualifier); OffsetKey oldStart = context.trackOffset(context.getStartOffset(), false); int start = CharArrayUtil.shiftForward( context.getDocument().getCharsSequence(), context.getStartOffset(), " \t\n"); if (shouldParenthesizeQualifier(context.getFile(), start, qualifierContext.getTailOffset())) { final String space = CodeStyleSettingsManager.getSettings(qualifierContext.getProject()) .SPACE_WITHIN_PARENTHESES ? " " : ""; document.insertString(start, "(" + space); document.insertString(qualifierContext.getTailOffset(), space + ")"); } final char atTail = document.getCharsSequence().charAt(context.getTailOffset() - 1); if (atTail != ';') { LOG.error( LogMessageEx.createEvent( "Unexpected character", "atTail=" + atTail + "\n" + "offset=" + context.getTailOffset() + "\n" + "item=" + this + "\n" + "item.class=" + this.getClass() + "\n" + DebugUtil.currentStackTrace(), AttachmentFactory.createAttachment(context.getDocument()))); } document.replaceString(context.getTailOffset() - 1, context.getTailOffset(), "."); CompletionUtil.emulateInsertion(getDelegate(), context.getTailOffset(), context); context.commitDocument(); int formatStart = context.getOffset(oldStart); int formatEnd = context.getTailOffset(); if (formatStart >= 0 && formatEnd >= 0) { CodeStyleManager.getInstance(context.getProject()) .reformatText(context.getFile(), formatStart, formatEnd); } }
@Override public void apply(Editor editor, JavaSmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException { if (psiElement instanceof PsiEnumConstant) { int insertionOffset = psiElement.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, ","); } } }
@Nullable private TextRange getSelectedComments(CharSequence text, String prefix, String suffix) { TextRange commentedRange = null; if (myCaret.hasSelection()) { int selectionStart = myCaret.getSelectionStart(); selectionStart = CharArrayUtil.shiftForward(text, selectionStart, " \t\n"); int selectionEnd = myCaret.getSelectionEnd() - 1; selectionEnd = CharArrayUtil.shiftBackward(text, selectionEnd, " \t\n") + 1; if (selectionEnd - selectionStart >= prefix.length() + suffix.length() && CharArrayUtil.regionMatches(text, selectionStart, prefix) && CharArrayUtil.regionMatches(text, selectionEnd - suffix.length(), suffix)) { commentedRange = new TextRange(selectionStart, selectionEnd); } } return commentedRange; }
private boolean isLineCommented( final int line, final CharSequence chars, final Commenter commenter) { boolean commented; int lineEndForBlockCommenting = -1; int lineStart = myDocument.getLineStartOffset(line); lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t"); if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter; commented = selfManagingCommenter.isLineCommented( line, lineStart, myDocument, myCommenterStateMap.get(selfManagingCommenter)); } else { String prefix = commenter.getLineCommentPrefix(); if (prefix != null) { commented = CharArrayUtil.regionMatches(chars, lineStart, prefix) || prefix.endsWith(" ") && CharArrayUtil.regionMatches(chars, lineStart, prefix.trim() + "\n"); } else { prefix = commenter.getBlockCommentPrefix(); String suffix = commenter.getBlockCommentSuffix(); final int textLength = myDocument.getTextLength(); lineEndForBlockCommenting = myDocument.getLineEndOffset(line); if (lineEndForBlockCommenting == textLength) { final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t"); if (shifted < textLength - 1) lineEndForBlockCommenting = shifted; } else { lineEndForBlockCommenting = CharArrayUtil.shiftBackward(chars, lineEndForBlockCommenting, " \t"); } commented = lineStart == lineEndForBlockCommenting && myStartLine != myEndLine || CharArrayUtil.regionMatches(chars, lineStart, prefix) && CharArrayUtil.regionMatches( chars, lineEndForBlockCommenting - suffix.length(), suffix); } } if (commented) { myStartOffsets[line - myStartLine] = lineStart; myEndOffsets[line - myStartLine] = lineEndForBlockCommenting; } return commented; }
@Override public Result preprocessEnter( @NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Ref<Integer> caretOffsetRef, @NotNull final Ref<Integer> caretAdvance, @NotNull final DataContext dataContext, final EditorActionHandler originalHandler) { int caretOffset = caretOffsetRef.get().intValue(); PsiElement psiAtOffset = file.findElementAt(caretOffset); if (psiAtOffset != null && psiAtOffset.getTextOffset() < caretOffset) { ASTNode token = psiAtOffset.getNode(); Document document = editor.getDocument(); CharSequence text = document.getText(); final Language language = psiAtOffset.getLanguage(); final Commenter languageCommenter = LanguageCommenters.INSTANCE.forLanguage(language); final CodeDocumentationAwareCommenter commenter = languageCommenter instanceof CodeDocumentationAwareCommenter ? (CodeDocumentationAwareCommenter) languageCommenter : null; if (commenter != null && token.getElementType() == commenter.getLineCommentTokenType()) { final int offset = CharArrayUtil.shiftForward(text, caretOffset, " \t"); if (offset < document.getTextLength() && text.charAt(offset) != '\n') { String prefix = commenter.getLineCommentPrefix(); assert prefix != null : "Line Comment type is set but Line Comment Prefix is null!"; if (!StringUtil.startsWith(text, offset, prefix)) { if (text.charAt(caretOffset) != ' ' && !prefix.endsWith(" ")) { prefix += " "; } document.insertString(caretOffset, prefix); return Result.Default; } else { int afterPrefix = offset + prefix.length(); if (afterPrefix < document.getTextLength() && text.charAt(afterPrefix) != ' ') { document.insertString(afterPrefix, " "); // caretAdvance.set(0); } caretOffsetRef.set(offset); } return Result.Default; } } } return Result.Continue; }
@Nullable private Commenter findCommenter(final int line) { final FileType fileType = myFile.getFileType(); if (fileType instanceof AbstractFileType) { return ((AbstractFileType) fileType).getCommenter(); } int lineStartOffset = myDocument.getLineStartOffset(line); int lineEndOffset = myDocument.getLineEndOffset(line) - 1; final CharSequence charSequence = myDocument.getCharsSequence(); lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t"); lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t"); final Language lineStartLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineStartOffset); final Language lineEndLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineEndOffset); return CommentByBlockCommentHandler.getCommenter( myFile, myEditor, lineStartLanguage, lineEndLanguage); }
private TextRange expandRange(int delOffset1, int delOffset2) { CharSequence chars = myDocument.getCharsSequence(); int offset1 = CharArrayUtil.shiftBackward(chars, delOffset1 - 1, " \t"); if (offset1 < 0 || chars.charAt(offset1) == '\n' || chars.charAt(offset1) == '\r') { int offset2 = CharArrayUtil.shiftForward(chars, delOffset2, " \t"); if (offset2 == myDocument.getTextLength() || chars.charAt(offset2) == '\r' || chars.charAt(offset2) == '\n') { delOffset1 = offset1 + 1; if (offset2 < myDocument.getTextLength()) { delOffset2 = offset2 + 1; if (chars.charAt(offset2) == '\r' && offset2 + 1 < myDocument.getTextLength() && chars.charAt(offset2 + 1) == '\n') { delOffset2++; } } } } return new TextRange(delOffset1, delOffset2); }
@Nullable private PsiComment createJavaDocStub( final CodeInsightSettings settings, final PsiComment comment, final Project project) { if (settings.JAVADOC_STUB_ON_ENTER) { final DocumentationProvider langDocumentationProvider = LanguageDocumentation.INSTANCE.forLanguage(comment.getParent().getLanguage()); @Nullable final CodeDocumentationProvider docProvider; if (langDocumentationProvider instanceof CompositeDocumentationProvider) { docProvider = ((CompositeDocumentationProvider) langDocumentationProvider) .getFirstCodeDocumentationProvider(); } else { docProvider = langDocumentationProvider instanceof CodeDocumentationProvider ? (CodeDocumentationProvider) langDocumentationProvider : null; } if (docProvider != null) { if (docProvider.findExistingDocComment(comment) != comment) return comment; String docStub = docProvider.generateDocumentationContentStub(comment); if (docStub != null && docStub.length() != 0) { myOffset = CharArrayUtil.shiftForwardUntil( myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR); myOffset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR); myDocument.insertString(myOffset, docStub); } } PsiDocumentManager.getInstance(project).commitAllDocuments(); return PsiTreeUtil.getNonStrictParentOfType( myFile.findElementAt(myOffset), PsiComment.class); } return comment; }
private static boolean shouldSkipLine(final PsiFile file, Document doc, int line) { final int start = doc.getLineStartOffset(line); final int end = doc.getLineEndOffset(line); final int _start = CharArrayUtil.shiftForward(doc.getCharsSequence(), start, " \n\t"); if (_start >= end) { return true; } TextRange alreadyChecked = null; for (PsiElement elem = file.findElementAt(_start); elem != null && elem.getTextOffset() <= end && (alreadyChecked == null || !alreadyChecked.contains(elem.getTextRange())); elem = elem.getNextSibling()) { for (PsiElement _elem = elem; _elem.getTextOffset() >= _start; _elem = _elem.getParent()) { alreadyChecked = _elem.getTextRange(); if (_elem instanceof PsiDeclarationStatement) { final PsiElement[] declared = ((PsiDeclarationStatement) _elem).getDeclaredElements(); for (PsiElement declaredElement : declared) { if (declaredElement instanceof PsiVariable) { return false; } } } if (_elem instanceof PsiJavaCodeReferenceElement) { final PsiElement resolved = ((PsiJavaCodeReferenceElement) _elem).resolve(); if (resolved instanceof PsiVariable) { return false; } } } } return true; }
/** * 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</code> 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</code> otherwise */ private static boolean processExistingBlankLine( @NotNull Editor editor, @Nullable PsiCodeBlock codeBlock, @Nullable PsiElement element) { PsiWhiteSpace whiteSpace = null; if (codeBlock == null) { if (element != null) { final PsiElement next = PsiTreeUtil.nextLeaf(element); 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()); if (dataContext == null) { i = CharArrayUtil.shiftForwardUntil(whiteSpaceText, i, "\n"); if (i >= whiteSpaceText.length()) { i = whiteSpaceText.length(); } editor.getCaretModel().moveToOffset(i + textRange.getStartOffset()); } else { actionHandler.execute(editor, dataContext); } return true; }
@RequiredReadAction protected void composeDescription( @NotNull CommonProblemDescriptor description, int i, @NotNull StringBuffer buf, @NotNull RefEntity refElement) { PsiElement expression = description instanceof ProblemDescriptor ? ((ProblemDescriptor) description).getPsiElement() : null; StringBuilder anchor = new StringBuilder(); VirtualFile vFile = null; if (expression != null) { vFile = expression.getContainingFile().getVirtualFile(); if (vFile instanceof VirtualFileWindow) vFile = ((VirtualFileWindow) vFile).getDelegate(); //noinspection HardCodedStringLiteral anchor.append("<a HREF=\""); try { if (myExporter == null) { //noinspection HardCodedStringLiteral anchor.append(new URL(vFile.getUrl() + "#descr:" + i)); } else { anchor.append(myExporter.getURL(refElement)); } } catch (MalformedURLException e) { LOG.error(e); } anchor.append("\">"); anchor.append( ProblemDescriptorUtil.extractHighlightedText(description, expression) .replaceAll("\\$", "\\\\\\$")); //noinspection HardCodedStringLiteral anchor.append("</a>"); } else { //noinspection HardCodedStringLiteral anchor.append("<font style=\"font-weight:bold; color:#FF0000\";>"); anchor.append(InspectionsBundle.message("inspection.export.results.invalidated.item")); //noinspection HardCodedStringLiteral anchor.append("</font>"); } String descriptionTemplate = description.getDescriptionTemplate(); //noinspection HardCodedStringLiteral final String reference = "#ref"; final boolean containsReference = descriptionTemplate.contains(reference); String res = descriptionTemplate.replaceAll(reference, anchor.toString()); final int lineNumber = description instanceof ProblemDescriptor ? ((ProblemDescriptor) description).getLineNumber() : -1; StringBuffer lineAnchor = new StringBuffer(); if (expression != null && lineNumber > 0) { Document doc = FileDocumentManager.getInstance().getDocument(vFile); lineAnchor.append(InspectionsBundle.message("inspection.export.results.at.line")).append(" "); if (myExporter == null) { //noinspection HardCodedStringLiteral lineAnchor.append("<a HREF=\""); try { int offset = doc.getLineStartOffset(lineNumber - 1); offset = CharArrayUtil.shiftForward(doc.getCharsSequence(), offset, " \t"); lineAnchor.append(new URL(vFile.getUrl() + "#" + offset)); } catch (MalformedURLException e) { LOG.error(e); } lineAnchor.append("\">"); } lineAnchor.append(Integer.toString(lineNumber)); //noinspection HardCodedStringLiteral lineAnchor.append("</a>"); //noinspection HardCodedStringLiteral final String location = "#loc"; if (!res.contains(location)) { res += " (" + location + ")"; } res = res.replaceAll(location, lineAnchor.toString()); } buf.append(res); buf.append(BR).append(BR); composeAdditionalDescription(buf, refElement); }
private void doComment() { myStartLine = myDocument.getLineNumber(myStartOffset); myEndLine = myDocument.getLineNumber(myEndOffset); if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) { myEndLine--; } myStartOffsets = new int[myEndLine - myStartLine + 1]; myEndOffsets = new int[myEndLine - myStartLine + 1]; myCommenters = new Commenter[myEndLine - myStartLine + 1]; myCommenterStateMap = new THashMap<SelfManagingCommenter, CommenterDataHolder>(); CharSequence chars = myDocument.getCharsSequence(); boolean singleline = myStartLine == myEndLine; int offset = myDocument.getLineStartOffset(myStartLine); offset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t"); final Language languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange( offset, CharArrayUtil.shiftBackward( myDocument.getCharsSequence(), myDocument.getLineEndOffset(myEndLine), " \t\n"), myFile); Commenter blockSuitableCommenter = languageSuitableForCompleteFragment == null ? LanguageCommenters.INSTANCE.forLanguage(myFile.getLanguage()) : null; if (blockSuitableCommenter == null && myFile.getFileType() instanceof AbstractFileType) { blockSuitableCommenter = new Commenter() { final SyntaxTable mySyntaxTable = ((AbstractFileType) myFile.getFileType()).getSyntaxTable(); @Nullable public String getLineCommentPrefix() { return mySyntaxTable.getLineComment(); } @Nullable public String getBlockCommentPrefix() { return mySyntaxTable.getStartComment(); } @Nullable public String getBlockCommentSuffix() { return mySyntaxTable.getEndComment(); } public String getCommentedBlockCommentPrefix() { return null; } public String getCommentedBlockCommentSuffix() { return null; } }; } boolean allLineCommented = true; boolean commentWithIndent = !CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN; for (int line = myStartLine; line <= myEndLine; line++) { Commenter commenter = blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line); if (commenter == null) return; if (commenter.getLineCommentPrefix() == null && (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) { return; } if (commenter instanceof SelfManagingCommenter && myCommenterStateMap.get(commenter) == null) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter; CommenterDataHolder state = selfManagingCommenter.createLineCommentingState( myStartLine, myEndLine, myDocument, myFile); if (state == null) state = SelfManagingCommenter.EMPTY_STATE; myCommenterStateMap.put(selfManagingCommenter, state); } myCommenters[line - myStartLine] = commenter; if (!isLineCommented(line, chars, commenter) && (singleline || !isLineEmpty(line))) { allLineCommented = false; if (commenter instanceof IndentedCommenter) { final Boolean value = ((IndentedCommenter) commenter).forceIndentedLineComment(); if (value != null) { commentWithIndent = value; } } break; } } if (!allLineCommented) { if (!commentWithIndent) { doDefaultCommenting(blockSuitableCommenter); } else { doIndentCommenting(blockSuitableCommenter); } } else { for (int line = myEndLine; line >= myStartLine; line--) { uncommentLine(line); // int offset1 = myStartOffsets[line - myStartLine]; // int offset2 = myEndOffsets[line - myStartLine]; // if (offset1 == offset2) continue; // Commenter commenter = myCommenters[line - myStartLine]; // String prefix = commenter.getBlockCommentPrefix(); // if (prefix == null || !myDocument.getText().substring(offset1, // myDocument.getTextLength()).startsWith(prefix)) { // prefix = commenter.getLineCommentPrefix(); // } // // String suffix = commenter.getBlockCommentSuffix(); // if (suffix == null && prefix != null) suffix = ""; // // if (prefix != null && suffix != null) { // final int suffixLen = suffix.length(); // final int prefixLen = prefix.length(); // if (offset2 >= 0) { // if (!CharArrayUtil.regionMatches(chars, offset1 + prefixLen, prefix)) { // myDocument.deleteString(offset2 - suffixLen, offset2); // } // } // if (offset1 >= 0) { // for (int i = offset2 - suffixLen - 1; i > offset1 + prefixLen; --i) { // if (CharArrayUtil.regionMatches(chars, i, suffix)) { // myDocument.deleteString(i, i + suffixLen); // } // else if (CharArrayUtil.regionMatches(chars, i, prefix)) { // myDocument.deleteString(i, i + prefixLen); // } // } // myDocument.deleteString(offset1, offset1 + prefixLen); // } // } } } }
private void commentLine(int line, int offset, @Nullable Commenter commenter) { if (commenter == null) commenter = findCommenter(line); if (commenter == null) return; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter; selfManagingCommenter.commentLine( line, offset, myDocument, myCommenterStateMap.get(selfManagingCommenter)); return; } String prefix = commenter.getLineCommentPrefix(); if (prefix != null) { if (commenter instanceof CommenterWithLineSuffix) { int endOffset = myDocument.getLineEndOffset(line); endOffset = CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), endOffset, " \t"); int shiftedStartOffset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t"); String lineSuffix = ((CommenterWithLineSuffix) commenter).getLineCommentSuffix(); if (!CharArrayUtil.regionMatches( myDocument.getCharsSequence(), shiftedStartOffset, prefix)) { if (!CharArrayUtil.regionMatches( myDocument.getCharsSequence(), endOffset - lineSuffix.length(), lineSuffix)) { myDocument.insertString(endOffset, lineSuffix); } myDocument.insertString(offset, prefix); } } else { myDocument.insertString(offset, prefix); } } else { prefix = commenter.getBlockCommentPrefix(); String suffix = commenter.getBlockCommentSuffix(); if (prefix == null || suffix == null) return; int endOffset = myDocument.getLineEndOffset(line); if (endOffset == offset && myStartLine != myEndLine) return; final int textLength = myDocument.getTextLength(); final CharSequence chars = myDocument.getCharsSequence(); offset = CharArrayUtil.shiftForward(chars, offset, " \t"); if (endOffset == textLength) { final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t"); if (shifted < textLength - 1) endOffset = shifted; } else { endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t"); } if (endOffset < offset || offset == textLength - 1 && line != myDocument.getLineCount() - 1) { return; } final String text = chars.subSequence(offset, endOffset).toString(); final IntArrayList prefixes = new IntArrayList(); final IntArrayList suffixes = new IntArrayList(); final String commentedSuffix = commenter.getCommentedBlockCommentSuffix(); final String commentedPrefix = commenter.getCommentedBlockCommentPrefix(); for (int position = 0; position < text.length(); ) { int nearestPrefix = text.indexOf(prefix, position); if (nearestPrefix == -1) { nearestPrefix = text.length(); } int nearestSuffix = text.indexOf(suffix, position); if (nearestSuffix == -1) { nearestSuffix = text.length(); } if (Math.min(nearestPrefix, nearestSuffix) == text.length()) { break; } if (nearestPrefix < nearestSuffix) { prefixes.add(nearestPrefix); position = nearestPrefix + prefix.length(); } else { suffixes.add(nearestSuffix); position = nearestSuffix + suffix.length(); } } if (!(commentedSuffix == null && !suffixes.isEmpty() && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) { myDocument.insertString(endOffset, suffix); } int nearestPrefix = prefixes.size() - 1; int nearestSuffix = suffixes.size() - 1; while (nearestPrefix >= 0 || nearestSuffix >= 0) { if (nearestSuffix == -1 || nearestPrefix != -1 && prefixes.get(nearestPrefix) > suffixes.get(nearestSuffix)) { final int position = prefixes.get(nearestPrefix); nearestPrefix--; if (commentedPrefix != null) { myDocument.replaceString( offset + position, offset + position + prefix.length(), commentedPrefix); } else if (position != 0) { myDocument.insertString(offset + position, suffix); } } else { final int position = suffixes.get(nearestSuffix); nearestSuffix--; if (commentedSuffix != null) { myDocument.replaceString( offset + position, offset + position + suffix.length(), commentedSuffix); } else if (offset + position + suffix.length() < endOffset) { myDocument.insertString(offset + position + suffix.length(), prefix); } } } if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) { myDocument.insertString(offset, prefix); } } }
private static void indentBlockWithFormatter( Project project, Document document, int startOffset, int endOffset, PsiFile file) { // Algorithm: the main idea is to process the first line of the pasted block, adjust its indent // if necessary, calculate indent // adjustment string and apply to each line of the pasted block starting from the second one. // // We differentiate the following possible states here: // --- pasted block doesn't start new line, i.e. there are non-white space symbols before it // at the first line. // Example: // old content [pasted line 1 // pasted line 2] // Indent adjustment string is just the first line indent then. // // --- pasted block starts with empty line(s) // Example: // old content [ // pasted line 1 // pasted line 2] // We parse existing indents of the pasted block then, adjust its first non-blank line via // formatter and adjust indent // of subsequent pasted lines in order to preserve old indentation. // // --- pasted block is located at the new line and starts with white space symbols. // Example: // [ pasted line 1 // pasted line 2] // We parse existing indents of the pasted block then, adjust its first line via formatter // and adjust indent of the pasted lines // starting from the second one in order to preserve old indentation. // // --- pasted block is located at the new line but doesn't start with white space symbols. // Example: // [pasted line 1 // pasted line 2] // We adjust the first line via formatter then and apply first line's indent to all // subsequent pasted lines. CharSequence chars = document.getCharsSequence(); final int firstLine = document.getLineNumber(startOffset); final int firstLineStart = document.getLineStartOffset(firstLine); // There is a possible case that we paste block that ends with new line that is empty or // contains only white space symbols. // We want to preserve indent for the original document line where paste was performed. // Example: // Original: // if (test) { // <caret> } // // Pasting: 'int i = 1;\n' // Expected: // if (test) { // int i = 1; // } boolean saveLastLineIndent = false; for (int i = endOffset - 1; i >= startOffset; i--) { final char c = chars.charAt(i); if (c == '\n') { saveLastLineIndent = true; break; } if (c != ' ' && c != '\t') { break; } } final int lastLine; if (saveLastLineIndent) { lastLine = document.getLineNumber(endOffset) - 1; // Remove white space symbols at the pasted text if any. int start = document.getLineStartOffset(lastLine + 1); if (start < endOffset) { int i = CharArrayUtil.shiftForward(chars, start, " \t"); if (i > start) { i = Math.min(i, endOffset); document.deleteString(start, i); } } // Insert white space from the start line of the pasted block. int indentToKeepEndOffset = Math.min(startOffset, CharArrayUtil.shiftForward(chars, firstLineStart, " \t")); if (indentToKeepEndOffset > firstLineStart) { document.insertString(start, chars.subSequence(firstLineStart, indentToKeepEndOffset)); } } else { lastLine = document.getLineNumber(endOffset); } final int i = CharArrayUtil.shiftBackward(chars, startOffset - 1, " \t"); // Handle a situation when pasted block doesn't start a new line. if (chars.charAt(startOffset) != '\n' && i > 0 && chars.charAt(i) != '\n') { int firstNonWsOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t"); if (firstNonWsOffset > firstLineStart) { CharSequence toInsert = chars.subSequence(firstLineStart, firstNonWsOffset); for (int line = firstLine + 1; line <= lastLine; line++) { document.insertString(document.getLineStartOffset(line), toInsert); } } return; } // Sync document and PSI for correct formatting processing. PsiDocumentManager.getInstance(project).commitAllDocuments(); if (file == null) { return; } CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); final int j = CharArrayUtil.shiftForward(chars, startOffset, " \t\n"); if (j >= endOffset) { // Pasted text contains white space/line feed symbols only, do nothing. return; } final int anchorLine = document.getLineNumber(j); final int anchorLineStart = document.getLineStartOffset(anchorLine); codeStyleManager.adjustLineIndent(file, j); // Handle situation when pasted block starts with non-white space symbols. if (anchorLine == firstLine && j == startOffset) { int indentOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t"); if (indentOffset > firstLineStart) { CharSequence toInsert = chars.subSequence(firstLineStart, indentOffset); for (int line = firstLine + 1; line <= lastLine; line++) { document.insertString(document.getLineStartOffset(line), toInsert); } } return; } // Handle situation when pasted block starts from white space symbols. Assume that the pasted // text started at the line start, // i.e. correct indentation level is stored at the blocks structure. final int firstNonWsOffset = CharArrayUtil.shiftForward(chars, anchorLineStart, " \t"); final int diff = firstNonWsOffset - j; if (diff == 0) { return; } if (diff > 0) { CharSequence toInsert = chars.subSequence(anchorLineStart, anchorLineStart + diff); for (int line = anchorLine + 1; line <= lastLine; line++) { document.insertString(document.getLineStartOffset(line), toInsert); } return; } // We've pasted text to the non-first column and exact white space between the line start and // caret position on the moment of paste // has been removed by formatter during 'adjust line indent' // Example: // copied text: // ' line1 // line2' // after paste: // line start -> ' I line1 // line2' (I - caret position during 'paste') // formatter removed white space between the line start and caret position, so, current // document state is: // ' line1 // line2' if (anchorLine == firstLine && -diff == startOffset - firstLineStart) { return; } if (anchorLine != firstLine || -diff > startOffset - firstLineStart) { final int desiredSymbolsToRemove; if (anchorLine == firstLine) { desiredSymbolsToRemove = -diff - (startOffset - firstLineStart); } else { desiredSymbolsToRemove = -diff; } for (int line = anchorLine + 1; line <= lastLine; line++) { int currentLineStart = document.getLineStartOffset(line); int currentLineIndentOffset = CharArrayUtil.shiftForward(chars, currentLineStart, " \t"); int symbolsToRemove = Math.min(currentLineIndentOffset - currentLineStart, desiredSymbolsToRemove); if (symbolsToRemove > 0) { document.deleteString(currentLineStart, currentLineStart + symbolsToRemove); } } } else { CharSequence toInsert = chars.subSequence(anchorLineStart, diff + startOffset); for (int line = anchorLine + 1; line <= lastLine; line++) { document.insertString(document.getLineStartOffset(line), toInsert); } } }
private static void doPaste( final Editor editor, final Project project, final PsiFile file, final Document document, final Producer<Transferable> producer) { Transferable content = null; if (producer != null) { content = producer.produce(); } else { CopyPasteManager manager = CopyPasteManager.getInstance(); if (manager.areDataFlavorsAvailable(DataFlavor.stringFlavor)) { content = manager.getContents(); if (content != null) { manager.stopKillRings(); } } } if (content != null) { String text = null; try { text = (String) content.getTransferData(DataFlavor.stringFlavor); } catch (Exception e) { editor.getComponent().getToolkit().beep(); } if (text == null) return; final CodeInsightSettings settings = CodeInsightSettings.getInstance(); final Map<CopyPastePostProcessor, TextBlockTransferableData> extraData = new HashMap<CopyPastePostProcessor, TextBlockTransferableData>(); for (CopyPastePostProcessor processor : Extensions.getExtensions(CopyPastePostProcessor.EP_NAME)) { TextBlockTransferableData data = processor.extractTransferableData(content); if (data != null) { extraData.put(processor, data); } } text = TextBlockTransferable.convertLineSeparators(text, "\n", extraData.values()); final CaretModel caretModel = editor.getCaretModel(); final SelectionModel selectionModel = editor.getSelectionModel(); final int col = caretModel.getLogicalPosition().column; // There is a possible case that we want to perform paste while there is an active selection // at the editor and caret is located // inside it (e.g. Ctrl+A is pressed while caret is not at the zero column). We want to insert // the text at selection start column // then, hence, inserted block of text should be indented according to the selection start as // well. final int blockIndentAnchorColumn; final int caretOffset = caretModel.getOffset(); if (selectionModel.hasSelection() && caretOffset >= selectionModel.getSelectionStart()) { blockIndentAnchorColumn = editor.offsetToLogicalPosition(selectionModel.getSelectionStart()).column; } else { blockIndentAnchorColumn = col; } // We assume that EditorModificationUtil.insertStringAtCaret() is smart enough to remove // currently selected text (if any). RawText rawText = RawText.fromTransferable(content); String newText = text; for (CopyPastePreProcessor preProcessor : Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) { newText = preProcessor.preprocessOnPaste(project, file, editor, newText, rawText); } int indentOptions = text.equals(newText) ? settings.REFORMAT_ON_PASTE : CodeInsightSettings.REFORMAT_BLOCK; text = newText; if (LanguageFormatting.INSTANCE.forContext(file) == null && indentOptions != CodeInsightSettings.NO_REFORMAT) { indentOptions = CodeInsightSettings.INDENT_BLOCK; } final String _text = text; ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { EditorModificationUtil.insertStringAtCaret(editor, _text, false, true); } }); int length = text.length(); int offset = caretModel.getOffset() - length; if (offset < 0) { length += offset; offset = 0; } final RangeMarker bounds = document.createRangeMarker(offset, offset + length); caretModel.moveToOffset(bounds.getEndOffset()); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); selectionModel.removeSelection(); final Ref<Boolean> indented = new Ref<Boolean>(Boolean.FALSE); for (Map.Entry<CopyPastePostProcessor, TextBlockTransferableData> e : extraData.entrySet()) { //noinspection unchecked e.getKey() .processTransferableData(project, editor, bounds, caretOffset, indented, e.getValue()); } boolean pastedTextContainsWhiteSpacesOnly = CharArrayUtil.shiftForward(document.getCharsSequence(), bounds.getStartOffset(), " \n\t") >= bounds.getEndOffset(); VirtualFile virtualFile = file.getVirtualFile(); if (!pastedTextContainsWhiteSpacesOnly && (virtualFile == null || !SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile))) { final int indentOptions1 = indentOptions; ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { switch (indentOptions1) { case CodeInsightSettings.INDENT_BLOCK: if (!indented.get()) { indentBlock( project, editor, bounds.getStartOffset(), bounds.getEndOffset(), blockIndentAnchorColumn); } break; case CodeInsightSettings.INDENT_EACH_LINE: if (!indented.get()) { indentEachLine( project, editor, bounds.getStartOffset(), bounds.getEndOffset()); } break; case CodeInsightSettings.REFORMAT_BLOCK: indentEachLine( project, editor, bounds.getStartOffset(), bounds .getEndOffset()); // this is needed for example when inserting a // comment before method reformatBlock( project, editor, bounds.getStartOffset(), bounds.getEndOffset()); break; } } }); } if (bounds.isValid()) { caretModel.moveToOffset(bounds.getEndOffset()); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); selectionModel.removeSelection(); editor.putUserData(EditorEx.LAST_PASTED_REGION, TextRange.create(bounds)); } } }
private void executeWriteActionInner(Editor editor, DataContext dataContext, Project project) { CodeInsightSettings settings = CodeInsightSettings.getInstance(); if (project == null) { myOriginalHandler.execute(editor, dataContext); return; } final Document document = editor.getDocument(); final PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project); if (file == null) { myOriginalHandler.execute(editor, dataContext); return; } CommandProcessor.getInstance() .setCurrentCommandName(CodeInsightBundle.message("command.name.typing")); EditorModificationUtil.deleteSelectedText(editor); int caretOffset = editor.getCaretModel().getOffset(); CharSequence text = document.getCharsSequence(); int length = document.getTextLength(); if (caretOffset < length && text.charAt(caretOffset) != '\n') { int offset1 = CharArrayUtil.shiftBackward(text, caretOffset, " \t"); if (offset1 < 0 || text.charAt(offset1) == '\n') { int offset2 = CharArrayUtil.shiftForward(text, offset1 + 1, " \t"); boolean isEmptyLine = offset2 >= length || text.charAt(offset2) == '\n'; if (!isEmptyLine) { // we are in leading spaces of a non-empty line myOriginalHandler.execute(editor, dataContext); return; } } } final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); documentManager.commitDocument(document); boolean forceIndent = false; boolean forceSkipIndent = false; Ref<Integer> caretOffsetRef = new Ref<Integer>(caretOffset); Ref<Integer> caretAdvanceRef = new Ref<Integer>(0); final EnterHandlerDelegate[] delegates = Extensions.getExtensions(EnterHandlerDelegate.EP_NAME); for (EnterHandlerDelegate delegate : delegates) { EnterHandlerDelegate.Result result = delegate.preprocessEnter( file, editor, caretOffsetRef, caretAdvanceRef, dataContext, myOriginalHandler); if (caretOffsetRef.get() > document.getTextLength()) { throw new AssertionError("Wrong caret offset change by " + delegate); } if (result == EnterHandlerDelegate.Result.Stop) { return; } if (result != EnterHandlerDelegate.Result.Continue) { if (result == EnterHandlerDelegate.Result.DefaultForceIndent) { forceIndent = true; } else if (result == EnterHandlerDelegate.Result.DefaultSkipIndent) { forceSkipIndent = true; } break; } } text = document.getCharsSequence(); // update after changes done in preprocessEnter() caretOffset = caretOffsetRef.get().intValue(); boolean isFirstColumn = caretOffset == 0 || text.charAt(caretOffset - 1) == '\n'; final boolean insertSpace = !isFirstColumn && !(caretOffset >= text.length() || text.charAt(caretOffset) == ' ' || text.charAt(caretOffset) == '\t'); editor.getCaretModel().moveToOffset(caretOffset); myOriginalHandler.execute(editor, dataContext); if (!editor.isInsertMode() || forceSkipIndent) { return; } if (settings.SMART_INDENT_ON_ENTER || forceIndent) { caretOffset += 1; caretOffset = CharArrayUtil.shiftForward(editor.getDocument().getCharsSequence(), caretOffset, " \t"); } else { caretOffset = editor.getCaretModel().getOffset(); } documentManager.commitAllDocuments(); final DoEnterAction action = new DoEnterAction( file, editor, document, dataContext, caretOffset, !insertSpace, caretAdvanceRef.get(), project); action.setForceIndent(forceIndent); action.run(); documentManager.commitDocument(document); for (EnterHandlerDelegate delegate : delegates) { if (delegate.postProcessEnter(file, editor, dataContext) == EnterHandlerDelegate.Result.Stop) { break; } } documentManager.commitDocument(document); }
private boolean insertDocAsterisk( int lineStart, boolean docAsterisk, boolean previousLineIndentUsed, CodeDocumentationAwareCommenter commenter) { PsiElement atLineStart = myFile.findElementAt(lineStart); if (atLineStart == null) return false; final String linePrefix = commenter.getDocumentationCommentLinePrefix(); final String docPrefix = commenter.getDocumentationCommentPrefix(); final String text = atLineStart.getText(); final TextRange textRange = atLineStart.getTextRange(); if (text.equals(linePrefix) || text.equals(docPrefix) || docPrefix != null && text.regionMatches( lineStart - textRange.getStartOffset(), docPrefix, 0, docPrefix.length()) || linePrefix != null && text.regionMatches( lineStart - textRange.getStartOffset(), linePrefix, 0, linePrefix.length())) { PsiElement element = myFile.findElementAt(myOffset); if (element == null) return false; PsiComment comment = element instanceof PsiComment ? (PsiComment) element : PsiTreeUtil.getParentOfType(element, PsiComment.class, false); if (comment != null) { int commentEnd = comment.getTextRange().getEndOffset(); if (myOffset >= commentEnd) { docAsterisk = false; } else { removeTrailingSpaces(myDocument, myOffset); String toInsert = previousLineIndentUsed ? "*" : CodeDocumentationUtil.createDocCommentLine("", getProject(), commenter); myDocument.insertString(myOffset, toInsert); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); } } else { docAsterisk = false; } } else if (linePrefix != null && atLineStart instanceof PsiComment && ((PsiComment) atLineStart).getTokenType() == commenter.getBlockCommentTokenType()) { // Check if C-Style comment already uses asterisks. boolean usesAstersk = false; int commentLine = myDocument.getLineNumber(textRange.getStartOffset()); if (commentLine < myDocument.getLineCount() - 1 && textRange.getEndOffset() >= myOffset) { int nextLineOffset = myDocument.getLineStartOffset(commentLine + 1); if (nextLineOffset < textRange.getEndOffset()) { final CharSequence chars = myDocument.getCharsSequence(); nextLineOffset = CharArrayUtil.shiftForward(chars, nextLineOffset, " \t"); usesAstersk = CharArrayUtil.regionMatches(chars, nextLineOffset, linePrefix); } } if (usesAstersk) { removeTrailingSpaces(myDocument, myOffset); myDocument.insertString(myOffset, linePrefix + " "); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); } docAsterisk = usesAstersk; } else { docAsterisk = false; } return docAsterisk; }
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { myProject = project; myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); myEditor = editor; PsiElement context = myFile.getContext(); if (context != null && (context.textContains('\'') || context.textContains('\"'))) { String s = context.getText(); if (StringUtil.startsWith(s, "\"") || StringUtil.startsWith(s, "\'")) { myFile = context.getContainingFile(); myEditor = editor instanceof EditorWindow ? ((EditorWindow) editor).getDelegate() : editor; } } myDocument = myEditor.getDocument(); if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) { return; } PsiDocumentManager.getInstance(project).commitDocument(myDocument); FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line"); myCodeStyleManager = CodeStyleManager.getInstance(myProject); final SelectionModel selectionModel = myEditor.getSelectionModel(); boolean hasSelection = selectionModel.hasSelection(); myStartOffset = selectionModel.getSelectionStart(); myEndOffset = selectionModel.getSelectionEnd(); FoldRegion fold = myEditor.getFoldingModel().getCollapsedRegionAtOffset(myStartOffset); if (fold != null && fold.shouldNeverExpand() && fold.getStartOffset() == myStartOffset && fold.getEndOffset() == myEndOffset) { // Foldings that never expand are automatically selected, so the fact it is selected must not // interfer with commenter's logic hasSelection = false; } if (myDocument.getTextLength() == 0) return; while (true) { int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset)); FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd); if (collapsedAt != null) { final int endOffset = collapsedAt.getEndOffset(); if (endOffset <= myEndOffset) { break; } myEndOffset = endOffset; } else { break; } } boolean wholeLinesSelected = !hasSelection || myStartOffset == myDocument.getLineStartOffset(myDocument.getLineNumber(myStartOffset)) && myEndOffset == myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset - 1)) + 1; boolean startingNewLineComment = !hasSelection && isLineEmpty(myDocument.getLineNumber(myStartOffset)) && !Comparing.equal( IdeActions.ACTION_COMMENT_LINE, ActionManagerEx.getInstanceEx().getPrevPreformedActionId()); doComment(); if (startingNewLineComment) { final Commenter commenter = myCommenters[0]; if (commenter != null) { String prefix; if (commenter instanceof SelfManagingCommenter) { prefix = ((SelfManagingCommenter) commenter) .getCommentPrefix( myStartLine, myDocument, myCommenterStateMap.get((SelfManagingCommenter) commenter)); if (prefix == null) prefix = ""; // TODO } else { prefix = commenter.getLineCommentPrefix(); if (prefix == null) prefix = commenter.getBlockCommentPrefix(); } int lineStart = myDocument.getLineStartOffset(myStartLine); lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t"); lineStart += prefix.length(); lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t"); if (lineStart > myDocument.getTextLength()) lineStart = myDocument.getTextLength(); myEditor.getCaretModel().moveToOffset(lineStart); myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } } else { if (!hasSelection) { // Don't tweak caret position if we're already located on the last document line. LogicalPosition position = myEditor.getCaretModel().getLogicalPosition(); if (position.line < myDocument.getLineCount() - 1) { int verticalShift = 1 + myEditor.getSoftWrapModel().getSoftWrapsForLine(position.line).size() - position.softWrapLinesOnCurrentLogicalLine; myEditor.getCaretModel().moveCaretRelatively(0, verticalShift, false, false, true); } } else { if (wholeLinesSelected) { selectionModel.setSelection(myStartOffset, selectionModel.getSelectionEnd()); } } } }
private static Pair<Set<String>, Set<TextWithImports>> findReferencedVars( Set<String> visibleVars, SourcePosition position) { final int line = position.getLine(); if (line < 0) { return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet()); } final PsiFile positionFile = position.getFile(); if (!positionFile.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { return Pair.create(visibleVars, Collections.<TextWithImports>emptySet()); } final VirtualFile vFile = positionFile.getVirtualFile(); final Document doc = vFile != null ? FileDocumentManager.getInstance().getDocument(vFile) : null; if (doc == null || doc.getLineCount() == 0 || line > (doc.getLineCount() - 1)) { return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet()); } final TextRange limit = calculateLimitRange(positionFile, doc, line); int startLine = Math.max(limit.getStartOffset(), line - 1); startLine = Math.min(startLine, limit.getEndOffset()); while (startLine > limit.getStartOffset() && shouldSkipLine(positionFile, doc, startLine)) { startLine--; } final int startOffset = doc.getLineStartOffset(startLine); int endLine = Math.min(line + 2, limit.getEndOffset()); while (endLine < limit.getEndOffset() && shouldSkipLine(positionFile, doc, endLine)) { endLine++; } final int endOffset = doc.getLineEndOffset(endLine); final TextRange lineRange = new TextRange(startOffset, endOffset); if (!lineRange.isEmpty()) { final int offset = CharArrayUtil.shiftForward(doc.getCharsSequence(), doc.getLineStartOffset(line), " \t"); PsiElement element = positionFile.findElementAt(offset); if (element != null) { PsiMethod method = PsiTreeUtil.getNonStrictParentOfType(element, PsiMethod.class); if (method != null) { element = method; } else { PsiField field = PsiTreeUtil.getNonStrictParentOfType(element, PsiField.class); if (field != null) { element = field; } else { final PsiClassInitializer initializer = PsiTreeUtil.getNonStrictParentOfType(element, PsiClassInitializer.class); if (initializer != null) { element = initializer; } } } //noinspection unchecked if (element instanceof PsiCompiledElement) { return Pair.create(visibleVars, Collections.<TextWithImports>emptySet()); } else { VariablesCollector collector = new VariablesCollector(visibleVars, adjustRange(element, lineRange)); element.accept(collector); return Pair.create(collector.getVars(), collector.getExpressions()); } } } return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet()); }
/** Calculates line indents for the {@link #myDocument target document}. */ void calculate() { final FileType fileType = myFile.getFileType(); int prevLineIndent = -1; for (int line = 0; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); int lineStart = myDocument.getLineStartOffset(line); int lineEnd = myDocument.getLineEndOffset(line); final int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); if (nonWhitespaceOffset == lineEnd) { lineIndents[line] = -1; // Blank line marker } else { final int column = ((EditorImpl) myEditor).calcColumnNumber(nonWhitespaceOffset, line, true, myChars); if (prevLineIndent > 0 && prevLineIndent > column) { lineIndents[line] = calcIndent(line, nonWhitespaceOffset, lineEnd, column); } else { lineIndents[line] = column; } prevLineIndent = lineIndents[line]; } } int topIndent = 0; for (int line = 0; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); if (lineIndents[line] >= 0) { topIndent = lineIndents[line]; } else { int startLine = line; while (line < lineIndents.length && lineIndents[line] < 0) { //noinspection AssignmentToForLoopParameter line++; } int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; int indent = Math.min(topIndent, bottomIndent); if (bottomIndent < topIndent) { int lineStart = myDocument.getLineStartOffset(line); int lineEnd = myDocument.getLineEndOffset(line); int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); HighlighterIterator iterator = myEditor.getHighlighter().createIterator(nonWhitespaceOffset); if (BraceMatchingUtil.isRBraceToken(iterator, myChars, fileType)) { indent = topIndent; } } for (int blankLine = startLine; blankLine < line; blankLine++) { assert lineIndents[blankLine] == -1; lineIndents[blankLine] = Math.min(topIndent, indent); } //noinspection AssignmentToForLoopParameter line--; // will be incremented back at the end of the loop; } } }
@Override public void run() { CaretModel caretModel = myEditor.getCaretModel(); try { final CharSequence chars = myDocument.getCharsSequence(); int i = CharArrayUtil.shiftBackwardUntil(chars, myOffset - 1, LINE_SEPARATOR) - 1; i = CharArrayUtil.shiftBackwardUntil(chars, i, LINE_SEPARATOR) + 1; if (i < 0) i = 0; int lineStart = CharArrayUtil.shiftForward(chars, i, " \t"); CodeDocumentationUtil.CommentContext commentContext = CodeDocumentationUtil.tryParseCommentContext(myFile, chars, myOffset, lineStart); PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(getProject()); if (commentContext.docStart) { PsiElement element = myFile.findElementAt(commentContext.lineStart); final String text = element.getText(); final PsiElement parent = element.getParent(); if (text.equals(commentContext.commenter.getDocumentationCommentPrefix()) && isDocComment(parent, commentContext.commenter) || text.startsWith(commentContext.commenter.getDocumentationCommentPrefix()) && element instanceof PsiComment) { PsiComment comment = isDocComment(parent, commentContext.commenter) ? (PsiComment) parent : (PsiComment) element; int commentEnd = comment.getTextRange().getEndOffset(); if (myOffset >= commentEnd) { commentContext.docStart = false; } else { if (isCommentComplete(comment, commentContext.commenter, myEditor)) { if (myOffset >= commentEnd) { commentContext.docAsterisk = false; commentContext.docStart = false; } else { commentContext.docAsterisk = true; commentContext.docStart = false; } } else { generateJavadoc(commentContext.commenter); } } } else { commentContext.docStart = false; } } else if (commentContext.cStyleStart) { PsiElement element = myFile.findElementAt(commentContext.lineStart); if (element instanceof PsiComment && commentContext.commenter.getBlockCommentTokenType() == ((PsiComment) element).getTokenType()) { final PsiComment comment = (PsiComment) element; int commentEnd = comment.getTextRange().getEndOffset(); if (myOffset >= commentEnd && myOffset < myFile.getTextRange().getEndOffset()) { commentContext.docStart = false; } else { if (isCommentComplete(comment, commentContext.commenter, myEditor)) { if (myOffset >= commentEnd) { commentContext.docAsterisk = false; commentContext.docStart = false; } else { commentContext.docAsterisk = true; commentContext.docStart = false; } } else { final int currentEndOfLine = CharArrayUtil.shiftForwardUntil(chars, myOffset, "\n"); myDocument.insertString( currentEndOfLine, " " + commentContext.commenter.getBlockCommentSuffix()); int lstart = CharArrayUtil.shiftBackwardUntil(chars, myOffset, "\n"); myDocument.insertString(currentEndOfLine, chars.subSequence(lstart, myOffset)); psiDocumentManager.commitDocument(myDocument); } } } else { commentContext.docStart = false; } } String indentInsideJavadoc = null; if (myOffset < myDocument.getTextLength()) { final int line = myDocument.getLineNumber(myOffset); if (line > 0 && (commentContext.docAsterisk || commentContext.docStart)) { indentInsideJavadoc = CodeDocumentationUtil.getIndentInsideJavadoc( myDocument, myDocument.getLineStartOffset(line - 1)); } } if (commentContext.docAsterisk) { commentContext.docAsterisk = insertDocAsterisk( commentContext.lineStart, commentContext.docAsterisk, !StringUtil.isEmpty(indentInsideJavadoc), commentContext.commenter); } boolean docIndentApplied = false; CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); if (codeInsightSettings.SMART_INDENT_ON_ENTER || myForceIndent || commentContext.docStart || commentContext.docAsterisk || commentContext.slashSlash) { final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject()); myOffset = codeStyleManager.adjustLineIndent(myFile, myOffset); psiDocumentManager.commitAllDocuments(); if (!StringUtil.isEmpty(indentInsideJavadoc) && myOffset < myDocument.getTextLength()) { myDocument.insertString(myOffset + 1, indentInsideJavadoc); myOffset += indentInsideJavadoc.length(); docIndentApplied = true; } if (myForceIndent && indentInsideJavadoc != null) { int indentSize = CodeStyleSettingsManager.getSettings(myProject).getIndentSize(myFile.getFileType()); myDocument.insertString(myOffset + 1, StringUtil.repeatSymbol(' ', indentSize)); myCaretAdvance += indentSize; } } if ((commentContext.docAsterisk || commentContext.docStart || commentContext.slashSlash) && !docIndentApplied) { if (myInsertSpace) { if (myOffset == myDocument.getTextLength()) { myDocument.insertString(myOffset, " "); } myDocument.insertString(myOffset + 1, " "); } final char c = myDocument.getCharsSequence().charAt(myOffset); if (c != '\n') { myOffset += 1; } } if ((commentContext.docAsterisk || commentContext.slashSlash) && !commentContext.docStart) { myCaretAdvance += commentContext.slashSlash ? commentContext.commenter.getLineCommentPrefix().length() : 1; } } catch (IncorrectOperationException e) { LOG.error(e); } myOffset = Math.min(myOffset, myDocument.getTextLength()); caretModel.moveToOffset(myOffset); myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); myEditor.getSelectionModel().removeSelection(); if (myCaretAdvance != 0) { LogicalPosition caretPosition = caretModel.getLogicalPosition(); LogicalPosition pos = new LogicalPosition(caretPosition.line, caretPosition.column + myCaretAdvance); caretModel.moveToLogicalPosition(pos); } }
@Override @SuppressWarnings({"AssignmentToForLoopParameter"}) public void paint( @NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) { int startOffset = highlighter.getStartOffset(); final Document doc = highlighter.getDocument(); if (startOffset >= doc.getTextLength()) return; final int endOffset = highlighter.getEndOffset(); final int endLine = doc.getLineNumber(endOffset); int off; int startLine = doc.getLineNumber(startOffset); IndentGuideDescriptor descriptor = editor.getIndentsModel().getDescriptor(startLine, endLine); final CharSequence chars = doc.getCharsSequence(); do { int start = doc.getLineStartOffset(startLine); int end = doc.getLineEndOffset(startLine); off = CharArrayUtil.shiftForward(chars, start, end, " \t"); startLine--; } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n'); final VisualPosition startPosition = editor.offsetToVisualPosition(off); int indentColumn = startPosition.column; // It's considered that indent guide can cross not only white space but comments, javadocs // etc. Hence, there is a possible // case that the first indent guide line is, say, single-line comment where comment // symbols ('//') are located at the first // visual column. We need to calculate correct indent guide column then. int lineShift = 1; if (indentColumn <= 0 && descriptor != null) { indentColumn = descriptor.indentLevel; lineShift = 0; } if (indentColumn <= 0) return; final FoldingModel foldingModel = editor.getFoldingModel(); if (foldingModel.isOffsetCollapsed(off)) return; final FoldRegion headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off))); final FoldRegion tailRegion = foldingModel.getCollapsedRegionAtOffset( doc.getLineStartOffset(doc.getLineNumber(endOffset))); if (tailRegion != null && tailRegion == headerRegion) return; final boolean selected; final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide(); if (guide != null) { final CaretModel caretModel = editor.getCaretModel(); final int caretOffset = caretModel.getOffset(); selected = caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == indentColumn; } else { selected = false; } Point start = editor.visualPositionToXY( new VisualPosition(startPosition.line + lineShift, indentColumn)); final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset); Point end = editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column)); int maxY = end.y; if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) { maxY += editor.getLineHeight(); } Rectangle clip = g.getClipBounds(); if (clip != null) { if (clip.y >= maxY || clip.y + clip.height <= start.y) { return; } maxY = Math.min(maxY, clip.y + clip.height); } final EditorColorsScheme scheme = editor.getColorsScheme(); g.setColor( selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR)); // There is a possible case that indent line intersects soft wrap-introduced text. // Example: // this is a long line <soft-wrap> // that| is soft-wrapped // | // | <- vertical indent // // Also it's possible that no additional intersections are added because of soft wrap: // this is a long line <soft-wrap> // | that is soft-wrapped // | // | <- vertical indent // We want to use the following approach then: // 1. Show only active indent if it crosses soft wrap-introduced text; // 2. Show indent as is if it doesn't intersect with soft wrap-introduced text; if (selected) { g.drawLine(start.x + 2, start.y, start.x + 2, maxY); } else { int y = start.y; int newY = start.y; SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int lineHeight = editor.getLineHeight(); for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) { List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i); int logicalLineHeight = softWraps.size() * lineHeight; if (i > startLine + lineShift) { logicalLineHeight += lineHeight; // We assume that initial 'y' value points just below the target // line. } if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) { if (y < newY || i > startLine + lineShift) { // There is a possible case that soft wrap is located on // indent start line. g.drawLine(start.x + 2, y, start.x + 2, newY + lineHeight); } newY += logicalLineHeight; y = newY; } else { newY += logicalLineHeight; } FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i)); if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) { i = doc.getLineNumber(foldRegion.getEndOffset()); } } if (y < maxY) { g.drawLine(start.x + 2, y, start.x + 2, maxY); } } }
private void generateJavadoc(CodeDocumentationAwareCommenter commenter) throws IncorrectOperationException { CodeInsightSettings settings = CodeInsightSettings.getInstance(); StringBuilder buffer = new StringBuilder(); final String docCommentLinePrefix = commenter.getDocumentationCommentLinePrefix(); if (docCommentLinePrefix == null) { return; } // There are at least two approaches for completing javadoc in case there is a text between // current caret position and line end: // 1. Move that tail text below the javadoc. Use-case: // Before: // /**<caret>public void foo() {} // After: // /** // */ // public void foo() {} // 2. Move the tail text inside the javadoc. Use-case: // Before: // /**This is <caret>javadoc description // After: // /** This is // * javadoc description // */ // The later is most relevant when we have 'auto wrap when typing reaches right margin' option // set, i.e. user starts javadoc // and types until right margin is reached. We want the wrapped text tail to be located inside // javadoc and continue typing // inside it. So, we have a control flow branch below that does the trick. buffer.append(docCommentLinePrefix); if (DataManager.getInstance() .loadFromDataContext( myDataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) == Boolean.TRUE) { myDocument.insertString(myOffset, buffer); // We create new buffer here because the one referenced by current 'buffer' variable value // may be already referenced at another // place (e.g. 'undo' processing stuff). buffer = new StringBuilder(LINE_SEPARATOR).append(commenter.getDocumentationCommentSuffix()); int line = myDocument.getLineNumber(myOffset); myOffset = myDocument.getLineEndOffset(line); } else { buffer.append(LINE_SEPARATOR); buffer.append(commenter.getDocumentationCommentSuffix()); } PsiComment comment = createComment(buffer, settings); if (comment == null) { return; } myOffset = comment.getTextRange().getStartOffset(); CharSequence text = myDocument.getCharsSequence(); myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, LINE_SEPARATOR); myOffset = CharArrayUtil.shiftForward(text, myOffset, LINE_SEPARATOR); myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, docCommentLinePrefix) + 1; removeTrailingSpaces(myDocument, myOffset); if (!CodeStyleSettingsManager.getSettings(getProject()).JD_LEADING_ASTERISKS_ARE_ENABLED) { LOG.assertTrue( CharArrayUtil.regionMatches( myDocument.getCharsSequence(), myOffset - docCommentLinePrefix.length(), docCommentLinePrefix)); myDocument.deleteString(myOffset - docCommentLinePrefix.length(), myOffset); myOffset--; } else { myDocument.insertString(myOffset, " "); myOffset++; } PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); }