function Debugger(sel) {
var dbg = this;
var $elt = this.$elt = $(sel);
var perlVarPopoverDelay = 400; // Time in ms the mouse has to rest over a variable before it gets the value
this.filewindowDiv = $elt.find('div#filewindow');
this.stackDiv = $elt.find('div#stack');
this.watchDiv = $elt.find('div#watch-expressions');
// templates
Handlebars.registerHelper('ifDefined', function(val, options) {
if ((val !== undefined) && (val !== null) && (val !== '')) {
return options.fn(this)
} else {
return options.inverse(this);
}
});
Handlebars.registerPartial('breakpoint-code-template', $('#breakpoint-code-template').html() );
Handlebars.registerPartial('action-code-template', $('#action-code-template').html() );
Handlebars.registerPartial('breakpoint-right-click-template', $('#breakpoint-right-click-template').html() );
this.templates = {
fileTab: Handlebars.compile( $('#file-tab-template').html() ),
navTab: Handlebars.compile( $('#nav-tab-template').html() ),
navPane: Handlebars.compile( $('#nav-pane-template').html() ),
currentSubAndArgs: Handlebars.compile( $('#current-sub-and-args-template').html() ),
breakpointRightClickMenu: Handlebars.compile( $('#breakpoint-right-click-template').html() ),
saveLoadBreakpointsModal: Handlebars.compile($('#save-load-breakpoints-modal-template').html() ),
subPickerTemplate: Handlebars.compile($('#sub-picker-template').html() ),
quickBreakpointModal: Handlebars.compile($('#quick-breakpoint-entry-template').html() ),
};
// The step in, over, run buttons
var $control_buttons = this.$control_buttons = $('.control-button').attr('disabled',true);
var restInterface = this.restInterface = new RestInterface('');
function whenStackTabIsShown($elt, cb) {
var stackId = $elt.closest('.tab-pane').attr('id'),
relatedTab = $('#stack-tabs a[href="#'+stackId+'"]');
relatedTab.on('shown.whenStackTabIsShown', function(e) {
// This cb is fired both when the active tab is switched, and during
// mouseover of the tab - the latter may be a bug in Bootstrap
// relatedTarget is true when the anchor was actually clicked, and
// refers to what was previously the active tab.
// relatedTarget is undefined during mouseover of the anchor
if (e.relatedTarget) {
relatedTab.off('shown.whenStackTabIsShown');
cb();
}
});
}
// Called for each .managed-height element when the window resizes
// Set the height so the the bottom of the element is the bottom of the
// window. This makes the scroll bar work for that element
function setElementHeight() {
var $elt = $(this),
offset = $elt.offset(),
height;
if ($elt.hasClass('program-code-container') && offset && offset.top === 0) {
// This one is invisible. Find which code pane is visible and copy it's height
height = $('.program-code-container')
.filter(function() { return $(this).css('display') === 'block' })
.css('height');
} else {
// setting the element's height does not account for any padding
// .css('padding-top') returns a string like "14px", so we need to slice off
// the last 2 chars
var padding = parseInt( $elt.css('padding-top').slice(0, -2))
+
parseInt( $elt.css('padding-bottom').slice(0, -2));
height = ( $(window).height() - $elt.offset().top - padding ) + 'px';
}
$elt.css({'height': height});
};
function _tab_mapper(a) { return [ a, parseInt(a.getAttribute('data-frameno')) ]; }
function _tab_sorter(a,b) { return ( a[1] - b[1] ) }
function _tab_unmapper(a) { return a[0] }
this.stackFrameChanged = function(frame_obj, old_frameno, new_frameno) {
var $tabs = this.stackDiv.find('ul.nav'),
$tab = $tabs.find('#tab-' + frame_obj.serial),
$panes = this.stackDiv.find('div.tab-content'),
$codePane = $panes.find('#pane-' + frame_obj.serial);
var insertStackFrameForLevel = function($tab_elt, $pane_elt) {
var tab_elts_in_frame_order = $tabs.children().get().map(_tab_mapper).sort(_tab_sorter).map(_tab_unmapper),
elt_frameno = $tab_elt.attr('data-frameno'),
i;
if (tab_elts_in_frame_order.length == 0
|| elt_frameno > parseInt(tab_elts_in_frame_order[ tab_elts_in_frame_order.length - 1].getAttribute('data-frameno'))
) {
// This new one goes after the last one
$tabs.append($tab_elt);
} else {
for(i = 0; i < tab_elts_in_frame_order.length; i++) {
if (elt_frameno <= parseInt(tab_elts_in_frame_order[i].getAttribute('data-frameno'))) {
$tab_elt.insertBefore(tab_elts_in_frame_order[i]);
break;
}
}
}
if ($pane_elt) {
$panes.append($pane_elt);
}
};
if (new_frameno === null) {
// a frame got removed
$tab.remove();
$codePane.remove();
$(document).trigger('codePaneRemoved', $codePane);
} else if (old_frameno === null) {
// This is a brand new frame
var is_string_eval = (frame_obj.subname === '(eval)') && (frame_obj.evaltext !== null);
$tab = $( this.templates.navTab({
serial: frame_obj.serial,
frameno: new_frameno,
longlabel: frame_obj.subroutine,
label: frame_obj.subname,
filename: frame_obj.filename,
lineno: frame_obj.line,
is_string_eval: is_string_eval,
wantarray: frame_obj.sigil,
active: $tabs.children().length == 0
}))
.tooltip();
$codePane = $( this.templates.navPane({
serial: frame_obj.serial,
frameno: new_frameno,
filename: frame_obj.filename,
active: $panes.children().length == 0
}));
insertStackFrameForLevel($tab, $codePane);
setElementHeight.call($codePane.find('.managed-height'));
var needs_args = 1;
$tab.find('a').on('click', function() {
if (needs_args) {
needs_args = 0;
dbg.stackManager.updateFrameWithArgs(frame_obj)
.fail(function(jqxhr, text_status, error_thrown) {
alert('Error getting details for stack frame '+frameobj.frameno+': '+error_thrown);
})
.done(function(frame_obj) {
var subArgs = frame_obj.args.map(function(arg) {
var can_render = (typeof(arg) === 'object') && (arg !== null) && ('render' in arg);
return (can_render ? arg.render('condensed') : arg);
});
$codePane.find('.current-sub-and-args')
.append( dbg.templates.currentSubAndArgs({ subroutine: frame_obj.subname, subArgs: subArgs }));
});
}
});
this.fileManager.loadFile(frame_obj.filename)
.done(function($codeTableElt) {
var $copy = $codeTableElt.clone();
dbg.breakpointManager.markCodePaneLineNumbersForBreakpointsAndActions($copy);
$codePane.find('.program-code-container').append($copy);
setCurrentLineForCodeTable($copy, frame_obj.line);
});
} else {
// This frame existed before
if (old_frameno !== new_frameno) {
// It changed levels
$tab.detach();
$tab.attr('data-frameno', new_frameno);
$codePane.attr('data-frameno', new_frameno);
insertStackFrameForLevel($tab);
}
// TODO: only change the tab's title if the line changed
// update the tab tooltip
var title = $tab.prop('title');
title.replace(/\d+$/, frame_obj.line);
$tab.prop('title', title);
// Update the line in the code pane
var codeTable = $codePane.find('.program-code');
setCurrentLineForCodeTable(codeTable, frame_obj.line);
}
};
var _scrollCodeTableLineIntoView = function(codeTable, line) {
if (line == undefined) {
return; // Can happen during cleanup when the program is exiting
}
var activeLine = codeTable.find('.code-line:nth-child('+line+')');
if (activeLine.length === 0) {
// Bad info. This can happen when the program terminates, line is
// 0 and activeLine.length === 0
return;
}
// Find out if it's visible
var container = codeTable.closest('div.managed-height');
var containerTop = container.offset().top;
var containerBottom = containerTop + container.height();
var elemTop = activeLine.offset().top;
var elemBottom = elemTop + activeLine.height();
var isVisible = ((elemBottom >= containerTop) && (elemTop <= containerBottom)
&& (elemBottom <= containerBottom) && (elemTop >= containerTop) );
if (!isVisible) {
//var overShoot = (elemBottom < containerTop) ? -4 : 4;
// Make it the 4th line from the top
var overShoot = -4;
container.scrollTo(activeLine, { over: overShoot });
}
return activeLine;
};
function scrollActiveCodeTableLineIntoView(codeTable, line) {
var scrollCodeTable = function() {
_scrollCodeTableLineIntoView(codeTable, line);
};
var codeTableTabPane = codeTable.closest('.tab-pane');
if (codeTableTabPane.hasClass('active')) {
// this is the currently active tab, scroll it now
scrollCodeTable();
} else {
// Not the active tab - arrange for it to scroll the first time it becomes active
whenStackTabIsShown(codeTableTabPane, scrollCodeTable);
}
}
// Set the given line "active" (hightlited) and scroll it into view
// if necessary
setCurrentLineForCodeTable = function(codeTable, line) {
if (line == undefined) {
return; // Can happen during cleanup when the program is exiting
}
codeTable.find('.active').removeClass('active');
var activeLine = codeTable.find('.code-line:nth-child('+line+')').addClass('active');
scrollActiveCodeTableLineIntoView(codeTable, line);
};
function setCurrentLineForCodeTableEvent(e) {
var $codeTable = $(e.currentTarget).siblings('.program-code-container').find('.program-code'),
currentLine = $codeTable.find('.code-line.active').attr('data-lineno');
setCurrentLineForCodeTable($codeTable, currentLine);
}
this.run = function() {
};
// This function is called after each control button has returned control
// to us (step, run, etc). The stack tabs will already be updated, but
// enything else that needs updated goes in here.
function done_after_stack_update(next_statement) {
$control_buttons.attr('disabled', false);
$('#stack-tabs a:first').trigger('click');
this.watchedExpressionManager.updateExpressions();
this.setCurrentStatementForCodeTable(next_statement);
}
function notifyProgramHasCompletelyExited() {
$control_buttons.attr('disabled', true);
restInterface.disconnect();
$('<span class=alert>Debugged program has exited</span>')
.appendTo('#controls');
alert('Debugged program has exited');
}
var $original_line_elt;
var original_line_of_code = '';
this.setCurrentStatementForCodeTable = function(next_statement) {
var $topLevelCodePane = $('div.tab-pane[data-frameno=0]'),
$line = $topLevelCodePane.find('.code-line.active span.code'),
stackFrame = this.stackManager.frame(0),
filename = stackFrame.filename,
lineno = stackFrame.line,
this_line_code = $line.text();
if ($original_line_elt) {
$original_line_elt.html(original_line_of_code);
$original_line_elt = undefined;
}
if (next_statement) {
// Basic chars that need to be escaped (but not parens)
var next_statement_re = next_statement.replace(/([.*+?^=!:${}|\[\]\/\\])/g, "\\$1");
// The deparsed next_statement can have whitespace differences from the original
next_statement_re = next_statement_re.replace(/\s+/g, '\\s*');
// parens can also be optional, maybe with whitespace before/after
next_statement_re = next_statement_re.replace(/\(/g, '\\s*\\(?\\s*');
next_statement_re = next_statement_re.replace(/\)/g, '\\s*\\)?\\s*');
// qq(...) is the same as "..."
next_statement_re = next_statement_re.replace(/(?:"|qq\\s\*\\\()(.*)(?:"|\\\))/, function(quoted, str) {
return '(?:"|qq\\s*\\(?:\(|\{|\[|\/))' + str + '(?:"|qq\\(?:\)|\}|\]|\/))';
});
next_statement_re = RegExp(next_statement_re);
// Wrap a <span> around the next_statement
var matched = false;
var marked_up_this_line_code = this_line_code.replace(next_statement_re, function (code) {
matched = true;
return '<span class="next-statement">' + code + '</span>' });
if (matched) {
// save the original look of the current line so we can restore it next time we're
// back in this function
$original_line_elt = $line;
original_line_of_code = $line.html();
// Write the marked up line back into the window
$line.html(marked_up_this_line_code);
} else {
console.warn("didn't find next_statement on line " + lineno + ": " + next_statement);
}
}
};
this.controlButtonClicked = function(e) {
e.preventDefault();
$control_buttons.attr('disabled', true);
var button_action = $(e.currentTarget).attr('data-action'),
rest_method = this.restInterface[button_action],
response_handler,
d;
if (button_action == 'exit') {
response_handler = function() { $elt.trigger('hangup') };
} else if (rest_method) {
response_handler = this.handleControlButtonResponse.bind(this);
}
d = rest_method.call(this.restInterface);
d.done(response_handler);
};
this.handleControlButtonResponse = function(response) {
var events = [],
watchedExpressionManager = this.watchedExpressionManager;
if (response.events) {
events = response.events.map(function(event) {
return new ProgramEvent(event, watchedExpressionManager);
});
}
this.stackManager.update()
.progress(this.stackFrameChanged.bind(this))
.done( done_after_stack_update.bind(this, response.next_statement) );
events.forEach(function(event) {
event.render($elt)
.done(function(button) {
if (button == 'exit') {
restInterface.exit()
.done($elt.trigger('hangup'));
}
});
});
};
// Called by popoverPerlVar to draw the resulting data
var $perlVarPopover = null;
function drawPerlPopover(data, $prior_elt) {
if (! $prior_elt.hasClass('hovered-perl-var')) {
return; // User moved the pointer before we got the response
}
var perl_value = PerlValue.parseFromEval(data),
popover_args;
if (! $perlVarPopover) {
return;
}
popover_args = { trigger: 'manual',
placement: 'bottom',
html: true,
container: $elt,
content: perl_value.renderValue()
};
var header = perl_value.renderHeader();
if (header && (header.length > 0)) {
popover_args.title = header;
}
$perlVarPopover.popover(popover_args)
.popover('show');
}
// Event for when the user hovers over a perl variable in the code
function popoverPerlVar(e) {
var $elt = $(e.currentTarget),
eval = $elt.attr('data-eval'),
stack_level = $elt.closest('.tab-pane').attr('data-frameno'),
timer = $elt.data('timerid');
if (timer != null) {
// mouseout before the timer fired - remove the timer
window.clearTimeout(timer);
}
// Remove any previous hover var elements
$('.hovered-perl-var').removeClass('hovered-perl-var');
if ($perlVarPopover) {
$perlVarPopover.popover('destroy');
}
$elt.addClass('hovered-perl-var');
// Is this an indexed part of a variable
var also = $elt.attr('data-part-of');
if (also) {
// hilite it, too
$elt.siblings('[data-eval="'+also+'"]').addClass('hovered-perl-var');
}
$perlVarPopover = $elt;
var timer = window.setTimeout(function() {
if ($perlVarPopover) {
$perlVarPopover.data('timerid', null);
restInterface.getVarAtLevel(eval, stack_level)
.done(function(data) { drawPerlPopover(data, $elt) });
}
}, perlVarPopoverDelay);
$perlVarPopover.data('timerid', timer);
}
function removePopoverPerlVar (e) {
var $target = $(e.target),
part_of = $target.attr('data-part-of');
if (part_of !== undefined) {
$target = $target.add('[data-eval="'+part_of+'"]');
}
$target.removeClass('hovered-perl-var');
if ($perlVarPopover) {
$perlVarPopover.popover('destroy');
$perlVarPopover = undefined;
}
}
function addPerlVarToWatchExpressions(e) {
var $target = $(e.target),
expr = $target.attr('data-eval');
e.preventDefault();
this.watchedExpressionManager.addExpression(expr, false);
}
var $lastControlKeyElement = $control_buttons.filter('[data-action="stepin"]');
function setKeybindings() {
// Seems that divs can't receive key events, so we can't bind the
// handler to this.elt :(
$(document).keypress(function(e) {
if ($(e.target).closest('form').length) {
// Don't handle events for elements like text inputs inside forms
return;
}
if (e.ctrlKey || e.altKey || e.metaKey) {
// Don't process when these modifier keys are used
// Shift as a modifier is accounted for in e.which
return;
}
switch (e.which) {
case 115: //s
$lastControlKeyElement = $control_buttons.filter('[data-action="stepin"]')
.click();
e.stopPropagation();
break;
case 110: // n
$lastControlKeyElement = $control_buttons.filter('[data-action="stepover"]')
.click();
e.stopPropagation();
break;
case 114: // r
$control_buttons.filter('[data-action="stepout"]')
.click();
e.stopPropagation();
break;
case 99: // c
$control_buttons.filter('[data-action="continue"]')
.click();
e.stopPropagation();
break;
case 113: // q
control_buttons.filter('[data-action="exit"]')
.click();
e.stopPropagation();
break;
case 13: // cr
$lastControlKeyElement.click();
e.stopPropagation();
break;
case 120: // x
$('#add-watch-expr').click();
e.stopPropagation();
e.preventDefault();
break;
case 46: // .
dbg.stackDiv.find('.tab-pane.active .current-sub-and-args').click();
e.stopPropagation();
break;
case 102: // f
$('#add-file').click();
e.stopPropagation();
break;
case 76: // L
$('#breakpoint-container-handle').click();
e.stopPropagation();
break;
case 66: //B
dbg.stackDiv.find('.tab-pane.active .code-line.active span.lineno').click();
e.stopPropagation();
break;
case 98: //b
dbg.quickBreakpointDialog();
e.stopPropagation();
break;
}
});
}
var quick_bp_dialog_open = false;
function breakpoint_and_continue_to_dialog_handler(params) {
if (quick_bp_dialog_open) {
return;
}
quick_bp_dialog_open = true;
var modal = $(this.templates.quickBreakpointModal(params['dialog']));
modal.appendTo($elt)
.modal({ backdrop: true, keyboard: true, show: true })
.on('hidden', function() {
quick_bp_dialog_open = false;
modal.remove();
})
.keyup(function(e) {
if (e.keyCode == 27) { // escape - abort editing
modal.modal('hide');
return false;
}
})
.find('input')
.focus();
modal.find('form')
.submit(function(e) {
e.preventDefault();
var bp_text = $(e.target).find('[name=breakpoint]').val();
parseQuickBreakpointText(bp_text)
.done(params['done'])
.fail(function(message) {
alert(message);
})
.always(function() {
modal.modal('hide');
});
});
}
function QB_isCurrentLine(text) {
var d = $.Deferred();
if (text.match(/^\.$/)) { // A single period
var current_frameno = $('div#stack-panes .tab-pane.active').attr('data-frameno'),
stack_frame = dbg.stackManager.frame(current_frameno);
d.resolve(stack_frame.filename, stack_frame.line);
} else {
d.reject(undefined);
}
return d;
}
function QB_isLineNumber(text) {
var d = $.Deferred();
var matches = text.match(/^\d+$/); // match only digits - a line number in the current stackframe's file
if (matches) {
var line = dbg.stackDiv.find('.tab-pane.active .code-line[data-lineno='+text+']');
if (line.length == 0) {
d.reject('No such line number in the current file');
} else if (line.hasClass('unbreakable')) {
d.reject('Line ' + text + ' is not breakable');
} else {
var file = dbg.stackDiv.find('.tab-pane.active .program-code-container').attr('data-filename');
d.resolve(file, text);
}
} else {
d.reject(undefined);
}
return d.promise();
}
function QB_isFQSubroutine(text) {
var d = $.Deferred();
var matches = text.match(/^(.+?::\w+)(?::(\d+))?$/); // match "package::subname" or "package::subname:line"
if (matches) {
var subname = matches[1],
line = matches[2];
restInterface.subInfo(subname)
.done(function(data) {
_QB_findBreakableLineInSub(line, d, data);
})
.fail(function() {
d.reject(undefined);
})
} else {
d.reject(undefined);
}
return d.promise();
}
function QB_isSubroutine(text) {
var d = $.Deferred();
var matches = text.match(/^(.+?)(?::(\d+))?$/); // match either "string" or "string:numbers"
if (matches) {
var subname = matches[1],
line = matches[2],
current_frameno = $('div#stack-panes .tab-pane.active').attr('data-frameno'),
stack_frame = dbg.stackManager.frame(current_frameno),
current_package = stack_frame.package;
restInterface.subInfo(current_package + '::' + subname)
.done(function(data) {
_QB_findBreakableLineInSub(line, d, data);
})
.fail(function(data) {
d.reject(undefined);
});
} else {
d.reject(undefined);
}
return d.promise();
}
function _QB_findBreakableLineInSub(requested_line_str, deferred, subinfo_data) {
var start_breakable_search,
sub_start_line = parseInt(subinfo_data.line, 10),
sub_end_line = parseInt(subinfo_data.end, 10),
requested_line = parseInt(requested_line_str, 10);
if (requested_line_str) { // They wanted to stop on a particular line
var sub_length = sub_end_line - sub_start_line;
if (requested_line > sub_length) {
deferred.reject(requested_line_str + ' is outside the range of sub ' + subinfo_data.subroutine);
}
start_breakable_search = sub_start_line + requested_line;
if (dbg.fileManager.isBreakable(subinfo_data.filename, sub_start_line)) {
// if the sub's first line is breakable, then line "1" of the sub
// is the same as the sub's start line
start_breakable_search = start_breakable_search - 1;
}
} else {
start_breakable_search = sub_start_line;
}
for(var break_on_line = start_breakable_search; break_on_line < subinfo_data.end; break_on_line++) {
if (dbg.fileManager.isBreakable(subinfo_data.filename, break_on_line)) {
deferred.resolve(subinfo_data.filename, break_on_line);
}
}
deferred.reject("Couldn't file a breakable line within '"
+ subinfo_data.subroutine
+ '. Tried line ' + requested_line + ' to the end of the function');
}
function QB_isFile(text) {
var d = $.Deferred();
var matches = text.match(/^(.+?):(\d+)$/); // match "string:numbers"
if (matches) {
var filename = matches[1],
line = matches[2];
if (dbg.fileManager.isLoaded(filename)) {
if (dbg.fileManager.isBreakable(filename, line)) {
d.resolve(filename, line);
} else {
d.reject(filename + ':' + line + ' is not breakable');
}
} else {
d.reject(undefined);
}
} else {
d.reject(undefined);
}
return d.promise();
}
function QB_unparsable(text) {
var d = $.Deferred();
d.reject("Could not parse breakpoint '" + text + "'");
return d.promise();
}
var quick_breakpoint_resolvers = [
QB_isCurrentLine,
QB_isLineNumber,
QB_isFQSubroutine,
QB_isSubroutine,
QB_isFile,
QB_unparsable,
];
function parseQuickBreakpointText(text) {
var d = $.Deferred();
function tryBreakpointResolver(i) {
var resolver = quick_breakpoint_resolvers[i],
p = resolver(text);
p.done(function(filename, line) {
d.resolve(filename, line); // try just putting d in the done()?
})
.fail(function(message) {
if (message) {
d.reject(message);
} else {
tryBreakpointResolver(i + 1);
}
})
}
tryBreakpointResolver(0);
return d.promise();
}
this.quickBreakpointDialog = function() {
breakpoint_and_continue_to_dialog_handler.call(this, {
dialog: {
title: 'Quick-add Breakpoint',
prompt: 'New breakpoint',
ok_button: 'Add'
},
done: function(filename, line) {
dbg.breakpointManager.createBreakpoint({filename: filename, line: line});
}
});
};
this.continueToDialog = function() {
breakpoint_and_continue_to_dialog_handler.call(this, {
dialog: {
title: 'Continue To...',
prompt: 'Continue to',
ok_button: 'Continue'
},
done: function(filename, line) {
restInterface.createBreakpoint({
filename: filename,
line: line,
once: 1,
code: '1'
});
$control_buttons.filter('[data-action="continue"]')
.click();
}
});
};
var $breakpointPane = $('#breakpoint-container'),
$breakpointToggleIcon = $('#breakpoint-container-handle-icon');
function breakpointPaneIsExtended(value) {
if (value === undefined) {
return $breakpointPane.hasClass('extended');
} else if (value) {
$breakpointPane.addClass('extended');
} else {
$breakpointPane.removeClass('extended');
}
}
function toggleBreakpointContainer(e) {
if (breakpointPaneIsExtended()) {
$breakpointPane.animate(
{ width: 0 },
'fast',
function() {
breakpointPaneIsExtended(false);
$breakpointToggleIcon.removeClass('icon-chevron-left').addClass('icon-chevron-right');
});
} else {
breakpointPaneIsExtended(true);
$breakpointPane.animate(
{ width: $('#side-tray').width() },
'fast',
function() {
$breakpointToggleIcon.removeClass('icon-chevron-right').addClass('icon-chevron-left');
});
}
}
// Set up handlers for moving the border between the code pane and the watch window
function setupResizeWatchPane() {
var $divider = $('#drag-divider'),
$controls = $('#controls'),
handleWidth = $('#breakpoint-container-handle').outerWidth(),
$ghostDivider;
$(document).on('mousedown.resize', '#drag-divider', function(e) {
e.preventDefault();
var dividerOffset = $divider.offset();
$ghostDivider = $('<div id="ghost-divider">')
.css({
height: $divider.outerHeight(),
top: dividerOffset.top,
left: dividerOffset.left
})
.appendTo('body');
var minLeft = $controls.offset().left + $controls.width();
$(document).on('mousemove.resize', function(e) {
if (e.pageX <= minLeft) {
// Don't let it get smaller than the control buttons width
return;
}
$ghostDivider.css('left', e.pageX+2);
});
});
$(document).on('mouseup.resize', function(e) {
if ($ghostDivider) {
var width = $(window).width() - $ghostDivider.offset().left;
$('#columnator').css('padding-right', '' + width + 'px');
$divider.next()
.css({ width: width - 1 - handleWidth,
'margin-right': 1 - width + handleWidth });
if (breakpointPaneIsExtended()) {
$breakpointPane.css('width', $('#side-tray').width());
}
$ghostDivider.remove();
$(document).off('mousemove.resize');
$ghostDivider = undefined;
}
});
}
// Called when the "+" button in the file tab is clicked
function pickFileToLoad(e) {
var fileManager = this.fileManager,
that = this,
modal;
function generateNewTabId() {
var id = 'stack'; // A tab that will always exist to force a pick
while( $('#file-content #'+id).length) {
id = Math.floor(Math.random()*1000000);
}
return id;
}
function renderNewFile($codeElt, filename, line) {
var tabId = generateNewTabId(),
$newTab = $( that.templates.fileTab({ id: tabId, label: filename, closable: true})),
$container = $( that.templates.navPane({ serial: tabId, filename: filename, banner: filename })),
$copy = $codeElt.clone().attr('id', tabId);
// add the new nav tab
$('#add-file').closest('li').before($newTab)
// add the new nav pane contents
dbg.breakpointManager.markCodePaneLineNumbersForBreakpointsAndActions($copy);
$container.find('.program-code-container').append($copy);
$('#file-content').append($container);
// Make the new tab active
$newTab.find('a').click();
// Set its height
setElementHeight.call($container.find('.managed-height'));
// Scroll the requested sub into view
_scrollCodeTableLineIntoView($copy, line);
}
function getSubInfo(pkg, subname) {
var fq_name = pkg + '::' + subname;
modal.modal('hide');
restInterface.subInfo(fq_name)
.done(function(subinfo) {
fileManager.loadFile(subinfo.filename)
.done(function($codeElt) {
renderNewFile($codeElt, subinfo.filename, subinfo.line);
})
.fail(function(jqxhr, text_status, error_thrown) {
alert('Loading file '+subinfo.filename+' failed: '+error_thrown)
});
})
.fail(function(jqxhr, text_status, error_thrown) {
alert('Getting subroutine info for '+fq_name+' failed: '+error_thrown);
});
}
modal = $( this.templates.subPickerTemplate())
.appendTo($elt)
.modal({ backdrop: true, keyboard: true, show: true })
.on('hidden', function() { modal.remove() })
.filePicker({ picked: getSubInfo, restInterface: restInterface });
}
function removeLoadedFile(e) {
var $tabLi = $(e.currentTarget).closest('li'),
tabId = $tabLi.find('a').attr('href'),
$filePane = $(tabId),
prevTabLink = $tabLi.prev().find('a');
prevTabLink.click();
$tabLi.remove();
$filePane.remove();
}
function _loadConfig_response_handler_for_saveLoadBreakpoints(settings) {
dbg.breakpointManager.sync();
if (settings['additional'] && settings['additional']['watch_expressions']) {
var watch_expressions = settings['additional']['watch_expressions'];
for (var i = 0; i < watch_expressions.length; i++) {
dbg.watchedExpressionManager.addExpression( watch_expressions[i], false);
}
}
}
this.defaultConfigFileName = function() {
return this.programName + '.hdb';
};
function saveLoadBreakpoints(e) {
e.preventDefault();
var isSave = $(e.currentTarget).attr('id') === 'save-breakpoints',
savefile = dbg.defaultConfigFileName(),
modal = $(this.templates.saveLoadBreakpointsModal({
action: isSave ? 'Save' : 'Load',
filename: savefile
}));
modal.appendTo($elt)
.modal({ backdrop: true, keyboard: true, show: true })
.focus()
.on('hidden', function() { modal.remove() });
modal.find('form')
.submit(function(e) {
e.preventDefault();
var ri_method = restInterface[isSave ? 'saveConfig' : 'loadConfig'];
additional = {};
if (isSave) {
additional['watch_expressions'] = dbg.watchedExpressionManager.expressions();
console.log(additional);
}
ri_method.call(restInterface, savefile, additional)
.fail(function(jqxhr, text_status, error_thrown) {
alert((isSave ? 'Saving' : 'Loading')
+ ' config failed: ' + error_thrown);
})
.done(function(settings) {
if (! isSave) {
_loadConfig_response_handler_for_saveLoadBreakpoints(settings)
}
})
.always(function() {
modal.modal('hide').remove();
});
});
}
// Initialization
this.restInterface.programName()
.done(function(program_name) {
$elt.find('li#program-name a').html(program_name)
.attr('title', program_name);
dbg.programName = program_name;
})
.done(function() {
// This has to happen after the above where it sets the programName
dbg.restInterface.loadConfig(dbg.defaultConfigFileName())
.done(function(settings) {
_loadConfig_response_handler_for_saveLoadBreakpoints(settings);
});
});
this.fileManager = new FileManager(this.restInterface);
this.breakpointManager = new BreakpointManager(this.$elt, this.restInterface);
this.watchedExpressionManager = new WatchedExprManager(this.restInterface);
this.watchedExpressionManager.loadWatchpoints();
this.stackManager = new StackManager(this.restInterface);
this.stackManager.initialize()
.progress(this.stackFrameChanged.bind(this))
.done(function(status) {
done_after_stack_update.call(dbg);
dbg.setCurrentStatementForCodeTable(status.next_statement);
});
// Events
$elt.on('click', '.control-button[name="stepin"][disabled!="disabled"]', this.controlButtonClicked.bind(this))
.on('click', '.control-button[name="stepout"][disabled!="disabled"]', this.controlButtonClicked.bind(this))
.on('click', '.control-button[name="stepover"][disabled!="disabled"]', this.controlButtonClicked.bind(this))
.on('click', '.control-button[name="continue"][disabled!="disabled"]', this.controlButtonClicked.bind(this))
.on('click', '.control-button[name="exit"][disabled!="disabled"]', this.controlButtonClicked.bind(this))
.on('click', '.control-button[name="continue-to"][disabled!="disabled"]', this.continueToDialog.bind(this))
.on('mouseenter', '.popup-perl-var', popoverPerlVar.bind(this))
.on('mouseleave', '.popup-perl-var', removePopoverPerlVar.bind(this))
.on('contextmenu', '.popup-perl-var', addPerlVarToWatchExpressions.bind(this))
.on('click', '.current-sub-and-args', setCurrentLineForCodeTableEvent.bind(this))
.on('hangup', notifyProgramHasCompletelyExited.bind(this))
.on('click', '#add-file', pickFileToLoad.bind(this))
.on('click', '.remove-loaded-file', removeLoadedFile.bind(this))
.on('click', '.save-load-breakpoints', saveLoadBreakpoints.bind(this));
$('#breakpoint-container-handle').click(toggleBreakpointContainer.bind(this));
setKeybindings();
setupResizeWatchPane();
$('.managed-height').each(setElementHeight);
$(window).resize(function() { $('.managed-height').each(setElementHeight) });
}