package org.eclipse.xtext.formatting2.internal; import static java.lang.String.*; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.eclipse.xtext.formatting2.AbstractFormatter2; import org.eclipse.xtext.formatting2.FormatterPreferenceKeys; import org.eclipse.xtext.formatting2.FormatterRequest; import org.eclipse.xtext.formatting2.IFormattableDocument; import org.eclipse.xtext.formatting2.ITextReplacer; import org.eclipse.xtext.formatting2.ITextReplacerContext; import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion; import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart; import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement; import org.eclipse.xtext.formatting2.regionaccess.ITextSegment; import org.eclipse.xtext.preferences.ITypedPreferenceValues; import org.eclipse.xtext.util.ITextRegion; import org.eclipse.xtext.util.Tuples; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; public class TextReplacerContext implements ITextReplacerContext { private boolean autowrap = false; private Integer canAutowrap = null; private final IFormattableDocument document; private final int indentation; private boolean nextReplacerIsChild = false; private final ITextReplacerContext previous; private TextSegmentSet<ITextReplacement> replacements = null; private final ITextReplacer replacer; public TextReplacerContext(IFormattableDocument document) { this(document, null, 0, null); } protected TextReplacerContext(IFormattableDocument document, ITextReplacerContext previous, int indentation, ITextReplacer replacer) { super(); this.document = document; this.indentation = indentation; this.previous = previous; this.replacer = replacer; this.replacements = createTextReplacementsSet(); } @Override public Integer canAutowrap() { return canAutowrap; } protected TextSegmentSet<ITextReplacement> createTextReplacementsSet() { return new ArrayListTextSegmentSet<ITextReplacement>(Functions.<ITextReplacement>identity(), new Function<ITextReplacement, String>() { @Override public String apply(ITextReplacement input) { return input.getReplacementText(); } }, getDocument().getRequest().isEnableDebugTracing()); } @Override public IFormattableDocument getDocument() { return document; } @Override public AbstractFormatter2 getFormatter() { return document.getFormatter(); } @Override public int getIndentation() { return this.indentation; } @Override public String getIndentationString() { return getIndentationString(getIndentation()); } @Override public String getIndentationString(int indentationLevel) { AbstractFormatter2 formatter = document.getFormatter(); return Strings.repeat(formatter.getPreference(FormatterPreferenceKeys.indentation), indentationLevel); } @Override public int getLeadingCharsInLineCount() { ITextRegionAccess access = getDocument().getRequest().getTextRegionAccess(); int lastOffset = replacer.getRegion().getOffset(); ITextReplacerContext current = this; int count = 0; while (current != null) { Iterator<ITextReplacement> localReplacements = current.getLocalReplacementsReverse().iterator(); while (localReplacements.hasNext()) { ITextReplacement rep = localReplacements.next(); int endOffset = rep.getEndOffset(); if (endOffset > lastOffset) { // System.out.println("error"); continue; } String between = access.textForOffset(endOffset, lastOffset - endOffset); int idx = between.lastIndexOf('\n'); if (idx >= 0) return count + logicalLength(between.substring(idx + 1)); count += logicalLength(between); String text = rep.getReplacementText(); int idx2 = text.lastIndexOf('\n'); if (idx2 >= 0) return count + logicalLength(text.substring(idx2 + 1)); count += logicalLength(text); lastOffset = rep.getOffset(); } current = current.getPreviousContext(); } String rest = access.textForOffset(0, lastOffset); int idx = rest.lastIndexOf('\n'); if (idx >= 0) return count + logicalLength(rest.substring(idx + 1)); count += lastOffset; return count; } @Override public Iterable<ITextReplacement> getLocalReplacements() { if (replacements != null) return replacements; else return Collections.<ITextReplacement>emptyList(); } @Override public Iterable<ITextReplacement> getLocalReplacementsReverse() { if (replacements != null) return replacements.reverseIterable(); else return Collections.<ITextReplacement>emptyList(); } @Override public String getNewLinesString(int count) { return Strings.repeat(document.getFormatter().getPreference(FormatterPreferenceKeys.lineSeparator), count); } @Override public ITextReplacerContext getPreviousContext() { return previous; } @Override public List<ITextReplacement> getReplacementsUntil(ITextReplacerContext first) { ITextReplacerContext current = this; List<Iterable<ITextReplacement>> reversedReplacements = Lists.newArrayList(); while (current != null) { Iterable<ITextReplacement> localReplacements = current.getLocalReplacements(); if (!Iterables.isEmpty(localReplacements)) reversedReplacements.add(localReplacements); if (current == first) break; current = current.getPreviousContext(); } Collections.reverse(reversedReplacements); List<ITextReplacement> flattenedReplacements = new TextReplacementList<ITextReplacement>(); for (Iterable<ITextReplacement> chunk : reversedReplacements) Iterables.addAll(flattenedReplacements, chunk); return flattenedReplacements; } @Override public ITextReplacer getReplacer() { return replacer; } @Override public boolean isAutowrap() { return autowrap; } protected boolean isInRequestedRange(ITextReplacement repl) { Collection<ITextRegion> regions = document.getRequest().getRegions(); if (regions.isEmpty()) return true; for (org.eclipse.xtext.util.ITextRegion region : regions) if (region.getOffset() <= repl.getOffset() && region.getOffset() + region.getLength() >= repl.getEndOffset()) return true; return false; } @Override public boolean isInsideFormattedRegion() { return true; // TODO: implement } protected int logicalLength(String text) { ITypedPreferenceValues preferences = getDocument().getRequest().getPreferences(); String indentation = preferences.getPreference(FormatterPreferenceKeys.indentation); if (!"\t".equals(indentation)) return text.length(); @SuppressWarnings("deprecation") int tabWidth = preferences.getPreference(FormatterPreferenceKeys.indentationLength); if (tabWidth < 0) { tabWidth = preferences.getPreference(FormatterPreferenceKeys.tabWidth); } int length = 0; for (int i = 0; i < text.length(); i++) if (text.charAt(i) == '\t') length += tabWidth; else length++; return length; } @Override public void addReplacement(ITextReplacement replacement) { Preconditions.checkNotNull(replacer); ITextSegment replacerRegion = replacer.getRegion(); FormatterRequest request = document.getRequest(); if (!replacerRegion.contains(replacement)) { String frameTitle = replacer.getClass().getSimpleName(); ITextSegment frameRegion = replacer.getRegion(); String replacerTitle = replacement.getReplacementText(); RegionsOutsideFrameException exception = new RegionsOutsideFrameException(frameTitle, frameRegion, Tuples.create(replacerTitle, (ITextSegment) replacement)); request.getExceptionHandler().accept(exception); return; } if (!isInRequestedRange(replacement)) { return; } if (!isInUndefinedRegion(replacement)) { if (request.isFormatUndefinedHiddenRegionsOnly()) { return; } if (!request.allowIdentityEdits() && isIdentityEdit(replacement)) { return; } } try { replacements.add(replacement); } catch (ConflictingRegionsException e) { request.getExceptionHandler().accept(e); } } protected boolean isInUndefinedRegion(ITextReplacement repl) { ITextSegment replacerRegion = replacer.getRegion(); IHiddenRegion hidden = null; if (replacerRegion instanceof IHiddenRegionPart) { hidden = ((IHiddenRegionPart) replacerRegion).getHiddenRegion(); } else if (replacerRegion instanceof IHiddenRegion) { hidden = (IHiddenRegion) replacerRegion; } return hidden == null || hidden.isUndefined(); } protected boolean isIdentityEdit(ITextReplacement replacement) { return replacement.getText().equals(replacement.getReplacementText()); } @Override public void setAutowrap(boolean value) { this.autowrap = value; this.replacements = createTextReplacementsSet(); } @Override public void setCanAutowrap(Integer value) { this.canAutowrap = value; } @Override public void setNextReplacerIsChild() { this.nextReplacerIsChild = true; } @Override public String toString() { TextReplacerContext current = this; List<String> lines = Lists.newArrayList(); for (int i = 0; current != null; i++) { if (i > 15) { lines.add("(...)"); break; } lines.add(current.toStringLocal()); current = (TextReplacerContext) current.getPreviousContext(); } return Joiner.on('\n').join(Lists.reverse(lines)); } protected String toStringLocal() { List<String> items = Lists.newArrayList(); if (autowrap) items.add("autowrap"); if (canAutowrap != null && canAutowrap >= 0) items.add("canAutowrap"); if (replacer != null) { ITextSegment region = replacer.getRegion(); items.add(format("replacer=[%d-%d-%s|%s]", region.getOffset(), region.getLength(), replacer.getClass().getSimpleName(), replacer.toString())); } if (replacements != null) for (ITextReplacement r : replacements) { String fmt = "replacement=[%d-%d|%s]"; items.add(format(fmt, r.getOffset(), r.getLength(), r.getReplacementText())); } return Joiner.on("; ").join(items); } @Override public ITextReplacerContext withDocument(IFormattableDocument document) { TextReplacerContext context = new TextReplacerContext(document, this, indentation, null); if (this.nextReplacerIsChild) context.setNextReplacerIsChild(); return context; } @Override public ITextReplacerContext withIndentation(int indentation) { return new TextReplacerContext(document, this, indentation, null); } @Override public ITextReplacerContext withReplacer(ITextReplacer replacer) { ITextReplacerContext current = this; while (current != null) { ITextReplacer lastReplacer = current.getReplacer(); if (lastReplacer != null) { if (nextReplacerIsChild) { Preconditions.checkArgument(lastReplacer.getRegion().contains(replacer.getRegion())); } else { Preconditions .checkArgument(lastReplacer.getRegion().getEndOffset() <= replacer.getRegion().getOffset()); } break; } current = current.getPreviousContext(); } return new TextReplacerContext(document, this, indentation, replacer); } protected ITextSegment getRegion(int index) { ITextReplacerContext current = this; while (current != null) { ITextReplacer replacer2 = current.getReplacer(); if (replacer2 != null) { if (index == 0) { return replacer2.getRegion(); } else index--; } current = current.getPreviousContext(); } return null; } @Override public boolean isWrapInRegion() { ITextRegionAccess access = getDocument().getRequest().getTextRegionAccess(); ITextSegment region = getReplacer().getRegion(); int lastOffset = region.getOffset(); for (ITextReplacement rep : this.getLocalReplacements()) { int endOffset = rep.getOffset(); String between = access.textForOffset(lastOffset, endOffset - lastOffset); if (between.contains("\n") || rep.getReplacementText().contains("\n")) { return true; } lastOffset = rep.getEndOffset(); } String rest = access.textForOffset(lastOffset, region.getEndOffset() - lastOffset); if (rest.contains("\n")) { return true; } return false; } @Override public boolean isWrapSincePrevious() { ITextRegionAccess access = getDocument().getRequest().getTextRegionAccess(); ITextSegment region = getRegion(0); ITextSegment previousRegion = getRegion(1); if (previousRegion != null) { int offset = previousRegion.getEndOffset(); String between = access.textForOffset(offset, region.getOffset() - offset); if (between.contains("\n")) { return true; } } return false; } }