c9-core/plugins/c9.ide.scm/blame.js

404 wiersze
13 KiB
JavaScript
Czysty Zwykły widok Historia

2017-01-06 10:47:08 +00:00
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 = [];
2018-04-12 18:33:33 +00:00
this.$cache = [];
2017-01-06 10:47:08 +00:00
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;
2018-04-12 18:33:33 +00:00
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);
2017-01-06 10:47:08 +00:00
};
this.detachFromEditor = function() {
if (!this.editor) return;
var editor = this.editor;
var gutter = editor.renderer.$gutterLayer;
2018-04-12 18:33:33 +00:00
gutter.off("afterRender", this.drawGutter);
2017-01-06 10:47:08 +00:00
editor.blameGutter = gutter.blameColumn = this.editor = null;
2018-04-12 18:33:33 +00:00
editor.renderer.setMargin(0);
2017-01-06 10:47:08 +00:00
2018-04-12 18:33:33 +00:00
this.element.remove();
this.resizer.remove();
2017-01-06 10:47:08 +00:00
};
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);
};
2018-04-12 18:33:33 +00:00
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,
};
2017-01-06 10:47:08 +00:00
}
2018-04-12 18:33:33 +00:00
if (!data)
continue;
if (commit)
add(commit.data, commit.row, commit.cell, cell);
commit = {
row: row,
cell: cell,
data: data,
};
2017-01-06 10:47:08 +00:00
}
2018-04-12 18:33:33 +00:00
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);
2017-01-06 10:47:08 +00:00
}
2018-04-12 18:33:33 +00:00
while (cacheIndex < cache.length) {
cache.pop().remove();
}
function getTop(element) {
return parseInt(element.style.top);
2017-01-06 10:47:08 +00:00
}
};
this.onMousedown = function(e) {
2018-04-12 18:33:33 +00:00
var target = e.target;
2017-01-06 10:47:08 +00:00
if (target == this.closeButton) {
this.removeData();
2018-04-12 18:33:33 +00:00
return event.stopEvent(e);
2017-01-06 10:47:08 +00:00
}
if (target == this.resizer) {
var rect = this.editor.blameGutter.element.getBoundingClientRect();
var mouseHandler = this.editor.$mouseHandler;
2018-04-12 18:33:33 +00:00
apf.plane.setCursor("ew-resize");
this.editor.blameGutter.resizer.classList.add("hover");
2017-01-06 10:47:08 +00:00
mouseHandler.resizeBlameGutter = function() {
2018-04-12 18:33:33 +00:00
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);
2017-01-06 10:47:08 +00:00
};
2018-04-12 18:33:33 +00:00
mouseHandler.resizeBlameGutterEnd = function() {
apf.plane.unsetCursor();
this.editor.blameGutter.resizer.classList.remove("hover");
};
mouseHandler.setState("resizeBlameGutter");
2017-01-06 10:47:08 +00:00
mouseHandler.captureMouse(e, mouseHandler.resizeBlameGutter.bind(mouseHandler));
2018-04-12 18:33:33 +00:00
return event.stopEvent(e);
2017-01-06 10:47:08 +00:00
}
if (dom.hasCssClass(target, "ace_blame-cell")) {
var gutter = this.editor.renderer.$gutterLayer;
2018-04-12 18:33:33 +00:00
var blameData = gutter.blameColumn.blameData;
2017-01-06 10:47:08 +00:00
2018-04-12 18:33:33 +00:00
var blameCell = blameData[target.index];
2017-01-06 10:47:08 +00:00
if (!blameCell)
2018-04-12 18:33:33 +00:00
return event.stopEvent(e);
gutter.blameColumn.selectedHash = blameCell.data.hash;
this.editor.renderer.$loop.schedule(this.editor.renderer.CHANGE_GUTTER);
return event.stopEvent(e);
2017-01-06 10:47:08 +00:00
}
};
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) {
2018-04-12 18:33:33 +00:00
tooltip.style.top = e.clientY + 10 + "px";
tooltip.style.left = e.clientX + 10 + "px";
2017-01-06 10:47:08 +00:00
} 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);
};
});