com.intellij.codeInsight.generation.CommentByLineCommentHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.codeInsight.generation.CommentByLineCommentHandler.java

Source

/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.codeInsight.generation;

import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.codeInsight.CommentUtil;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.ide.highlighter.custom.SyntaxTable;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.lang.Commenter;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageCommenters;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.impl.AbstractFileType;
import com.intellij.openapi.fileTypes.impl.CustomSyntaxTableFileType;
import com.intellij.openapi.project.Project;
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;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.Indent;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.DocumentUtil;
import com.intellij.util.containers.IntArrayList;
import com.intellij.util.text.CharArrayUtil;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mustbe.consulo.RequiredDispatchThread;

import java.util.Map;

public class CommentByLineCommentHandler implements CodeInsightActionHandler {

    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;

    @RequiredDispatchThread
    @Override
    public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
        if (!CodeInsightUtilBase.prepareEditorForWrite(editor))
            return;
        myProject = project;
        myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage());
        myEditor = editor;

        PsiElement context = InjectedLanguageManager.getInstance(myFile.getProject()).getInjectionHost(myFile);

        if (context != null && (context.textContains('\'') || context.textContains('\"'))) {
            String s = context.getText();
            if (StringUtil.startsWith(s, "\"") || StringUtil.startsWith(s, "\'")) {
                myFile = context.getContainingFile();
                myEditor = editor instanceof EditorWindow ? ((EditorWindow) editor).getDelegate() : editor;
            }
        }

        myDocument = myEditor.getDocument();
        if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) {
            return;
        }

        PsiDocumentManager.getInstance(project).commitDocument(myDocument);

        FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line");

        myCodeStyleManager = CodeStyleManager.getInstance(myProject);

        final SelectionModel selectionModel = myEditor.getSelectionModel();

        boolean hasSelection = selectionModel.hasSelection();
        myStartOffset = selectionModel.getSelectionStart();
        myEndOffset = selectionModel.getSelectionEnd();

        FoldRegion fold = myEditor.getFoldingModel().getCollapsedRegionAtOffset(myStartOffset);
        if (fold != null && fold.shouldNeverExpand() && fold.getStartOffset() == myStartOffset
                && fold.getEndOffset() == myEndOffset) {
            // Foldings that never expand are automatically selected, so the fact it is selected must not interfer with commenter's logic
            hasSelection = false;
        }

        if (myDocument.getTextLength() == 0)
            return;

        while (true) {
            int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset));
            FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd);
            if (collapsedAt != null) {
                final int endOffset = collapsedAt.getEndOffset();
                if (endOffset <= myEndOffset) {
                    break;
                }
                myEndOffset = endOffset;
            } else {
                break;
            }
        }

        boolean wholeLinesSelected = !hasSelection || myStartOffset == myDocument
                .getLineStartOffset(myDocument.getLineNumber(myStartOffset))
                && myEndOffset == myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset - 1)) + 1;

        boolean startingNewLineComment = !hasSelection && isLineEmpty(myDocument.getLineNumber(myStartOffset))
                && !Comparing.equal(IdeActions.ACTION_COMMENT_LINE,
                        ActionManagerEx.getInstanceEx().getPrevPreformedActionId());
        doComment();

        if (startingNewLineComment) {
            final Commenter commenter = myCommenters[0];
            if (commenter != null) {
                String prefix;
                if (commenter instanceof SelfManagingCommenter) {
                    prefix = ((SelfManagingCommenter) commenter).getCommentPrefix(myStartLine, myDocument,
                            myCommenterStateMap.get((SelfManagingCommenter) commenter));
                    if (prefix == null)
                        prefix = ""; // TODO
                } else {
                    prefix = commenter.getLineCommentPrefix();
                    if (prefix == null)
                        prefix = commenter.getBlockCommentPrefix();
                }

                int lineStart = myDocument.getLineStartOffset(myStartLine);
                lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t");
                lineStart += prefix.length();
                lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t");
                if (lineStart > myDocument.getTextLength())
                    lineStart = myDocument.getTextLength();
                myEditor.getCaretModel().moveToOffset(lineStart);
                myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
            }
        } else {
            if (!hasSelection) {
                // Don't tweak caret position if we're already located on the last document line.
                LogicalPosition position = myEditor.getCaretModel().getLogicalPosition();
                if (position.line < myDocument.getLineCount() - 1) {
                    int verticalShift = 1 + myEditor.getSoftWrapModel().getSoftWrapsForLine(position.line).size()
                            - position.softWrapLinesOnCurrentLogicalLine;
                    myEditor.getCaretModel().moveCaretRelatively(0, verticalShift, false, false, true);
                }
            } else {
                if (wholeLinesSelected) {
                    selectionModel.setSelection(myStartOffset, selectionModel.getSelectionEnd());
                }
            }
        }
    }

    private 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;
    }

    @Override
    public boolean startInWriteAction() {
        return true;
    }

    private void doComment() {
        myStartLine = myDocument.getLineNumber(myStartOffset);
        myEndLine = myDocument.getLineNumber(myEndOffset);

        if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) {
            myEndLine--;
        }

        myStartOffsets = new int[myEndLine - myStartLine + 1];
        myEndOffsets = new int[myEndLine - myStartLine + 1];
        myCommenters = new Commenter[myEndLine - myStartLine + 1];
        myCommenterStateMap = new THashMap<SelfManagingCommenter, CommenterDataHolder>();
        CharSequence chars = myDocument.getCharsSequence();

        boolean singleline = myStartLine == myEndLine;
        int offset = myDocument.getLineStartOffset(myStartLine);
        offset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t");

        final Language languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange(offset,
                CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), myDocument.getLineEndOffset(myEndLine),
                        " \t\n"),
                myFile);

        Commenter blockSuitableCommenter = languageSuitableForCompleteFragment == null
                ? LanguageCommenters.INSTANCE.forLanguage(myFile.getLanguage())
                : null;
        if (blockSuitableCommenter == null && myFile.getFileType() instanceof CustomSyntaxTableFileType) {
            blockSuitableCommenter = new Commenter() {
                final SyntaxTable mySyntaxTable = ((CustomSyntaxTableFileType) myFile.getFileType())
                        .getSyntaxTable();

                @Override
                @Nullable
                public String getLineCommentPrefix() {
                    return mySyntaxTable.getLineComment();
                }

                @Override
                @Nullable
                public String getBlockCommentPrefix() {
                    return mySyntaxTable.getStartComment();
                }

                @Override
                @Nullable
                public String getBlockCommentSuffix() {
                    return mySyntaxTable.getEndComment();
                }

                @Override
                public String getCommentedBlockCommentPrefix() {
                    return null;
                }

                @Override
                public String getCommentedBlockCommentSuffix() {
                    return null;
                }
            };
        }

        boolean allLineCommented = true;
        boolean commentWithIndent = !CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN;

        for (int line = myStartLine; line <= myEndLine; line++) {
            Commenter commenter = blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line);
            if (commenter == null)
                return;
            if (commenter.getLineCommentPrefix() == null
                    && (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) {
                return;
            }

            if (commenter instanceof SelfManagingCommenter && myCommenterStateMap.get(commenter) == null) {
                final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
                CommenterDataHolder state = selfManagingCommenter.createLineCommentingState(myStartLine, myEndLine,
                        myDocument, myFile);
                if (state == null)
                    state = SelfManagingCommenter.EMPTY_STATE;
                myCommenterStateMap.put(selfManagingCommenter, state);
            }

            myCommenters[line - myStartLine] = commenter;
            if (!isLineCommented(line, chars, commenter) && (singleline || !isLineEmpty(line))) {
                allLineCommented = false;
                if (commenter instanceof IndentedCommenter) {
                    final Boolean value = ((IndentedCommenter) commenter).forceIndentedLineComment();
                    if (value != null) {
                        commentWithIndent = value;
                    }
                }
                break;
            }
        }

        if (!allLineCommented) {
            if (!commentWithIndent) {
                doDefaultCommenting(blockSuitableCommenter);
            } else {
                doIndentCommenting(blockSuitableCommenter);
            }
        } else {
            for (int line = myEndLine; line >= myStartLine; line--) {
                uncommentLine(line);
                //int offset1 = myStartOffsets[line - myStartLine];
                //int offset2 = myEndOffsets[line - myStartLine];
                //if (offset1 == offset2) continue;
                //Commenter commenter = myCommenters[line - myStartLine];
                //String prefix = commenter.getBlockCommentPrefix();
                //if (prefix == null || !myDocument.getText().substring(offset1, myDocument.getTextLength()).startsWith(prefix)) {
                //  prefix = commenter.getLineCommentPrefix();
                //}
                //
                //String suffix = commenter.getBlockCommentSuffix();
                //if (suffix == null && prefix != null) suffix = "";
                //
                //if (prefix != null && suffix != null) {
                //  final int suffixLen = suffix.length();
                //  final int prefixLen = prefix.length();
                //  if (offset2 >= 0) {
                //    if (!CharArrayUtil.regionMatches(chars, offset1 + prefixLen, prefix)) {
                //      myDocument.deleteString(offset2 - suffixLen, offset2);
                //    }
                //  }
                //  if (offset1 >= 0) {
                //    for (int i = offset2 - suffixLen - 1; i > offset1 + prefixLen; --i) {
                //      if (CharArrayUtil.regionMatches(chars, i, suffix)) {
                //        myDocument.deleteString(i, i + suffixLen);
                //      }
                //      else if (CharArrayUtil.regionMatches(chars, i, prefix)) {
                //        myDocument.deleteString(i, i + prefixLen);
                //      }
                //    }
                //    myDocument.deleteString(offset1, offset1 + prefixLen);
                //  }
                //}
            }
        }
    }

    private boolean isLineCommented(final int line, final CharSequence chars, final Commenter commenter) {
        boolean commented;
        int lineEndForBlockCommenting = -1;
        int lineStart = myDocument.getLineStartOffset(line);
        lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t");

        if (commenter instanceof SelfManagingCommenter) {
            final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
            commented = selfManagingCommenter.isLineCommented(line, lineStart, myDocument,
                    myCommenterStateMap.get(selfManagingCommenter));
        } else {
            String prefix = commenter.getLineCommentPrefix();

            if (prefix != null) {
                commented = CharArrayUtil.regionMatches(chars, lineStart, StringUtil.trimTrailing(prefix));
            } else {
                prefix = commenter.getBlockCommentPrefix();
                String suffix = commenter.getBlockCommentSuffix();
                final int textLength = myDocument.getTextLength();
                lineEndForBlockCommenting = myDocument.getLineEndOffset(line);
                if (lineEndForBlockCommenting == textLength) {
                    final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
                    if (shifted < textLength - 1)
                        lineEndForBlockCommenting = shifted;
                } else {
                    lineEndForBlockCommenting = CharArrayUtil.shiftBackward(chars, lineEndForBlockCommenting,
                            " \t");
                }
                commented = lineStart == lineEndForBlockCommenting && myStartLine != myEndLine
                        || CharArrayUtil.regionMatches(chars, lineStart, prefix) && CharArrayUtil
                                .regionMatches(chars, lineEndForBlockCommenting - suffix.length(), suffix);
            }
        }

        if (commented) {
            myStartOffsets[line - myStartLine] = lineStart;
            myEndOffsets[line - myStartLine] = lineEndForBlockCommenting;
        }

        return commented;
    }

    @Nullable
    private Commenter findCommenter(final int line) {
        final FileType fileType = myFile.getFileType();
        if (fileType instanceof AbstractFileType) {
            return ((AbstractFileType) fileType).getCommenter();
        }

        int lineStartOffset = myDocument.getLineStartOffset(line);
        int lineEndOffset = myDocument.getLineEndOffset(line) - 1;
        final CharSequence charSequence = myDocument.getCharsSequence();
        lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t");
        lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t");
        final Language lineStartLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineStartOffset);
        final Language lineEndLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineEndOffset);
        return CommentByBlockCommentHandler.getCommenter(myFile, myEditor, lineStartLanguage, lineEndLanguage);
    }

    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;
    }

    private int getCommentStart(int line) {
        int offset = myDocument.getLineStartOffset(line);
        CharSequence chars = myDocument.getCharsSequence();
        offset = CharArrayUtil.shiftForward(chars, offset, " \t");
        final Commenter commenter = findCommenter(line);
        if (commenter == null)
            return -1;
        String prefix = commenter.getLineCommentPrefix();
        if (prefix == null)
            prefix = commenter.getBlockCommentPrefix();
        if (prefix == null)
            return -1;
        return CharArrayUtil.regionMatches(chars, offset, prefix) ? offset : -1;
    }

    public void doDefaultCommenting(final Commenter commenter) {
        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 offset = myDocument.getLineStartOffset(line);
                            commentLine(line, offset, commenter);
                        }
                    }
                });
    }

    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);

        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;
                                }
                                buffer.append(c);
                                offset++;
                            }
                            commentLine(line, offset, commenter);
                        }
                    }
                });
    }

    private void uncommentRange(int startOffset, int endOffset, @NotNull Commenter commenter) {
        final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
        final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
        final String prefix = commenter.getBlockCommentPrefix();
        final String suffix = commenter.getBlockCommentSuffix();
        if (prefix == null || suffix == null) {
            return;
        }
        if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(myDocument.getCharsSequence(),
                endOffset - suffix.length(), suffix)) {
            myDocument.deleteString(endOffset - suffix.length(), endOffset);
        }
        if (commentedPrefix != null && commentedSuffix != null) {
            CommentByBlockCommentHandler.commentNestedComments(myDocument, new TextRange(startOffset, endOffset),
                    commenter);
        }
        myDocument.deleteString(startOffset, startOffset + prefix.length());
    }

    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 matchesTrimmed = false;
            boolean commented = CharArrayUtil.regionMatches(chars, startOffset, prefix)
                    || (matchesTrimmed = prefix.endsWith(" ")
                            && CharArrayUtil.regionMatches(chars, startOffset, prefix.trim()));
            assert commented;

            int charsToDelete = matchesTrimmed ? 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);
        }
    }

    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);
            }
        }
    }
}