function WatchedExprManager(rest_api) {
var $watchExpressions = $('#watch-expressions-container'),
watchedExpressionTemplate = Handlebars.compile( $('#watched-expr-template').html() ),
watchedExpressionToDivId = {},
watchedExprSerial = 0;
function isAlreadyWatching(expr) {
return (expr in watchedExpressionToDivId);
}
function exprForElt($elt) {
var divId = $elt.closest('.watched-expression').attr('id');
for (var expr in watchedExpressionToDivId) {
if (watchedExpressionToDivId[expr] == divId) {
return expr;
}
}
return null;
}
function setIsEditing($elt, value) {
if (value) {
$elt.closest('.expr-container').addClass('editing');
} else {
$elt.closest('.expr-container').removeClass('editing');
}
}
function isEditing($elt) {
return $elt.closest('.expr-container').hasClass('editing');
}
function renderPerlValueIntoElement(expr, perlValue, $elt) {
$elt.find('.value').empty().append(perlValue.render());
}
function makeEvalResultDoneHandler(expr, $elt) {
return function(data) {
// We're always eval-ing in list context
// If the array contains 1 item, use that 1 item instead
if (typeof(data) == 'object'
&& data.__reftype == 'ARRAY'
&& data.__value.length == 1
) {
data = data.__value[0];
}
var perlValue = PerlValue.parseFromEval(data);
renderPerlValueIntoElement(expr, perlValue, $elt);
};
}
function makeEvalResultFailHandler(expr, $elt) {
return function(jqxhr, text_status, error_thrown) {
if (jqxhr.status == 409) {
// exception
var ex = new PerlValue.exception(jqxhr.responseJSON);
renderPerlValueIntoElement(expr, ex, $elt);
1;
}
};
}
this.updateExpressions = function() {
$.each(watchedExpressionToDivId, function(expr, divId) {
var $elt = $('#' + divId);
rest_api.eval(expr, 1)
.done( makeEvalResultDoneHandler(expr, $elt))
.fail( makeEvalResultFailHandler(expr, $elt));
});
};
var $addExprButton = $('#add-watch-expr');
function submitAddedWatchExpression(e) {
var $target = $(e.target),
expr = $target.find('input').val(),
$container = $target.closest('.expr-container'),
$watchedExprDiv = $container.find('.watched-expression'),
originalExpr = exprForElt($watchedExprDiv);
expr = expr.replace(/^\s+|\s+$/g,''); // Remove leading and trailing spaces
e.preventDefault();
setIsEditing($target, false);
if ((expr.length) && (expr == originalExpr)) {
// They didn't change it
$container.find('.watched-expression-form').hide();
} else if (isAlreadyWatching(expr)) {
// new, but it duplicates something already there
$container.remove();
alert('Already watching '+expr);
} else {
retrieveCurrentValueForWatchedExpressionForm($target);
}
$addExprButton.prop('disabled', false);
}
function retrieveCurrentValueForWatchedExpressionForm($form) {
var expr = $form.find('input').val(),
$container = $form.closest('.expr-container'),
$watchedExprDiv = $container.find('.watched-expression');
watchedExpressionToDivId[expr] = $watchedExprDiv.attr('id');
$form.closest('.watched-expression-form').hide();
$watchedExprDiv.find('.expr').text(expr)
rest_api.eval(expr, 1)
.done( makeEvalResultDoneHandler(expr, $watchedExprDiv))
.fail( makeEvalResultFailHandler(expr, $watchedExprDiv))
.always(function() { $watchedExprDiv.show(); });
}
function appendNewWatchExpressionFormToPane(expr, isWatchpoint) {
var divId = 'watch' + (++watchedExprSerial),
$container = $(watchedExpressionTemplate({id: divId, expr: expr, isWatchpoint: isWatchpoint})),
form;
$container.find('.watched-expression').hide();
$container.appendTo($watchExpressions);
$form = $container.find('.watched-expression-form');
return $form;
}
function addOrEditWatchExpression(e) {
var $target = $(e.target),
$form, revert;
e.preventDefault();
$addExprButton.prop('disabled', true); // disable the add button
if ($target.hasClass('expr')) {
// editing an existing expression
$form = $target.closest('.expr-container').find('.watched-expression-form').show();
var originalExpr = $target.closest('.watched-expression').attr('data-expr');
revert = function() {
$form.hide()
.find('input[type="text"]').val(originalExpr);
setIsEditing($form, false);
};
} else {
// Adding a new expression
$form = appendNewWatchExpressionFormToPane();
revert = function() {
$container.remove();
setIsEditing($form, false);
};
}
setIsEditing($form, true);
$form.find('input[type="text"]').keyup(function(e) {
// If the user presses escape, abort the form
if (e.keyCode == 27) {
e.preventDefault();
$addExprButton.prop('disabled', false); // re-enable the add button
revert();
}
})
.trigger('focus');
}
function removeWatchedExpression(e) {
var $target = $(e.target),
$elt = $target.closest('.expr-container'),
divId = $target.closest('.watched-expression').attr('id');
if (isEditing($target)) {
$addExprButton.prop('disabled', false);
}
$elt.remove();
for (var expr in watchedExpressionToDivId) {
if (watchedExpressionToDivId[expr] == divId) {
delete watchedExpressionToDivId[expr];
break;
}
}
}
function toggleWatchpoint(e) {
var $target=$(e.target),
$elt = $target.closest('.expr-container'),
divId = $target.closest('.watched-expression').attr('id'),
expr = exprForElt($target);
// change/click callback trigger *after* the checkbox element changes state
e.stopPropagation();
e.preventDefault();
var current_state = $target.is(':checked'),
toggle_fcn = (current_state ? rest_api.createWatchpoint : rest_api.deleteWatchpoint).bind(rest_api);
toggle_fcn(expr)
.done(function(result) {
1;
})
.fail(function(jqxhr, text_status, error_thrown) {
// didn't work, change the checkbox back to whatever it was before
$target.attr('checked', ! current_state);
alert("Error setting watchpoint: " + text_status);
1;
});
};
this.addExpression = function(expr, is_watchpoint) {
if (! isAlreadyWatching(expr)) {
var $form = appendNewWatchExpressionFormToPane(expr, is_watchpoint);
retrieveCurrentValueForWatchedExpressionForm($form);
}
};
this.loadWatchpoints = function() {
var watchmgr = this;
rest_api.getWatchpoints()
.done(function(wp_list) {
wp_list.forEach(function(wp) {
watchmgr.addExpression(wp.expr, true);
});
})
.fail(function(jqxhr, text_status, error_thrown) {
alert('Error loading watchpoints: ' + text_status);
});
};
this.flash = function(expr) {
var divId = watchedExpressionToDivId[expr],
$element = $("#" + divId + ' .expr'),
count = 10; // even number so it returns to normal when done
function toggler() {
$element.toggleClass('flashed');
if (--count) {
setTimeout(toggler, 500);
}
}
toggler();
};
function toggleCollapseWatchExpression(e) {
var $target = $(e.target),
$toToggle = $target.siblings('dl,ul'),
is_collapsed = $toToggle.css('display') == 'none';
e.preventDefault();
$toToggle.toggle(100, function() {
if (is_collapsed) {
$target.removeClass('collapsed');
} else {
$target.addClass('collapsed');
}
});
}
$('#add-watch-expr').on('click', addOrEditWatchExpression);
$('#watch-expressions')
.on('click', '.remove-watched-expression', removeWatchedExpression)
.on('click', '.expr-collapse-button', toggleCollapseWatchExpression)
.on('dblclick', '.expr', addOrEditWatchExpression)
.on('submit', 'form', submitAddedWatchExpression)
.on('change', '.expr-container:not(.editing) input:checkbox.enable-watchpoint', toggleWatchpoint);
}