// Copyright (C) 2015 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 net.codemirror.lib; import static com.google.gwt.dom.client.Style.Display.INLINE_BLOCK; import static com.google.gwt.dom.client.Style.Unit.PX; import static net.codemirror.lib.CodeMirror.LineClassWhere.WRAP; import static net.codemirror.lib.CodeMirror.style; import com.google.gerrit.client.FormatUtil; import com.google.gerrit.client.RangeInfo; import com.google.gerrit.client.blame.BlameInfo; import com.google.gerrit.client.diff.DisplaySide; import com.google.gerrit.client.rpc.Natives; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.dom.client.Element; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.user.client.DOM; import java.util.Date; import java.util.Objects; import net.codemirror.lib.CodeMirror.LineHandle; /** Additional features added to CodeMirror by Gerrit Code Review. */ public class Extras { private static final String ANNOTATION_GUTTER_ID = "CodeMirror-lint-markers"; private static final BlameConfig C = GWT.create(BlameConfig.class); static final native Extras get(CodeMirror c) /*-{ return c.gerritExtras }-*/; private static native void set(CodeMirror c, Extras e) /*-{ c.gerritExtras = e }-*/; static void attach(CodeMirror c) { set(c, new Extras(c)); } private final CodeMirror cm; private Element margin; private DisplaySide side; private double charWidthPx; private double lineHeightPx; private LineHandle activeLine; private boolean annotated; private Extras(CodeMirror cm) { this.cm = cm; } public DisplaySide side() { return side; } public void side(DisplaySide s) { side = s; } public double charWidthPx() { if (charWidthPx <= 1) { int len = 100; StringBuilder s = new StringBuilder(); for (int i = 0; i < len; i++) { s.append('m'); } Element e = DOM.createSpan(); e.getStyle().setDisplay(INLINE_BLOCK); e.setInnerText(s.toString()); cm.measure().appendChild(e); charWidthPx = ((double) e.getOffsetWidth()) / len; e.removeFromParent(); } return charWidthPx; } public double lineHeightPx() { if (lineHeightPx <= 1) { Element p = DOM.createDiv(); int lines = 1; for (int i = 0; i < lines; i++) { Element e = DOM.createDiv(); p.appendChild(e); Element pre = DOM.createElement("pre"); pre.setInnerText("gqyŚŻŹŃ"); e.appendChild(pre); } cm.measure().appendChild(p); lineHeightPx = ((double) p.getOffsetHeight()) / lines; p.removeFromParent(); } return lineHeightPx; } public void lineLength(int columns) { if (margin == null) { margin = DOM.createDiv(); margin.setClassName(style().margin()); cm.mover().appendChild(margin); } margin.getStyle().setMarginLeft(columns * charWidthPx(), PX); } public void showTabs(boolean show) { Element e = cm.getWrapperElement(); if (show) { e.addClassName(style().showTabs()); } else { e.removeClassName(style().showTabs()); } } public final boolean hasActiveLine() { return activeLine != null; } public final LineHandle activeLine() { return activeLine; } public final boolean activeLine(LineHandle line) { if (Objects.equals(activeLine, line)) { return false; } if (activeLine != null) { cm.removeLineClass(activeLine, WRAP, style().activeLine()); } activeLine = line; cm.addLineClass(activeLine, WRAP, style().activeLine()); return true; } public final void clearActiveLine() { if (activeLine != null) { cm.removeLineClass(activeLine, WRAP, style().activeLine()); activeLine = null; } } public boolean isAnnotated() { return annotated; } public final void clearAnnotations() { JsArrayString gutters = ((JsArrayString) JsArrayString.createArray()); cm.setOption("gutters", gutters); annotated = false; } public final void setAnnotations(JsArray<BlameInfo> blameInfos) { if (blameInfos.length() > 0) { setBlameInfo(blameInfos); JsArrayString gutters = ((JsArrayString) JsArrayString.createArray()); gutters.push(ANNOTATION_GUTTER_ID); cm.setOption("gutters", gutters); annotated = true; DateTimeFormat format = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_SHORT); JsArray<LintLine> annotations = JsArray.createArray().cast(); for (BlameInfo blameInfo : Natives.asList(blameInfos)) { for (RangeInfo range : Natives.asList(blameInfo.ranges())) { Date commitTime = new Date(blameInfo.time() * 1000L); String shortId = blameInfo.id().substring(0, 8); String shortBlame = C.shortBlameMsg(shortId, format.format(commitTime), blameInfo.author()); String detailedBlame = C.detailedBlameMsg( blameInfo.id(), blameInfo.author(), FormatUtil.mediumFormat(commitTime), blameInfo.commitMsg()); annotations.push( LintLine.create(shortBlame, detailedBlame, shortId, Pos.create(range.start() - 1))); } } cm.setOption("lint", getAnnotation(annotations)); } } private native JavaScriptObject getAnnotation(JsArray<LintLine> annotations) /*-{ return { getAnnotations: function(text, options, cm) { return annotations; } }; }-*/; public final native JsArray<BlameInfo> getBlameInfo() /*-{ return this.blameInfos; }-*/; public final native void setBlameInfo(JsArray<BlameInfo> blameInfos) /*-{ this['blameInfos'] = blameInfos; }-*/; public final void toggleAnnotation() { toggleAnnotation(getBlameInfo()); } public final void toggleAnnotation(JsArray<BlameInfo> blameInfos) { if (isAnnotated()) { clearAnnotations(); } else { setAnnotations(blameInfos); } } }