private static void logInitial(
      @NotNull Editor editor,
      @NotNull int[] startOffsets,
      @NotNull int[] endOffsets,
      int indentSymbolsToStrip,
      int firstLineStartOffset) {
    if (!Registry.is("editor.richcopy.debug")) {
      return;
    }

    StringBuilder buffer = new StringBuilder();
    Document document = editor.getDocument();
    CharSequence text = document.getCharsSequence();
    for (int i = 0; i < startOffsets.length; i++) {
      int start = startOffsets[i];
      int lineStart = document.getLineStartOffset(document.getLineNumber(start));
      int end = endOffsets[i];
      int lineEnd = document.getLineEndOffset(document.getLineNumber(end));
      buffer
          .append("    region #")
          .append(i)
          .append(": ")
          .append(start)
          .append('-')
          .append(end)
          .append(", text at range ")
          .append(lineStart)
          .append('-')
          .append(lineEnd)
          .append(": \n'")
          .append(text.subSequence(lineStart, lineEnd))
          .append("'\n");
    }
    if (buffer.length() > 0) {
      buffer.setLength(buffer.length() - 1);
    }
    LOG.info(
        String.format(
            "Preparing syntax-aware text. Given: %s selection, indent symbols to strip=%d, first line start offset=%d, selected text:%n%s",
            startOffsets.length > 1 ? "block" : "regular",
            indentSymbolsToStrip,
            firstLineStartOffset,
            buffer));
  }
 private Indent computeMinIndent(
     int line1,
     int line2,
     CharSequence chars,
     CodeStyleManager codeStyleManager,
     FileType fileType) {
   Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType);
   if (line1 > 0) {
     int commentOffset = getCommentStart(line1 - 1);
     if (commentOffset >= 0) {
       int lineStart = myDocument.getLineStartOffset(line1 - 1);
       String space = chars.subSequence(lineStart, commentOffset).toString();
       Indent indent = codeStyleManager.getIndent(space, fileType);
       minIndent = minIndent != null ? indent.min(minIndent) : indent;
     }
   }
   if (minIndent == null) {
     minIndent = codeStyleManager.zeroIndent();
   }
   return minIndent;
 }
  public Map<TemplateImpl, String> findMatchingTemplates(
      final PsiFile file,
      Editor editor,
      @Nullable Character shortcutChar,
      TemplateSettings templateSettings) {
    final Document document = editor.getDocument();
    CharSequence text = document.getCharsSequence();
    final int caretOffset = editor.getCaretModel().getOffset();

    List<TemplateImpl> candidatesWithoutArgument =
        findMatchingTemplates(text, caretOffset, shortcutChar, templateSettings, false);

    int argumentOffset = passArgumentBack(text, caretOffset);
    String argument = null;
    if (argumentOffset >= 0) {
      argument = text.subSequence(argumentOffset, caretOffset).toString();
      if (argumentOffset > 0 && text.charAt(argumentOffset - 1) == ' ') {
        if (argumentOffset - 2 >= 0
            && Character.isJavaIdentifierPart(text.charAt(argumentOffset - 2))) {
          argumentOffset--;
        }
      }
    }
    List<TemplateImpl> candidatesWithArgument =
        findMatchingTemplates(text, argumentOffset, shortcutChar, templateSettings, true);

    if (candidatesWithArgument.isEmpty() && candidatesWithoutArgument.isEmpty()) {
      return null;
    }

    candidatesWithoutArgument =
        filterApplicableCandidates(file, caretOffset, candidatesWithoutArgument);
    candidatesWithArgument =
        filterApplicableCandidates(file, argumentOffset, candidatesWithArgument);
    Map<TemplateImpl, String> candidate2Argument = new HashMap<TemplateImpl, String>();
    addToMap(candidate2Argument, candidatesWithoutArgument, null);
    addToMap(candidate2Argument, candidatesWithArgument, argument);
    return candidate2Argument;
  }
  public static List<TemplateImpl> findMatchingTemplates(
      CharSequence text,
      int caretOffset,
      @Nullable Character shortcutChar,
      TemplateSettings settings,
      boolean hasArgument) {
    List<TemplateImpl> candidates = Collections.emptyList();
    for (int i = settings.getMaxKeyLength(); i >= 1; i--) {
      int wordStart = caretOffset - i;
      if (wordStart < 0) {
        continue;
      }
      String key = text.subSequence(wordStart, caretOffset).toString();
      if (Character.isJavaIdentifierStart(key.charAt(0))) {
        if (wordStart > 0 && Character.isJavaIdentifierPart(text.charAt(wordStart - 1))) {
          continue;
        }
      }

      candidates = settings.collectMatchingCandidates(key, shortcutChar, hasArgument);
      if (!candidates.isEmpty()) break;
    }
    return candidates;
  }
