blob: 5555e84c12406ddd8380872c9d4c624d9879409a [file] [log] [blame]
/*=============================================================================#
# 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("&lt;"); //$NON-NLS-1$
break;
case '>':
this.sb.append("&gt;"); //$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) {}
}
}