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 insertString(final int offset, @NotNull CharSequence s) { synchronized (myLock) { LOG.assertTrue(offset >= myShreds.get(0).getPrefix().length(), myShreds.get(0).getPrefix()); LOG.assertTrue( offset <= getTextLength() - myShreds.get(myShreds.size() - 1).getSuffix().length(), myShreds.get(myShreds.size() - 1).getSuffix()); } if (isOneLine()) { s = StringUtil.replace(s.toString(), "\n", ""); } myDelegate.insertString(injectedToHost(offset), s); }
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; }
public DocumentWindowImpl(@NotNull DocumentEx delegate, boolean oneLine, @NotNull Place shreds) { myDelegate = delegate; myOneLine = oneLine; synchronized (myLock) { myShreds = shreds; } myPrefixLineCount = Math.max(1, 1 + StringUtil.countNewLines(shreds.get(0).getPrefix())); mySuffixLineCount = Math.max(1, 1 + StringUtil.countNewLines(shreds.get(shreds.size() - 1).getSuffix())); }
@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 boolean isValid() { PsiLanguageInjectionHost.Shred[] shreds; synchronized (myLock) { shreds = myShreds.toArray(new PsiLanguageInjectionHost.Shred[myShreds.size()]); } // can grab PsiLock in SmartPsiPointer.restore() for (PsiLanguageInjectionHost.Shred shred : shreds) { if (!shred.isValid()) return false; } return true; }
@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 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 @NotNull public Segment[] getHostRanges() { synchronized (myLock) { List<Segment> markers = new ArrayList<Segment>(myShreds.size()); for (PsiLanguageInjectionHost.Shred shred : myShreds) { Segment hostMarker = shred.getHostRangeMarker(); if (hostMarker != null) { markers.add(hostMarker); } } return markers.isEmpty() ? Segment.EMPTY_ARRAY : markers.toArray(new Segment[markers.size()]); } }
// minimum sequence of text replacement operations for each host range // result[i] == null means no change // result[i] == "" means delete // result[i] == string means replace public String[] calculateMinEditSequence(String newText) { synchronized (myLock) { String[] result = new String[myShreds.size()]; String hostText = myDelegate.getText(); calculateMinEditSequence(hostText, newText, result, 0, result.length - 1); for (int i = 0; i < result.length; i++) { String change = result[i]; if (change == null) continue; String prefix = myShreds.get(i).getPrefix(); String suffix = myShreds.get(i).getSuffix(); assert change.startsWith(prefix) : change + "/" + prefix; assert change.endsWith(suffix) : change + "/" + suffix; result[i] = StringUtil.trimEnd(StringUtil.trimStart(change, prefix), suffix); } return result; } }
@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(); } }