| /*=============================================================================# |
| # Copyright (c) 2009, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.internal.ltk.ui.refactoring; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.text.edits.CopyTargetEdit; |
| import org.eclipse.text.edits.DeleteEdit; |
| import org.eclipse.text.edits.InsertEdit; |
| import org.eclipse.text.edits.MoveSourceEdit; |
| import org.eclipse.text.edits.MoveTargetEdit; |
| import org.eclipse.text.edits.RangeMarker; |
| import org.eclipse.text.edits.ReplaceEdit; |
| import org.eclipse.text.edits.TextEdit; |
| import org.eclipse.text.edits.TextEditVisitor; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| |
| |
| /** |
| * Class to annotate edits made by a quick fix/assist to be shown via the quick fix pop-up preview. |
| * E.g. the added changes are shown in bold. |
| */ |
| @NonNullByDefault |
| public class TextEditAnnotator extends TextEditVisitor { |
| |
| |
| private int writtenToOffset= 0; |
| private int chunks; |
| |
| private final StringBuilder sb; |
| |
| private final IDocument previewDocument; |
| |
| |
| public TextEditAnnotator(final StringBuilder sb, final IDocument previewDoc) { |
| this.sb= sb; |
| this.previewDocument= previewDoc; |
| } |
| |
| |
| public void unchangedUntil(final int offset) { |
| if (offset > this.writtenToOffset) { |
| appendContent(this.previewDocument, this.writtenToOffset, offset, true); |
| this.writtenToOffset= offset; |
| } |
| } |
| |
| |
| @Override |
| public boolean visit(final MoveTargetEdit edit) { |
| return true; //rangeAdded(edit); |
| } |
| |
| @Override |
| public boolean visit(final CopyTargetEdit edit) { |
| return true; //return rangeAdded(edit); |
| } |
| |
| @Override |
| public boolean visit(final InsertEdit edit) { |
| return rangeAdded(edit); |
| } |
| |
| @Override |
| public boolean visit(final ReplaceEdit edit) { |
| if (edit.getLength() > 0) { |
| return rangeAdded(edit); |
| } |
| return rangeRemoved(edit); |
| } |
| |
| @Override |
| public boolean visit(final MoveSourceEdit edit) { |
| return rangeRemoved(edit); |
| } |
| |
| @Override |
| public boolean visit(final DeleteEdit edit) { |
| return rangeRemoved(edit); |
| } |
| |
| @Override |
| public boolean visit(final RangeMarker edit) { |
| unchangedUntil(edit.getOffset()); |
| return true; |
| } |
| |
| |
| protected boolean rangeRemoved(final TextEdit edit) { |
| unchangedUntil(edit.getOffset()); |
| return false; |
| } |
| |
| protected boolean rangeAdded(final TextEdit edit) { |
| return annotateEdit(edit, "<b>", "</b>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| protected boolean annotateEdit(final TextEdit edit, final String startTag, final String endTag) { |
| unchangedUntil(edit.getOffset()); |
| this.sb.append(startTag); |
| appendContent(this.previewDocument, edit.getOffset(), edit.getExclusiveEnd(), false); |
| this.sb.append(endTag); |
| this.writtenToOffset= edit.getExclusiveEnd(); |
| return false; |
| } |
| |
| private void appendContent(final IDocument text, final int startOffset, final int endOffset, |
| final boolean surroundLinesOnly) { |
| final int surroundLines= 1; |
| try { |
| final boolean firstChunk= (this.chunks++ == 0); |
| int startLine= text.getLineOfOffset( |
| (surroundLinesOnly && firstChunk) ? endOffset : startOffset ); |
| final int endLine= text.getLineOfOffset(endOffset); |
| |
| boolean dotsAdded= false; |
| if (surroundLinesOnly && startOffset == 0) { // no surround lines for the top no-change range |
| startLine= endLine; |
| dotsAdded= true; |
| } |
| |
| for (int i= startLine; i <= endLine; i++) { |
| if (surroundLinesOnly) { |
| if ((i - startLine > surroundLines) && (endLine - i > surroundLines)) { |
| if (!dotsAdded) { |
| this.sb.append("...<br/>"); //$NON-NLS-1$ |
| dotsAdded= true; |
| } |
| else if (endOffset == text.getLength()) { |
| return; // no surround lines for the bottom no-change range |
| } |
| continue; |
| } |
| } |
| |
| final IRegion lineInfo= text.getLineInformation(i); |
| final int start= lineInfo.getOffset(); |
| final int end= start + lineInfo.getLength(); |
| |
| final int from= Math.max(start, startOffset); |
| final int to= Math.min(end, endOffset); |
| final String content= text.get(from, to - from); |
| if (surroundLinesOnly && (from == start) && content.isEmpty()) { |
| continue; // ignore empty lines except when range started in the middle of a line |
| } |
| for (int k= 0; k < content.length(); k++) { |
| final char ch= content.charAt(k); |
| switch (ch) { |
| case '<': |
| this.sb.append("<"); //$NON-NLS-1$ |
| break; |
| case '>': |
| this.sb.append(">"); //$NON-NLS-1$ |
| break; |
| default: |
| this.sb.append(ch); |
| break; |
| } |
| } |
| if (to == end && to != endOffset) { // new line when at the end of the line, and not end of range |
| this.sb.append("<br/>"); //$NON-NLS-1$ |
| } |
| } |
| } |
| catch (final BadLocationException e) {} |
| } |
| |
| } |