diff options
Diffstat (limited to 'platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java')
-rw-r--r-- | platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java | 623 |
1 files changed, 324 insertions, 299 deletions
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java index ba73ac14d715..d62f51bb2e02 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,13 @@ package com.intellij.codeInsight.generation; -import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.CodeInsightUtilBase; import com.intellij.codeInsight.CommentUtil; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.highlighter.custom.SyntaxTable; import com.intellij.injected.editor.EditorWindow; +import com.intellij.injected.editor.InjectedCaret; import com.intellij.lang.Commenter; import com.intellij.lang.Language; import com.intellij.lang.LanguageCommenters; @@ -38,7 +39,6 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; @@ -53,181 +53,245 @@ import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -public class CommentByLineCommentHandler implements CodeInsightActionHandler { - +public class CommentByLineCommentHandler extends MultiCaretCodeInsightActionHandler { private Project myProject; - private PsiFile myFile; - private Document myDocument; - private Editor myEditor; - private int myStartOffset; - private int myEndOffset; - private int myStartLine; - private int myEndLine; - private int[] myStartOffsets; - private int[] myEndOffsets; - private Commenter[] myCommenters; - private Map<SelfManagingCommenter, CommenterDataHolder> myCommenterStateMap; private CodeStyleManager myCodeStyleManager; + private final List<Block> myBlocks = new ArrayList<Block>(); + @Override - public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + // first pass - adjacent carets are grouped into blocks + public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return; myProject = project; - myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); - myEditor = editor; + file = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); - PsiElement context = InjectedLanguageManager.getInstance(myFile.getProject()).getInjectionHost(myFile); + PsiElement context = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(file); 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; + file = context.getContainingFile(); + editor = editor instanceof EditorWindow ? ((EditorWindow)editor).getDelegate() : editor; + caret = caret instanceof InjectedCaret ? ((InjectedCaret)caret).getDelegate() : caret; } } - myDocument = myEditor.getDocument(); - if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) { + Document document = editor.getDocument(); + if (!FileDocumentManager.getInstance().requestWriting(document, 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(); + boolean hasSelection = caret.hasSelection(); + int startOffset = caret.getSelectionStart(); + int endOffset = caret.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 + FoldRegion fold = editor.getFoldingModel().getCollapsedRegionAtOffset(startOffset); + if (fold != null && fold.shouldNeverExpand() && fold.getStartOffset() == startOffset && fold.getEndOffset() == endOffset) { + // Foldings that never expand are automatically selected, so the fact it is selected must not interfere with commenter's logic hasSelection = false; } - if (myDocument.getTextLength() == 0) return; + if (document.getTextLength() == 0) return; while (true) { - int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset)); - FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd); + int lastLineEnd = document.getLineEndOffset(document.getLineNumber(endOffset)); + FoldRegion collapsedAt = editor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd); if (collapsedAt != null) { - final int endOffset = collapsedAt.getEndOffset(); - if (endOffset <= myEndOffset) { + final int regionEndOffset = collapsedAt.getEndOffset(); + if (regionEndOffset <= endOffset) { break; } - myEndOffset = endOffset; + endOffset = regionEndOffset; } 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 startLine = document.getLineNumber(startOffset); + int endLine = document.getLineNumber(endOffset); - 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); - } + if (endLine > startLine && document.getLineStartOffset(endLine) == endOffset) { + endLine--; + } + + Block lastBlock = myBlocks.isEmpty() ? null : myBlocks.get(myBlocks.size() - 1); + Block currentBlock; + if (lastBlock == null || lastBlock.editor != editor || lastBlock.psiFile != file || endLine < (lastBlock.startLine - 1)) { + currentBlock = new Block(); + currentBlock.editor = editor; + currentBlock.psiFile = file; + currentBlock.endLine = endLine; + myBlocks.add(currentBlock); } 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()); - } - } + currentBlock = lastBlock; } - } + currentBlock.carets.add(caret); + currentBlock.startLine = startLine; - 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; + boolean wholeLinesSelected = !hasSelection || + startOffset == document.getLineStartOffset(document.getLineNumber(startOffset)) && + endOffset == document.getLineEndOffset(document.getLineNumber(endOffset - 1)) + 1; + boolean startingNewLineComment = !hasSelection + && isLineEmpty(document, document.getLineNumber(startOffset)) + && !Comparing.equal(IdeActions.ACTION_COMMENT_LINE, + ActionManagerEx.getInstanceEx().getPrevPreformedActionId()); + currentBlock.caretUpdate = startingNewLineComment ? CaretUpdate.PUT_AT_COMMENT_START : + !hasSelection ? CaretUpdate.SHIFT_DOWN : + wholeLinesSelected ? CaretUpdate.RESTORE_SELECTION : null; } - return true; - } @Override - public boolean startInWriteAction() { - return true; - } + public void postInvoke() { + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line"); + + myCodeStyleManager = CodeStyleManager.getInstance(myProject); - private void doComment() { - myStartLine = myDocument.getLineNumber(myStartOffset); - myEndLine = myDocument.getLineNumber(myEndOffset); + // second pass - determining whether we need to comment or to uncomment + boolean allLinesCommented = true; + for (Block block : myBlocks) { + int startLine = block.startLine; + int endLine = block.endLine; + Document document = block.editor.getDocument(); + PsiFile psiFile = block.psiFile; + + block.startOffsets = new int[endLine - startLine + 1]; + block.endOffsets = new int[endLine - startLine + 1]; + block.commenters = new Commenter[endLine - startLine + 1]; + block.commenterStateMap = new THashMap<SelfManagingCommenter, CommenterDataHolder>(); + CharSequence chars = document.getCharsSequence(); + + boolean singleline = startLine == endLine; + int offset = document.getLineStartOffset(startLine); + offset = CharArrayUtil.shiftForward(chars, offset, " \t"); - if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) { - myEndLine--; - } + int endOffset = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(endLine), " \t\n"); - 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(); + block.blockSuitableCommenter = getBlockSuitableCommenter(psiFile, offset, endOffset); + block.commentWithIndent = + !CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(psiFile.getLanguage()).LINE_COMMENT_AT_FIRST_COLUMN; + + for (int line = startLine; line <= endLine; line++) { + Commenter commenter = block.blockSuitableCommenter != null ? block.blockSuitableCommenter : findCommenter(block.editor, psiFile, line); + if (commenter == null || commenter.getLineCommentPrefix() == null + && (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) { + block.skip = true; + break; + } - boolean singleline = myStartLine == myEndLine; - int offset = myDocument.getLineStartOffset(myStartLine); - offset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t"); + if (commenter instanceof SelfManagingCommenter && block.commenterStateMap.get(commenter) == null) { + final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; + CommenterDataHolder state = selfManagingCommenter.createLineCommentingState(startLine, endLine, document, psiFile); + if (state == null) state = SelfManagingCommenter.EMPTY_STATE; + block.commenterStateMap.put(selfManagingCommenter, state); + } - int endOffset = CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), myDocument.getLineEndOffset(myEndLine), " \t\n"); + block.commenters[line - startLine] = commenter; + if (!isLineCommented(block, line, commenter) && (singleline || !isLineEmpty(document, line))) { + allLinesCommented = false; + if (commenter instanceof IndentedCommenter) { + final Boolean value = ((IndentedCommenter)commenter).forceIndentedLineComment(); + if (value != null) { + block.commentWithIndent = value; + } + } + break; + } + } + } + boolean moveCarets = true; + for (Block block : myBlocks) { + if (block.carets.size() > 1 && block.startLine != block.endLine) { + moveCarets = false; + break; + } + } + // third pass - actual change + for (Block block : myBlocks) { + if (!block.skip) { + if (!allLinesCommented) { + if (!block.commentWithIndent) { + doDefaultCommenting(block); + } + else { + doIndentCommenting(block); + } + } + else { + for (int line = block.endLine; line >= block.startLine; line--) { + uncommentLine(block, line); + } + } + } + + if (!moveCarets || block.caretUpdate == null) { + continue; + } + Document document = block.editor.getDocument(); + for (Caret caret : block.carets) { + switch (block.caretUpdate) { + case PUT_AT_COMMENT_START: + final Commenter commenter = block.commenters[0]; + if (commenter != null) { + String prefix; + if (commenter instanceof SelfManagingCommenter) { + prefix = ((SelfManagingCommenter)commenter).getCommentPrefix(block.startLine, + document, + block.commenterStateMap.get((SelfManagingCommenter)commenter)); + if (prefix == null) prefix = ""; // TODO + } + else { + prefix = commenter.getLineCommentPrefix(); + if (prefix == null) prefix = commenter.getBlockCommentPrefix(); + } + + int lineStart = document.getLineStartOffset(block.startLine); + lineStart = CharArrayUtil.shiftForward(document.getCharsSequence(), lineStart, " \t"); + lineStart += prefix.length(); + lineStart = CharArrayUtil.shiftForward(document.getCharsSequence(), lineStart, " \t"); + if (lineStart > document.getTextLength()) lineStart = document.getTextLength(); + caret.moveToOffset(lineStart); + } + break; + case SHIFT_DOWN: + // Don't tweak caret position if we're already located on the last document line. + LogicalPosition position = caret.getLogicalPosition(); + if (position.line < document.getLineCount() - 1) { + int verticalShift = 1 + block.editor.getSoftWrapModel().getSoftWrapsForLine(position.line).size() + - position.softWrapLinesOnCurrentLogicalLine; + caret.moveCaretRelatively(0, verticalShift, false, true); + } + break; + case RESTORE_SELECTION: + caret.setSelection(document.getLineStartOffset(document.getLineNumber(caret.getSelectionStart())), caret.getSelectionEnd()); + } + } + } + } + + private static Commenter getBlockSuitableCommenter(final PsiFile file, int offset, int endOffset) { final Language languageSuitableForCompleteFragment; if (offset >= endOffset) { // we are on empty line - PsiElement element = myFile.findElementAt(offset); + PsiElement element = file.findElementAt(offset); if (element != null) languageSuitableForCompleteFragment = element.getParent().getLanguage(); else languageSuitableForCompleteFragment = null; - } else { - languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange(offset, endOffset, myFile); } + else { + languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange(offset, endOffset, file); + } + Commenter blockSuitableCommenter = - languageSuitableForCompleteFragment == null ? LanguageCommenters.INSTANCE.forLanguage(myFile.getLanguage()) : null; - if (blockSuitableCommenter == null && myFile.getFileType() instanceof CustomSyntaxTableFileType) { + languageSuitableForCompleteFragment == null ? LanguageCommenters.INSTANCE.forLanguage(file.getLanguage()) : null; + if (blockSuitableCommenter == null && file.getFileType() instanceof CustomSyntaxTableFileType) { blockSuitableCommenter = new Commenter() { - final SyntaxTable mySyntaxTable = ((CustomSyntaxTableFileType)myFile.getFileType()).getSyntaxTable(); + final SyntaxTable mySyntaxTable = ((CustomSyntaxTableFileType)file.getFileType()).getSyntaxTable(); @Override @Nullable @@ -259,96 +323,30 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { }; } - boolean allLineCommented = true; - boolean commentWithIndent = - !CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(myFile.getLanguage()).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; - } - } + return blockSuitableCommenter; + } - 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 static boolean isLineEmpty(Document document, final int line) { + final CharSequence chars = document.getCharsSequence(); + int start = document.getLineStartOffset(line); + int end = Math.min(document.getLineEndOffset(line), document.getTextLength() - 1); + for (int i = start; i <= end; i++) { + if (!Character.isWhitespace(chars.charAt(i))) return false; } + return true; } - private boolean isLineCommented(final int line, final CharSequence chars, final Commenter commenter) { + private static boolean isLineCommented(Block block, final int line, final Commenter commenter) { boolean commented; int lineEndForBlockCommenting = -1; - int lineStart = myDocument.getLineStartOffset(line); + Document document = block.editor.getDocument(); + int lineStart = document.getLineStartOffset(line); + CharSequence chars = document.getCharsSequence(); lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t"); if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - commented = selfManagingCommenter.isLineCommented(line, lineStart, myDocument, myCommenterStateMap.get(selfManagingCommenter)); + commented = selfManagingCommenter.isLineCommented(line, lineStart, document, block.commenterStateMap.get(selfManagingCommenter)); } else { String prefix = commenter.getLineCommentPrefix(); @@ -359,8 +357,8 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { else { prefix = commenter.getBlockCommentPrefix(); String suffix = commenter.getBlockCommentSuffix(); - final int textLength = myDocument.getTextLength(); - lineEndForBlockCommenting = myDocument.getLineEndOffset(line); + final int textLength = document.getTextLength(); + lineEndForBlockCommenting = document.getLineEndOffset(line); if (lineEndForBlockCommenting == textLength) { final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t"); if (shifted < textLength - 1) lineEndForBlockCommenting = shifted; @@ -368,59 +366,61 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { else { lineEndForBlockCommenting = CharArrayUtil.shiftBackward(chars, lineEndForBlockCommenting, " \t"); } - commented = lineStart == lineEndForBlockCommenting && myStartLine != myEndLine || + commented = lineStart == lineEndForBlockCommenting && block.startLine != block.endLine || CharArrayUtil.regionMatches(chars, lineStart, prefix) && CharArrayUtil.regionMatches(chars, lineEndForBlockCommenting - suffix.length(), suffix); } } if (commented) { - myStartOffsets[line - myStartLine] = lineStart; - myEndOffsets[line - myStartLine] = lineEndForBlockCommenting; + block.startOffsets[line - block.startLine] = lineStart; + block.endOffsets[line - block.startLine] = lineEndForBlockCommenting; } return commented; } @Nullable - private Commenter findCommenter(final int line) { - final FileType fileType = myFile.getFileType(); + private static Commenter findCommenter(Editor editor, PsiFile file, final int line) { + final FileType fileType = file.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(); + Document document = editor.getDocument(); + int lineStartOffset = document.getLineStartOffset(line); + int lineEndOffset = document.getLineEndOffset(line) - 1; + final CharSequence charSequence = document.getCharsSequence(); lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t"); lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t"); - final Language lineStartLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineStartOffset); - final Language lineEndLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineEndOffset); - return CommentByBlockCommentHandler.getCommenter(myFile, myEditor, lineStartLanguage, lineEndLanguage); + final Language lineStartLanguage = PsiUtilCore.getLanguageAtOffset(file, lineStartOffset); + final Language lineEndLanguage = PsiUtilCore.getLanguageAtOffset(file, lineEndOffset); + return CommentByBlockCommentHandler.getCommenter(file, editor, lineStartLanguage, lineEndLanguage); } - private Indent computeMinIndent(int line1, int line2, CharSequence chars, CodeStyleManager codeStyleManager, FileType fileType) { - Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType); + private Indent computeMinIndent(Editor editor, PsiFile psiFile, int line1, int line2, FileType fileType) { + Document document = editor.getDocument(); + Indent minIndent = CommentUtil.getMinLineIndent(myProject, document, line1, line2, fileType); if (line1 > 0) { - int commentOffset = getCommentStart(line1 - 1); + int commentOffset = getCommentStart(editor, psiFile, line1 - 1); if (commentOffset >= 0) { - int lineStart = myDocument.getLineStartOffset(line1 - 1); - String space = chars.subSequence(lineStart, commentOffset).toString(); - Indent indent = codeStyleManager.getIndent(space, fileType); + int lineStart = document.getLineStartOffset(line1 - 1); + String space = document.getCharsSequence().subSequence(lineStart, commentOffset).toString(); + Indent indent = myCodeStyleManager.getIndent(space, fileType); minIndent = minIndent != null ? indent.min(minIndent) : indent; } } if (minIndent == null) { - minIndent = codeStyleManager.zeroIndent(); + minIndent = myCodeStyleManager.zeroIndent(); } return minIndent; } - private int getCommentStart(int line) { - int offset = myDocument.getLineStartOffset(line); - CharSequence chars = myDocument.getCharsSequence(); + private static int getCommentStart(Editor editor, PsiFile psiFile, int line) { + int offset = editor.getDocument().getLineStartOffset(line); + CharSequence chars = editor.getDocument().getCharsSequence(); offset = CharArrayUtil.shiftForward(chars, offset, " \t"); - final Commenter commenter = findCommenter(line); + final Commenter commenter = findCommenter(editor, psiFile, line); if (commenter == null) return -1; String prefix = commenter.getLineCommentPrefix(); if (prefix == null) prefix = commenter.getBlockCommentPrefix(); @@ -428,53 +428,55 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { return CharArrayUtil.regionMatches(chars, offset, prefix) ? offset : -1; } - public void doDefaultCommenting(final Commenter commenter) { + public void doDefaultCommenting(final Block block) { + final Document document = block.editor.getDocument(); DocumentUtil.executeInBulk( - myDocument, myEndLine - myStartLine >= Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { + document, block.endLine - block.startLine >= Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { @Override public void run() { - for (int line = myEndLine; line >= myStartLine; line--) { - int offset = myDocument.getLineStartOffset(line); - commentLine(line, offset, commenter); + for (int line = block.endLine; line >= block.startLine; line--) { + int offset = document.getLineStartOffset(line); + commentLine(block, line, offset); } } }); } - private void doIndentCommenting(final Commenter commenter) { - final CharSequence chars = myDocument.getCharsSequence(); - final FileType fileType = myFile.getFileType(); - final Indent minIndent = computeMinIndent(myStartLine, myEndLine, chars, myCodeStyleManager, fileType); + private void doIndentCommenting(final Block block) { + final Document document = block.editor.getDocument(); + final CharSequence chars = document.getCharsSequence(); + final FileType fileType = block.psiFile.getFileType(); + final Indent minIndent = computeMinIndent(block.editor, block.psiFile, block.startLine, block.endLine, fileType); DocumentUtil.executeInBulk( - myDocument, myEndLine - myStartLine > Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { - @Override - public void run() { - for (int line = myEndLine; line >= myStartLine; line--) { - int lineStart = myDocument.getLineStartOffset(line); - int offset = lineStart; - final StringBuilder buffer = new StringBuilder(); - 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; + document, block.endLine - block.startLine > Registry.intValue("comment.by.line.bulk.lines.trigger"), new Runnable() { + @Override + public void run() { + for (int line = block.endLine; line >= block.startLine; line--) { + int lineStart = document.getLineStartOffset(line); + int offset = lineStart; + final StringBuilder buffer = new StringBuilder(); + 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); + document.replaceString(lineStart, offset, newSpace); + offset = lineStart + newSpace.length(); + break; + } + buffer.append(c); + offset++; } - buffer.append(c); - offset++; + commentLine(block, line, offset); } - commentLine(line, offset, commenter); } - } - }); + }); } - private void uncommentRange(int startOffset, int endOffset, @NotNull Commenter commenter) { + private static void uncommentRange(Document document, int startOffset, int endOffset, @NotNull Commenter commenter) { final String commentedSuffix = commenter.getCommentedBlockCommentSuffix(); final String commentedPrefix = commenter.getCommentedBlockCommentPrefix(); final String prefix = commenter.getBlockCommentPrefix(); @@ -482,52 +484,53 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { if (prefix == null || suffix == null) { return; } - if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(myDocument.getCharsSequence(), endOffset - suffix.length(), suffix)) { - myDocument.deleteString(endOffset - suffix.length(), endOffset); - endOffset = myDocument.getTextLength(); + if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(document.getCharsSequence(), endOffset - suffix.length(), suffix)) { + document.deleteString(endOffset - suffix.length(), endOffset); + endOffset = document.getTextLength(); } if (commentedPrefix != null && commentedSuffix != null) { - CommentByBlockCommentHandler.commentNestedComments(myDocument, new TextRange(startOffset, endOffset), commenter); + CommentByBlockCommentHandler.commentNestedComments(document, new TextRange(startOffset, endOffset), commenter); } - myDocument.deleteString(startOffset, startOffset + prefix.length()); + document.deleteString(startOffset, startOffset + prefix.length()); } - private void uncommentLine(int line) { - Commenter commenter = myCommenters[line - myStartLine]; - if (commenter == null) commenter = findCommenter(line); + private static void uncommentLine(Block block, int line) { + Document document = block.editor.getDocument(); + Commenter commenter = block.commenters[line - block.startLine]; + if (commenter == null) commenter = findCommenter(block.editor, block.psiFile, line); if (commenter == null) return; - final int startOffset = myStartOffsets[line - myStartLine]; + final int startOffset = block.startOffsets[line - block.startLine]; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - selfManagingCommenter.uncommentLine(line, startOffset, myDocument, myCommenterStateMap.get(selfManagingCommenter)); + selfManagingCommenter.uncommentLine(line, startOffset, document, block.commenterStateMap.get(selfManagingCommenter)); return; } - final int endOffset = myEndOffsets[line - myStartLine]; + final int endOffset = block.endOffsets[line - block.startLine]; if (startOffset == endOffset) { return; } String prefix = commenter.getLineCommentPrefix(); if (prefix != null) { - CharSequence chars = myDocument.getCharsSequence(); + CharSequence chars = document.getCharsSequence(); if (commenter instanceof CommenterWithLineSuffix) { CommenterWithLineSuffix commenterWithLineSuffix = (CommenterWithLineSuffix)commenter; String suffix = commenterWithLineSuffix.getLineCommentSuffix(); - int theEnd = endOffset > 0 ? endOffset : myDocument.getLineEndOffset(line); + int theEnd = endOffset > 0 ? endOffset : document.getLineEndOffset(line); while (theEnd > startOffset && Character.isWhitespace(chars.charAt(theEnd - 1))) { theEnd--; } - String lineText = myDocument.getText(new TextRange(startOffset, theEnd)); + String lineText = document.getText(new TextRange(startOffset, theEnd)); if (lineText.indexOf(suffix) != -1) { int start = startOffset + lineText.indexOf(suffix); - myDocument.deleteString(start, start + suffix.length()); + document.deleteString(start, start + suffix.length()); } } @@ -544,10 +547,10 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { charsToDelete++; } } - myDocument.deleteString(startOffset, startOffset + charsToDelete); + document.deleteString(startOffset, startOffset + charsToDelete); return; } - String text = myDocument.getCharsSequence().subSequence(startOffset, endOffset).toString(); + String text = document.getCharsSequence().subSequence(startOffset, endOffset).toString(); prefix = commenter.getBlockCommentPrefix(); final String suffix = commenter.getBlockCommentSuffix(); @@ -575,45 +578,47 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { 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); + uncommentRange(document, startOffset + prefixes.get(i), Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset), commenter); } } - private void commentLine(int line, int offset, @Nullable Commenter commenter) { - if (commenter == null) commenter = findCommenter(line); + private static void commentLine(Block block, int line, int offset) { + Commenter commenter = block.blockSuitableCommenter; + Document document = block.editor.getDocument(); + if (commenter == null) commenter = findCommenter(block.editor, block.psiFile, line); if (commenter == null) return; if (commenter instanceof SelfManagingCommenter) { final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter)commenter; - selfManagingCommenter.commentLine(line, offset, myDocument, myCommenterStateMap.get(selfManagingCommenter)); + selfManagingCommenter.commentLine(line, offset, document, block.commenterStateMap.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"); + int endOffset = document.getLineEndOffset(line); + endOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), endOffset, " \t"); + int shiftedStartOffset = CharArrayUtil.shiftForward(document.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); + if (!CharArrayUtil.regionMatches(document.getCharsSequence(), shiftedStartOffset, prefix)) { + if (!CharArrayUtil.regionMatches(document.getCharsSequence(), endOffset - lineSuffix.length(), lineSuffix)) { + document.insertString(endOffset, lineSuffix); } - myDocument.insertString(offset, prefix); + document.insertString(offset, prefix); } } else { - myDocument.insertString(offset, prefix); + document.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(); + int endOffset = document.getLineEndOffset(line); + if (endOffset == offset && block.startLine != block.endLine) return; + final int textLength = document.getTextLength(); + final CharSequence chars = document.getCharsSequence(); offset = CharArrayUtil.shiftForward(chars, offset, " \t"); if (endOffset == textLength) { final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t"); @@ -623,7 +628,7 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t"); } if (endOffset < offset || - offset == textLength - 1 && line != myDocument.getLineCount() - 1) { + offset == textLength - 1 && line != document.getLineCount() - 1) { return; } final String text = chars.subSequence(offset, endOffset).toString(); @@ -653,7 +658,7 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { } } if (!(commentedSuffix == null && !suffixes.isEmpty() && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) { - myDocument.insertString(endOffset, suffix); + document.insertString(endOffset, suffix); } int nearestPrefix = prefixes.size() - 1; int nearestSuffix = suffixes.size() - 1; @@ -662,26 +667,46 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { final int position = prefixes.get(nearestPrefix); nearestPrefix--; if (commentedPrefix != null) { - myDocument.replaceString(offset + position, offset + position + prefix.length(), commentedPrefix); + document.replaceString(offset + position, offset + position + prefix.length(), commentedPrefix); } else if (position != 0) { - myDocument.insertString(offset + position, suffix); + document.insertString(offset + position, suffix); } } else { final int position = suffixes.get(nearestSuffix); nearestSuffix--; if (commentedSuffix != null) { - myDocument.replaceString(offset + position, offset + position + suffix.length(), commentedSuffix); + document.replaceString(offset + position, offset + position + suffix.length(), commentedSuffix); } else if (offset + position + suffix.length() < endOffset) { - myDocument.insertString(offset + position + suffix.length(), prefix); + document.insertString(offset + position + suffix.length(), prefix); } } } if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) { - myDocument.insertString(offset, prefix); + document.insertString(offset, prefix); } } } + + private static class Block { + private Editor editor; + private PsiFile psiFile; + private List<Caret> carets = new ArrayList<Caret>(); + private int startLine; + private int endLine; + private int[] startOffsets; + private int[] endOffsets; + private Commenter blockSuitableCommenter; + private Commenter[] commenters; + private Map<SelfManagingCommenter, CommenterDataHolder> commenterStateMap; + private boolean commentWithIndent; + private CaretUpdate caretUpdate; + private boolean skip; + } + + private enum CaretUpdate { + PUT_AT_COMMENT_START, SHIFT_DOWN, RESTORE_SELECTION + } } |