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();
   }
 }
 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();
   }
 }
 @Override
 public void selectInEditor() {
   if (!isValid()) return;
   Editor editor = openTextEditor(true);
   Segment marker = getFirstSegment();
   editor.getSelectionModel().setSelection(marker.getStartOffset(), marker.getEndOffset());
 }
  @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);
  }
 @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 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];
  }
 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
  @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;
  }
  @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
  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);
  }
 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 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;
 }
 @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();
   }
 }
  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();
    }
  }
  @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;
  }
 @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();
 }
 @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);
       }
     }
   }
 }
  @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();
    }
  }