|  | // 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); | 
|  | } | 
|  | } | 
|  | } |