@Override
  @Nullable
  public PsiElement restoreElement() {
    long typeAndId = myStubElementTypeAndId;
    int stubId = (int) typeAndId;
    if (stubId != -1) {
      PsiFile file = restoreFile();
      if (!(file instanceof PsiFileWithStubSupport)) return null;
      short index = (short) (typeAndId >> 32);
      IStubElementType stubElementType = (IStubElementType) IElementType.find(index);
      return PsiAnchor.restoreFromStubIndex(
          (PsiFileWithStubSupport) file, stubId, stubElementType, false);
    }

    Segment psiRange = getPsiRange();
    if (psiRange == null) return null;

    PsiFile file = restoreFile();
    if (file == null) return null;
    PsiElement anchor = file.findElementAt(psiRange.getStartOffset());
    if (anchor == null) return null;

    TextRange range = anchor.getTextRange();
    if (range.getStartOffset() != psiRange.getStartOffset()
        || range.getEndOffset() != psiRange.getEndOffset()) return null;

    for (SmartPointerAnchorProvider provider : SmartPointerAnchorProvider.EP_NAME.getExtensions()) {
      final PsiElement element = provider.restoreElement(anchor);
      if (element != null) return element;
    }
    return null;
  }
 public int hostToInjectedUnescaped(int hostOffset) {
   synchronized (myLock) {
     Segment hostRangeMarker = myShreds.get(0).getHostRangeMarker();
     if (hostRangeMarker == null || hostOffset < hostRangeMarker.getStartOffset())
       return myShreds.get(0).getPrefix().length();
     int offset = 0;
     for (int i = 0; i < myShreds.size(); i++) {
       offset += myShreds.get(i).getPrefix().length();
       Segment currentRange = myShreds.get(i).getHostRangeMarker();
       if (currentRange == null) continue;
       Segment nextRange =
           i == myShreds.size() - 1 ? null : myShreds.get(i + 1).getHostRangeMarker();
       if (nextRange == null || hostOffset < nextRange.getStartOffset()) {
         if (hostOffset >= currentRange.getEndOffset()) {
           offset += myShreds.get(i).getRange().getLength();
         } else {
           // todo use escaper to convert host-range delta into injected space
           offset += hostOffset - currentRange.getStartOffset();
         }
         return offset;
       }
       offset += myShreds.get(i).getRange().getLength();
       offset += myShreds.get(i).getSuffix().length();
     }
     return getTextLength() - myShreds.get(myShreds.size() - 1).getSuffix().length();
   }
 }
 private int injectedToHost(int offset, boolean preferLeftFragment) {
   synchronized (myLock) {
     if (offset < myShreds.get(0).getPrefix().length()) {
       Segment hostRangeMarker = myShreds.get(0).getHostRangeMarker();
       return hostRangeMarker == null ? 0 : hostRangeMarker.getStartOffset();
     }
     int prevEnd = 0;
     for (int i = 0; i < myShreds.size(); i++) {
       Segment currentRange = myShreds.get(i).getHostRangeMarker();
       if (currentRange == null) continue;
       offset -= myShreds.get(i).getPrefix().length();
       int length = currentRange.getEndOffset() - currentRange.getStartOffset();
       if (offset < 0) {
         return preferLeftFragment ? prevEnd : currentRange.getStartOffset() - 1;
       }
       if (offset == 0) {
         return preferLeftFragment && i != 0 ? prevEnd : currentRange.getStartOffset();
       }
       if (offset < length || offset == length && preferLeftFragment) {
         return currentRange.getStartOffset() + offset;
       }
       offset -= length;
       offset -= myShreds.get(i).getSuffix().length();
       prevEnd = currentRange.getEndOffset();
     }
     Segment hostRangeMarker = myShreds.get(myShreds.size() - 1).getHostRangeMarker();
     return hostRangeMarker == null ? 0 : hostRangeMarker.getEndOffset();
   }
 }
  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();
    }
  }
  @Nullable("null means we were unable to calculate")
  LogicalPosition hostToInjectedInVirtualSpace(@NotNull LogicalPosition hPos) {
    // beware the virtual space
    int hLineStartOffset =
        hPos.line >= myDelegate.getLineCount()
            ? myDelegate.getTextLength()
            : myDelegate.getLineStartOffset(hPos.line);
    int iLineStartOffset = hostToInjected(hLineStartOffset);
    int iLine = getLineNumber(iLineStartOffset);

    synchronized (myLock) {
      for (int i = myShreds.size() - 1; i >= 0; i--) {
        PsiLanguageInjectionHost.Shred shred = myShreds.get(i);
        if (!shred.isValid()) continue;
        Segment hostRangeMarker = shred.getHostRangeMarker();
        if (hostRangeMarker == null) continue;
        int hShredEndOffset = hostRangeMarker.getEndOffset();
        int hShredStartOffset = hostRangeMarker.getStartOffset();

        int hShredStartLine = myDelegate.getLineNumber(hShredStartOffset);
        int hShredEndLine = myDelegate.getLineNumber(hShredEndOffset);

        if (hShredStartLine <= hPos.line && hPos.line <= hShredEndLine) {
          int hColumnOfShredEnd = hShredEndOffset - hLineStartOffset;
          int iColumnOfShredEnd = hostToInjected(hShredEndOffset) - iLineStartOffset;
          int iColumn = iColumnOfShredEnd + hPos.column - hColumnOfShredEnd;
          return new LogicalPosition(iLine, iColumn);
        }
      }
    }

    return null;
  }
 @Override
 @Deprecated
 @Nullable
 public TextRange intersectWithEditable(@NotNull TextRange rangeToEdit) {
   int startOffset = -1;
   int endOffset = -1;
   synchronized (myLock) {
     int offset = 0;
     for (PsiLanguageInjectionHost.Shred shred : myShreds) {
       Segment hostRange = shred.getHostRangeMarker();
       if (hostRange == null) continue;
       offset += shred.getPrefix().length();
       int length = hostRange.getEndOffset() - hostRange.getStartOffset();
       TextRange intersection =
           new ProperTextRange(offset, offset + length).intersection(rangeToEdit);
       if (intersection != null) {
         if (startOffset == -1) {
           startOffset = intersection.getStartOffset();
         }
         endOffset = intersection.getEndOffset();
       }
       offset += length;
       offset += shred.getSuffix().length();
     }
   }
   if (startOffset == -1) return null;
   return new ProperTextRange(startOffset, endOffset);
 }
  @Override
  public boolean pointsToTheSameElementAs(@NotNull final SmartPointerElementInfo other) {
    if (other instanceof SelfElementInfo) {
      SelfElementInfo otherInfo = (SelfElementInfo) other;
      if (!getVirtualFile().equals(other.getVirtualFile()) || myType != otherInfo.myType)
        return false;

      Segment range1 = getPsiRange();
      Segment range2 = otherInfo.getPsiRange();
      return range1 != null
          && range2 != null
          && range1.getStartOffset() == range2.getStartOffset()
          && range1.getEndOffset() == range2.getEndOffset();
    }
    return areRestoredElementsEqual(other);
  }
 private void initGuardedBlocks(Place shreds) {
   int origOffset = -1;
   int curOffset = 0;
   for (PsiLanguageInjectionHost.Shred shred : shreds) {
     Segment hostRangeMarker = shred.getHostRangeMarker();
     int start = shred.getRange().getStartOffset() + shred.getPrefix().length();
     int end = shred.getRange().getEndOffset() - shred.getSuffix().length();
     if (curOffset < start) {
       RangeMarker guard = myNewDocument.createGuardedBlock(curOffset, start);
       if (curOffset == 0 && shred == shreds.get(0)) guard.setGreedyToLeft(true);
       String padding =
           origOffset < 0
               ? ""
               : myOrigDocument.getText().substring(origOffset, hostRangeMarker.getStartOffset());
       guard.putUserData(REPLACEMENT_KEY, fixQuotes(padding));
     }
     curOffset = end;
     origOffset = hostRangeMarker.getEndOffset();
   }
   if (curOffset < myNewDocument.getTextLength()) {
     RangeMarker guard =
         myNewDocument.createGuardedBlock(curOffset, myNewDocument.getTextLength());
     guard.setGreedyToRight(true);
     guard.putUserData(REPLACEMENT_KEY, "");
   }
 }
 @Override
 public void selectInEditor() {
   if (!isValid()) return;
   Editor editor = openTextEditor(true);
   Segment marker = getFirstSegment();
   editor.getSelectionModel().setSelection(marker.getStartOffset(), marker.getEndOffset());
 }
  @Override
  public int getLineStartOffset(int line) {
    LOG.assertTrue(line >= 0, line);
    if (line == 0) return 0;
    String hostText = myDelegate.getText();

    int[] pos = new int[2]; // pos[0] = curLine; pos[1] == offset;
    synchronized (myLock) {
      for (PsiLanguageInjectionHost.Shred shred : myShreds) {
        Segment hostRange = shred.getHostRangeMarker();
        if (hostRange == null) continue;

        int found = countNewLinesIn(shred.getPrefix(), pos, line);
        if (found != -1) return found;

        CharSequence text =
            hostText.subSequence(hostRange.getStartOffset(), hostRange.getEndOffset());
        found = countNewLinesIn(text, pos, line);
        if (found != -1) return found;

        found = countNewLinesIn(shred.getSuffix(), pos, line);
        if (found != -1) return found;
      }
    }

    return pos[1];
  }
  @Override
  public void highlightInEditor() {
    if (!isValid()) return;

    Segment marker = getFirstSegment();
    SelectInEditorManager.getInstance(getProject())
        .selectInEditor(getFile(), marker.getStartOffset(), marker.getEndOffset(), false, false);
  }
 void setRange(@Nullable Segment range) {
   if (range != null) {
     myStartOffset = range.getStartOffset();
     myEndOffset = range.getEndOffset();
   } else {
     myStartOffset = -1;
     myEndOffset = -1;
   }
 }
  @Override
  public PsiElement restoreElement() {
    Segment segment = getPsiRange();
    if (segment == null) return null;

    PsiFile file = restoreFile();
    if (file == null || !file.isValid()) return null;

    return findElementInside(file, segment.getStartOffset(), segment.getEndOffset(), myType);
  }
  @Override
  @Nullable
  public FileEditorLocation getLocation() {
    VirtualFile virtualFile = getFile();
    if (virtualFile == null) return null;
    FileEditor editor = FileEditorManager.getInstance(getProject()).getSelectedEditor(virtualFile);
    if (!(editor instanceof TextEditor)) return null;

    Segment segment = getUsageInfo().getSegment();
    if (segment == null) return null;
    return new TextEditorLocation(segment.getStartOffset(), (TextEditor) editor);
  }
 private String getRangeText(@NotNull String hostText, int hostNum) {
   synchronized (myLock) {
     PsiLanguageInjectionHost.Shred shred = myShreds.get(hostNum);
     Segment hostRangeMarker = shred.getHostRangeMarker();
     return shred.getPrefix()
         + (hostRangeMarker == null
             ? ""
             : hostText.substring(
                 hostRangeMarker.getStartOffset(), hostRangeMarker.getEndOffset()))
         + shred.getSuffix();
   }
 }
  @Override
  public boolean areRangesEqual(@NotNull DocumentWindow otherd) {
    DocumentWindowImpl window = (DocumentWindowImpl) otherd;
    Place shreds = getShreds();
    Place otherShreds = window.getShreds();
    if (shreds.size() != otherShreds.size()) return false;
    for (int i = 0; i < shreds.size(); i++) {
      PsiLanguageInjectionHost.Shred shred = shreds.get(i);
      PsiLanguageInjectionHost.Shred otherShred = otherShreds.get(i);
      if (!shred.getPrefix().equals(otherShred.getPrefix())) return false;
      if (!shred.getSuffix().equals(otherShred.getSuffix())) return false;

      Segment hostRange = shred.getHostRangeMarker();
      Segment other = otherShred.getHostRangeMarker();
      if (hostRange == null
          || other == null
          || hostRange.getStartOffset() != other.getStartOffset()) return false;
      if (hostRange.getEndOffset() != other.getEndOffset()) return false;
    }
    return true;
  }
  @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;
  }
  @Override
  public Element export(
      @NotNull RefEntity refEntity, @NotNull final Element element, final int actualLine) {
    refEntity = getRefinedElement(refEntity);

    Element problem = new Element("problem");

    if (refEntity instanceof RefElement) {
      final RefElement refElement = (RefElement) refEntity;
      final SmartPsiElementPointer pointer = refElement.getPointer();
      PsiFile psiFile = pointer.getContainingFile();
      if (psiFile == null) return null;

      Element fileElement = new Element("file");
      Element lineElement = new Element("line");
      final VirtualFile virtualFile = psiFile.getVirtualFile();
      LOG.assertTrue(virtualFile != null);
      fileElement.addContent(virtualFile.getUrl());

      if (actualLine == -1) {
        final Document document =
            PsiDocumentManager.getInstance(pointer.getProject()).getDocument(psiFile);
        LOG.assertTrue(document != null);
        final Segment range = pointer.getRange();
        lineElement.addContent(
            String.valueOf(
                range != null ? document.getLineNumber(range.getStartOffset()) + 1 : -1));
      } else {
        lineElement.addContent(String.valueOf(actualLine));
      }

      problem.addContent(fileElement);
      problem.addContent(lineElement);

      appendModule(problem, refElement.getModule());
    } else if (refEntity instanceof RefModule) {
      final RefModule refModule = (RefModule) refEntity;
      final VirtualFile moduleFile = refModule.getModule().getModuleFile();
      final Element fileElement = new Element("file");
      fileElement.addContent(moduleFile != null ? moduleFile.getUrl() : refEntity.getName());
      problem.addContent(fileElement);
      appendModule(problem, refModule);
    }

    for (RefManagerExtension extension : myExtensions.values()) {
      extension.export(refEntity, problem);
    }

    new SmartRefElementPointerImpl(refEntity, true).writeExternal(problem);
    element.addContent(problem);
    return problem;
  }
  @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();
    }
  }
 // by start offset
 @Override
 public int compareTo(final UsageInfo2UsageAdapter o) {
   VirtualFile containingFile = getFile();
   int shift1 = 0;
   if (containingFile instanceof VirtualFileWindow) {
     shift1 = ((VirtualFileWindow) containingFile).getDocumentWindow().injectedToHost(0);
     containingFile = ((VirtualFileWindow) containingFile).getDelegate();
   }
   VirtualFile oContainingFile = o.getFile();
   int shift2 = 0;
   if (oContainingFile instanceof VirtualFileWindow) {
     shift2 = ((VirtualFileWindow) oContainingFile).getDocumentWindow().injectedToHost(0);
     oContainingFile = ((VirtualFileWindow) oContainingFile).getDelegate();
   }
   if (containingFile == null && oContainingFile == null
       || !Comparing.equal(containingFile, oContainingFile)) {
     return 0;
   }
   Segment s1 = getFirstSegment();
   Segment s2 = o.getFirstSegment();
   if (s1 == null || s2 == null) return 0;
   return s1.getStartOffset() + shift1 - s2.getStartOffset() - shift2;
 }
 @Override
 public int hostToInjected(int hostOffset) {
   synchronized (myLock) {
     Segment hostRangeMarker = myShreds.get(0).getHostRangeMarker();
     if (hostRangeMarker == null || hostOffset < hostRangeMarker.getStartOffset())
       return myShreds.get(0).getPrefix().length();
     int offset = 0;
     for (int i = 0; i < myShreds.size(); i++) {
       offset += myShreds.get(i).getPrefix().length();
       Segment currentRange = myShreds.get(i).getHostRangeMarker();
       if (currentRange == null) continue;
       Segment nextRange =
           i == myShreds.size() - 1 ? null : myShreds.get(i + 1).getHostRangeMarker();
       if (nextRange == null || hostOffset < nextRange.getStartOffset()) {
         if (hostOffset >= currentRange.getEndOffset()) hostOffset = currentRange.getEndOffset();
         return offset + hostOffset - currentRange.getStartOffset();
       }
       offset += currentRange.getEndOffset() - currentRange.getStartOffset();
       offset += myShreds.get(i).getSuffix().length();
     }
     return getTextLength() - myShreds.get(myShreds.size() - 1).getSuffix().length();
   }
 }
 @Override
 public int getTextLength() {
   int length = 0;
   synchronized (myLock) {
     for (PsiLanguageInjectionHost.Shred shred : myShreds) {
       Segment hostRange = shred.getHostRangeMarker();
       if (hostRange == null) continue;
       length += shred.getPrefix().length();
       length += hostRange.getEndOffset() - hostRange.getStartOffset();
       length += shred.getSuffix().length();
     }
   }
   return length;
 }
 @NotNull
 private String calcText() {
   StringBuilder text = new StringBuilder();
   CharSequence hostText = myDelegate.getCharsSequence();
   synchronized (myLock) {
     for (PsiLanguageInjectionHost.Shred shred : myShreds) {
       Segment hostRange = shred.getHostRangeMarker();
       if (hostRange != null) {
         text.append(shred.getPrefix());
         text.append(hostText, hostRange.getStartOffset(), hostRange.getEndOffset());
         text.append(shred.getSuffix());
       }
     }
   }
   return text.toString();
 }
  @NotNull
  @Override
  public List<TextRange> getNonEditableFragments(@NotNull DocumentWindow window) {
    List<TextRange> result = ContainerUtil.newArrayList();
    int offset = 0;
    for (PsiLanguageInjectionHost.Shred shred : ((DocumentWindowImpl) window).getShreds()) {
      Segment hostRange = shred.getHostRangeMarker();
      if (hostRange == null) continue;

      offset = appendRange(result, offset, shred.getPrefix().length());
      offset += hostRange.getEndOffset() - hostRange.getStartOffset();
      offset = appendRange(result, offset, shred.getSuffix().length());
    }

    return result;
  }
  public static void renameNonCodeUsages(
      @NotNull Project project, @NotNull NonCodeUsageInfo[] usages) {
    PsiDocumentManager.getInstance(project).commitAllDocuments();
    Map<Document, List<UsageOffset>> docsToOffsetsMap = new HashMap<Document, List<UsageOffset>>();
    final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
    for (NonCodeUsageInfo usage : usages) {
      PsiElement element = usage.getElement();

      if (element == null) continue;
      element = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(element, true);
      if (element == null) continue;

      final ProperTextRange rangeInElement = usage.getRangeInElement();
      if (rangeInElement == null) continue;

      final PsiFile containingFile = element.getContainingFile();
      final Document document = psiDocumentManager.getDocument(containingFile);

      final Segment segment = usage.getSegment();
      LOG.assertTrue(segment != null);
      int fileOffset = segment.getStartOffset();

      List<UsageOffset> list = docsToOffsetsMap.get(document);
      if (list == null) {
        list = new ArrayList<UsageOffset>();
        docsToOffsetsMap.put(document, list);
      }

      list.add(new UsageOffset(fileOffset, fileOffset + rangeInElement.getLength(), usage.newText));
    }

    for (Document document : docsToOffsetsMap.keySet()) {
      List<UsageOffset> list = docsToOffsetsMap.get(document);
      LOG.assertTrue(list != null, document);
      UsageOffset[] offsets = list.toArray(new UsageOffset[list.size()]);
      Arrays.sort(offsets);

      for (int i = offsets.length - 1; i >= 0; i--) {
        UsageOffset usageOffset = offsets[i];
        document.replaceString(usageOffset.startOffset, usageOffset.endOffset, usageOffset.newText);
      }
      PsiDocumentManager.getInstance(project).commitDocument(document);
    }
    PsiDocumentManager.getInstance(project).commitAllDocuments();
  }
  @Override
  public int injectedToHostLine(int line) {
    if (line < myPrefixLineCount) {
      synchronized (myLock) {
        Segment hostRangeMarker = myShreds.get(0).getHostRangeMarker();
        return hostRangeMarker == null
            ? 0
            : myDelegate.getLineNumber(hostRangeMarker.getStartOffset());
      }
    }
    int lineCount = getLineCount();
    if (line > lineCount - mySuffixLineCount) {
      return lineCount;
    }
    int offset = getLineStartOffset(line);
    int hostOffset = injectedToHost(offset);

    return myDelegate.getLineNumber(hostOffset);
  }
 @Override
 public void setText(@NotNull CharSequence text) {
   synchronized (myLock) {
     LOG.assertTrue(text.toString().startsWith(myShreds.get(0).getPrefix()));
     LOG.assertTrue(text.toString().endsWith(myShreds.get(myShreds.size() - 1).getSuffix()));
     if (isOneLine()) {
       text = StringUtil.replace(text.toString(), "\n", "");
     }
     String[] changes = calculateMinEditSequence(text.toString());
     assert changes.length == myShreds.size();
     for (int i = 0; i < changes.length; i++) {
       String change = changes[i];
       if (change != null) {
         Segment hostRange = myShreds.get(i).getHostRangeMarker();
         if (hostRange == null) continue;
         myDelegate.replaceString(hostRange.getStartOffset(), hostRange.getEndOffset(), change);
       }
     }
   }
 }
 public int hashCode() {
   synchronized (myLock) {
     Segment hostRangeMarker = myShreds.get(0).getHostRangeMarker();
     return hostRangeMarker == null ? -1 : hostRangeMarker.getStartOffset();
   }
 }