Move syntax highlighting back to client

Mozilla Rhino is just slow running the JavaScript for prettify.
Its quicker inside of most modern browsers, especially ones that
have a good JIT like Goole Chrome or Safari 4.

Move the rendering back onto the client side.  To do this correctly
we have to ship the entire file to the client.  So what we do is,
we send the entire "A" file, the old image, and only the new lines
from the "B" file, the new image.  When formatting locally on the
client we recreate the full "B" file in order to syntax highlight it.

If a file is considered large, over 9000 lines, we still only send
part of the file and we disable the syntax highlighting.  This way
the client doesn't get bogged down in rendering lots of text when
the file is something really massive.

Bug: issue 439
Change-Id: Ib70c6526af09f84ebbfe467cfbb27c75ca7c9ad7
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce 2010-02-16 11:02:36 -08:00
parent 6caaa60a2d
commit 47cfa6d8cf
11 changed files with 414 additions and 180 deletions

View File

@ -14,9 +14,13 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.prettify.client.ClientSideFormatter;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.PrettyFormatter;
import com.google.gerrit.prettify.common.PrettySettings;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.prettify.common.SparseHtmlFile;
import com.google.gerrit.reviewdb.Change;
import org.eclipse.jgit.diff.Edit;
@ -86,11 +90,45 @@ public class PatchScript {
return b;
}
public SparseHtmlFile getSparseHtmlFileA() {
PrettySettings s = new PrettySettings(settings.getPrettySettings());
s.setFileName(a.getPath());
s.setShowWhiteSpaceErrors(false);
PrettyFormatter f = ClientSideFormatter.FACTORY.get();
f.setPrettySettings(s);
f.setEditFilter(PrettyFormatter.A);
f.setEditList(getEditList());
f.format(a);
return f;
}
public SparseHtmlFile getSparseHtmlFileB() {
PrettySettings s = new PrettySettings(settings.getPrettySettings());
s.setFileName(b.getPath());
PrettyFormatter f = ClientSideFormatter.FACTORY.get();
f.setPrettySettings(s);
f.setEditFilter(PrettyFormatter.B);
f.setEditList(getEditList());
if (s.isSyntaxHighlighting() && a.isWholeFile() && !b.isWholeFile()) {
f.format(b.completeWithContext(a, getEditList()));
} else {
f.format(b);
}
return f;
}
public List<Edit> getEdits() {
return edits;
}
public Iterable<EditList.Hunk> getHunks() {
return new EditList(edits, getContext(), a.size(), b.size()).getHunks();
return getEditList().getHunks();
}
private EditList getEditList() {
return new EditList(edits, getContext(), a.size(), b.size());
}
}

View File

@ -26,7 +26,7 @@ import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.SparseFileContent;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;

View File

