private static void logInitial( @NotNull Editor editor, @NotNull int[] startOffsets, @NotNull int[] endOffsets, int indentSymbolsToStrip, int firstLineStartOffset) { if (!Registry.is("editor.richcopy.debug")) { return; } StringBuilder buffer = new StringBuilder(); Document document = editor.getDocument(); CharSequence text = document.getCharsSequence(); for (int i = 0; i < startOffsets.length; i++) { int start = startOffsets[i]; int lineStart = document.getLineStartOffset(document.getLineNumber(start)); int end = endOffsets[i]; int lineEnd = document.getLineEndOffset(document.getLineNumber(end)); buffer .append(" region #") .append(i) .append(": ") .append(start) .append('-') .append(end) .append(", text at range ") .append(lineStart) .append('-') .append(lineEnd) .append(": \n'") .append(text.subSequence(lineStart, lineEnd)) .append("'\n"); } if (buffer.length() > 0) { buffer.setLength(buffer.length() - 1); } LOG.info( String.format( "Preparing syntax-aware text. Given: %s selection, indent symbols to strip=%d, first line start offset=%d, selected text:%n%s", startOffsets.length > 1 ? "block" : "regular", indentSymbolsToStrip, firstLineStartOffset, buffer)); }
private Indent computeMinIndent( int line1, int line2, CharSequence chars, CodeStyleManager codeStyleManager, FileType fileType) { Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType); if (line1 > 0) { int commentOffset = getCommentStart(line1 - 1); if (commentOffset >= 0) { int lineStart = myDocument.getLineStartOffset(line1 - 1); String space = chars.subSequence(lineStart, commentOffset).toString(); Indent indent = codeStyleManager.getIndent(space, fileType); minIndent = minIndent != null ? indent.min(minIndent) : indent; } } if (minIndent == null) { minIndent = codeStyleManager.zeroIndent(); } return minIndent; }
public Map<TemplateImpl, String> findMatchingTemplates( final PsiFile file, Editor editor, @Nullable Character shortcutChar, TemplateSettings templateSettings) { final Document document = editor.getDocument(); CharSequence text = document.getCharsSequence(); final int caretOffset = editor.getCaretModel().getOffset(); List<TemplateImpl> candidatesWithoutArgument = findMatchingTemplates(text, caretOffset, shortcutChar, templateSettings, false); int argumentOffset = passArgumentBack(text, caretOffset); String argument = null; if (argumentOffset >= 0) { argument = text.subSequence(argumentOffset, caretOffset).toString(); if (argumentOffset > 0 && text.charAt(argumentOffset - 1) == ' ') { if (argumentOffset - 2 >= 0 && Character.isJavaIdentifierPart(text.charAt(argumentOffset - 2))) { argumentOffset--; } } } List<TemplateImpl> candidatesWithArgument = findMatchingTemplates(text, argumentOffset, shortcutChar, templateSettings, true); if (candidatesWithArgument.isEmpty() && candidatesWithoutArgument.isEmpty()) { return null; } candidatesWithoutArgument = filterApplicableCandidates(file, caretOffset, candidatesWithoutArgument); candidatesWithArgument = filterApplicableCandidates(file, argumentOffset, candidatesWithArgument); Map<TemplateImpl, String> candidate2Argument = new HashMap<TemplateImpl, String>(); addToMap(candidate2Argument, candidatesWithoutArgument, null); addToMap(candidate2Argument, candidatesWithArgument, argument); return candidate2Argument; }
public static List<TemplateImpl> findMatchingTemplates( CharSequence text, int caretOffset, @Nullable Character shortcutChar, TemplateSettings settings, boolean hasArgument) { List<TemplateImpl> candidates = Collections.emptyList(); for (int i = settings.getMaxKeyLength(); i >= 1; i--) { int wordStart = caretOffset - i; if (wordStart < 0) { continue; } String key = text.subSequence(wordStart, caretOffset).toString(); if (Character.isJavaIdentifierStart(key.charAt(0))) { if (wordStart > 0 && Character.isJavaIdentifierPart(text.charAt(wordStart - 1))) { continue; } } candidates = settings.collectMatchingCandidates(key, shortcutChar, hasArgument); if (!candidates.isEmpty()) break; } return candidates; }
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); } } }
@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); } }
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); } } }