blob: 47dc03876aa86775c2077d364b39a33d238f1a5e [file] [log] [blame] [raw]
// Copyright (c) 2012-2016, Matt Godbolt
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
function parseLines(lines, callback) {
var re = /^\/tmp\/[^:]+:([0-9]+)(:([0-9]+))?:\s+(.*)/;
$.each(lines.split('\n'), function (_, line) {
line = line.trim();
if (line !== "") {
var match = line.match(re);
if (match) {
callback(parseInt(match[1]), match[4].trim());
} else {
callback(null, line);
}
}
});
}
// Function called on the editor's window, or on a slot's window
function clearBackground(cm) {
for (var i = 0; i < cm.lineCount(); ++i) {
cm.removeLineClass(i, "background", null);
}
}
const NumRainbowColours = 12;
// This function is called in function initialise in static/gcc.js
function Compiler(domRoot, origFilters, windowLocalPrefix, onEditorChangeCallback, lang) {
console.log("[TRACE] Entering function Compiler()");
// Global array that will contain the slots.
slots = [];
var Slot = function(id) {
// this id will be used in the DOM, ex : class="slot"+id
// the get_available_id could be placed here but that could
// lead to confusion if the Slot is not pushed in slots
this.id = id;
this.asmCodeMirror = null;
this.currentAssembly = null;
this.pendingTimeOut = null;
this.lastUpdateAsm = null;
// lastRequest contains the previous request in the slot
// it can be used to prevent useless re-compilation
// if no parameters were really changed (e.g. spaces moved)
this.lastRequest = null;
this.pendingDiffs = [];
}
diffs = [];
var Diff = function(id) {
this.id = id;
this.beforeSlot = null;
this.afterSlot = null;
this.currentDiff = null;
this.asmCodeMirror = null;
this.zones = null;
// this.pendingTimeOut = null;
// this.lastUpdateAsm = null;
// this.lastRequest = null;
// used only if the editor's code is modified,
// to prevent two diff generation by waiting for the second one.
this.remainingTriggers = 2;
}
function contains(array, object) {
for (var i = 0; i < array.length; i++) {
if (array[i] === object) {
return true;
}
}
return false;
}
// adds diff to the set (array) slot.pendingDiffs
function add_to_pendings(slot, diff) {
if (!contains(slot.pendingDiffs,diff)) {
slot.pendingDiffs.push(diff);
}
}
// returns the smallest Natural that is not used as an id
// (suppose that ids are Naturals, but any other way of getting
// an unique id usable in HTML's classes should work)
function get_available_id(array) {
if (array.length == 0) return 0;
used_ids = [];
for (var i = 0; i < array.length; i++) {
used_ids.push(array[i].id)
}
used_ids.sort();
var prev = -1;
for (var i = 0; i < used_ids.length; i++) {
if (used_ids[i] - prev > 1) return prev + 1;
prev = used_ids[i];
}
return used_ids.length;
}
setSetting('leaderSlot', null); // TODO : remove ?
var compilersById = {};
var compilersByAlias = {};
var pendingTimeoutInEditor = null;
var cppEditor = null;
// default filters
var filters_ = $.extend({}, origFilters);
var ignoreChanges = true; // Horrible hack to avoid onEditorChange doing anything on first starting, ie before we've set anything up.
function setCompilerById(compilerId,slot) {
var compilerNode = domRoot.find('#slot'+slot.id+' .compiler');
compilerNode.text(compilersById[compilerId].name);
compilerNode.attr('data', compilerId);
}
function currentCompilerId(slot) {
return domRoot.find('#slot'+slot.id+' .compiler').attr('data');
}
function currentCompiler(slot) {
return compilersById[currentCompilerId(slot)];
}
// set the autocompile button on static/index.html
$('.autocompile').click(function () {
$('.autocompile').toggleClass('active');
onEditorChange();
setSetting('autocompile', $('.autocompile').hasClass('active')); });
$('.autocompile').toggleClass('active', getSetting("autocompile") !== "false");
// handle filter options that are specific to a compiler
function patchUpFilters(filters) {
filters = $.extend({}, filters);
// TODO : correct the fact that filters should be slot specific !!
var compiler = currentCompiler(slots[0]);
var compilerSupportsBinary = compiler ? compiler.supportsBinary : true;
if (filters.binary && !(OPTIONS.supportsBinary && compilerSupportsBinary)) {
filters.binary = false;
}
return filters;
}
var cmMode;
switch (lang.toLowerCase()) {
default:
cmMode = "text/x-c++src";
break;
case "c":
cmMode = "text/x-c";
break;
case "rust":
cmMode = "text/x-rustsrc";
break;
case "d":
cmMode = "text/x-d";
break;
case "go":
cmMode = "text/x-go";
break;
}
// Set up the editor's window
// [0] is mandatory here because domRoot.find() returns an array of all
// matching elements. It is not a problem for jquery which applies it's
// functions to all elements, whereas CodeMirror works on a single DOM node.
cppEditor = CodeMirror.fromTextArea(domRoot.find(".editor textarea")[0], {
lineNumbers: true,
matchBrackets: true,
useCPP: true,
mode: cmMode
});
// With reference to "fix typing '#' in emacs mode"
// https://github.com/mattgodbolt/gcc-explorer/pull/131
cppEditor.setOption("extraKeys", {
"Alt-F": false
});
cppEditor.on("change", function () {
if ($('.autocompile').hasClass('active')) {
onEditorChange();
}
});
// Load/Save/Remove setting from the browser
function getSetting(name) {
return window.localStorage[windowLocalPrefix + "." + name];
}
function setSetting(name, value) {
window.localStorage[windowLocalPrefix + "." + name] = value;
}
function removeSetting(name) {
// source: http://stackoverflow.com/questions/9943220/how-to-delete-a-localstorage-item-when-the-browser-window-tab-is-closed
window.localStorage.removeItem(windowLocalPrefix + "." + name);
}
var codeText = getSetting('code');
if (!codeText) codeText = $(".template.lang." + lang.replace(/[^a-zA-Z]/g, '').toLowerCase()).text();
if (codeText) cppEditor.setValue(codeText);
ignoreChanges = false;
// function similar to setCompilersInSlot
// TODO : should be returned by Compiler() !
// (not executed in setCompilers)
function setLeaderSlotMenu() {
domRoot.find('#commonParams .slots li').remove();
// fills the leader-slot list
for (var i = 0; i < slots.length; i++) {
var elem = $('<li><a href="#">' + slots[i].id + '</a></li>');
domRoot.find('#commonParams .slots').append(elem);
(function (i) {
elem.click(function () {
var leaderSlotMenuNode = domRoot.find('#commonParams .leaderSlot');
leaderSlotMenuNode.text('leader slot : '+slots[i].id);
setSetting('leaderSlot', slots[i].id);
onEditorChange(true);
});
})(i);
}
}
// auxiliary function to onCompileResponse(),
// used to display the compiler error messages in the editor's window
function makeErrNode(text) {
var clazz = "error";
if (text.match(/^warning/)) clazz = "warning";
if (text.match(/^note/)) clazz = "note";
var node = $('<div class="' + clazz + ' inline-msg"><span class="icon">!!</span><span class="msg"></span></div>');
node.find(".msg").text(text);
return node[0];
}
// keep track of error widgets to delete them if need be
var errorWidgets = [];
// Compilation's callback
function onCompileResponse(slot, request, data) {
var leaderSlot = getSetting('leaderSlot');
console.log("[CALLBACK] onCompileResponse() with slot = "+slot.id+", leaderSlot = ",+leaderSlot);
var stdout = data.stdout || "";
var stderr = data.stderr || "";
if (data.code === 0) {
stdout += "Compiled ok in slot " + slot.id + "\n";
} else {
stderr += "Compilation failed in slot " + slot.id + "\n";
}
if (_gaq) {
// TODO : to modify to handle the slots ?
_gaq.push(['_trackEvent', 'Compile', request.compiler, request.options, data.code]);
_gaq.push(['_trackTiming', 'Compile', 'Timing', new Date() - request.timestamp]);
}
domRoot.find('#slot'+slot.id+' .result .output :visible').remove();
// only show in Editor messages comming from the leaderSlot
if (slot.id == leaderSlot) {
for (var i = 0; i < errorWidgets.length; ++i)
cppEditor.removeLineWidget(errorWidgets[i]);
errorWidgets.length = 0;
}
var numLines = 0;
parseLines(stderr + stdout, function (lineNum, msg) {
if (numLines > 50) return;
if (numLines === 50) {
lineNum = null;
msg = "Too many output lines...truncated";
}
numLines++;
var elem = domRoot.find('#slot'+slot.id+' .result .output .template').clone().appendTo(domRoot.find('#slot'+slot.id+' .result .output')).removeClass('template');
if (lineNum) {
// only show in Editor messages comming from the leaderSlot
if (slot.id == leaderSlot) {
errorWidgets.push(cppEditor.addLineWidget(lineNum - 1, makeErrNode(msg), {
coverGutter: false, noHScroll: true
}));
}
elem.html($('<a href="#">').text(lineNum + " : " + msg)).click(function () {
cppEditor.setSelection({line: lineNum - 1, ch: 0}, {line: lineNum, ch: 0});
return false;
});
} else {
elem.text(msg);
}
});
slot.currentAssembly = data.asm || fakeAsm("[no output]");
updateAsm(slot);
for (var i = 0; i < slot.pendingDiffs.length; i++) {
onDiffChange(slot.pendingDiffs[i], request.fromEditor);
}
}
function numberUsedLines(asm) {
var sourceLines = {};
$.each(asm, function (_, x) {
if (x.source) sourceLines[x.source - 1] = true;
});
var ordinal = 0;
$.each(sourceLines, function (k, _) {
sourceLines[k] = ordinal++;
});
var asmLines = {};
$.each(asm, function (index, x) {
if (x.source) asmLines[index] = sourceLines[x.source - 1];
});
return {source: sourceLines, asm: asmLines};
}
function updateAsm(slot,forceUpdate) {
console.log("[CALLBACK] updateAsm() with slot = " + slot.id + ", forceUpdate = " + forceUpdate);
if (!slot.currentAssembly) return;
var hashedUpdate = JSON.stringify(slot.currentAssembly);
// TODO : real hash here ?
if (!forceUpdate && slot.lastUpdatedAsm == hashedUpdate) {
return;
}
slot.lastUpdatedAsm = hashedUpdate;
var asmText = $.map(slot.currentAssembly, function (x) {
return x.text;
}).join("\n");
var numberedLines = numberUsedLines(slot.currentAssembly);
cppEditor.operation(function () {
clearBackground(cppEditor);
});
var filters = currentFilters();
slot.asmCodeMirror.operation(function () {
slot.asmCodeMirror.setValue(asmText);
clearBackground(slot.asmCodeMirror);
var addrToAddrDiv = {};
$.each(slot.currentAssembly, function (line, obj) {
var address = obj.address ? obj.address.toString(16) : "";
var div = $("<div class='address cm-number'>" + address + "</div>");
addrToAddrDiv[address] = {div: div, line: line};
slot.asmCodeMirror.setGutterMarker(line, 'address', div[0]);
});
$.each(slot.currentAssembly, function (line, obj) {
var opcodes = $("<div class='opcodes'></div>");
if (obj.opcodes) {
var title = [];
$.each(obj.opcodes, function (_, op) {
var opcodeNum = "00" + op.toString(16);
opcodeNum = opcodeNum.substr(opcodeNum.length - 2);
title.push(opcodeNum);
var opcode = $("<span class='opcode'>" + opcodeNum + "</span>");
opcodes.append(opcode);
});
opcodes.attr('title', title.join(" "));
}
slot.asmCodeMirror.setGutterMarker(line, 'opcodes', opcodes[0]);
if (obj.links) {
$.each(obj.links, function (_, link) {
var from = {line: line, ch: link.offset};
var to = {line: line, ch: link.offset + link.length};
var address = link.to.toString(16);
var thing = $("<a href='#' class='cm-number'>" + address + "</a>");
slot.asmCodeMirror.markText(
from, to, {replacedWith: thing[0], handleMouseEvents: false});
var dest = addrToAddrDiv[address];
if (dest) {
thing.on('hover', function (e) {
var entered = e.type == "mouseenter";
dest.div.toggleClass("highlighted", entered);
thing.toggleClass("highlighted", entered);
});
thing.on('click', function (e) {
slot.asmCodeMirror.scrollIntoView({line: dest.line, ch: 0}, 30);
dest.div.toggleClass("highlighted", false);
thing.toggleClass("highlighted", false);
});
}
});
}
});
if (filters.binary) {
slot.asmCodeMirror.setOption('lineNumbers', false);
slot.asmCodeMirror.setOption('gutters', ['address', 'opcodes']);
} else {
slot.asmCodeMirror.setOption('lineNumbers', true);
slot.asmCodeMirror.setOption('gutters', ['CodeMirror-linenumbers']);
}
});
if (filters.colouriseAsm) {
// colorise the editor
cppEditor.operation(function () {
$.each(numberedLines.source, function (line, ordinal) {
cppEditor.addLineClass(parseInt(line),
"background", "rainbow-" + (ordinal % NumRainbowColours));
});
});
// colorise the assembly in slot
slot.asmCodeMirror.operation(function () {
$.each(numberedLines.asm, function (line, ordinal) {
slot.asmCodeMirror.addLineClass(parseInt(line),
"background", "rainbow-" + (ordinal % NumRainbowColours));
});
});
}
}
function fakeAsm(text) {
return [{text: text, source: null}];
}
// slot's parameters callback
// TODO : refactor with onEditorChange : those functions could call an updateSlot(slot)
function onParamChange(slot) {
console.log("[CALLBACK] onParamChange() on slot "+slot.id);
if (ignoreChanges) return; // Ugly hack during startup.
if (slot.pendingTimeoutInSlot) {
console.log("[TIME] Clearing time out in slot " + slot.id);
clearTimeout(slot.pendingTimeoutInSlot);
}
console.log("[TIME] Setting time out in slot " + slot.id);
slot.pendingTimeoutInSlot = setTimeout(function () {
console.log("[TIME] Timed out : compiling in slot " + slot.id + " triggered by modification of params...");
(function(slot) {
var data = {
fromEditor: false,
source: cppEditor.getValue(),
compiler: currentCompilerId(slot),
options: $('#slot'+slot.id+' .compiler_options').val(),
filters: currentFilters()
};
setSetting('compiler'+slot.id, data.compiler);
setSetting('compilerOptions'+slot.id, data.options);
var stringifiedReq = JSON.stringify(data);
if (stringifiedReq == slot.lastRequest) return;
slot.lastRequest = stringifiedReq;
data.timestamp = new Date();
$.ajax({
type: 'POST',
url: '/compile',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
onCompileResponse(slot, data, result);
},
error: function (xhr, e_status, error) {
console.log("AJAX request failed, reason : " + error);
},
cache: false
});
setSetting('code', cppEditor.getValue());
slot.currentAssembly = fakeAsm("[Processing...]");
updateAsm(slot);
}) (slot);
}, 750); // Time in ms after which action is taken (if inactivity)
// (maybe redundant) execute the callback passed to Compiler()
onEditorChangeCallback();
}
function onEditorChange(force) {
console.log("[CALLBACK] onEditorChange()");
if (ignoreChanges) return; // Ugly hack during startup.
if (pendingTimeoutInEditor) {
console.log("[TIME] Clearing time out in editor");
clearTimeout(pendingTimeoutInEditor);
}
console.log("[TIME] Setting time out in editor");
pendingTimeoutInEditor = setTimeout(function () {
console.log("[TIME] Timed out in editor : compiling in all "+slots.length+ " slots...");
for (var i = 0; i < slots.length; i++) {
(function(slot) {
var data = {
fromEditor: true,
source: cppEditor.getValue(),
compiler: currentCompilerId(slot),
options: $('#slot'+slot.id+' .compiler_options').val(),
filters: currentFilters()
};
setSetting('compiler'+slot.id, data.compiler);
setSetting('compilerOptions'+slot.id, data.options);
var stringifiedReq = JSON.stringify(data);
if (!force && stringifiedReq == slot.lastRequest) return;
slot.lastRequest = stringifiedReq;
data.timestamp = new Date();
$.ajax({
type: 'POST',
url: '/compile',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
onCompileResponse(slot, data, result);
},
error: function (xhr, e_status, error) {
console.log("AJAX request failed, reason : " + error);
},
cache: false
});
slot.currentAssembly = fakeAsm("[Processing...]");
updateAsm(slot);
}) (slots[i]);
}
}, 750); // Time in ms after which action is taken (if inactivity)
setSetting('code', cppEditor.getValue());
for (var i = 0; i < slots.length; i++) {
(function(slot) {
updateAsm(slot);
}) (slots[i]);
}
// execute the callback passed to Compiler()
onEditorChangeCallback();
}
function setSource(code) {
cppEditor.setValue(code);
}
function getSource() {
return cppEditor.getValue();
}
function serialiseState(compress) {
console.log("[WINDOW] Serialising state...");
// Memorize informations on slots
var slotIds = []; // necessary only to link with iiffs
var compilersInSlots = [];
var optionsInSlots = [];
slots.forEach(function(slot) {
slotIds.push(slot.id);
compilersInSlots.push(currentCompilerId(slot));
optionsInSlots.push(domRoot.find('#slot'+slot.id+' .compiler_options').val());
});
// Memorize informations on diffs
var diffIds = [];
var slotsInDiffs = [];
diffs.forEach(function(diff) {
diffIds.push(diff.id);
slotsInDiffs.push({before: diff.beforeSlot.id,
after: diff.afterSlot.id});
});
var state = {
slotCount: slots.length,
slotIds: slotIds,
compilersInSlots: compilersInSlots,
optionsInSlots: optionsInSlots,
diffCount: diffs.length,
diffIds: diffIds,
slotsInDiffs: slotsInDiffs
};
if (compress) {
state.sourcez = LZString.compressToBase64(cppEditor.getValue());
} else {
state.source = cppEditor.getValue();
}
return state;
}
function deserialiseState(state, compilers, defaultCompiler) {
console.log("[WINDOW] Deserialising state ...");
if (state.hasOwnProperty('sourcez')) {
cppEditor.setValue(LZString.decompressFromBase64(state.sourcez));
} else {
cppEditor.setValue(state.source);
}
if (slots.length != 0) {
console.log("[WINDOW] Deserialisation : deleting existing slots ...");
while (slots.length != 0) {
delete_and_unplace_slot(slots[slots.length - 1]);
}
}
if (diffs.length != 0) {
console.log("[WINDOW] Deserialisation : deleting existing diffs ...");
while (diffs.length != 0) {
delete_and_unplace_diff(diffs[diffs.length - 1]);
}
}
// Deserialise
console.log("[WINDOW] Deserialisation : deserializing slots...");
for (var i = 0; i < state.slotCount; i++) {
var newSlot = create_and_place_slot(compilers,
defaultCompiler,
state.slotIds[i]);
setCompilerById(state.compilersInSlots[i],newSlot);
domRoot.find('#slot'+newSlot.id+' .compiler_options').val(state.optionsInSlots[i]);
// Somewhat hackily persist compiler into local storage else when the ajax response comes in
// with the list of compilers it can splat over the deserialized version.
// The whole serialize/hash/localStorage code is a mess! TODO(mg): fix
setSetting('compiler'+newSlot.id, state.compilersInSlots[i]);
}
console.log("[WINDOW] Deserialisation : deserializing diffs...");
for (var i = 0; i < state.diffCount; i++) {
var newDiff = create_and_place_diff(state.diffIds[i]);
setDiffButton(newDiff,
"before",
get_slot_by_id(state.slotsInDiffs[i]["before"]));
setDiffButton(newDiff,
"after",
get_slot_by_id(state.slotsInDiffs[i]["after"]));
onDiffChange(newDiff);
}
resizeEditors();
return true;
}
// TODO : split in two functions : one is slot dependant, the other set parameters common to all slots
function updateCompilerAndButtons(slot) {
var compiler = currentCompiler(slot);
domRoot.find('#slot'+slot.id+' .compilerVersion').text(compiler.name + " (" + compiler.version + ")");
var filters = currentFilters();
var supportsIntel = compiler.intelAsm || filters.binary;
domRoot.find('#commonParams .filter button.btn[value="intel"]').toggleClass("disabled", !supportsIntel);
domRoot.find('#commonParams .filter button.btn[value="binary"]').toggleClass("disabled", !compiler.supportsBinary).toggle(OPTIONS.supportsBinary);
domRoot.find('#commonParams .filter .nonbinary').toggleClass("disabled", !!filters.binary);
}
function onCompilerChange(slot) {
console.log("[DEBUG] onCompilerChange called with slot.id = "+slot.id);
onParamChange(slot);
updateCompilerAndButtons(slot);
setAllDiffSlotsMenus();
}
function onDiffChange(diff, fromEditor) {
console.log("[DEBUG] inside onDiffChange with diff id = "+diff.id);
console.log("[DEBUG] inside onDiffChange seen fromEditor = "+fromEditor);
if (fromEditor == false) {
diff.remainingTriggers = 2;
} else {
diff.remainingTriggers = diff.remainingTriggers - 1;
if (diff.remainingTriggers == 0) {
diff.remainingTriggers = 2;
} else {
return null;
}
}
// If one slot is not mentioned, stop before making the ajax request
if (diff.beforeSlot == null || diff.afterSlot == null) {
return null;
}
var data = {
// it should also be possible to use .currentAsembly
before: diff.beforeSlot.asmCodeMirror.getValue(),
after: diff.afterSlot.asmCodeMirror.getValue()
}
$.ajax({
type: 'POST',
url: '/diff',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
//console.log("Success : "+JSON.stringify(result));
onDiffResponse(diff, data, result);
},
error: function (xhr, e_status, error) {
console.log("AJAX request for diff failed, reason : " + error);
},
cache: false
});
}
function onDiffResponse(diff, data, result) {
console.log("[CALLBACK] onDiffResponse() with diff = "+diff.id);
// console.log("[DEBUG] result: "+result);
diff.currentDiff = result.computedDiff;
diff.zones = result.zones;
updateDiff(diff);
}
function updateDiff(diff) {
console.log("[CALLBACK] updateDiff() with diff = " + diff.id);
// console.log("[DEBUG] currentDiff: " + JSON.stringify(diff.currentDiff));
if (!diff.currentDiff) {
return;
}
diff.asmCodeMirror.operation(function () {
diff.asmCodeMirror.setValue(diff.currentDiff);
clearBackground(diff.asmCodeMirror);
});
doc = diff.asmCodeMirror.getDoc();
var computeLineChCoord = buildComputeLineChCoord(diff.currentDiff);
// Same colors as in phabricator's diffs
var cssStyles = ["background-color: rgba(151,234,151,.6);",
"background-color: rgba(251,175,175,.7);"];
colorMarkedZones = [];
for (var i = 0; i<diff.zones.length; i++) {
for (var j = 0; j<diff.zones[i].length; j++) {
colorMarkedZones.push(
doc.markText(computeLineChCoord(diff.zones[i][j].begin),
computeLineChCoord(diff.zones[i][j].end+1),
{css: cssStyles[i]}));
}
}
}
// This function is required to place multiline marks in a CodeMirror
// windows: markText accepts only coordinates in the form (line, column)
// with line and column starting at 1.
function buildComputeLineChCoord(text) {
// assume text is 1 line containing '\n' to break lines
// below calculations are placed outside the function to speed up
var splitedStr = text.split("\n");
for (var i = 0; i<splitedStr.length; i++) {
splitedStr[i] = splitedStr[i] +"\n";
}
var lastPosInLine = [];
var currentSum = splitedStr[0].length - 1;
// console.log("Last pos in line "+0+": "+currentSum);
lastPosInLine.push(currentSum)
for (var i = 1; i < splitedStr.length; i++) {
currentSum = currentSum + splitedStr[i].length;
lastPosInLine.push(currentSum)
// console.log("Last pos in line "+i+": "+currentSum);
}
return function(pos) {
var line = 0;
while(lastPosInLine[line] < pos) {
line = line + 1;
}
if (line == 0) {
var ch = pos;
} else {
var ch = pos - lastPosInLine[line-1] - 1;
}
return {line: line, ch: ch};
}
}
function mapCompiler(compiler) {
if (!compilersById[compiler]) {
// Handle old settings and try the alias table.
compiler = compilersByAlias[compiler];
if (compiler) compiler = compiler.id;
}
return compiler;
}
// added has auxiliary to setCompilers, in order not to break interface
// TODO : consider refactoring as some tasks are repeated
function setCompilersInSlot(compilers, defaultCompiler, slot) {
console.log("[INIT] in setCompilersInSlot(), compilers = "+
JSON.stringify(compilers)+", slot = "+slot.id);
domRoot.find('#slot'+slot.id+' .compilers li').remove();
compilersById = {};
compilersByAlias = {};
// fills the compiler list
$.each(compilers, function (index, arg) {
compilersById[arg.id] = arg;
if (arg.alias) compilersByAlias[arg.alias] = arg;
var elem = $('<li><a href="#">' + arg.name + '</a></li>');
domRoot.find('#slot'+slot.id+' .compilers').append(elem);
(function () {
elem.click(function () {
setCompilerById(arg.id,slot);
onCompilerChange(slot);
});
})(elem.find("a"), arg.id);
});
var compiler = getSetting('compiler'+slot.id);
if (!compiler) {
compiler = defaultCompiler;
compiler = mapCompiler(compiler);
if (!compiler)
console.log("Could not map the default compiler id. " +
"Please double check your configuration file.");
} else {
compiler = mapCompiler(compiler);
if (!compiler)
console.log("Could not map the compiler found in settings. " +
"Please clear your browser cache.");
}
if (compiler) {
setCompilerById(compiler,slot);
}
onCompilerChange(slot);
}
function setCompilers(compilers, defaultCompiler) {
console.log("[INIT] setCompilers() was called with compilers = "+
JSON.stringify(compilers)+", defaultCompiler = "+defaultCompiler);
setLeaderSlotMenu();
for (var i = 0; i < slots.length; i++) {
(function(slot){
setCompilersInSlot(compilers,defaultCompiler,slot);
})(slots[i]);
}
}
function currentFilters() {
return patchUpFilters(filters_);
}
function setFilters(f) {
filters_ = $.extend({}, f);
slots.forEach(function (slot) {
onParamChange(slot);
updateCompilerAndButtons(slot);
});
}
function setEditorHeight(height) {
const MinHeight = 80;
if (height < MinHeight) height = MinHeight;
cppEditor.setSize(null, height);
for (var i = 0; i < slots.length; i++) {
slots[i].asmCodeMirror.setSize(null, height);
}
for (var i = 0; i < diffs.length; i++) {
diffs[i].asmCodeMirror.setSize(null, height);
}
}
// External wrapper used by gcc.js only
function refreshSlot(slot) {
onCompilerChange(slot);
}
// Auxiliary function to slot_use_DOM
function generate_change_callback(slot) {
return function callback() {
onParamChange(slot);
}
}
// Function to call each time a slot is added to the page.
// This function requires that the slot's DOM object already exists.
function slot_use_DOM(slot) {
slot.asmCodeMirror = CodeMirror.fromTextArea(
domRoot.find("#slot"+slot.id+" .asm textarea")[0], {
lineNumbers: true,
mode: "text/x-asm",
readOnly: true,
gutters: ['CodeMirror-linenumbers'],
lineWrapping: true
});
// handle compiler option (slot specific) such as '-O1'
domRoot.find('#slot'+slot.id+' .compiler_options').change(
generate_change_callback(slot)).keyup(
generate_change_callback(slot));
setLeaderSlotMenu();
if (slots.length == 1) {
// "force" menu update if this is the first slot added
var leaderSlotMenuNode = domRoot.find('#commonParams .leaderSlot');
leaderSlotMenuNode.text('leader slot : '+slots[0].id);
}
}
function diff_use_DOM(diff) {
diff.asmCodeMirror = CodeMirror.fromTextArea(
domRoot.find("#diff"+diff.id+" .diffText textarea")[0], {
lineNumbers: true,
mode: "text/x-asm",
readOnly: true,
gutters: ['CodeMirror-linenumbers'],
lineWrapping: true
});
}
function get_slots_ids() {
var ids = [];
for (var i = 0; i < slots.length; i++) {
ids.push(slots[i].id);
}
return ids;
}
// TODO : refactor !
function get_diffs_ids() {
var ids = [];
for (var i = 0; i < diffs.length; i++) {
ids.push(diffs[i].id);
}
return ids;
}
function slot_ctor(optionalId) {
var newSlot = new Slot();
if (optionalId) {
newSlot.id = optionalId;
} else {
newSlot.id = get_available_id(slots);
}
slots.push(newSlot);
setSetting('slotIds',JSON.stringify(get_slots_ids()));
return newSlot;
}
function diff_ctor(optionalId) {
var newDiff = new Diff();
if (optionalId) {
newDiff.id = optionalId;
} else {
newDiff.id = get_available_id(diffs);
}
diffs.push(newDiff);
setSetting('diffIds',JSON.stringify(get_diffs_ids()));
return newDiff;
}
// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
function remove_id_in_array(id, array) {
for(var i = 0; i<array.length; i++) {
if (array[i].id == id) {
array.remove(i);
break;
}
}
}
function slot_dtor(slot) {
removeSetting('compiler'+slot.id);
removeSetting('compilerOptions'+slot.id);
remove_id_in_array(slot.id, slots);
// after the deletion, update the browser's settings :
setSetting('slotIds', JSON.stringify(get_slots_ids()));
setLeaderSlotMenu();
}
function diff_dtor(diff) {
remove_id_in_array(diff.id, diffs);
// after the deletion, update the browser's settings :
setSetting('diffIds', JSON.stringify(get_diffs_ids()));
}
function get_slot_by_id(slotId) {
for (var i = 0; i < slots.length; i++) {
if (slots[i].id == slotId) return slots[i];
}
return null;
}
function get_diff_by_id(diffId) {
for (var i = 0; i < diffs.length; i++) {
if (diffs[i].id == diffId) return diffs[i];
}
return null;
}
function setPanelListSortable() {
// source : JQuery UI examples
var panelList = $('#draggablePanelList');
panelList.sortable({
// Do not set the new-slot button sortable (nor a place to set windows)
items: "li:not(.panel-sortable-disabled)", // source : JQuery examples
// Omit this to make then entire <li>...</li> draggable.
handle: '.panel-heading',
update: function() {
$('.panel', panelList).each(function(index, elem) {
var $listItem = $(elem),
newIndex = $listItem.index();
// Persist the new indices.
});
}
});
}
function slot_DOM_ctor(slot) {
// source : http://stackoverflow.com/questions/10126395/how-to-jquery-clone-and-change-id
var slotTemplate = $('#slotTemplate');
var clone = slotTemplate.clone().prop('id', 'slot'+slot.id);
var last = $('#new-slot');
last.before(clone); // insert right before the "+" button
$('#slot'+slot.id+' .title').text("Slot "+slot.id+" (drag me) ");
$('#slot'+slot.id).show();
$('#slot'+slot.id+' .closeButton').on('click', function(e) {
console.log("[UI] User clicked on closeButton in slot "+slot.id);
var slotToDelete = get_slot_by_id(slot.id);
delete_and_unplace_slot(slotToDelete);
});
setPanelListSortable();
}
function diff_DOM_ctor(diff) {
var diffTemplate = $('#diffTemplate');
var clone = diffTemplate.clone().prop('id', 'diff'+diff.id);
var last = $('#new-diff');
last.before(clone); // insert right before the "+" button
$('#diff'+diff.id+' .title').text("Diff "+diff.id+" (drag me) ");
$('#diff'+diff.id).show();
$('#diff'+diff.id+' .closeButton').on('click', function(e) {
console.log("[UI] User clicked on closeButton in diff "+diff.id);
var diffToDelete = get_diff_by_id(diff.id);
delete_and_unplace_diff(diffToDelete);
});
setPanelListSortable();
setDiffSlotsMenus(diff);
}
function slot_DOM_dtor(slot) {
$('#slot'+slot.id).remove();
}
function diff_DOM_dtor(diff) {
$('#diff'+diff.id).remove();
}
function create_and_place_slot(compilers,defaultCompiler,optionalId) {
var newSlot = slot_ctor(optionalId);
slot_DOM_ctor(newSlot);
slot_use_DOM(newSlot);
setCompilersInSlot(compilers, defaultCompiler, newSlot);
return newSlot;
}
function create_and_place_diff(optionalId) {
var newDiff = diff_ctor(optionalId);
diff_DOM_ctor(newDiff);
diff_use_DOM(newDiff);
return newDiff;
}
function create_and_place_diff_UI(optionalId) {
var newDiff = create_and_place_diff(optionalId);
if (slots.length > 0) {
setDiffButton(newDiff,"before",slots[0]);
if (slots.length > 1) {
setDiffButton(newDiff,"after",slots[1]);
} else {
setDiffButton(newDiff,"after",slots[0]);
}
onDiffChange(newDiff);
}
return newDiff;
}
function delete_and_unplace_slot(slot) {
slot_DOM_dtor(slot);
slot_dtor(slot);
}
function delete_and_unplace_diff(diff) {
diff_DOM_dtor(diff);
diff_dtor(diff);
}
// refresh (or place) the two drop-down lists of the diff panel containging
// descriptions of the slots that can be used to make a diff
// Auxilary to setDiffSlotsMenus and deserialisation
function setDiffButton(diff, className, slot) {
// className can be "before" or "after"
if (slot == null) {
console.log("[DEBUG] setDiffButton: diff.id = " +
diff.id + ", "+className+" -> "+"null.id");
} else {
console.log("[DEBUG] setDiffButton: diff.id = " +
diff.id + ", "+className+" -> "+slot.id);
}
diff[className+'Slot'] = slot;
if (slot != null) {
var diffSlotMenuNode = domRoot.find('#diff'+diff.id+' .'+className+' .slot');
diffSlotMenuNode.text('\''+className+'\' slot : '+slot.id);
setSetting('diff'+diff.id+className,slot.id);
add_to_pendings(slot, diff);
}
}
function setDiffSlotsMenus(diff) {
// className can be "before" or "after"
function setSlotMenu(className) {
console.log("[DEBUG] : setSlotMenu with "+className+
" and diff.id = "+diff.id);
domRoot.find('#diff'+diff.id+' .'+className+' li').remove();
for (var i = 0; i < slots.length; i++) {
var elem = $('<li><a href="#">' + slots[i].id + '</a></li>');
domRoot.find('#diff'+diff.id+' .'+className+' .slots').append(elem);
(function (i) {
elem.click(function () {
// TODO : check if modifying diff with [] survive
// minifying http://stackoverflow.com/questions/4244896/
console.log("[UI] user set "+slots[i].id+" as "+className+
" slot in diff with id "+diff.id);
setDiffButton(diff, className, slots[i]);
//diff[className+'Slot'] = slots[i];
//var diffSlotMenuNode = domRoot.find('#diff'+diff.id+' .'+className+' .slot');
//diffSlotMenuNode.text('\''+className+'\' slot : '+slots[i].id);
onDiffChange(diff);
});
})(i);
}
}
setSlotMenu("before");
setSlotMenu("after");
}
// refresh (or place) all of the drop-down lists containging descriptions of
// the slots that can be used to make a diff
function setAllDiffSlotsMenus() {
console.log("[DEBUG] : inside setAllDiffSlotsMenus, diffs.length = "+diffs.length);
for (var i = 0; i<diffs.length; i++) {
setDiffSlotsMenus(diffs[i]);
}
}
// on startup, for each slot,
// if a setting is defined, set it on static/index.html page
var slotIds = getSetting('slotIds');
if (slotIds) {
console.log("[STARTUP] found slot data : restoring from previous session");
slotIds = JSON.parse(slotIds);
for (var i = 0; i < slotIds.length; i++) {
var newSlot = slot_ctor(slotIds[i]);
slot_DOM_ctor(newSlot);
slot_use_DOM(newSlot);
if (getSetting('compilerOptions'+slotIds[i])) {
domRoot.find('#slot'+newSlot.id+' .compiler_options').val(getSetting('compilerOptions'+newSlot.id));
} else {
console.log("[STARTUP] There was a problem while restoring previous session.");
}
}
setSetting('leaderSlot', slots[0].id);
}
// on startup, for each diff,
// if a setting is defined, set it on static/index.html page
var diffIds = getSetting('diffIds');
if (diffIds) {
console.log("[STARTUP] found diff data : restoring from previous session");
diffIds = JSON.parse(diffIds);
for (var i = 0; i < diffIds.length; i++) {
var newDiff = create_and_place_diff(diffIds[i]);
var beforeSlot = get_slot_by_id(getSetting('diff'+newDiff.id+"before"));
setDiffButton(newDiff, "before", beforeSlot);
var afterSlot = get_slot_by_id(getSetting('diff'+newDiff.id+"after"));
setDiffButton(newDiff, "after", afterSlot);
//onDiffChange(newDiff);
}
}
return {
serialiseState: serialiseState,
deserialiseState: deserialiseState,
setCompilers: setCompilers,
getSource: getSource,
setSource: setSource,
setFilters: setFilters,
setEditorHeight: setEditorHeight,
setCompilersInSlot : setCompilersInSlot,
create_and_place_slot: create_and_place_slot,
create_and_place_diff_UI: create_and_place_diff_UI,
refreshSlot: refreshSlot
};
}