public void commentRange( int startOffset, int endOffset, String commentPrefix, String commentSuffix, Commenter commenter) { final CharSequence chars = myDocument.getCharsSequence(); LogicalPosition caretPosition = myCaret.getLogicalPosition(); if (startOffset == 0 || chars.charAt(startOffset - 1) == '\n') { if (endOffset == myDocument.getTextLength() || endOffset > 0 && chars.charAt(endOffset - 1) == '\n') { CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(myProject); CommonCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(myFile.getLanguage()); String space; if (!settings.BLOCK_COMMENT_AT_FIRST_COLUMN) { final FileType fileType = myFile.getFileType(); int line1 = myEditor.offsetToLogicalPosition(startOffset).line; int line2 = myEditor.offsetToLogicalPosition(endOffset - 1).line; Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType); if (minIndent == null) { minIndent = codeStyleManager.zeroIndent(); } space = codeStyleManager.fillIndent(minIndent, fileType); } else { space = ""; } final StringBuilder nestingPrefix = new StringBuilder(space).append(commentPrefix); if (!commentPrefix.endsWith("\n")) { nestingPrefix.append("\n"); } final StringBuilder nestingSuffix = new StringBuilder(space); nestingSuffix.append( commentSuffix.startsWith("\n") ? commentSuffix.substring(1) : commentSuffix); nestingSuffix.append("\n"); TextRange range = insertNestedComments( startOffset, endOffset, nestingPrefix.toString(), nestingSuffix.toString(), commenter); myCaret.setSelection(range.getStartOffset(), range.getEndOffset()); LogicalPosition pos = new LogicalPosition(caretPosition.line + 1, caretPosition.column); myCaret.moveToLogicalPosition(pos); return; } } TextRange range = insertNestedComments(startOffset, endOffset, commentPrefix, commentSuffix, commenter); myCaret.setSelection(range.getStartOffset(), range.getEndOffset()); LogicalPosition pos = new LogicalPosition(caretPosition.line, caretPosition.column + commentPrefix.length()); myCaret.moveToLogicalPosition(pos); }
private static int getArgumentOffset(int caretOffset, String argument, CharSequence text) { int argumentOffset = caretOffset - argument.length(); if (argumentOffset > 0 && text.charAt(argumentOffset - 1) == ' ') { if (argumentOffset - 2 >= 0 && Character.isJavaIdentifierPart(text.charAt(argumentOffset - 2))) { argumentOffset--; } } return argumentOffset; }
private void addTextIfPossible(int endOffset) { if (endOffset <= myStartOffset) { return; } for (int i = myStartOffset; i < endOffset; i++) { char c = myText.charAt(i); switch (c) { case '\n': myIndentSymbolsToStripAtCurrentLine = myIndentSymbolsToStrip; builder.addText(myStartOffset + myOffsetShift, i + myOffsetShift + 1); myStartOffset = i + 1; break; // Intended fall-through. case ' ': case '\t': if (myIndentSymbolsToStripAtCurrentLine > 0) { myIndentSymbolsToStripAtCurrentLine--; myStartOffset++; continue; } default: myIndentSymbolsToStripAtCurrentLine = 0; } } if (myStartOffset < endOffset) { builder.addText(myStartOffset + myOffsetShift, endOffset + myOffsetShift); myStartOffset = endOffset; } }
private int wrapPositionForTabbedTextWithOptimization( @NotNull CharSequence text, int tabSize, int startLineOffset, int endLineOffset, int targetRangeEndOffset) { int width = 0; int symbolWidth; int result = Integer.MAX_VALUE; boolean wrapLine = false; for (int i = startLineOffset; i < Math.min(endLineOffset, targetRangeEndOffset); i++) { char c = text.charAt(i); switch (c) { case '\t': symbolWidth = tabSize - (width % tabSize); break; default: symbolWidth = 1; } if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= mySettings.RIGHT_MARGIN && (Math.min(endLineOffset, targetRangeEndOffset) - i) >= FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS) { // Remember preferred position. result = i - 1; } if (width + symbolWidth >= mySettings.RIGHT_MARGIN) { wrapLine = true; break; } width += symbolWidth; } return wrapLine ? result : -1; }
private void doIndentCommenting(Commenter commenter) { CharSequence chars = myDocument.getCharsSequence(); final FileType fileType = myFile.getFileType(); Indent minIndent = computeMinIndent(myStartLine, myEndLine, chars, myCodeStyleManager, fileType); for (int line = myEndLine; line >= myStartLine; line--) { int lineStart = myDocument.getLineStartOffset(line); int offset = lineStart; final StringBuilder buffer = StringBuilderSpinAllocator.alloc(); try { while (true) { String space = buffer.toString(); Indent indent = myCodeStyleManager.getIndent(space, fileType); if (indent.isGreaterThan(minIndent) || indent.equals(minIndent)) break; char c = chars.charAt(offset); if (c != ' ' && c != '\t') { String newSpace = myCodeStyleManager.fillIndent(minIndent, fileType); myDocument.replaceString(lineStart, offset, newSpace); offset = lineStart + newSpace.length(); break; } buffer.append(c); offset++; } } finally { StringBuilderSpinAllocator.dispose(buffer); } commentLine(line, offset, commenter); } }
@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); } }
private boolean isLineEmpty(final int line) { final CharSequence chars = myDocument.getCharsSequence(); int start = myDocument.getLineStartOffset(line); int end = Math.min(myDocument.getLineEndOffset(line), myDocument.getTextLength() - 1); for (int i = start; i <= end; i++) { if (!Character.isWhitespace(chars.charAt(i))) return false; } return true; }
private static int passArgumentBack(CharSequence text, int caretOffset) { int i = caretOffset - 1; for (; i >= 0; i--) { char c = text.charAt(i); if (isDelimiter(c)) { break; } } return i + 1; }
private static void indentEachLine( Project project, Editor editor, int startOffset, int endOffset) { PsiDocumentManager.getInstance(project).commitAllDocuments(); PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); final CharSequence text = editor.getDocument().getCharsSequence(); if (startOffset > 0 && endOffset > startOffset + 1 && text.charAt(endOffset - 1) == '\n' && text.charAt(startOffset - 1) == '\n') { // There is a possible situation that pasted text ends by a line feed. We don't want to // proceed it when a text is // pasted at the first line column. // Example: // text to paste: // 'if (true) { // ' // source: // if (true) { // int i = 1; // int j = 1; // } // // // We get the following on paste then: // if (true) { // if (true) { // int i = 1; // int j = 1; // } // // We don't want line 'int i = 1;' to be indented here. endOffset--; } try { codeStyleManager.adjustLineIndent(file, new TextRange(startOffset, endOffset)); } catch (IncorrectOperationException e) { LOG.error(e); } }
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); }
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; }
private void paintWhitespace( Graphics2D g, CharSequence text, float x, int y, int start, int end, EditorImpl.LineWhitespacePaintingStrategy whitespacePaintingStrategy, VisualLineFragmentsIterator.Fragment fragment) { g.setColor(myEditor.getColorsScheme().getColor(EditorColors.WHITESPACES_COLOR)); boolean isRtl = fragment.isRtl(); int baseStartOffset = fragment.getStartOffset(); int startOffset = isRtl ? baseStartOffset - start : baseStartOffset + start; for (int i = start; i < end; i++) { int charOffset = isRtl ? baseStartOffset - i - 1 : baseStartOffset + i; char c = text.charAt(charOffset); if (" \t\u3000".indexOf(c) >= 0 && whitespacePaintingStrategy.showWhitespaceAtOffset(charOffset)) { int startX = (int) fragment.offsetToX( x, startOffset, isRtl ? baseStartOffset - i : baseStartOffset + i); int endX = (int) fragment.offsetToX( x, startOffset, isRtl ? baseStartOffset - i - 1 : baseStartOffset + i + 1); if (c == ' ') { g.fillRect((startX + endX) / 2, y, 1, 1); } else if (c == '\t') { endX -= myView.getPlainSpaceWidth() / 4; int height = myView.getCharHeight(); int halfHeight = height / 2; int mid = y - halfHeight; int top = y - height; UIUtil.drawLine(g, startX, mid, endX, mid); UIUtil.drawLine(g, endX, y, endX, top); g.fillPolygon( new int[] {endX - halfHeight, endX - halfHeight, endX}, new int[] {y, y - height, y - halfHeight}, 3); } else if (c == '\u3000') { // ideographic space final int charHeight = myView.getCharHeight(); g.drawRect(startX + 2, y - charHeight, endX - startX - 4, charHeight); } } } }
private static void removeTrailingSpaces(final Document document, final int startOffset) { int endOffset = startOffset; final CharSequence charsSequence = document.getCharsSequence(); for (int i = startOffset; i < charsSequence.length(); i++) { final char c = charsSequence.charAt(i); endOffset = i; if (c == '\n') { break; } if (c != ' ' && c != '\t') { return; } } document.deleteString(startOffset, endOffset); }
private int wrapPositionForTabbedTextWithoutOptimization( @NotNull Editor editor, @NotNull CharSequence text, int spaceSize, int startLineOffset, int endLineOffset, int targetRangeEndOffset) { int width = 0; int x = 0; int newX; int symbolWidth; int result = Integer.MAX_VALUE; boolean wrapLine = false; for (int i = startLineOffset; i < Math.min(endLineOffset, targetRangeEndOffset); i++) { char c = text.charAt(i); switch (c) { case '\t': newX = EditorUtil.nextTabStop(x, editor); int diffInPixels = newX - x; symbolWidth = diffInPixels / spaceSize; if (diffInPixels % spaceSize > 0) { symbolWidth++; } break; default: newX = x + EditorUtil.charWidth(c, Font.PLAIN, editor); symbolWidth = 1; } if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= mySettings.RIGHT_MARGIN && (Math.min(endLineOffset, targetRangeEndOffset) - i) >= FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS) { result = i - 1; } if (width + symbolWidth >= mySettings.RIGHT_MARGIN) { wrapLine = true; break; } x = newX; width += symbolWidth; } return wrapLine ? result : -1; }
public void advance() { myCurrentFontFamilyName = myNextFontFamilyName; myCurrentStartOffset = myCurrentOffset; for (; myCurrentOffset < myEndOffset; myCurrentOffset++) { FontInfo fontInfo = ComplementaryFontsRegistry.getFontAbleToDisplay( myCharSequence.charAt(myCurrentOffset), myFontSize, myFontStyle, myDefaultFontFamilyName); String fontFamilyName = fontInfo.getFont().getFamily(); if (myCurrentFontFamilyName == null) { myCurrentFontFamilyName = fontFamilyName; } else if (!myCurrentFontFamilyName.equals(fontFamilyName)) { myNextFontFamilyName = fontFamilyName; break; } } }
/** * Checks if it's worth to try to wrap target line (it's long enough) and tries to calculate * preferred wrap position. * * @param editor target editor * @param text text contained at the given editor * @param tabSize tab space to use (number of visual columns occupied by a tab) * @param spaceSize space width in pixels * @param startLineOffset start offset of the text line to process * @param endLineOffset end offset of the text line to process * @param targetRangeEndOffset target text region's end offset * @return negative value if no wrapping should be performed for the target line; preferred wrap * position otherwise */ private int calculatePreferredWrapPosition( @NotNull Editor editor, @NotNull CharSequence text, int tabSize, int spaceSize, int startLineOffset, int endLineOffset, int targetRangeEndOffset) { boolean hasTabs = false; boolean canOptimize = true; boolean hasNonSpaceSymbols = false; loop: for (int i = startLineOffset; i < Math.min(endLineOffset, targetRangeEndOffset); i++) { char c = text.charAt(i); switch (c) { case '\t': { hasTabs = true; if (hasNonSpaceSymbols) { canOptimize = false; break loop; } } case ' ': break; default: hasNonSpaceSymbols = true; } } if (!hasTabs) { return wrapPositionForTextWithoutTabs(startLineOffset, endLineOffset, targetRangeEndOffset); } else if (canOptimize) { return wrapPositionForTabbedTextWithOptimization( text, tabSize, startLineOffset, endLineOffset, targetRangeEndOffset); } else { return wrapPositionForTabbedTextWithoutOptimization( editor, text, spaceSize, startLineOffset, endLineOffset, targetRangeEndOffset); } }
private static Pair<Integer /* start offset to use */, Integer /* indent symbols to strip */> calcIndentSymbolsToStrip(@NotNull Document document, int startOffset, int endOffset) { int startLine = document.getLineNumber(startOffset); int endLine = document.getLineNumber(endOffset); CharSequence text = document.getCharsSequence(); int maximumCommonIndent = Integer.MAX_VALUE; int firstLineStart = startOffset; int firstLineEnd = startOffset; for (int line = startLine; line <= endLine; line++) { int lineStartOffset = document.getLineStartOffset(line); int lineEndOffset = document.getLineEndOffset(line); if (line == startLine) { firstLineStart = lineStartOffset; firstLineEnd = lineEndOffset; } int nonWsOffset = lineEndOffset; for (int i = lineStartOffset; i < lineEndOffset && (i - lineStartOffset) < maximumCommonIndent && i < endOffset; i++) { char c = text.charAt(i); if (c != ' ' && c != '\t') { nonWsOffset = i; break; } } if (nonWsOffset >= lineEndOffset) { continue; // Blank line } int indent = nonWsOffset - lineStartOffset; maximumCommonIndent = Math.min(maximumCommonIndent, indent); if (maximumCommonIndent == 0) { break; } } int startOffsetToUse = Math.min(firstLineEnd, Math.max(startOffset, firstLineStart + maximumCommonIndent)); return Pair.create(startOffsetToUse, maximumCommonIndent); }
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; }
@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 uncommentLine(int line) { Commenter commenter = myCommenters[line - myStartLine]; if (commenter == null) commenter = findCommenter(line); if (commenter == null) return; final int startOffset = myStartOffsets[line - myStartLine]; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter; selfManagingCommenter.uncommentLine( line, startOffset, myDocument, myCommenterStateMap.get(selfManagingCommenter)); return; } final int endOffset = myEndOffsets[line - myStartLine]; if (startOffset == endOffset) { return; } String prefix = commenter.getLineCommentPrefix(); if (prefix != null) { CharSequence chars = myDocument.getCharsSequence(); if (commenter instanceof CommenterWithLineSuffix) { CommenterWithLineSuffix commenterWithLineSuffix = (CommenterWithLineSuffix) commenter; String suffix = commenterWithLineSuffix.getLineCommentSuffix(); int theEnd = endOffset > 0 ? endOffset : myDocument.getLineEndOffset(line); while (theEnd > startOffset && Character.isWhitespace(chars.charAt(theEnd - 1))) { theEnd--; } String lineText = myDocument.getText(new TextRange(startOffset, theEnd)); if (lineText.indexOf(suffix) != -1) { int start = startOffset + lineText.indexOf(suffix); myDocument.deleteString(start, start + suffix.length()); } } boolean skipNewLine = false; boolean commented = CharArrayUtil.regionMatches(chars, startOffset, prefix) || (skipNewLine = prefix.endsWith(" ") && CharArrayUtil.regionMatches(chars, startOffset, prefix.trim() + "\n")); assert commented; int charsToDelete = skipNewLine ? prefix.trim().length() : prefix.length(); int theEnd = endOffset > 0 ? endOffset : chars.length(); // if there's exactly one space after line comment prefix and before the text that follows in // the same line, delete the space too if (startOffset + charsToDelete < theEnd - 1 && chars.charAt(startOffset + charsToDelete) == ' ') { if (startOffset + charsToDelete == theEnd - 2 || chars.charAt(startOffset + charsToDelete + 1) != ' ') { charsToDelete++; } } myDocument.deleteString(startOffset, startOffset + charsToDelete); return; } String text = myDocument.getCharsSequence().subSequence(startOffset, endOffset).toString(); prefix = commenter.getBlockCommentPrefix(); final String suffix = commenter.getBlockCommentSuffix(); if (prefix == null || suffix == null) { return; } IntArrayList prefixes = new IntArrayList(); IntArrayList suffixes = new IntArrayList(); for (int position = 0; position < text.length(); ) { int prefixPos = text.indexOf(prefix, position); if (prefixPos == -1) { break; } prefixes.add(prefixPos); position = prefixPos + prefix.length(); int suffixPos = text.indexOf(suffix, position); if (suffixPos == -1) { suffixPos = text.length() - suffix.length(); } suffixes.add(suffixPos); position = suffixPos + suffix.length(); } assert prefixes.size() == suffixes.size(); for (int i = prefixes.size() - 1; i >= 0; i--) { uncommentRange( startOffset + prefixes.get(i), Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset), commenter); } }
public void moveCaretRelatively( int columnShift, int lineShift, boolean withSelection, boolean blockSelection, boolean scrollToCaret) { assertIsDispatchThread(); SelectionModel selectionModel = myEditor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : getLogicalPosition(); EditorSettings editorSettings = myEditor.getSettings(); VisualPosition visualCaret = getVisualPosition(); int desiredX = myDesiredX; if (columnShift == 0) { if (myDesiredX < 0) { desiredX = myEditor.visualPositionToXY(visualCaret).x; } } else { myDesiredX = desiredX = -1; } int newLineNumber = visualCaret.line + lineShift; int newColumnNumber = visualCaret.column + columnShift; if (desiredX >= 0 && !ApplicationManager.getApplication().isUnitTestMode()) { newColumnNumber = myEditor.xyToVisualPosition( new Point(desiredX, Math.max(0, newLineNumber) * myEditor.getLineHeight())) .column; } Document document = myEditor.getDocument(); if (!editorSettings.isVirtualSpace() && columnShift == 0 && getLogicalPosition().softWrapLinesOnCurrentLogicalLine <= 0) { newColumnNumber = myEditor.getLastColumnNumber(); } else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) { int lastLine = document.getLineCount() - 1; if (lastLine < 0) lastLine = 0; if (EditorModificationUtil.calcAfterLineEnd(myEditor) >= 0 && newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) { newColumnNumber = 0; newLineNumber++; } } else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == -1) { if (newColumnNumber < 0 && newLineNumber > 0) { newLineNumber--; newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber); } } if (newColumnNumber < 0) newColumnNumber = 0; // There is a possible case that caret is located at the first line and user presses 'Shift+Up'. // We want to select all text // from the document start to the current caret position then. So, we have a dedicated flag for // tracking that. boolean selectToDocumentStart = false; if (newLineNumber < 0) { selectToDocumentStart = true; newLineNumber = 0; // We want to move caret to the first column if it's already located at the first line and // 'Up' is pressed. newColumnNumber = 0; desiredX = -1; } VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber); int lastColumnNumber = newColumnNumber; if (!editorSettings.isCaretInsideTabs() && !myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber)); int offset = myEditor.logicalPositionToOffset(log); if (offset >= document.getTextLength()) { int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column; // We want to move caret to the last column if if it's located at the last line and 'Down' // is pressed. newColumnNumber = lastColumnNumber = Math.max(lastOffsetColumn, newColumnNumber); desiredX = -1; } CharSequence text = document.getCharsSequence(); if (offset >= 0 && offset < document.getTextLength()) { if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) { if (columnShift <= 0) { newColumnNumber = myEditor.offsetToVisualPosition(offset).column; } else { SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1); // There is a possible case that tabulation symbol is the last document symbol // represented on a visual line before // soft wrap. We can't just use column from 'offset + 1' because it would point on a // next visual line. if (softWrap == null) { newColumnNumber = myEditor.offsetToVisualPosition(offset + 1).column; } else { newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber); } } } } } pos = new VisualPosition(newLineNumber, newColumnNumber); if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) { LogicalPosition logical = myEditor.visualToLogicalPosition(pos); int softWrapOffset = myEditor.logicalPositionToOffset(logical); if (columnShift >= 0) { moveToOffset(softWrapOffset); } else { int line = myEditor.offsetToVisualLine(softWrapOffset - 1); moveToVisualPosition( new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line))); } } else { moveToVisualPosition(pos); if (!editorSettings.isVirtualSpace() && columnShift == 0) { myEditor.setLastColumnNumber(lastColumnNumber); } } if (withSelection) { if (blockSelection) { selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition()); } else { if (selectToDocumentStart) { selectionModel.setSelection(selectionStart, 0); } else if (pos.line >= myEditor.getVisibleLineCount()) { if (selectionStart < document.getTextLength()) { selectionModel.setSelection(selectionStart, document.getTextLength()); } } else { selectionModel.setSelection(selectionStart, getVisualPosition(), getOffset()); } } } else { selectionModel.removeSelection(); } if (scrollToCaret) { myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } if (desiredX >= 0) { myDesiredX = desiredX; } EditorActionUtil.selectNonexpandableFold(myEditor); }
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 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); }