mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 23:40:18 +00:00
commit.js: Interface for selecting lines to (un-)stage.
This commit is contained in:
+302
-2
@@ -1,3 +1,6 @@
|
||||
/* Commit: Interface for selecting, staging, discarding, and unstaging
|
||||
hunks, individual lines, or ranges of lines. */
|
||||
|
||||
var showNewFile = function(file)
|
||||
{
|
||||
setTitle("New file: " + file.path);
|
||||
@@ -72,13 +75,102 @@ var showFileChanges = function(file, cached) {
|
||||
displayDiff(changes, cached);
|
||||
}
|
||||
|
||||
/* Set the event handlers for mouse clicks/drags */
|
||||
var setSelectHandlers = function()
|
||||
{
|
||||
document.onmousedown = function(event) {
|
||||
if(event.which != 1) return false;
|
||||
deselect();
|
||||
currentSelection = false;
|
||||
}
|
||||
document.onselectstart = function () {return false;}; /* prevent normal text selection */
|
||||
|
||||
var list = document.getElementsByClassName("lines");
|
||||
|
||||
document.onmouseup = function(event) {
|
||||
// Handle button releases outside of lines list
|
||||
for (i = 0; i < list.length; ++i) {
|
||||
file = list[i];
|
||||
file.onmouseover = null;
|
||||
file.onmouseup = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < list.length; ++i) {
|
||||
var file = list[i];
|
||||
file.ondblclick = function (event) {
|
||||
var file = event.target.parentNode;
|
||||
if (file.id = "selected")
|
||||
file = file.parentNode;
|
||||
var start = event.target;
|
||||
var elem_class = start.getAttribute("class");
|
||||
if(!elem_class || !(elem_class == "addline" | elem_class == "delline"))
|
||||
return false;
|
||||
deselect();
|
||||
var bounds = findsubhunk(start);
|
||||
showSelection(file,bounds[0],bounds[1],true);
|
||||
return false;
|
||||
};
|
||||
|
||||
file.onmousedown = function(event) {
|
||||
if (event.which != 1)
|
||||
return false;
|
||||
var elem_class = event.target.getAttribute("class")
|
||||
event.stopPropagation();
|
||||
if (elem_class == "hunkheader" || elem_class == "hunkbutton")
|
||||
return false;
|
||||
|
||||
var file = event.target.parentNode;
|
||||
if (file.id && file.id == "selected")
|
||||
file = file.parentNode;
|
||||
|
||||
file.onmouseup = function(event) {
|
||||
file.onmouseover = null;
|
||||
file.onmouseup = null;
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
};
|
||||
|
||||
if (event.shiftKey && currentSelection) { // Extend selection
|
||||
var index = parseInt(event.target.getAttribute("index"));
|
||||
var min = parseInt(currentSelection.bounds[0].getAttribute("index"));
|
||||
var max = parseInt(currentSelection.bounds[1].getAttribute("index"));
|
||||
var ender = 1;
|
||||
if(min > max) {
|
||||
var tmp = min; min = max; max = tmp;
|
||||
ender = 0;
|
||||
}
|
||||
|
||||
if (index < min)
|
||||
showSelection(file,currentSelection.bounds[ender],
|
||||
event.target);
|
||||
else if (index > max)
|
||||
showSelection(file,currentSelection.bounds[1-ender],
|
||||
event.target);
|
||||
else showSelection(file,currentSelection.bounds[0],event.target);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
file.onmouseover = function(event2) {
|
||||
showSelection(file, event.srcElement, event2.target);
|
||||
return false;
|
||||
};
|
||||
showSelection(file, event.srcElement, event.srcElement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var diffHeader;
|
||||
var originalDiff;
|
||||
var originalCached;
|
||||
|
||||
var displayDiff = function(diff, cached)
|
||||
{
|
||||
diffHeader = diff.split("\n").slice(0,4).join("\n");
|
||||
originalDiff = diff;
|
||||
originalCached = cached;
|
||||
|
||||
$("diff").style.display = "";
|
||||
highlightDiff(diff, $("diff"));
|
||||
@@ -93,6 +185,7 @@ var displayDiff = function(diff, cached)
|
||||
header.innerHTML = "<a href='#' class='hunkbutton' onclick='discardHunk(this, event); return false'>Discard</a>" + header.innerHTML;
|
||||
}
|
||||
}
|
||||
setSelectHandlers();
|
||||
}
|
||||
|
||||
var getNextText = function(element)
|
||||
@@ -116,8 +209,7 @@ var getLines = function (hunkHeader)
|
||||
end = end2;
|
||||
if (end == -1)
|
||||
end = originalDiff.length;
|
||||
var hunkText = originalDiff.substring(start, end)+'\n';
|
||||
return hunkText;
|
||||
return originalDiff.substring(start, end)+'\n';
|
||||
}
|
||||
|
||||
/* Get the full hunk test, including diff top header */
|
||||
@@ -156,3 +248,211 @@ var discardHunk = function(hunk, event)
|
||||
alert(hunkText);
|
||||
}
|
||||
}
|
||||
|
||||
/* Find all contiguous add/del lines. A quick way to select "just this
|
||||
* chunk". */
|
||||
var findsubhunk = function(start) {
|
||||
var findBound = function(direction) {
|
||||
var element=start;
|
||||
for (var next = element[direction]; next; next = next[direction]) {
|
||||
var elem_class = next.getAttribute("class");
|
||||
if (elem_class == "hunkheader" || elem_class == "noopline")
|
||||
break;
|
||||
element=next;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
return [findBound("previousSibling"), findBound("nextSibling")];
|
||||
}
|
||||
|
||||
/* Remove existing selection */
|
||||
var deselect = function() {
|
||||
var selection = document.getElementById("selected");
|
||||
if (selection) {
|
||||
while (selection.childNodes[1])
|
||||
selection.parentNode.insertBefore(selection.childNodes[1], selection);
|
||||
selection.parentNode.removeChild(selection);
|
||||
}
|
||||
}
|
||||
|
||||
/* Stage individual selected lines. Note that for staging, unselected
|
||||
* delete lines are context, and v.v. for unstaging. */
|
||||
var stageLines = function(reverse) {
|
||||
var selection = document.getElementById("selected");
|
||||
if(!selection) return false;
|
||||
currentSelection = false;
|
||||
var hunkHeader = false;
|
||||
var preselect = 0,elem_class;
|
||||
|
||||
for(var next = selection.previousSibling; next; next = next.previousSibling) {
|
||||
elem_class = next.getAttribute("class");
|
||||
if(elem_class == "hunkheader") {
|
||||
hunkHeader = next.lastChild.data;
|
||||
break;
|
||||
}
|
||||
preselect++;
|
||||
}
|
||||
|
||||
if (!hunkHeader) return false;
|
||||
|
||||
var sel_len = selection.children.length-1;
|
||||
var subhunkText = getLines(hunkHeader);
|
||||
var lines = subhunkText.split('\n');
|
||||
lines.shift(); // Trim old hunk header (we'll compute our own)
|
||||
if (lines[lines.length-1] == "") lines.pop(); // Omit final newline
|
||||
|
||||
var m;
|
||||
if (m = hunkHeader.match(/@@ \-(\d+)(,\d+)? \+(\d+)(,\d+)? @@/)) {
|
||||
var start_old = parseInt(m[1]);
|
||||
var start_new = parseInt(m[3]);
|
||||
} else return false;
|
||||
|
||||
var patch = "", count = [0,0];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var l = lines[i];
|
||||
var firstChar = l.charAt(0);
|
||||
if (i < preselect || i >= preselect+sel_len) { // Before/after select
|
||||
if(firstChar == (reverse?'+':"-")) // It's context now, make it so!
|
||||
l = ' '+l.substr(1);
|
||||
if(firstChar != (reverse?'-':"+")) { // Skip unincluded changes
|
||||
patch += l+"\n";
|
||||
count[0]++; count[1]++;
|
||||
}
|
||||
} else { // In the selection
|
||||
if (firstChar == '-') {
|
||||
count[0]++;
|
||||
} else if (firstChar == '+') {
|
||||
count[1]++;
|
||||
} else {
|
||||
count[0]++; count[1]++;
|
||||
}
|
||||
patch += l+"\n";
|
||||
}
|
||||
}
|
||||
patch = diffHeader + '\n' + "@@ -" + start_old.toString() + "," + count[0].toString() +
|
||||
" +" + start_new.toString() + "," + count[1].toString() + " @@\n"+patch;
|
||||
|
||||
addHunkText(patch,reverse);
|
||||
}
|
||||
|
||||
/* Compute the selection before actually making it. Return as object
|
||||
* with 2-element array "bounds", and "good", which indicates if the
|
||||
* selection contains add/del lines. */
|
||||
var computeSelection = function(list, from,to)
|
||||
{
|
||||
var startIndex = parseInt(from.getAttribute("index"));
|
||||
var endIndex = parseInt(to.getAttribute("index"));
|
||||
if (startIndex == -1 || endIndex == -1)
|
||||
return false;
|
||||
|
||||
var up = (startIndex < endIndex);
|
||||
var nextelem = up?"nextSibling":"previousSibling";
|
||||
|
||||
var insel = from.parentNode && from.parentNode.id == "selected";
|
||||
var good = false;
|
||||
for(var elem = last = from;;elem = elem[nextelem]) {
|
||||
if(!insel && elem.id && elem.id == "selected") {
|
||||
// Descend into selection div
|
||||
elem = up?elem.childNodes[1]:elem.lastChild;
|
||||
insel = true;
|
||||
}
|
||||
|
||||
var elem_class = elem.getAttribute("class");
|
||||
if(elem_class) {
|
||||
if(elem_class == "hunkheader") {
|
||||
elem = last;
|
||||
break; // Stay inside this hunk
|
||||
}
|
||||
if(!good && (elem_class == "addline" || elem_class == "delline"))
|
||||
good = true; // A good selection
|
||||
}
|
||||
if (elem == to) break;
|
||||
|
||||
if (insel) {
|
||||
if (up?
|
||||
elem == elem.parentNode.lastChild:
|
||||
elem == elem.parentNode.childNodes[1]) {
|
||||
// Come up out of selection div
|
||||
last = elem;
|
||||
insel = false;
|
||||
elem = elem.parentNode;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
last = elem;
|
||||
}
|
||||
to = elem;
|
||||
return {bounds:[from,to],good:good};
|
||||
}
|
||||
|
||||
|
||||
var currentSelection = false;
|
||||
|
||||
/* Highlight the selection (if it is new)
|
||||
|
||||
If trust is set, it is assumed that the selection is pre-computed,
|
||||
and it is not recomputed. Trust also assumes deselection has
|
||||
already occurred
|
||||
*/
|
||||
var showSelection = function(file, from, to, trust)
|
||||
{
|
||||
if(trust) // No need to compute bounds.
|
||||
var sel = {bounds:[from,to],good:true};
|
||||
else
|
||||
var sel = computeSelection(file,from,to);
|
||||
|
||||
if (!sel) {
|
||||
currentSelection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if(currentSelection &&
|
||||
currentSelection.bounds[0] == sel.bounds[0] &&
|
||||
currentSelection.bounds[1] == sel.bounds[1] &&
|
||||
currentSelection.good == sel.good) {
|
||||
return; // Same selection
|
||||
} else {
|
||||
currentSelection = sel;
|
||||
}
|
||||
|
||||
if(!trust) deselect();
|
||||
|
||||
var beg = parseInt(sel.bounds[0].getAttribute("index"));
|
||||
var end = parseInt(sel.bounds[1].getAttribute("index"));
|
||||
|
||||
if (beg > end) {
|
||||
var tmp = beg;
|
||||
beg = end;
|
||||
end = tmp;
|
||||
}
|
||||
|
||||
var elementList = [];
|
||||
for (var i = beg; i <= end; ++i)
|
||||
elementList.push(from.parentNode.childNodes[i]);
|
||||
|
||||
var selection = document.createElement("div");
|
||||
selection.setAttribute("id", "selected");
|
||||
|
||||
var button = document.createElement('a');
|
||||
button.setAttribute("href","#");
|
||||
button.appendChild(document.createTextNode(
|
||||
(originalCached?"Uns":"S")+"tage line"+
|
||||
(elementList.length > 1?"s":"")));
|
||||
button.setAttribute("class","hunkbutton");
|
||||
button.setAttribute("id","stagelines");
|
||||
|
||||
if (sel.good) {
|
||||
button.setAttribute('onclick','stageLines('+
|
||||
(originalCached?'true':'false')+
|
||||
'); return false;');
|
||||
} else {
|
||||
button.setAttribute("class","disabled");
|
||||
}
|
||||
selection.appendChild(button);
|
||||
|
||||
file.insertBefore(selection, from);
|
||||
for (i = 0; i < elementList.length; i++)
|
||||
selection.appendChild(elementList[i]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user