@Override
  public void actionPerformed(AnActionEvent e) {
    Project project = e.getData(PlatformDataKeys.PROJECT);
    Editor editor = e.getData(PlatformDataKeys.EDITOR);

    if (editor == null) {
      return;
    }

    if (!editor.getDocument().isWritable()) {
      return;
    }

    Document document = editor.getDocument();
    SelectionModel selection = editor.getSelectionModel();
    String selectedText = selection.getSelectedText();

    String autoAlignedText;
    int startOffset;
    int endOffset;

    if (selectedText != null) {
      // just align the selected text
      autoAlignedText = aligner.align(selectedText);
      startOffset = selection.getSelectionStart();
      endOffset = selection.getSelectionEnd();
    } else {
      // auto-align the whole document
      autoAlignedText = aligner.align(document.getText());
      startOffset = 0;
      endOffset = document.getTextLength();
    }

    replaceString(project, document, autoAlignedText, startOffset, endOffset);
  }
  /**
   * Emulates pressing <code>Enter</code> at current caret position.
   *
   * @param editor target editor
   * @param project target project
   * @param shifts two-elements array which is expected to be filled with the following info: 1. The
   *     first element holds added lines number; 2. The second element holds added symbols number;
   */
  private static void emulateEnter(
      @NotNull final Editor editor, @NotNull Project project, int[] shifts) {
    final DataContext dataContext = prepareContext(editor.getComponent(), project);
    int caretOffset = editor.getCaretModel().getOffset();
    Document document = editor.getDocument();
    SelectionModel selectionModel = editor.getSelectionModel();
    int startSelectionOffset = 0;
    int endSelectionOffset = 0;
    boolean restoreSelection = selectionModel.hasSelection();
    if (restoreSelection) {
      startSelectionOffset = selectionModel.getSelectionStart();
      endSelectionOffset = selectionModel.getSelectionEnd();
      selectionModel.removeSelection();
    }
    int textLengthBeforeWrap = document.getTextLength();
    int lineCountBeforeWrap = document.getLineCount();

    DataManager.getInstance()
        .saveInDataContext(dataContext, WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY, true);
    CommandProcessor commandProcessor = CommandProcessor.getInstance();
    try {
      Runnable command =
          new Runnable() {
            @Override
            public void run() {
              EditorActionManager.getInstance()
                  .getActionHandler(IdeActions.ACTION_EDITOR_ENTER)
                  .execute(editor, dataContext);
            }
          };
      if (commandProcessor.getCurrentCommand() == null) {
        commandProcessor.executeCommand(editor.getProject(), command, WRAP_LINE_COMMAND_NAME, null);
      } else {
        command.run();
      }
    } finally {
      DataManager.getInstance()
          .saveInDataContext(dataContext, WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY, null);
    }
    int symbolsDiff = document.getTextLength() - textLengthBeforeWrap;
    if (restoreSelection) {
      int newSelectionStart = startSelectionOffset;
      int newSelectionEnd = endSelectionOffset;
      if (startSelectionOffset >= caretOffset) {
        newSelectionStart += symbolsDiff;
      }
      if (endSelectionOffset >= caretOffset) {
        newSelectionEnd += symbolsDiff;
      }
      selectionModel.setSelection(newSelectionStart, newSelectionEnd);
    }
    shifts[0] = document.getLineCount() - lineCountBeforeWrap;
    shifts[1] = symbolsDiff;
  }
  public void testNoParenthesisSurrounderForLambdaParameter() {
    configureByFile(BASE_PATH + getTestName(false) + ".java");

    SurroundDescriptor item =
        ContainerUtil.getFirstItem(
            LanguageSurrounders.INSTANCE.allForLanguage(JavaLanguage.INSTANCE));
    assertNotNull(item);
    SelectionModel selectionModel = getEditor().getSelectionModel();
    PsiElement[] elements =
        item.getElementsToSurround(
            getFile(), selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
    assertEmpty(elements);
  }
  protected static List<TextRange> getSelectedRanges(@NotNull SelectionModel selectionModel) {
    final List<TextRange> ranges = new SmartList<TextRange>();
    if (selectionModel.hasSelection()) {
      TextRange range =
          TextRange.create(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
      ranges.add(range);
    } else if (selectionModel.hasBlockSelection()) {
      int[] starts = selectionModel.getBlockSelectionStarts();
      int[] ends = selectionModel.getBlockSelectionEnds();
      for (int i = 0; i < starts.length; i++) {
        ranges.add(TextRange.create(starts[i], ends[i]));
      }
    }

    return ranges;
  }
  private void doTest(@NotNull String fileName, Surrounder surrounder) {
    configureByFile(BASE_PATH + fileName + ".java");

    SurroundDescriptor item =
        ContainerUtil.getFirstItem(
            LanguageSurrounders.INSTANCE.allForLanguage(JavaLanguage.INSTANCE));
    assertNotNull(item);
    SelectionModel selectionModel = getEditor().getSelectionModel();
    PsiElement[] elements =
        item.getElementsToSurround(
            getFile(), selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
    assertTrue(surrounder.isApplicable(elements));

    SurroundWithHandler.invoke(getProject(), getEditor(), getFile(), surrounder);

    checkResultByFile(BASE_PATH + fileName + "_after.java");
  }
  private void handleSelection(Editor editor, SelectionModel selectionModel) {
    Document doc = editor.getDocument();

    if (doc == null) {
      return;
    }

    int start = selectionModel.getSelectionStart();
    int end = selectionModel.getSelectionEnd();
    char[] allChars = doc.getChars();

    StringBuffer selection = new StringBuffer();

    selection.append(allChars, start, end - start);

    this.m_transformer.transform(selection);

    doc.replaceString(start, end, selection.toString());
    selectionModel.setSelection(start, start + selection.length());
  }
 @NotNull
 public static Editor getInjectedEditorForInjectedFile(
     @NotNull Editor hostEditor, @Nullable final PsiFile injectedFile) {
   if (injectedFile == null || hostEditor instanceof EditorWindow || hostEditor.isDisposed())
     return hostEditor;
   Project project = hostEditor.getProject();
   if (project == null) project = injectedFile.getProject();
   Document document = PsiDocumentManager.getInstance(project).getDocument(injectedFile);
   if (!(document instanceof DocumentWindowImpl)) return hostEditor;
   DocumentWindowImpl documentWindow = (DocumentWindowImpl) document;
   SelectionModel selectionModel = hostEditor.getSelectionModel();
   if (selectionModel.hasSelection()) {
     int selstart = selectionModel.getSelectionStart();
     int selend = selectionModel.getSelectionEnd();
     if (!documentWindow.containsRange(selstart, selend)) {
       // selection spreads out the injected editor range
       return hostEditor;
     }
   }
   if (!documentWindow.isValid())
     return hostEditor; // since the moment we got hold of injectedFile and this moment call,
                        // document may have been dirtied
   return EditorWindow.create(documentWindow, (EditorImpl) hostEditor, injectedFile);
 }
Esempio n. 8
0
  @NotNull
  @Override
  public List<SearchScope> getPredefinedScopes(
      @NotNull final Project project,
      @Nullable final DataContext dataContext,
      boolean suggestSearchInLibs,
      boolean prevSearchFiles,
      boolean currentSelection,
      boolean usageView,
      boolean showEmptyScopes) {
    Collection<SearchScope> result = ContainerUtil.newLinkedHashSet();
    result.add(GlobalSearchScope.projectScope(project));
    if (suggestSearchInLibs) {
      result.add(GlobalSearchScope.allScope(project));
    }

    if (ModuleUtil.isSupportedRootType(project, JavaSourceRootType.TEST_SOURCE)) {
      result.add(GlobalSearchScopesCore.projectProductionScope(project));
      result.add(GlobalSearchScopesCore.projectTestScope(project));
    }

    final GlobalSearchScope openFilesScope = GlobalSearchScopes.openFilesScope(project);
    if (openFilesScope != GlobalSearchScope.EMPTY_SCOPE) {
      result.add(openFilesScope);
    } else if (showEmptyScopes) {
      result.add(
          new LocalSearchScope(PsiElement.EMPTY_ARRAY, IdeBundle.message("scope.open.files")));
    }

    final Editor selectedTextEditor =
        ApplicationManager.getApplication().isDispatchThread()
            ? FileEditorManager.getInstance(project).getSelectedTextEditor()
            : null;
    final PsiFile psiFile =
        (selectedTextEditor != null)
            ? PsiDocumentManager.getInstance(project).getPsiFile(selectedTextEditor.getDocument())
            : null;
    PsiFile currentFile = psiFile;

    if (dataContext != null) {
      PsiElement dataContextElement = CommonDataKeys.PSI_FILE.getData(dataContext);
      if (dataContextElement == null) {
        dataContextElement = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
      }

      if (dataContextElement == null && psiFile != null) {
        dataContextElement = psiFile;
      }

      if (dataContextElement != null) {
        if (!PlatformUtils.isCidr()) { // TODO: have an API to disable module scopes.
          Module module = ModuleUtilCore.findModuleForPsiElement(dataContextElement);
          if (module == null) {
            module = LangDataKeys.MODULE.getData(dataContext);
          }
          if (module != null && !(ModuleType.get(module) instanceof InternalModuleType)) {
            result.add(module.getModuleScope());
          }
        }
        if (currentFile == null) {
          currentFile = dataContextElement.getContainingFile();
        }
      }
    }

    if (currentFile != null || showEmptyScopes) {
      PsiElement[] scope =
          currentFile != null ? new PsiElement[] {currentFile} : PsiElement.EMPTY_ARRAY;
      result.add(new LocalSearchScope(scope, IdeBundle.message("scope.current.file")));
    }

    if (currentSelection && selectedTextEditor != null && psiFile != null) {
      SelectionModel selectionModel = selectedTextEditor.getSelectionModel();
      if (selectionModel.hasSelection()) {
        int start = selectionModel.getSelectionStart();
        final PsiElement startElement = psiFile.findElementAt(start);
        if (startElement != null) {
          int end = selectionModel.getSelectionEnd();
          final PsiElement endElement = psiFile.findElementAt(end);
          if (endElement != null) {
            final PsiElement parent = PsiTreeUtil.findCommonParent(startElement, endElement);
            if (parent != null) {
              final List<PsiElement> elements = new ArrayList<PsiElement>();
              final PsiElement[] children = parent.getChildren();
              TextRange selection = new TextRange(start, end);
              for (PsiElement child : children) {
                if (!(child instanceof PsiWhiteSpace)
                    && child.getContainingFile() != null
                    && selection.contains(child.getTextOffset())) {
                  elements.add(child);
                }
              }
              if (!elements.isEmpty()) {
                SearchScope local =
                    new LocalSearchScope(
                        PsiUtilCore.toPsiElementArray(elements),
                        IdeBundle.message("scope.selection"));
                result.add(local);
              }
            }
          }
        }
      }
    }

    if (usageView) {
      addHierarchyScope(project, result);
      UsageView selectedUsageView = UsageViewManager.getInstance(project).getSelectedUsageView();
      if (selectedUsageView != null && !selectedUsageView.isSearchInProgress()) {
        final Set<Usage> usages = ContainerUtil.newTroveSet(selectedUsageView.getUsages());
        usages.removeAll(selectedUsageView.getExcludedUsages());
        final List<PsiElement> results = new ArrayList<PsiElement>(usages.size());

        if (prevSearchFiles) {
          final Set<VirtualFile> files = collectFiles(usages, true);
          if (!files.isEmpty()) {
            GlobalSearchScope prev =
                new GlobalSearchScope(project) {
                  private Set<VirtualFile> myFiles = null;

                  @NotNull
                  @Override
                  public String getDisplayName() {
                    return IdeBundle.message("scope.files.in.previous.search.result");
                  }

                  @Override
                  public synchronized boolean contains(@NotNull VirtualFile file) {
                    if (myFiles == null) {
                      myFiles = collectFiles(usages, false);
                    }
                    return myFiles.contains(file);
                  }

                  @Override
                  public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
                    return 0;
                  }

                  @Override
                  public boolean isSearchInModuleContent(@NotNull Module aModule) {
                    return true;
                  }

                  @Override
                  public boolean isSearchInLibraries() {
                    return true;
                  }
                };
            result.add(prev);
          }
        } else {
          for (Usage usage : usages) {
            if (usage instanceof PsiElementUsage) {
              final PsiElement element = ((PsiElementUsage) usage).getElement();
              if (element != null && element.isValid() && element.getContainingFile() != null) {
                results.add(element);
              }
            }
          }

          if (!results.isEmpty()) {
            result.add(
                new LocalSearchScope(
                    PsiUtilCore.toPsiElementArray(results),
                    IdeBundle.message("scope.previous.search.results")));
          }
        }
      }
    }

    final FavoritesManager favoritesManager = FavoritesManager.getInstance(project);
    if (favoritesManager != null) {
      for (final String favorite : favoritesManager.getAvailableFavoritesListNames()) {
        final Collection<TreeItem<Pair<AbstractUrl, String>>> rootUrls =
            favoritesManager.getFavoritesListRootUrls(favorite);
        if (rootUrls.isEmpty()) continue; // ignore unused root
        result.add(
            new GlobalSearchScope(project) {
              @NotNull
              @Override
              public String getDisplayName() {
                return "Favorite \'" + favorite + "\'";
              }

              @Override
              public boolean contains(@NotNull final VirtualFile file) {
                return ApplicationManager.getApplication()
                    .runReadAction(
                        (Computable<Boolean>) () -> favoritesManager.contains(favorite, file));
              }

              @Override
              public int compare(
                  @NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
                return 0;
              }

              @Override
              public boolean isSearchInModuleContent(@NotNull final Module aModule) {
                return true;
              }

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

    ContainerUtil.addIfNotNull(result, getSelectedFilesScope(project, dataContext));

    return ContainerUtil.newArrayList(result);
  }
  private void dumpEditorMarkupAndSelection(PrintStream dumpStream) {
    dumpStream.println(mySearchResults.getFindModel());
    if (myReplacementPreviewText != null) {
      dumpStream.println("--");
      dumpStream.println("Replacement Preview: " + myReplacementPreviewText);
    }
    dumpStream.println("--");

    Editor editor = mySearchResults.getEditor();

    RangeHighlighter[] highlighters = editor.getMarkupModel().getAllHighlighters();
    List<Pair<Integer, Character>> ranges = new ArrayList<Pair<Integer, Character>>();
    for (RangeHighlighter highlighter : highlighters) {
      ranges.add(new Pair<Integer, Character>(highlighter.getStartOffset(), '['));
      ranges.add(new Pair<Integer, Character>(highlighter.getEndOffset(), ']'));
    }

    SelectionModel selectionModel = editor.getSelectionModel();

    if (selectionModel.getSelectionStart() != selectionModel.getSelectionEnd()) {
      ranges.add(new Pair<Integer, Character>(selectionModel.getSelectionStart(), '<'));
      ranges.add(new Pair<Integer, Character>(selectionModel.getSelectionEnd(), '>'));
    }
    ranges.add(new Pair<Integer, Character>(-1, '\n'));
    ranges.add(new Pair<Integer, Character>(editor.getDocument().getTextLength() + 1, '\n'));
    ContainerUtil.sort(
        ranges,
        new Comparator<Pair<Integer, Character>>() {
          @Override
          public int compare(Pair<Integer, Character> pair, Pair<Integer, Character> pair2) {
            int res = pair.first - pair2.first;
            if (res == 0) {

              Character c1 = pair.second;
              Character c2 = pair2.second;
              if (c1 == '<' && c2 == '[') {
                return 1;
              } else if (c1 == '[' && c2 == '<') {
                return -1;
              }
              return c1.compareTo(c2);
            }
            return res;
          }
        });

    Document document = editor.getDocument();
    for (int i = 0; i < ranges.size() - 1; ++i) {
      Pair<Integer, Character> pair = ranges.get(i);
      Pair<Integer, Character> pair1 = ranges.get(i + 1);
      dumpStream.print(
          pair.second
              + document.getText(
                  TextRange.create(
                      Math.max(pair.first, 0), Math.min(pair1.first, document.getTextLength()))));
    }
    dumpStream.println("\n--");

    if (NotFound) {
      dumpStream.println("Not Found");
      dumpStream.println("--");
      NotFound = false;
    }

    for (RangeHighlighter highlighter : highlighters) {
      dumpStream.println(highlighter + " : " + highlighter.getTextAttributes());
    }
    dumpStream.println("------------");
  }
  public final void move(Editor editor, final PsiFile file) {
    myMover.beforeMove(editor, myInfo, myIsDown);
    final Document document = editor.getDocument();
    final int start =
        StatementUpDownMover.getLineStartSafeOffset(document, myInfo.toMove.startLine);
    final int end = StatementUpDownMover.getLineStartSafeOffset(document, myInfo.toMove.endLine);
    myInfo.range1 = document.createRangeMarker(start, end);

    String textToInsert = document.getCharsSequence().subSequence(start, end).toString();
    if (!StringUtil.endsWithChar(textToInsert, '\n')) textToInsert += '\n';

    final int start2 = document.getLineStartOffset(myInfo.toMove2.startLine);
    final int end2 = StatementUpDownMover.getLineStartSafeOffset(document, myInfo.toMove2.endLine);
    String textToInsert2 = document.getCharsSequence().subSequence(start2, end2).toString();
    if (!StringUtil.endsWithChar(textToInsert2, '\n')) textToInsert2 += '\n';
    myInfo.range2 = document.createRangeMarker(start2, end2);
    if (myInfo.range1.getStartOffset() < myInfo.range2.getStartOffset()) {
      myInfo.range1.setGreedyToLeft(true);
      myInfo.range1.setGreedyToRight(false);
      myInfo.range2.setGreedyToLeft(true);
      myInfo.range2.setGreedyToRight(true);
    } else {
      myInfo.range1.setGreedyToLeft(true);
      myInfo.range1.setGreedyToRight(true);
      myInfo.range2.setGreedyToLeft(true);
      myInfo.range2.setGreedyToRight(false);
    }

    final CaretModel caretModel = editor.getCaretModel();
    final int caretRelativePos = caretModel.getOffset() - start;
    final SelectionModel selectionModel = editor.getSelectionModel();
    final int selectionStart = selectionModel.getSelectionStart();
    final int selectionEnd = selectionModel.getSelectionEnd();
    final boolean hasSelection = selectionModel.hasSelection();

    // to prevent flicker
    caretModel.moveToOffset(0);

    // There is a possible case that the user performs, say, method move. It's also possible that
    // one (or both) of moved methods
    // are folded. We want to preserve their states then. The problem is that folding processing is
    // based on PSI element pointers
    // and the pointers behave as following during move up/down:
    //     method1() {}
    //     method2() {}
    // Pointer for the fold region from method1 points to 'method2()' now and vice versa (check
    // range markers processing on
    // document change for further information). I.e. information about fold regions statuses holds
    // the data swapped for
    // 'method1' and 'method2'. Hence, we want to apply correct 'collapsed' status.
    FoldRegion topRegion = null;
    FoldRegion bottomRegion = null;
    for (FoldRegion foldRegion : editor.getFoldingModel().getAllFoldRegions()) {
      if (!foldRegion.isValid()
          || (!contains(myInfo.range1, foldRegion) && !contains(myInfo.range2, foldRegion))) {
        continue;
      }
      if (contains(myInfo.range1, foldRegion) && !contains(topRegion, foldRegion)) {
        topRegion = foldRegion;
      } else if (contains(myInfo.range2, foldRegion) && !contains(bottomRegion, foldRegion)) {
        bottomRegion = foldRegion;
      }
    }

    document.insertString(myInfo.range1.getStartOffset(), textToInsert2);
    document.deleteString(
        myInfo.range1.getStartOffset() + textToInsert2.length(), myInfo.range1.getEndOffset());

    document.insertString(myInfo.range2.getStartOffset(), textToInsert);
    int s = myInfo.range2.getStartOffset() + textToInsert.length();
    int e = myInfo.range2.getEndOffset();
    if (e > s) {
      document.deleteString(s, e);
    }

    final Project project = file.getProject();
    PsiDocumentManager.getInstance(project).commitAllDocuments();

    // Swap fold regions status if necessary.
    if (topRegion != null && bottomRegion != null) {
      final FoldRegion finalTopRegion = topRegion;
      final FoldRegion finalBottomRegion = bottomRegion;
      editor
          .getFoldingModel()
          .runBatchFoldingOperation(
              new Runnable() {
                @Override
                public void run() {
                  boolean topExpanded = finalTopRegion.isExpanded();
                  finalTopRegion.setExpanded(finalBottomRegion.isExpanded());
                  finalBottomRegion.setExpanded(topExpanded);
                }
              });
    }
    CodeFoldingManager.getInstance(project).allowFoldingOnCaretLine(editor);

    if (hasSelection) {
      restoreSelection(editor, selectionStart, selectionEnd, start, myInfo.range2.getStartOffset());
    }

    caretModel.moveToOffset(myInfo.range2.getStartOffset() + caretRelativePos);
    if (myInfo.indentTarget) {
      indentLinesIn(editor, file, document, project, myInfo.range2);
    }
    if (myInfo.indentSource) {
      indentLinesIn(editor, file, document, project, myInfo.range1);
    }

    myMover.afterMove(editor, file, myInfo, myIsDown);
    editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
  }
  public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
    myProject = project;
    myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage());
    myEditor = editor;

    PsiElement context = myFile.getContext();

    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());
        }
      }
    }
  }
  public void performAction(IntroduceOperation operation) {
    final PsiFile file = operation.getFile();
    if (!CommonRefactoringUtil.checkReadOnlyStatus(file)) {
      return;
    }
    final Editor editor = operation.getEditor();
    if (editor.getSettings().isVariableInplaceRenameEnabled()) {
      final TemplateState templateState =
          TemplateManagerImpl.getTemplateState(operation.getEditor());
      if (templateState != null && !templateState.isFinished()) {
        return;
      }
    }

    PsiElement element1 = null;
    PsiElement element2 = null;
    final SelectionModel selectionModel = editor.getSelectionModel();
    boolean singleElementSelection = false;
    if (selectionModel.hasSelection()) {
      element1 = file.findElementAt(selectionModel.getSelectionStart());
      element2 = file.findElementAt(selectionModel.getSelectionEnd() - 1);
      if (element1 instanceof PsiWhiteSpace) {
        int startOffset = element1.getTextRange().getEndOffset();
        element1 = file.findElementAt(startOffset);
      }
      if (element2 instanceof PsiWhiteSpace) {
        int endOffset = element2.getTextRange().getStartOffset();
        element2 = file.findElementAt(endOffset - 1);
      }
      if (element1 == element2) {
        singleElementSelection = true;
      }
    } else {
      if (smartIntroduce(operation)) {
        return;
      }
      final CaretModel caretModel = editor.getCaretModel();
      final Document document = editor.getDocument();
      int lineNumber = document.getLineNumber(caretModel.getOffset());
      if ((lineNumber >= 0) && (lineNumber < document.getLineCount())) {
        element1 = file.findElementAt(document.getLineStartOffset(lineNumber));
        element2 = file.findElementAt(document.getLineEndOffset(lineNumber) - 1);
      }
    }
    final Project project = operation.getProject();
    if (element1 == null || element2 == null) {
      showCannotPerformError(project, editor);
      return;
    }

    element1 = PyRefactoringUtil.getSelectedExpression(project, file, element1, element2);
    if (element1 == null) {
      showCannotPerformError(project, editor);
      return;
    }

    if (singleElementSelection && element1 instanceof PyStringLiteralExpression) {
      final PyStringLiteralExpression literal = (PyStringLiteralExpression) element1;
      // Currently introduce for substrings of a multi-part string literals is not supported
      if (literal.getStringNodes().size() > 1) {
        showCannotPerformError(project, editor);
        return;
      }
      final int offset = element1.getTextOffset();
      final TextRange selectionRange =
          TextRange.create(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
      final TextRange elementRange = element1.getTextRange();
      if (!elementRange.equals(selectionRange) && elementRange.contains(selectionRange)) {
        final TextRange innerRange = literal.getStringValueTextRange();
        final TextRange intersection = selectionRange.shiftRight(-offset).intersection(innerRange);
        final TextRange finalRange = intersection != null ? intersection : selectionRange;
        final String text = literal.getText();
        if (getFormatValueExpression(literal) != null && breaksStringFormatting(text, finalRange)
            || getNewStyleFormatValueExpression(literal) != null
                && breaksNewStyleStringFormatting(text, finalRange)
            || breaksStringEscaping(text, finalRange)) {
          showCannotPerformError(project, editor);
          return;
        }
        element1.putUserData(
            PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE, Pair.create(element1, finalRange));
      }
    }

    if (!checkIntroduceContext(file, editor, element1)) {
      return;
    }
    operation.setElement(element1);
    performActionOnElement(operation);
  }