Example #5
0
  private static void indentBlockWithFormatter(
      Project project, Document document, int startOffset, int endOffset, PsiFile file) {

    // Algorithm: the main idea is to process the first line of the pasted block, adjust its indent
    // if necessary, calculate indent
    // adjustment string and apply to each line of the pasted block starting from the second one.
    //
    // We differentiate the following possible states here:
    //   --- pasted block doesn't start new line, i.e. there are non-white space symbols before it
    // at the first line.
    //      Example:
    //         old content [pasted line 1
    //                pasted line 2]
    //      Indent adjustment string is just the first line indent then.
    //
    //   --- pasted block starts with empty line(s)
    //      Example:
    //         old content [
    //            pasted line 1
    //            pasted line 2]
    //      We parse existing indents of the pasted block then, adjust its first non-blank line via
    // formatter and adjust indent
    //      of subsequent pasted lines in order to preserve old indentation.
    //
    //   --- pasted block is located at the new line and starts with white space symbols.
    //       Example:
    //          [   pasted line 1
    //                 pasted line 2]
    //       We parse existing indents of the pasted block then, adjust its first line via formatter
    // and adjust indent of the pasted lines
    //       starting from the second one in order to preserve old indentation.
    //
    //   --- pasted block is located at the new line but doesn't start with white space symbols.
    //       Example:
    //           [pasted line 1
    //         pasted line 2]
    //       We adjust the first line via formatter then and apply first line's indent to all
    // subsequent pasted lines.

    CharSequence chars = document.getCharsSequence();
    final int firstLine = document.getLineNumber(startOffset);
    final int firstLineStart = document.getLineStartOffset(firstLine);

    // There is a possible case that we paste block that ends with new line that is empty or
    // contains only white space symbols.
    // We want to preserve indent for the original document line where paste was performed.
    // Example:
    //   Original:
    //       if (test) {
    //   <caret>    }
    //
    //   Pasting: 'int i = 1;\n'
    //   Expected:
    //       if (test) {
    //           int i = 1;
    //       }
    boolean saveLastLineIndent = false;
    for (int i = endOffset - 1; i >= startOffset; i--) {
      final char c = chars.charAt(i);
      if (c == '\n') {
        saveLastLineIndent = true;
        break;
      }
      if (c != ' ' && c != '\t') {
        break;
      }
    }

    final int lastLine;
    if (saveLastLineIndent) {
      lastLine = document.getLineNumber(endOffset) - 1;
      // Remove white space symbols at the pasted text if any.
      int start = document.getLineStartOffset(lastLine + 1);
      if (start < endOffset) {
        int i = CharArrayUtil.shiftForward(chars, start, " \t");
        if (i > start) {
          i = Math.min(i, endOffset);
          document.deleteString(start, i);
        }
      }

      // Insert white space from the start line of the pasted block.
      int indentToKeepEndOffset =
          Math.min(startOffset, CharArrayUtil.shiftForward(chars, firstLineStart, " \t"));
      if (indentToKeepEndOffset > firstLineStart) {
        document.insertString(start, chars.subSequence(firstLineStart, indentToKeepEndOffset));
      }
    } else {
      lastLine = document.getLineNumber(endOffset);
    }

    final int i = CharArrayUtil.shiftBackward(chars, startOffset - 1, " \t");

    // Handle a situation when pasted block doesn't start a new line.
    if (chars.charAt(startOffset) != '\n' && i > 0 && chars.charAt(i) != '\n') {
      int firstNonWsOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
      if (firstNonWsOffset > firstLineStart) {
        CharSequence toInsert = chars.subSequence(firstLineStart, firstNonWsOffset);
        for (int line = firstLine + 1; line <= lastLine; line++) {
          document.insertString(document.getLineStartOffset(line), toInsert);
        }
      }
      return;
    }

    // Sync document and PSI for correct formatting processing.
    PsiDocumentManager.getInstance(project).commitAllDocuments();
    if (file == null) {
      return;
    }
    CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);

    final int j = CharArrayUtil.shiftForward(chars, startOffset, " \t\n");
    if (j >= endOffset) {
      // Pasted text contains white space/line feed symbols only, do nothing.
      return;
    }

    final int anchorLine = document.getLineNumber(j);
    final int anchorLineStart = document.getLineStartOffset(anchorLine);
    codeStyleManager.adjustLineIndent(file, j);

    // Handle situation when pasted block starts with non-white space symbols.
    if (anchorLine == firstLine && j == startOffset) {
      int indentOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
      if (indentOffset > firstLineStart) {
        CharSequence toInsert = chars.subSequence(firstLineStart, indentOffset);
        for (int line = firstLine + 1; line <= lastLine; line++) {
          document.insertString(document.getLineStartOffset(line), toInsert);
        }
      }
      return;
    }

    // Handle situation when pasted block starts from white space symbols. Assume that the pasted
    // text started at the line start,
    // i.e. correct indentation level is stored at the blocks structure.
    final int firstNonWsOffset = CharArrayUtil.shiftForward(chars, anchorLineStart, " \t");
    final int diff = firstNonWsOffset - j;
    if (diff == 0) {
      return;
    }
    if (diff > 0) {
      CharSequence toInsert = chars.subSequence(anchorLineStart, anchorLineStart + diff);
      for (int line = anchorLine + 1; line <= lastLine; line++) {
        document.insertString(document.getLineStartOffset(line), toInsert);
      }
      return;
    }

    // We've pasted text to the non-first column and exact white space between the line start and
    // caret position on the moment of paste
    // has been removed by formatter during 'adjust line indent'
    // Example:
    //       copied text:
    //                 '   line1
    //                       line2'
    //       after paste:
    //          line start -> '   I   line1
    //                              line2' (I - caret position during 'paste')
    //       formatter removed white space between the line start and caret position, so, current
    // document state is:
    //                        '   line1
    //                              line2'
    if (anchorLine == firstLine && -diff == startOffset - firstLineStart) {
      return;
    }
    if (anchorLine != firstLine || -diff > startOffset - firstLineStart) {
      final int desiredSymbolsToRemove;
      if (anchorLine == firstLine) {
        desiredSymbolsToRemove = -diff - (startOffset - firstLineStart);
      } else {
        desiredSymbolsToRemove = -diff;
      }

      for (int line = anchorLine + 1; line <= lastLine; line++) {
        int currentLineStart = document.getLineStartOffset(line);
        int currentLineIndentOffset = CharArrayUtil.shiftForward(chars, currentLineStart, " \t");
        int symbolsToRemove =
            Math.min(currentLineIndentOffset - currentLineStart, desiredSymbolsToRemove);
        if (symbolsToRemove > 0) {
          document.deleteString(currentLineStart, currentLineStart + symbolsToRemove);
        }
      }
    } else {
      CharSequence toInsert = chars.subSequence(anchorLineStart, diff + startOffset);
      for (int line = anchorLine + 1; line <= lastLine; line++) {
        document.insertString(document.getLineStartOffset(line), toInsert);
      }
    }
  }
    @Override
    public void run() {
      CaretModel caretModel = myEditor.getCaretModel();
      try {
        final CharSequence chars = myDocument.getCharsSequence();
        int i = CharArrayUtil.shiftBackwardUntil(chars, myOffset - 1, LINE_SEPARATOR) - 1;
        i = CharArrayUtil.shiftBackwardUntil(chars, i, LINE_SEPARATOR) + 1;
        if (i < 0) i = 0;
        int lineStart = CharArrayUtil.shiftForward(chars, i, " \t");
        CodeDocumentationUtil.CommentContext commentContext =
            CodeDocumentationUtil.tryParseCommentContext(myFile, chars, myOffset, lineStart);

        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(getProject());
        if (commentContext.docStart) {
          PsiElement element = myFile.findElementAt(commentContext.lineStart);
          final String text = element.getText();
          final PsiElement parent = element.getParent();

          if (text.equals(commentContext.commenter.getDocumentationCommentPrefix())
                  && isDocComment(parent, commentContext.commenter)
              || text.startsWith(commentContext.commenter.getDocumentationCommentPrefix())
                  && element instanceof PsiComment) {
            PsiComment comment =
                isDocComment(parent, commentContext.commenter)
                    ? (PsiComment) parent
                    : (PsiComment) element;
            int commentEnd = comment.getTextRange().getEndOffset();

            if (myOffset >= commentEnd) {
              commentContext.docStart = false;
            } else {
              if (isCommentComplete(comment, commentContext.commenter, myEditor)) {
                if (myOffset >= commentEnd) {
                  commentContext.docAsterisk = false;
                  commentContext.docStart = false;
                } else {
                  commentContext.docAsterisk = true;
                  commentContext.docStart = false;
                }
              } else {
                generateJavadoc(commentContext.commenter);
              }
            }
          } else {
            commentContext.docStart = false;
          }
        } else if (commentContext.cStyleStart) {
          PsiElement element = myFile.findElementAt(commentContext.lineStart);
          if (element instanceof PsiComment
              && commentContext.commenter.getBlockCommentTokenType()
                  == ((PsiComment) element).getTokenType()) {
            final PsiComment comment = (PsiComment) element;
            int commentEnd = comment.getTextRange().getEndOffset();
            if (myOffset >= commentEnd && myOffset < myFile.getTextRange().getEndOffset()) {
              commentContext.docStart = false;
            } else {
              if (isCommentComplete(comment, commentContext.commenter, myEditor)) {
                if (myOffset >= commentEnd) {
                  commentContext.docAsterisk = false;
                  commentContext.docStart = false;
                } else {
                  commentContext.docAsterisk = true;
                  commentContext.docStart = false;
                }
              } else {
                final int currentEndOfLine = CharArrayUtil.shiftForwardUntil(chars, myOffset, "\n");
                myDocument.insertString(
                    currentEndOfLine, " " + commentContext.commenter.getBlockCommentSuffix());
                int lstart = CharArrayUtil.shiftBackwardUntil(chars, myOffset, "\n");
                myDocument.insertString(currentEndOfLine, chars.subSequence(lstart, myOffset));
                psiDocumentManager.commitDocument(myDocument);
              }
            }
          } else {
            commentContext.docStart = false;
          }
        }

        String indentInsideJavadoc = null;
        if (myOffset < myDocument.getTextLength()) {
          final int line = myDocument.getLineNumber(myOffset);
          if (line > 0 && (commentContext.docAsterisk || commentContext.docStart)) {
            indentInsideJavadoc =
                CodeDocumentationUtil.getIndentInsideJavadoc(
                    myDocument, myDocument.getLineStartOffset(line - 1));
          }
        }

        if (commentContext.docAsterisk) {
          commentContext.docAsterisk =
              insertDocAsterisk(
                  commentContext.lineStart,
                  commentContext.docAsterisk,
                  !StringUtil.isEmpty(indentInsideJavadoc),
                  commentContext.commenter);
        }

        boolean docIndentApplied = false;
        CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
        if (codeInsightSettings.SMART_INDENT_ON_ENTER
            || myForceIndent
            || commentContext.docStart
            || commentContext.docAsterisk
            || commentContext.slashSlash) {
          final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject());
          myOffset = codeStyleManager.adjustLineIndent(myFile, myOffset);
          psiDocumentManager.commitAllDocuments();

          if (!StringUtil.isEmpty(indentInsideJavadoc) && myOffset < myDocument.getTextLength()) {
            myDocument.insertString(myOffset + 1, indentInsideJavadoc);
            myOffset += indentInsideJavadoc.length();
            docIndentApplied = true;
          }

          if (myForceIndent && indentInsideJavadoc != null) {
            int indentSize =
                CodeStyleSettingsManager.getSettings(myProject).getIndentSize(myFile.getFileType());
            myDocument.insertString(myOffset + 1, StringUtil.repeatSymbol(' ', indentSize));
            myCaretAdvance += indentSize;
          }
        }

        if ((commentContext.docAsterisk || commentContext.docStart || commentContext.slashSlash)
            && !docIndentApplied) {
          if (myInsertSpace) {
            if (myOffset == myDocument.getTextLength()) {
              myDocument.insertString(myOffset, " ");
            }
            myDocument.insertString(myOffset + 1, " ");
          }

          final char c = myDocument.getCharsSequence().charAt(myOffset);
          if (c != '\n') {
            myOffset += 1;
          }
        }

        if ((commentContext.docAsterisk || commentContext.slashSlash) && !commentContext.docStart) {
          myCaretAdvance +=
              commentContext.slashSlash
                  ? commentContext.commenter.getLineCommentPrefix().length()
                  : 1;
        }
      } catch (IncorrectOperationException e) {
        LOG.error(e);
      }

      myOffset = Math.min(myOffset, myDocument.getTextLength());
      caretModel.moveToOffset(myOffset);
      myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
      myEditor.getSelectionModel().removeSelection();
      if (myCaretAdvance != 0) {
        LogicalPosition caretPosition = caretModel.getLogicalPosition();
        LogicalPosition pos =
            new LogicalPosition(caretPosition.line, caretPosition.column + myCaretAdvance);
        caretModel.moveToLogicalPosition(pos);
      }
    }
  private void commentLine(int line, int offset, @Nullable Commenter commenter) {
    if (commenter == null) commenter = findCommenter(line);
    if (commenter == null) return;
    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      selfManagingCommenter.commentLine(
          line, offset, myDocument, myCommenterStateMap.get(selfManagingCommenter));
      return;
    }

    String prefix = commenter.getLineCommentPrefix();
    if (prefix != null) {
      if (commenter instanceof CommenterWithLineSuffix) {
        int endOffset = myDocument.getLineEndOffset(line);
        endOffset = CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), endOffset, " \t");
        int shiftedStartOffset =
            CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t");
        String lineSuffix = ((CommenterWithLineSuffix) commenter).getLineCommentSuffix();
        if (!CharArrayUtil.regionMatches(
            myDocument.getCharsSequence(), shiftedStartOffset, prefix)) {
          if (!CharArrayUtil.regionMatches(
              myDocument.getCharsSequence(), endOffset - lineSuffix.length(), lineSuffix)) {
            myDocument.insertString(endOffset, lineSuffix);
          }
          myDocument.insertString(offset, prefix);
        }
      } else {
        myDocument.insertString(offset, prefix);
      }
    } else {
      prefix = commenter.getBlockCommentPrefix();
      String suffix = commenter.getBlockCommentSuffix();
      if (prefix == null || suffix == null) return;
      int endOffset = myDocument.getLineEndOffset(line);
      if (endOffset == offset && myStartLine != myEndLine) return;
      final int textLength = myDocument.getTextLength();
      final CharSequence chars = myDocument.getCharsSequence();
      offset = CharArrayUtil.shiftForward(chars, offset, " \t");
      if (endOffset == textLength) {
        final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
        if (shifted < textLength - 1) endOffset = shifted;
      } else {
        endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t");
      }
      if (endOffset < offset || offset == textLength - 1 && line != myDocument.getLineCount() - 1) {
        return;
      }
      final String text = chars.subSequence(offset, endOffset).toString();
      final IntArrayList prefixes = new IntArrayList();
      final IntArrayList suffixes = new IntArrayList();
      final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
      final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
      for (int position = 0; position < text.length(); ) {
        int nearestPrefix = text.indexOf(prefix, position);
        if (nearestPrefix == -1) {
          nearestPrefix = text.length();
        }
        int nearestSuffix = text.indexOf(suffix, position);
        if (nearestSuffix == -1) {
          nearestSuffix = text.length();
        }
        if (Math.min(nearestPrefix, nearestSuffix) == text.length()) {
          break;
        }
        if (nearestPrefix < nearestSuffix) {
          prefixes.add(nearestPrefix);
          position = nearestPrefix + prefix.length();
        } else {
          suffixes.add(nearestSuffix);
          position = nearestSuffix + suffix.length();
        }
      }
      if (!(commentedSuffix == null
          && !suffixes.isEmpty()
          && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) {
        myDocument.insertString(endOffset, suffix);
      }
      int nearestPrefix = prefixes.size() - 1;
      int nearestSuffix = suffixes.size() - 1;
      while (nearestPrefix >= 0 || nearestSuffix >= 0) {
        if (nearestSuffix == -1
            || nearestPrefix != -1 && prefixes.get(nearestPrefix) > suffixes.get(nearestSuffix)) {
          final int position = prefixes.get(nearestPrefix);
          nearestPrefix--;
          if (commentedPrefix != null) {
            myDocument.replaceString(
                offset + position, offset + position + prefix.length(), commentedPrefix);
          } else if (position != 0) {
            myDocument.insertString(offset + position, suffix);
          }
        } else {
          final int position = suffixes.get(nearestSuffix);
          nearestSuffix--;
          if (commentedSuffix != null) {
            myDocument.replaceString(
                offset + position, offset + position + suffix.length(), commentedSuffix);
          } else if (offset + position + suffix.length() < endOffset) {
            myDocument.insertString(offset + position + suffix.length(), prefix);
          }
        }
      }
      if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) {
        myDocument.insertString(offset, prefix);
      }
    }
  }