kopia lustrzana https://github.com/c9/core
404 wiersze
13 KiB
JavaScript
404 wiersze
13 KiB
JavaScript
define(function(require, exports, module) {
|
|
|
|
var dom = require("ace/lib/dom");
|
|
var event = require("ace/lib/event");
|
|
var css = require("text!./blame.css");
|
|
|
|
function BlameParser(str) {
|
|
this.commits = {};
|
|
this.lines = {};
|
|
|
|
this.settingCommitData = false;
|
|
this.currentCommitHash = "";
|
|
this.currentLineNumber = 1;
|
|
if (str) this.parse(str);
|
|
}
|
|
|
|
(function() {
|
|
/**
|
|
* The entry point for parsing the output from git blame -p
|
|
*
|
|
* @param {string} blame The output as a string
|
|
*/
|
|
this.parse = function(blame) {
|
|
var lines = blame.split("\n");
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
// If we detect a tab character we know it's a line of code
|
|
// So we can reset stateful variables
|
|
if (lines[i][0] == "\t") {
|
|
// The first tab is an addition made by git, so get rid of it
|
|
this.lines[this.currentLineNumber].code = lines[i].substr(1);
|
|
this.settingCommitData = false;
|
|
this.currentCommitHash = "";
|
|
}
|
|
else {
|
|
var arrLine = lines[i].split(" ");
|
|
|
|
// If we are in the process of collecting data about a commit summary
|
|
if (this.settingCommitData) {
|
|
this.parseCommitLine(arrLine);
|
|
}
|
|
else if (arrLine[0].length == 40) {
|
|
// 40 == the length of an Sha1
|
|
// This is really only an added check, we should be guaranteed
|
|
// that an Sha1 is expected here
|
|
|
|
this.currentCommitHash = arrLine[0];
|
|
this.currentLineNumber = arrLine[2];
|
|
|
|
// Setup the new lines hash
|
|
this.lines[arrLine[2]] = {
|
|
code: "",
|
|
hash: this.currentCommitHash,
|
|
originalLine: arrLine[1],
|
|
finalLine: arrLine[2],
|
|
numLines: arrLine[3] || -1
|
|
};
|
|
|
|
// Since the commit data (author, committer, summary, etc) only
|
|
// appear once in a porcelain output for every commit, we set
|
|
// it up once here and then expect that the next 8-11 lines of
|
|
// the file are dedicated to that data
|
|
if (!this.commits[arrLine[0]]) {
|
|
this.settingCommitData = true;
|
|
this.commits[arrLine[0]] = {
|
|
author: "",
|
|
authorMail: "",
|
|
authorTime: "",
|
|
authorTz: "",
|
|
committer: "",
|
|
committerMail: "",
|
|
committerTime: "",
|
|
committerTz: "",
|
|
summary: "",
|
|
previousHash: "",
|
|
filename: ""
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Parses and sets data from a line following a commit header
|
|
*
|
|
* @param {array} lineArr The current line split by a space
|
|
*/
|
|
this.parseCommitLine = function(lineArr) {
|
|
switch (lineArr[0]) {
|
|
case "author":
|
|
this.commits[this.currentCommitHash].author = lineArr.slice(1).join(" ");
|
|
break;
|
|
|
|
case "author-mail":
|
|
this.commits[this.currentCommitHash].authorMail = lineArr[1];
|
|
break;
|
|
|
|
case "author-time":
|
|
this.commits[this.currentCommitHash].authorTime = lineArr[1];
|
|
break;
|
|
|
|
case "author-tz":
|
|
this.commits[this.currentCommitHash].authorTz = lineArr[1];
|
|
break;
|
|
|
|
case "committer":
|
|
this.commits[this.currentCommitHash].committer = lineArr.slice(1).join(" ");
|
|
break;
|
|
|
|
case "committer-mail":
|
|
this.commits[this.currentCommitHash].committerMail = lineArr[1];
|
|
break;
|
|
|
|
case "committer-time":
|
|
this.commits[this.currentCommitHash].committerTime = lineArr[1];
|
|
break;
|
|
|
|
case "committer-tz":
|
|
this.commits[this.currentCommitHash].committerTz = lineArr[1];
|
|
break;
|
|
|
|
case "summary":
|
|
this.commits[this.currentCommitHash].summary = lineArr.slice(1).join(" ");
|
|
break;
|
|
|
|
case "filename":
|
|
this.commits[this.currentCommitHash].filename = lineArr[1];
|
|
break;
|
|
|
|
case "previous":
|
|
this.commits[this.currentCommitHash].previous = lineArr.slice(1).join(" ");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
this.getDisplayData = function() {
|
|
var commitData = this.commits;
|
|
var lineData = this.lines;
|
|
var textHash = {}, lastHash = "";
|
|
for (var i in lineData) {
|
|
if (lineData[i].numLines != -1 && lineData[i].hash != lastHash) {
|
|
lastHash = lineData[i].hash;
|
|
var time = new Date(parseInt(commitData[lineData[i].hash].authorTime, 10) * 1000);
|
|
var commit = commitData[lineData[i].hash];
|
|
textHash[i - 1] = {
|
|
text: commit.author +
|
|
" \xBB "/* +
|
|
lineData[i].hash.substr(0, 8),*/,
|
|
title: commitData[lineData[i].hash].summary,
|
|
data: lineData[i],
|
|
commit: commit,
|
|
time: time
|
|
};
|
|
}
|
|
}
|
|
return textHash;
|
|
};
|
|
}).call(BlameParser.prototype);
|
|
|
|
|
|
|
|
var BlameGutter = function(editor, blameStr) {
|
|
if (editor.blameGutter)
|
|
return editor.blameGutter;
|
|
|
|
if (css) {
|
|
dom.importCssString(css, "blameGutter");
|
|
css = "";
|
|
}
|
|
|
|
this.onMousedown = this.onMousedown.bind(this);
|
|
this.onChangeEditor = this.onChangeEditor.bind(this);
|
|
this.onMousemove = this.onMousemove.bind(this);
|
|
this.onMouseout = this.onMouseout.bind(this);
|
|
|
|
this.blameData = [];
|
|
this.$cache = [];
|
|
if (blameStr)
|
|
this.setData(blameStr);
|
|
|
|
this.attachToEditor(editor);
|
|
};
|
|
|
|
(function() {
|
|
this.attachToEditor = function(editor) {
|
|
if (this.editor) this.detachFromEditor();
|
|
|
|
if (!this.session) {
|
|
this.session = editor.session;
|
|
this.session.blameMarker = this;
|
|
this.session.on("changeEditor", this.onChangeEditor);
|
|
|
|
}
|
|
|
|
var gutter = editor.renderer.$gutterLayer;
|
|
this.editor = editor;
|
|
editor.blameGutter = this;
|
|
gutter.blameColumn = this;
|
|
|
|
this.element = dom.buildDom(["div", {
|
|
class: "ace_layer ace_blame-gutter-layer ace_gutter"
|
|
}], editor.container);
|
|
this.resizer = dom.buildDom(["div", { class: "ace_resizer_v" },
|
|
["div", { class: "ace_closeButton" }]
|
|
], editor.container);
|
|
this.closeButton = this.resizer.firstChild;
|
|
|
|
gutter.on("afterRender", this.drawGutter);
|
|
this.element.addEventListener("mousedown", this.onMousedown);
|
|
this.resizer.addEventListener("mousedown", this.onMousedown);
|
|
|
|
event.addListener(this.element, "mousemove", this.onMousemove);
|
|
event.addListener(this.element, "mouseout", this.onMouseout);
|
|
|
|
this.resizer.style.left =
|
|
this.element.style.width = "220px";
|
|
editor.renderer.setMargin(0, 0, 220, 0);
|
|
};
|
|
|
|
this.detachFromEditor = function() {
|
|
if (!this.editor) return;
|
|
|
|
var editor = this.editor;
|
|
var gutter = editor.renderer.$gutterLayer;
|
|
gutter.off("afterRender", this.drawGutter);
|
|
|
|
editor.blameGutter = gutter.blameColumn = this.editor = null;
|
|
|
|
editor.renderer.setMargin(0);
|
|
|
|
this.element.remove();
|
|
this.resizer.remove();
|
|
};
|
|
|
|
this.setData = function(blameStr) {
|
|
var parser = new BlameParser(blameStr);
|
|
var blameData = parser.getDisplayData(blameStr);
|
|
|
|
this.blameData = blameData;
|
|
if (this.editor)
|
|
this.editor.renderer.$loop.schedule(this.editor.renderer.CHANGE_GUTTER);
|
|
};
|
|
|
|
this.removeData = function() {
|
|
if (this.session)
|
|
this.session.off("changeEditor", this.onChangeEditor);
|
|
this.detachFromEditor();
|
|
};
|
|
|
|
this.onChangeEditor = function(e) {
|
|
if (e.oldEditor == this.editor)
|
|
this.detachFromEditor();
|
|
if (e.editor)
|
|
this.attachToEditor(e.editor);
|
|
};
|
|
|
|
this.drawGutter = function(e, gutter) {
|
|
var container = gutter.blameColumn.element;
|
|
var blameData = gutter.blameColumn.blameData;
|
|
var selectedHash = gutter.blameColumn.selectedHash;
|
|
|
|
var cells = gutter.$lines.cells;
|
|
var cache = gutter.blameColumn.$cache;
|
|
var cacheIndex = 0;
|
|
var offset = - getTop(gutter.element) + gutter.config.offset;
|
|
|
|
var commit;
|
|
for (var i = 0; i < cells.length; i++) {
|
|
var cell = cells[i];
|
|
var row = cell.row;
|
|
var data = blameData[row];
|
|
|
|
if (!data && i == 0) {
|
|
// find first row
|
|
while (!blameData[row] && row > 0) row--;
|
|
data = blameData[row]
|
|
commit = {
|
|
row: row,
|
|
cell: cell,
|
|
data: data,
|
|
};
|
|
}
|
|
|
|
if (!data)
|
|
continue;
|
|
|
|
if (commit)
|
|
add(commit.data, commit.row, commit.cell, cell);
|
|
|
|
commit = {
|
|
row: row,
|
|
cell: cell,
|
|
data: data,
|
|
};
|
|
}
|
|
if (commit.cell == cell)
|
|
add(commit.data, commit.row, commit.cell);
|
|
|
|
function add(data, row, firstCell, nextCell) {
|
|
var el = cache[cacheIndex++];
|
|
if (!el)
|
|
cache.push(el = dom.createElement("div"));
|
|
el.className = "ace_blame-cell " + (data.data.hash == selectedHash ? "selected" : "");
|
|
el.index = row;
|
|
el.textContent = data.text + " " + data.title;
|
|
|
|
var top = getTop(firstCell.element) - offset;
|
|
var next = nextCell ? getTop(nextCell.element) - offset : gutter.config.height;
|
|
el.style.top = top + "px";
|
|
el.style.height = next - top + "px";
|
|
|
|
container.appendChild(el);
|
|
}
|
|
|
|
while (cacheIndex < cache.length) {
|
|
cache.pop().remove();
|
|
}
|
|
|
|
function getTop(element) {
|
|
return parseInt(element.style.top);
|
|
}
|
|
};
|
|
|
|
this.onMousedown = function(e) {
|
|
var target = e.target;
|
|
|
|
if (target == this.closeButton) {
|
|
this.removeData();
|
|
return event.stopEvent(e);
|
|
}
|
|
|
|
if (target == this.resizer) {
|
|
var rect = this.editor.blameGutter.element.getBoundingClientRect();
|
|
var mouseHandler = this.editor.$mouseHandler;
|
|
apf.plane.setCursor("ew-resize");
|
|
this.editor.blameGutter.resizer.classList.add("hover");
|
|
mouseHandler.resizeBlameGutter = function() {
|
|
var gutterWidth = this.x - rect.left;
|
|
this.editor.blameGutter.resizer.style.left =
|
|
this.editor.blameGutter.element.style.width = gutterWidth + "px";
|
|
this.editor.renderer.setMargin(0, 0, gutterWidth, 0);
|
|
};
|
|
mouseHandler.resizeBlameGutterEnd = function() {
|
|
apf.plane.unsetCursor();
|
|
this.editor.blameGutter.resizer.classList.remove("hover");
|
|
};
|
|
mouseHandler.setState("resizeBlameGutter");
|
|
mouseHandler.captureMouse(e, mouseHandler.resizeBlameGutter.bind(mouseHandler));
|
|
return event.stopEvent(e);
|
|
}
|
|
|
|
if (dom.hasCssClass(target, "ace_blame-cell")) {
|
|
var gutter = this.editor.renderer.$gutterLayer;
|
|
var blameData = gutter.blameColumn.blameData;
|
|
|
|
var blameCell = blameData[target.index];
|
|
if (!blameCell)
|
|
return event.stopEvent(e);
|
|
gutter.blameColumn.selectedHash = blameCell.data.hash;
|
|
this.editor.renderer.$loop.schedule(this.editor.renderer.CHANGE_GUTTER);
|
|
return event.stopEvent(e);
|
|
}
|
|
};
|
|
|
|
this.onMousemove = function(e) {
|
|
var target = e.target;
|
|
var container = e.currentTarget;
|
|
var tooltip = this.editor.tooltip;
|
|
if (this.$highlightedCell != target) {
|
|
if (dom.hasCssClass(target, "ace_blame-cell")) {
|
|
tooltip.style.display = "block";
|
|
this.$highlightedCell = target;
|
|
tooltip.textContent = target.textContent;
|
|
}
|
|
}
|
|
|
|
if (this.$highlightedCell) {
|
|
tooltip.style.top = e.clientY + 10 + "px";
|
|
tooltip.style.left = e.clientX + 10 + "px";
|
|
} else {
|
|
this.onMouseout();
|
|
return;
|
|
}
|
|
};
|
|
this.onMouseout = function(e) {
|
|
// this.editor.tooltip.style.display = "none";
|
|
this.$highlightedCell = null;
|
|
};
|
|
}).call(BlameGutter.prototype);
|
|
|
|
|
|
exports.annotate = function annotate(editor, blameStr) {
|
|
return new BlameGutter(editor, blameStr);
|
|
};
|
|
|
|
|
|
|
|
}); |