@ -21,9 +21,9 @@ import static com.google.gerrit.client.patches.PatchLine.Type.REPLACE;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.EditList;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.SparseFileContent;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseHtmlFile;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gwt.core.client.GWT;
@ -67,8 +67,8 @@ public class SideBySideTable extends AbstractPatchContentTable {
@Override
protected void render(final PatchScript script) {
final SparseFileContent a = script.getA();
final SparseFileContent b = script.getB();
final SparseHtmlFile a = script.getSparseHtmlFileA();
final SparseHtmlFile b = script.getSparseHtmlFileB();
final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
final SafeHtmlBuilder nc = new SafeHtmlBuilder();
@ -86,7 +86,7 @@ public class SideBySideTable extends AbstractPatchContentTable {
while (hunk.next()) {
if (hunk.isContextLine()) {
openLine(nc);
final SafeHtml ctx = a.get(hunk.getCurA());
final SafeHtml ctx = a.getSafeHtmlLine(hunk.getCurA());
appendLineText(nc, hunk.getCurA(), CONTEXT, ctx);
if (ignoreWS && b.contains(hunk.getCurB())) {
appendLineText(nc, hunk.getCurB(), CONTEXT, b, hunk.getCurB());
@ -273,8 +273,8 @@ public class SideBySideTable extends AbstractPatchContentTable {
private void appendLineText(final SafeHtmlBuilder m,
final int lineNumberMinusOne, final PatchLine.Type type,
final SparseFileContent src, final int i) {
appendLineText(m, lineNumberMinusOne, type, src.get(i));
final SparseHtmlFile src, final int i) {
appendLineText(m, lineNumberMinusOne, type, src.getSafeHtmlLine(i));
}
private void appendLineText(final SafeHtmlBuilder m,

View File

@ -20,11 +20,11 @@ import static com.google.gerrit.client.patches.PatchLine.Type.INSERT;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.EditList;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.SparseFileContent;
import com.google.gerrit.common.data.EditList.Hunk;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseHtmlFile;
import com.google.gerrit.prettify.common.EditList.Hunk;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gwt.core.client.GWT;
@ -86,8 +86,8 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
@Override
protected void render(final PatchScript script) {
final SparseFileContent a = script.getA();
final SparseFileContent b = script.getB();
final SparseHtmlFile a = script.getSparseHtmlFileA();
final SparseHtmlFile b = script.getSparseHtmlFileB();
final SafeHtmlBuilder nc = new SafeHtmlBuilder();
// Display the patch header
@ -153,8 +153,10 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
closeLine(nc);
hunk.incA();
lines.add(new PatchLine(DELETE, hunk.getCurA(), 0));
if (a.size() == hunk.getCurA() && a.isMissingNewlineAtEnd())
if (a.size() == hunk.getCurA()
&& script.getA().isMissingNewlineAtEnd()) {
appendNoLF(nc);
}
} else if (hunk.isInsertedB()) {
openLine(nc);
@ -164,8 +166,10 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
closeLine(nc);
hunk.incB();
lines.add(new PatchLine(INSERT, 0, hunk.getCurB()));
if (b.size() == hunk.getCurB() && b.isMissingNewlineAtEnd())
if (b.size() == hunk.getCurB()
&& script.getB().isMissingNewlineAtEnd()) {
appendNoLF(nc);
}
}
}
}
@ -304,8 +308,8 @@ public class UnifiedDiffTable extends AbstractPatchContentTable {
}
private void appendLineText(final SafeHtmlBuilder m,
final PatchLine.Type type, final SparseFileContent src, final int i) {
final SafeHtml text = src.get(i);
final PatchLine.Type type, final SparseHtmlFile src, final int i) {
final SafeHtml text = src.getSafeHtmlLine(i);
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().diffText());
switch (type) {

View File

@ -15,14 +15,11 @@
package com.google.gerrit.httpd.rpc.patch;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.EditList;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.data.SparseFileContent;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
import com.google.gerrit.prettify.common.PrettyFactory;
import com.google.gerrit.prettify.common.PrettyFormatter;
import com.google.gerrit.prettify.common.PrettySettings;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.Patch.PatchType;
@ -65,7 +62,6 @@ class PatchScriptBuilder {
};
private final List<String> header;
private final PrettyFactory prettyFactory;
private Repository db;
private Change change;
private PatchScriptSettings settings;
@ -77,11 +73,11 @@ class PatchScriptBuilder {
private List<Edit> edits;
private final FileTypeRegistry registry;
private int context;
@Inject
PatchScriptBuilder(final FileTypeRegistry ftr, final PrettyFactory pf) {
PatchScriptBuilder(final FileTypeRegistry ftr) {
header = new ArrayList<String>();
prettyFactory = pf;
a = new Side();
b = new Side();
registry = ftr;
@ -97,6 +93,7 @@ class PatchScriptBuilder {
void setSettings(final PatchScriptSettings s) {
settings = s;
context = settings.getContext();
}
void setTrees(final ObjectId a, final ObjectId b) {
@ -104,10 +101,6 @@ class PatchScriptBuilder {
bId = b;
}
private int context() {
return settings.getContext();
}
PatchScript toPatchScript(final PatchListEntry contentWS,
final CommentDetail comments, final PatchListEntry contentAct)
throws IOException {
@ -133,7 +126,7 @@ class PatchScriptBuilder {
if (a.mode == FileMode.GITLINK || b.mode == FileMode.GITLINK) {
} else if (a.src == b.src && a.size() <= context()
} else if (a.src == b.src && a.size() <= context
&& contentAct.getEdits().isEmpty()) {
// Odd special case; the files are identical (100% rename or copy)
// and the user has asked for context that is larger than the file.
@ -144,9 +137,22 @@ class PatchScriptBuilder {
}
edits = new ArrayList<Edit>(1);
edits.add(new Edit(a.size(), a.size()));
} else {
if (BIG_FILE < Math.max(a.size(), b.size()) && 25 < context()) {
settings.setContext(25);
if (BIG_FILE < Math.max(a.size(), b.size())) {
// IF the file is really large, we disable things to avoid choking
// the browser client.
//
settings.setContext(Math.min(25, context));
settings.getPrettySettings().setSyntaxHighlighting(false);
context = settings.getContext();
} else if (settings.getPrettySettings().isSyntaxHighlighting()) {
// In order to syntax highlight the file properly we need to
// give the client the complete file contents. So force our
// context temporarily to the complete file size.
//
context = MAX_CONTEXT;
}
packContent();
}
@ -306,7 +312,7 @@ class PatchScriptBuilder {
}
private void packContent() {
EditList list = new EditList(edits, context(), a.size(), b.size());
EditList list = new EditList(edits, context, a.size(), b.size());
for (final EditList.Hunk hunk : list.getHunks()) {
while (hunk.next()) {
if (hunk.isContextLine()) {
@ -330,7 +336,7 @@ class PatchScriptBuilder {
ObjectId id;
FileMode mode;
byte[] srcContent;
PrettyFormatter src;
Text src;
MimeType mimeType = MimeUtil2.UNKNOWN_MIME_TYPE;
DisplayMethod displayMethod = DisplayMethod.DIFF;
final SparseFileContent dst = new SparseFileContent();
@ -387,26 +393,19 @@ class PatchScriptBuilder {
displayMethod = DisplayMethod.NONE;
}
if (!reuse && displayMethod == DisplayMethod.DIFF) {
PrettySettings s = new PrettySettings(settings.getPrettySettings());
s.setFileName(path);
src = prettyFactory.get();
if (other == null /* side A */) {
src.setEditFilter(PrettyFormatter.A);
s.setShowWhiteSpaceErrors(false);
if (!reuse) {
if (srcContent == Text.NO_BYTES) {
src = Text.EMPTY;
} else {
src.setEditFilter(PrettyFormatter.B);
s.setShowWhiteSpaceErrors(s.isShowWhiteSpaceErrors());
src = new Text(srcContent);
}
src.setEditList(edits);
src.format(s, Text.asString(srcContent, null));
}
if (srcContent.length > 0 && srcContent[srcContent.length - 1] != '\n') {
dst.setMissingNewlineAtEnd(true);
}
dst.setSize(size());
dst.setPath(path);
} catch (IOException err) {
throw new IOException("Cannot read " + within.name() + ":" + path, err);
}

View File

@ -51,8 +51,8 @@ public class ClientSideFormatter extends PrettyFormatter {
/*-{ eval(js); }-*/;
@Override
protected String prettify(String html) {
return go(html, settings.getFilename(), settings.getTabSize());
protected String prettify(String html, String type) {
return go(html, type, settings.getTabSize());
}
private static native String go(String srcText, String srcType, int tabSize)

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common.data;
package com.google.gerrit.prettify.common;
import org.eclipse.jgit.diff.Edit;
@ -37,6 +37,10 @@ public class EditList {
return edits;
}
public EditList getFullContext() {
return new EditList(edits, 5000000, aSize, bSize);
}
public Iterable<Hunk> getHunks() {
return new Iterable<Hunk>() {
public Iterator<Hunk> iterator() {
@ -112,6 +116,10 @@ public class EditList {
return bCur;
}
public Edit getCurEdit() {
return curEdit;
}
public int getEndA() {
return aEnd;
}

View File

@ -20,104 +20,161 @@ import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.ReplaceEdit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class PrettyFormatter {
public abstract class PrettyFormatter implements SparseHtmlFile {
public static abstract class EditFilter {
protected abstract int getBegin(Edit e);
protected abstract int getEnd(Edit e);
protected abstract String getStyleName();
protected final boolean in(int line, Edit e) {
return getBegin(e) <= line && line < getEnd(e);
final String get(SparseFileContent src, EditList.Hunk hunk) {
return src.get(getCur(hunk));
}
protected final boolean after(int line, Edit e) {
return getEnd(e) < line;
}
abstract String getStyleName();
abstract int getCur(EditList.Hunk hunk);
abstract int getBegin(Edit edit);
abstract int getEnd(Edit edit);
abstract boolean isModified(EditList.Hunk hunk);
abstract void incSelf(EditList.Hunk hunk);
abstract void incOther(EditList.Hunk hunk);
}
public static final EditFilter A = new EditFilter() {
@Override
protected String getStyleName() {
String getStyleName() {
return "wdd";
}
@Override
protected int getBegin(Edit e) {
return e.getBeginA();
int getCur(EditList.Hunk hunk) {
return hunk.getCurA();
}
@Override
protected int getEnd(Edit e) {
return e.getEndA();
int getBegin(Edit edit) {
return edit.getBeginA();
}
@Override
int getEnd(Edit edit) {
return edit.getEndA();
}
@Override
boolean isModified(EditList.Hunk hunk) {
return hunk.isDeletedA();
}
@Override
void incSelf(EditList.Hunk hunk) {
hunk.incA();
}
@Override
void incOther(EditList.Hunk hunk) {
hunk.incB();
}
};
public static final EditFilter B = new EditFilter() {
@Override
protected String getStyleName() {
String getStyleName() {
return "wdi";
}
@Override
protected int getBegin(Edit e) {
return e.getBeginB();
int getCur(EditList.Hunk hunk) {
return hunk.getCurB();
}
@Override
protected int getEnd(Edit e) {
return e.getEndB();
int getBegin(Edit edit) {
return edit.getBeginB();
}
@Override
int getEnd(Edit edit) {
return edit.getEndB();
}
@Override
boolean isModified(EditList.Hunk hunk) {
return hunk.isInsertedB();
}
@Override
void incSelf(EditList.Hunk hunk) {
hunk.incB();
}
@Override
void incOther(EditList.Hunk hunk) {
hunk.incA();
}
};
protected List<String> lines = Collections.emptyList();
protected EditFilter side = A;
protected List<Edit> lineEdits = Collections.emptyList();
protected SparseFileContent content;
protected EditFilter side;
protected EditList edits;
protected PrettySettings settings;
private int col;
private int line;
private int lineIdx;
private Tag lastTag;
private StringBuilder buf;
/** @return the line of formatted HTML. */
public SafeHtml getLine(int lineNo) {
return SafeHtml.asis(lines.get(lineNo));
public SafeHtml getSafeHtmlLine(int lineNo) {
return SafeHtml.asis(content.get(lineNo));
}
/** @return the number of lines in this formatter. */
public int size() {
return lines.size();
return content.size();
}
@Override
public boolean contains(int idx) {
return content.contains(idx);
}
public void setEditFilter(EditFilter f) {
side = f;
}
public void setEditList(List<Edit> all) {
lineEdits = all;
public void setEditList(EditList all) {
edits = all;
}
public void setPrettySettings(PrettySettings how) {
settings = how;
}
/**
* Parse and format a complete source code file.
*
* @param how the settings to apply to the formatter.
* @param srcText raw content of the file to format. The string will be HTML
* @param src raw content of the file to format. The line strings will be HTML
* escaped before processing, so it must be the raw text.
*/
public void format(PrettySettings how, String srcText) {
settings = how;
lines = new ArrayList<String>();
public void format(SparseFileContent src) {
content = new SparseFileContent();
content.setSize(src.size());
String html = toHTML(src);
if (settings.isSyntaxHighlighting() && getFileType() != null
&& src.isWholeFile()) {
// The prettify parsers don't like &#39; as an entity for the
// single quote character. Replace them all out so we don't
// confuse the parser.
//
html = html.replaceAll("&#39;", "'");
html = prettify(html, getFileType());
String html = toHTML(srcText);
if (settings.isSyntaxHighlighting()) {
html = prettify(html);
} else {
html = expandTabs(html);
html = html.replaceAll("\n", "<br />");
}
@ -126,7 +183,7 @@ public abstract class PrettyFormatter {
lastTag = Tag.NULL;
col = 0;
line = 0;
lineIdx = 0;
buf = new StringBuilder();
while (pos <= html.length()) {
@ -141,7 +198,7 @@ public abstract class PrettyFormatter {
htmlText(html.substring(textChunkStart, pos));
}
if (0 < buf.length()) {
lines.add(buf.toString());
content.addLine(src.mapIndexToLine(lineIdx), buf.toString());
}
break;
}
@ -164,10 +221,10 @@ public abstract class PrettyFormatter {
if (isBR(html, tagStart, tagEnd)) {
lastTag.close(buf, html);
lines.add(buf.toString());
content.addLine(src.mapIndexToLine(lineIdx), buf.toString());
buf = new StringBuilder();
col = 0;
line++;
lineIdx++;
} else if (html.charAt(tagStart + 1) == '/') {
lastTag = lastTag.pop(buf, html);
@ -222,7 +279,7 @@ public abstract class PrettyFormatter {
}
/** Run the prettify engine over the text and return the result. */
protected abstract String prettify(String html);
protected abstract String prettify(String html, String type);
private static boolean isBR(String html, int tagStart, int tagEnd) {
return tagEnd - tagStart == 5 //
@ -287,15 +344,9 @@ public abstract class PrettyFormatter {
}
}
private String toHTML(String src) {
private String toHTML(SparseFileContent src) {
SafeHtml html = colorLineEdits(src);
// The prettify parsers don't like &#39; as an entity for the
// single quote character. Replace them all out so we don't
// confuse the parser.
//
html = html.replaceAll("&#39;", "'");
if (settings.isShowWhiteSpaceErrors()) {
// We need to do whitespace errors before showing tabs, because
// these patterns rely on \t as a literal, before it expands.
@ -312,70 +363,85 @@ public abstract class PrettyFormatter {
return html.asString();
}
private SafeHtml colorLineEdits(String src) {
private SafeHtml colorLineEdits(SparseFileContent src) {
SafeHtmlBuilder buf = new SafeHtmlBuilder();
int lIdx = 0;
Edit lCur = lIdx < lineEdits.size() ? lineEdits.get(lIdx) : null;
ReplaceEdit lastReplace = null;
List<Edit> charEdits = null;
int lastPos = 0;
int lastIdx = 0;
int pos = 0;
int line = 0;
while (pos < src.length()) {
if (lCur instanceof ReplaceEdit && side.in(line, lCur)) {
List<Edit> wordEdits = ((ReplaceEdit) lCur).getInternalEdits();
if (!wordEdits.isEmpty()) {
// Copy the result using the word edits to guide us.
//
EditList hunkGenerator = edits;
if (src.isWholeFile()) {
hunkGenerator = hunkGenerator.getFullContext();
}
int last = 0;
for (Edit w : wordEdits) {
int b = side.getBegin(w);
int e = side.getEnd(w);
// If there is text between edits, copy it as-is.
for (final EditList.Hunk hunk : hunkGenerator.getHunks()) {
while (hunk.next()) {
if (hunk.isContextLine()) {
if (src.contains(side.getCur(hunk))) {
// If side is B and src isn't the complete file we can't
// add it to the buffer here. This can happen if the file
// was really large and we chose not to syntax highlight.
//
int cnt = b - last;
if (0 < cnt) {
buf.append(src.substring(pos, pos + cnt));
pos += cnt;
last = b;
}
buf.append(side.get(src, hunk));
buf.append('\n');
}
hunk.incBoth();
// If this is an edit, wrap it in a span.
//
cnt = e - b;
if (0 < cnt) {
buf.openSpan();
buf.setStyleName(side.getStyleName());
buf.append(src.substring(pos, pos + cnt));
buf.closeSpan();
pos += cnt;
last = e;
}
} else if (!side.isModified(hunk)) {
side.incOther(hunk);
} else if (hunk.getCurEdit() instanceof ReplaceEdit) {
if (lastReplace != hunk.getCurEdit()) {
lastReplace = (ReplaceEdit) hunk.getCurEdit();
charEdits = lastReplace.getInternalEdits();
lastPos = 0;
lastIdx = 0;
}
// We've consumed the entire region, so we are on the end.
// Fall through, what's left of this edit is only the tail
// of the final line.
//
line = side.getEnd(lCur) - 1;
final String line = side.get(src, hunk) + "\n";
for (int c = 0; c < line.length();) {
if (charEdits.size() <= lastIdx) {
buf.append(line.substring(c));
break;
}
final Edit edit = charEdits.get(lastIdx);
final int b = side.getBegin(edit) - lastPos;
final int e = side.getEnd(edit) - lastPos;
if (c < b) {
// There is text at the start of this line that is common
// with the other side. Copy it with no style around it.
//
final int n = Math.min(b, line.length());
buf.append(line.substring(c, n));
c = n;
}
if (c < e) {
final int n = Math.min(e, line.length());
buf.openSpan();
buf.setStyleName(side.getStyleName());
buf.append(line.substring(c, n));
buf.closeSpan();
c = n;
}
if (e <= c) {
lastIdx++;
}
}
lastPos += line.length();
side.incSelf(hunk);
} else {
buf.append(side.get(src, hunk));
buf.append('\n');
side.incSelf(hunk);
}
}
int lf = src.indexOf('\n', pos);
if (lf < 0)
lf = src.length();
else
lf++;
buf.append(src.substring(pos, lf));
pos = lf;
line++;
if (lCur != null && side.after(line, lCur)) {
lIdx++;
lCur = lIdx < lineEdits.size() ? lineEdits.get(lIdx) : null;
}
}
return buf;
}
@ -394,4 +460,29 @@ public abstract class PrettyFormatter {
src = src.replaceFirst("([ \t][ \t]*)(\r?(</span>)?\n?)$", r);
return src;
}
private String expandTabs(String html) {
StringBuilder tmp = new StringBuilder();
int i = 0;
if (settings.isShowTabs()) {
i = 1;
}
for (; i < settings.getTabSize(); i++) {
tmp.append("&nbsp;");
}
return html.replaceAll("\t", tmp.toString());
}
private String getFileType() {
String srcType = settings.getFilename();
if (srcType == null) {
return null;
}
int dot = srcType.lastIndexOf('.');
if (0 < dot) {
srcType = srcType.substring(dot + 1);
}
return srcType;
}
}

View File

@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common.data;
import com.google.gwtexpui.safehtml.client.SafeHtml;
package com.google.gerrit.prettify.common;
import java.util.ArrayList;
import java.util.List;
public class SparseFileContent {
protected String path;
protected List<Range> ranges;
protected int size;
protected boolean missingNewlineAtEnd;
@ -46,18 +45,50 @@ public class SparseFileContent {
missingNewlineAtEnd = missing;
}
public SafeHtml get(final int idx) {
public String getPath() {
return path;
}
public void setPath(String filePath) {
path = filePath;
}
public boolean isWholeFile() {
if (size == 0) {
return true;
} else if (1 == ranges.size()) {
Range r = ranges.get(0);
return r.base == 0 && r.end() == size;
} else {
return false;
}
}
public String get(final int idx) {
final String line = getLine(idx);
if (line == null) {
throw new ArrayIndexOutOfBoundsException(idx);
}
return SafeHtml.asis(line);
return line;
}
public boolean contains(final int idx) {
return getLine(idx) != null;
}
public int mapIndexToLine(int arrayIndex) {
final int origIndex = arrayIndex;
for (Range r : ranges) {
if (arrayIndex < r.lines.size()) {
return r.base + arrayIndex;
}
arrayIndex -= r.lines.size();
}
throw new ArrayIndexOutOfBoundsException(origIndex);
}
private String getLine(final int idx) {
// Most requests are sequential in nature, fetching the next
// line from the current range, or the next range.
@ -95,10 +126,6 @@ public class SparseFileContent {
return null;
}
public void addLine(final int i, final SafeHtml content) {
addLine(i, content.asString());
}
public void addLine(final int i, final String content) {
final Range r;
if (!ranges.isEmpty() && i == last().end()) {
@ -114,6 +141,51 @@ public class SparseFileContent {
return ranges.get(ranges.size() - 1);
}
public String asString() {
final StringBuilder b = new StringBuilder();
for (Range r : ranges) {
for (String l : r.lines) {
b.append(l);
b.append('\n');
}
}
if (0 < b.length() && isMissingNewlineAtEnd()) {
b.setLength(b.length() - 1);
}
return b.toString();
}
public SparseFileContent completeWithContext(SparseFileContent a,
EditList editList) {
ArrayList<String> lines = new ArrayList<String>(size);
for (final EditList.Hunk hunk : editList.getFullContext().getHunks()) {
while (hunk.next()) {
if (hunk.isContextLine()) {
lines.add(a.get(hunk.getCurA()));
hunk.incBoth();
} else if (hunk.isDeletedA()) {
hunk.incA();
} else if (hunk.isInsertedB()) {
lines.add(get(hunk.getCurB()));
hunk.incB();
}
}
}
Range range = new Range();
range.lines = lines;
SparseFileContent r = new SparseFileContent();
r.setSize(size());
r.setMissingNewlineAtEnd(isMissingNewlineAtEnd());
r.setPath(getPath());
r.ranges.add(range);
return r;
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder();

View File

@ -0,0 +1,28 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.prettify.common;
import com.google.gwtexpui.safehtml.client.SafeHtml;
public interface SparseHtmlFile {
/** @return the line of formatted HTML. */
public SafeHtml getSafeHtmlLine(int lineNo);
/** @return the number of lines in this sparse list. */
public int size();
/** @return true if the line is valid in this sparse list. */
public boolean contains(final int idx);
}

View File

@ -80,19 +80,13 @@ class ServerPrettyFactory implements PrettyFactory, Provider<PrettyFormatter> {
public PrettyFormatter get() {
return new PrettyFormatter() {
@Override
protected String prettify(String html) {
return prettyPrintOne(html, settings);
protected String prettify(String html, String type) {
return prettyPrintOne(html, type, settings);
}
};
}
private String prettyPrintOne(String srcText, PrettySettings how) {
String srcType = how.getFilename();
int dot = srcType.lastIndexOf('.');
if (0 < dot) {
srcType = srcType.substring(dot + 1);
}
private String prettyPrintOne(String srcText, String type, PrettySettings how) {
Context cx = contextFactory.enterContext();
try {
Scriptable callScope = cx.newObject(sharedScope);
@ -110,7 +104,7 @@ class ServerPrettyFactory implements PrettyFactory, Provider<PrettyFormatter> {
callScope.put("window", callScope, callWindow);
callScope.put("srcText", callScope, srcText);
callScope.put("srcType", callScope, srcType);
callScope.put("srcType", callScope, type);
String call = "prettyPrintOne(srcText, srcType)";
return cx.evaluateString(callScope, call, "<call>", 1, null).toString();