private void doReplaceString(int startOffset, int endOffset, CharSequence s) {
    assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null;
    assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null;

    List<Pair<TextRange, CharSequence>> hostRangesToModify;
    synchronized (myLock) {
      hostRangesToModify = new ArrayList<Pair<TextRange, CharSequence>>(myShreds.size());

      int offset = startOffset;
      int curRangeStart = 0;
      for (int i = 0; i < myShreds.size(); i++) {
        PsiLanguageInjectionHost.Shred shred = myShreds.get(i);
        curRangeStart += shred.getPrefix().length();
        if (offset < curRangeStart) offset = curRangeStart;
        Segment hostRange = shred.getHostRangeMarker();
        if (hostRange == null) continue;
        int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset();
        TextRange range = TextRange.from(curRangeStart, hostRangeLength);
        if (range.contains(offset)
            || range.getEndOffset() == offset /* in case of inserting at the end*/) {
          TextRange rangeToModify =
              new TextRange(offset, Math.min(range.getEndOffset(), endOffset));
          TextRange hostRangeToModify =
              rangeToModify.shiftRight(hostRange.getStartOffset() - curRangeStart);
          CharSequence toReplace =
              i == myShreds.size() - 1
                      || range.getEndOffset() + shred.getSuffix().length() >= endOffset
                  ? s
                  : s.subSequence(0, Math.min(hostRangeToModify.getLength(), s.length()));
          s = toReplace == s ? "" : s.subSequence(toReplace.length(), s.length());
          hostRangesToModify.add(Pair.create(hostRangeToModify, toReplace));
          offset = rangeToModify.getEndOffset();
        }
        curRangeStart += hostRangeLength;
        curRangeStart += shred.getSuffix().length();
        if (curRangeStart > endOffset) break;
      }
    }

    int delta = 0;
    for (Pair<TextRange, CharSequence> pair : hostRangesToModify) {
      TextRange hostRange = pair.getFirst();
      CharSequence replace = pair.getSecond();

      myDelegate.replaceString(
          hostRange.getStartOffset() + delta, hostRange.getEndOffset() + delta, replace);
      delta -= hostRange.getLength() - replace.length();
    }
  }
 private static void duplicateHighlighters(
     MarkupModel to, MarkupModel from, int offset, TextRange textRange) {
   for (RangeHighlighter rangeHighlighter : from.getAllHighlighters()) {
     if (!rangeHighlighter.isValid()) continue;
     Object tooltip = rangeHighlighter.getErrorStripeTooltip();
     HighlightInfo highlightInfo =
         tooltip instanceof HighlightInfo ? (HighlightInfo) tooltip : null;
     if (highlightInfo != null) {
       if (highlightInfo.getSeverity() != HighlightSeverity.INFORMATION) continue;
       if (highlightInfo.type.getAttributesKey() == EditorColors.IDENTIFIER_UNDER_CARET_ATTRIBUTES)
         continue;
     }
     final int localOffset = textRange.getStartOffset();
     final int start = Math.max(rangeHighlighter.getStartOffset(), localOffset) - localOffset;
     final int end =
         Math.min(rangeHighlighter.getEndOffset(), textRange.getEndOffset()) - localOffset;
     if (start > end) continue;
     final RangeHighlighter h =
         to.addRangeHighlighter(
             start + offset,
             end + offset,
             rangeHighlighter.getLayer(),
             rangeHighlighter.getTextAttributes(),
             rangeHighlighter.getTargetArea());
     ((RangeHighlighterEx) h)
         .setAfterEndOfLine(((RangeHighlighterEx) rangeHighlighter).isAfterEndOfLine());
   }
 }
  @Override
  public int getLineNumber(int offset) {
    int lineNumber = 0;
    String hostText = myDelegate.getText();
    synchronized (myLock) {
      for (PsiLanguageInjectionHost.Shred shred : myShreds) {
        String prefix = shred.getPrefix();
        String suffix = shred.getSuffix();
        lineNumber +=
            StringUtil.getLineBreakCount(prefix.substring(0, Math.min(offset, prefix.length())));
        if (offset < prefix.length()) {
          return lineNumber;
        }
        offset -= prefix.length();

        Segment currentRange = shred.getHostRangeMarker();
        if (currentRange == null) continue;
        int rangeLength = currentRange.getEndOffset() - currentRange.getStartOffset();
        CharSequence rangeText =
            hostText.subSequence(currentRange.getStartOffset(), currentRange.getEndOffset());

        lineNumber +=
            StringUtil.getLineBreakCount(rangeText.subSequence(0, Math.min(offset, rangeLength)));
        if (offset < rangeLength) {
          return lineNumber;
        }
        offset -= rangeLength;

        lineNumber +=
            StringUtil.getLineBreakCount(suffix.substring(0, Math.min(offset, suffix.length())));
        if (offset < suffix.length()) {
          return lineNumber;
        }

        offset -= suffix.length();
      }
    }
    lineNumber = getLineCount() - 1;
    return lineNumber < 0 ? 0 : lineNumber;
  }
 private void performRemove() {
   final int selectedRow = myInjectionsTable.getSelectedRow();
   if (selectedRow < 0) return;
   final List<InjInfo> selected = getSelectedInjections();
   for (InjInfo info : selected) {
     if (info.bundled) continue;
     info.cfgInfo.injectionInfos.remove(info);
   }
   myInjectionsTable.getListTableModel().setItems(getInjInfoList(myInfos));
   final int index =
       Math.min(myInjectionsTable.getListTableModel().getRowCount() - 1, selectedRow);
   myInjectionsTable.getSelectionModel().setSelectionInterval(index, index);
   TableUtil.scrollSelectionToVisible(myInjectionsTable);
   updateCountLabel();
 }
 public void registerFix(
     @Nullable IntentionAction action,
     @Nullable List<IntentionAction> options,
     @Nullable String displayName,
     @Nullable TextRange fixRange,
     @Nullable HighlightDisplayKey key) {
   if (action == null) return;
   if (fixRange == null) fixRange = new TextRange(startOffset, endOffset);
   if (quickFixActionRanges == null) {
     quickFixActionRanges = ContainerUtil.createLockFreeCopyOnWriteList();
   }
   IntentionActionDescriptor desc =
       new IntentionActionDescriptor(action, options, displayName, null, key, getProblemGroup());
   quickFixActionRanges.add(Pair.create(desc, fixRange));
   fixStartOffset = Math.min(fixStartOffset, fixRange.getStartOffset());
   fixEndOffset = Math.max(fixEndOffset, fixRange.getEndOffset());
   if (action instanceof HintAction) {
     setHint(true);
   }
 }
  @Override
  public void deleteString(final int startOffset, final int endOffset) {
    assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null;
    assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null;

    List<TextRange> hostRangesToDelete;
    synchronized (myLock) {
      hostRangesToDelete = new ArrayList<TextRange>(myShreds.size());

      int offset = startOffset;
      int curRangeStart = 0;
      for (PsiLanguageInjectionHost.Shred shred : myShreds) {
        curRangeStart += shred.getPrefix().length();
        if (offset < curRangeStart) offset = curRangeStart;
        if (offset >= endOffset) break;
        Segment hostRange = shred.getHostRangeMarker();
        if (hostRange == null) continue;
        int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset();
        TextRange range = TextRange.from(curRangeStart, hostRangeLength);
        if (range.contains(offset)) {
          TextRange rangeToDelete =
              new TextRange(offset, Math.min(range.getEndOffset(), endOffset));
          hostRangesToDelete.add(
              rangeToDelete.shiftRight(hostRange.getStartOffset() - curRangeStart));
          offset = rangeToDelete.getEndOffset();
        }
        curRangeStart += hostRangeLength;
        curRangeStart += shred.getSuffix().length();
      }
    }

    int delta = 0;
    for (TextRange hostRangeToDelete : hostRangesToDelete) {
      myDelegate.deleteString(
          hostRangeToDelete.getStartOffset() + delta, hostRangeToDelete.getEndOffset() + delta);
      delta -= hostRangeToDelete.getLength();
    }
  }
  private List<PostponedAction> normalizeAndReorderPostponedActions(
      final TreeSet<PostprocessFormattingTask> rangesToProcess, Document document) {
    final List<PostprocessFormattingTask> freeFormatingActions =
        new ArrayList<PostprocessFormattingTask>();
    final List<ReindentTask> indentActions = new ArrayList<ReindentTask>();

    PostprocessFormattingTask accumulatedTask = null;
    Iterator<PostprocessFormattingTask> iterator = rangesToProcess.iterator();
    while (iterator.hasNext()) {
      final PostprocessFormattingTask currentTask = iterator.next();
      if (accumulatedTask == null) {
        accumulatedTask = currentTask;
        iterator.remove();
      } else if (accumulatedTask.getStartOffset() > currentTask.getEndOffset()
          || accumulatedTask.getStartOffset() == currentTask.getEndOffset()
              && !canStickActionsTogether(accumulatedTask, currentTask)) {
        // action can be pushed
        if (accumulatedTask instanceof ReindentTask) {
          indentActions.add((ReindentTask) accumulatedTask);
        } else {
          freeFormatingActions.add(accumulatedTask);
        }

        accumulatedTask = currentTask;
        iterator.remove();
      } else if (accumulatedTask instanceof ReformatTask && currentTask instanceof ReindentTask) {
        // split accumulated reformat range into two
        if (accumulatedTask.getStartOffset() < currentTask.getStartOffset()) {
          final RangeMarker endOfRange =
              document.createRangeMarker(
                  accumulatedTask.getStartOffset(), currentTask.getStartOffset());
          // add heading reformat part
          rangesToProcess.add(new ReformatTask(endOfRange));
          // and manage heading whitespace because formatter does not edit it in previous action
          iterator = rangesToProcess.iterator();
          //noinspection StatementWithEmptyBody
          while (iterator.next().getRange() != currentTask.getRange()) ;
        }
        final RangeMarker rangeToProcess =
            document.createRangeMarker(currentTask.getEndOffset(), accumulatedTask.getEndOffset());
        freeFormatingActions.add(new ReformatWithHeadingWhitespaceTask(rangeToProcess));
        accumulatedTask = currentTask;
        iterator.remove();
      } else {
        if (!(accumulatedTask instanceof ReindentTask)) {
          iterator.remove();

          boolean withLeadingWhitespace =
              accumulatedTask instanceof ReformatWithHeadingWhitespaceTask;
          if (accumulatedTask instanceof ReformatTask
              && currentTask instanceof ReformatWithHeadingWhitespaceTask
              && accumulatedTask.getStartOffset() == currentTask.getStartOffset()) {
            withLeadingWhitespace = true;
          } else if (accumulatedTask instanceof ReformatWithHeadingWhitespaceTask
              && currentTask instanceof ReformatTask
              && accumulatedTask.getStartOffset() < currentTask.getStartOffset()) {
            withLeadingWhitespace = false;
          }
          int newStart = Math.min(accumulatedTask.getStartOffset(), currentTask.getStartOffset());
          int newEnd = Math.max(accumulatedTask.getEndOffset(), currentTask.getEndOffset());
          RangeMarker rangeMarker;

          if (accumulatedTask.getStartOffset() == newStart
              && accumulatedTask.getEndOffset() == newEnd) {
            rangeMarker = accumulatedTask.getRange();
          } else if (currentTask.getStartOffset() == newStart
              && currentTask.getEndOffset() == newEnd) {
            rangeMarker = currentTask.getRange();
          } else {
            rangeMarker = document.createRangeMarker(newStart, newEnd);
          }

          if (withLeadingWhitespace) {
            accumulatedTask = new ReformatWithHeadingWhitespaceTask(rangeMarker);
          } else {
            accumulatedTask = new ReformatTask(rangeMarker);
          }
        } else if (currentTask instanceof ReindentTask) {
          iterator.remove();
        } // TODO[ik]: need to be fixed to correctly process indent inside indent
      }
    }
    if (accumulatedTask != null) {
      if (accumulatedTask instanceof ReindentTask) {
        indentActions.add((ReindentTask) accumulatedTask);
      } else {
        freeFormatingActions.add(accumulatedTask);
      }
    }

    final List<PostponedAction> result = new ArrayList<PostponedAction>();
    Collections.reverse(freeFormatingActions);
    Collections.reverse(indentActions);

    if (!freeFormatingActions.isEmpty()) {
      FormatTextRanges ranges = new FormatTextRanges();
      for (PostprocessFormattingTask action : freeFormatingActions) {
        TextRange range = TextRange.create(action);
        ranges.add(range, action instanceof ReformatWithHeadingWhitespaceTask);
      }
      result.add(new ReformatRangesAction(ranges));
    }

    if (!indentActions.isEmpty()) {
      ReindentRangesAction reindentRangesAction = new ReindentRangesAction();
      for (ReindentTask action : indentActions) {
        reindentRangesAction.add(action.getRange(), action.getOldIndent());
      }
      result.add(reindentRangesAction);
    }

    return result;
  }
  protected String addTextRangeToHistory(
      TextRange textRange, final EditorEx consoleEditor, boolean preserveMarkup) {
    final Document history = myHistoryViewer.getDocument();
    final MarkupModel markupModel = DocumentMarkupModel.forDocument(history, myProject, true);
    if (myPrompt != null) {
      appendToHistoryDocument(history, myPrompt);
    }
    markupModel.addRangeHighlighter(
        history.getTextLength() - StringUtil.length(myPrompt),
        history.getTextLength(),
        HighlighterLayer.SYNTAX,
        ConsoleViewContentType.USER_INPUT.getAttributes(),
        HighlighterTargetArea.EXACT_RANGE);
    final int localStartOffset = textRange.getStartOffset();
    String text;
    EditorHighlighter highlighter;
    if (consoleEditor instanceof EditorWindow) {
      EditorWindow editorWindow = (EditorWindow) consoleEditor;
      EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
      PsiFile file = editorWindow.getInjectedFile();
      final VirtualFile virtualFile = file.getVirtualFile();
      assert virtualFile != null;
      highlighter = HighlighterFactory.createHighlighter(virtualFile, scheme, getProject());
      String fullText = InjectedLanguageUtil.getUnescapedText(file, null, null);
      highlighter.setText(fullText);
      text = textRange.substring(fullText);
    } else {
      text = consoleEditor.getDocument().getText(textRange);
      highlighter = consoleEditor.getHighlighter();
    }
    // offset can be changed after text trimming after insert due to buffer constraints
    appendToHistoryDocument(history, text);
    int offset = history.getTextLength() - text.length();

    final HighlighterIterator iterator = highlighter.createIterator(localStartOffset);
    final int localEndOffset = textRange.getEndOffset();
    while (!iterator.atEnd()) {
      final int itStart = iterator.getStart();
      if (itStart > localEndOffset) break;
      final int itEnd = iterator.getEnd();
      if (itEnd >= localStartOffset) {
        final int start = Math.max(itStart, localStartOffset) - localStartOffset + offset;
        final int end = Math.min(itEnd, localEndOffset) - localStartOffset + offset;
        markupModel.addRangeHighlighter(
            start,
            end,
            HighlighterLayer.SYNTAX,
            iterator.getTextAttributes(),
            HighlighterTargetArea.EXACT_RANGE);
      }
      iterator.advance();
    }
    if (preserveMarkup) {
      duplicateHighlighters(
          markupModel,
          DocumentMarkupModel.forDocument(consoleEditor.getDocument(), myProject, true),
          offset,
          textRange);
      duplicateHighlighters(markupModel, consoleEditor.getMarkupModel(), offset, textRange);
    }
    if (!text.endsWith("\n")) {
      appendToHistoryDocument(history, "\n");
    }
    return text;
  }
  private static Pair<Set<String>, Set<TextWithImports>> findReferencedVars(
      Set<String> visibleVars, SourcePosition position) {
    final int line = position.getLine();
    if (line < 0) {
      return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet());
    }
    final PsiFile positionFile = position.getFile();
    if (!positionFile.getLanguage().isKindOf(JavaLanguage.INSTANCE)) {
      return Pair.create(visibleVars, Collections.<TextWithImports>emptySet());
    }

    final VirtualFile vFile = positionFile.getVirtualFile();
    final Document doc =
        vFile != null ? FileDocumentManager.getInstance().getDocument(vFile) : null;
    if (doc == null || doc.getLineCount() == 0 || line > (doc.getLineCount() - 1)) {
      return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet());
    }

    final TextRange limit = calculateLimitRange(positionFile, doc, line);

    int startLine = Math.max(limit.getStartOffset(), line - 1);
    startLine = Math.min(startLine, limit.getEndOffset());
    while (startLine > limit.getStartOffset() && shouldSkipLine(positionFile, doc, startLine)) {
      startLine--;
    }
    final int startOffset = doc.getLineStartOffset(startLine);

    int endLine = Math.min(line + 2, limit.getEndOffset());
    while (endLine < limit.getEndOffset() && shouldSkipLine(positionFile, doc, endLine)) {
      endLine++;
    }
    final int endOffset = doc.getLineEndOffset(endLine);

    final TextRange lineRange = new TextRange(startOffset, endOffset);
    if (!lineRange.isEmpty()) {
      final int offset =
          CharArrayUtil.shiftForward(doc.getCharsSequence(), doc.getLineStartOffset(line), " \t");
      PsiElement element = positionFile.findElementAt(offset);
      if (element != null) {
        PsiMethod method = PsiTreeUtil.getNonStrictParentOfType(element, PsiMethod.class);
        if (method != null) {
          element = method;
        } else {
          PsiField field = PsiTreeUtil.getNonStrictParentOfType(element, PsiField.class);
          if (field != null) {
            element = field;
          } else {
            final PsiClassInitializer initializer =
                PsiTreeUtil.getNonStrictParentOfType(element, PsiClassInitializer.class);
            if (initializer != null) {
              element = initializer;
            }
          }
        }

        //noinspection unchecked
        if (element instanceof PsiCompiledElement) {
          return Pair.create(visibleVars, Collections.<TextWithImports>emptySet());
        } else {
          VariablesCollector collector =
              new VariablesCollector(visibleVars, adjustRange(element, lineRange));
          element.accept(collector);
          return Pair.create(collector.getVars(), collector.getExpressions());
        }
      }
    }
    return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet());
  }