271 lines
7.3 KiB
JavaScript
271 lines
7.3 KiB
JavaScript
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var module = angular.module('xe.ui.code.functionView', [
|
|
'xe.log',
|
|
'xe.session'
|
|
]);
|
|
|
|
|
|
module.controller('FunctionViewController', function(
|
|
$rootScope, $scope, $location, app, log, Breakpoint) {
|
|
$scope.codeType = 'source';
|
|
$scope.highlightInfo = null;
|
|
|
|
function refresh() {
|
|
if (!app.session || !app.session.dataSource) {
|
|
$scope.fn = null;
|
|
return;
|
|
}
|
|
var dataSource = app.session.dataSource;
|
|
|
|
dataSource.getFunction($scope.functionAddress).then(function(fn) {
|
|
$scope.fn = fn;
|
|
updateCode();
|
|
}, function(e) {
|
|
log.error('Unable to fetch function');
|
|
});
|
|
};
|
|
$rootScope.$on('refresh', refresh);
|
|
$scope.$watch('functionAddress', refresh);
|
|
|
|
function updateHighlight(address) {
|
|
if (!$scope.sourceLines || $scope.codeType != 'source') {
|
|
return;
|
|
}
|
|
if ($scope.highlightInfo) {
|
|
if ($scope.highlightInfo.address == address) {
|
|
return;
|
|
}
|
|
var oldLine = $scope.highlightInfo.line;
|
|
if ($scope.highlightInfo.widget) {
|
|
$scope.highlightInfo.widget.clear();
|
|
}
|
|
$scope.highlightInfo = null;
|
|
updateLine(oldLine);
|
|
}
|
|
// TODO(benvanik): a better mapping.
|
|
var line = -1;
|
|
for (var n = 0; n < $scope.sourceLines.length; n++) {
|
|
var sourceLine = $scope.sourceLines[n];
|
|
if (sourceLine[0] == 'i' &&
|
|
sourceLine[1] == address) {
|
|
line = n;
|
|
break;
|
|
}
|
|
}
|
|
if (line != -1) {
|
|
$scope.highlightInfo = {
|
|
address: address,
|
|
line: line,
|
|
widget: null
|
|
};
|
|
updateLine(line);
|
|
}
|
|
};
|
|
$scope.$watch(function() {
|
|
return $location.search();
|
|
}, function(search) {
|
|
updateHighlight(parseInt(search.a, 16));
|
|
});
|
|
|
|
var textArea = document.querySelector('.debugger-fnview-textarea');
|
|
$scope.codeMirror = CodeMirror.fromTextArea(textArea, {
|
|
mode: 'javascript',
|
|
theme: 'default',
|
|
indentUnit: 2,
|
|
tabSize: 2,
|
|
lineNumbers: false,
|
|
gutters: [
|
|
'debugger-fnview-gutter-icon',
|
|
'debugger-fnview-gutter-addr',
|
|
'debugger-fnview-gutter-code'
|
|
],
|
|
readOnly: true
|
|
});
|
|
|
|
function hex32(number) {
|
|
var str = "" + number.toString(16).toUpperCase();
|
|
while (str.length < 8) str = "0" + str;
|
|
return str;
|
|
};
|
|
|
|
function updateSourceCode(fn) {
|
|
var cm = $scope.codeMirror;
|
|
if (!fn) {
|
|
$scope.sourceLines = [];
|
|
cm.setValue('');
|
|
return;
|
|
}
|
|
|
|
var doc = cm.getDoc();
|
|
|
|
var lines = fn.disasm.source.lines;
|
|
$scope.sourceLines = lines;
|
|
|
|
var textContent = [];
|
|
for (var n = 0; n < lines.length; n++) {
|
|
var line = lines[n];
|
|
textContent.push(line[3]);
|
|
}
|
|
cm.setValue(textContent.join('\n'));
|
|
|
|
for (var n = 0; n < lines.length; n++) {
|
|
var line = lines[n];
|
|
|
|
var el = document.createElement('div');
|
|
el.classList.add('debugger-fnview-gutter-addr-el');
|
|
el.innerText = hex32(line[1]);
|
|
cm.setGutterMarker(n, 'debugger-fnview-gutter-addr', el);
|
|
if (line[0] != 'i') {
|
|
el.classList.add('debugger-fnview-gutter-addr-el-inactive');
|
|
}
|
|
|
|
if (line[0] == 'i') {
|
|
el = document.createElement('div');
|
|
el.classList.add('debugger-fnview-gutter-code-el');
|
|
el.innerText = hex32(line[2]);
|
|
cm.setGutterMarker(n, 'debugger-fnview-gutter-code', el);
|
|
|
|
updateLine(n);
|
|
}
|
|
}
|
|
};
|
|
function updateCode() {
|
|
var cm = $scope.codeMirror;
|
|
var fn = $scope.fn || null;
|
|
var codeType = $scope.codeType;
|
|
|
|
var gutters;
|
|
switch (codeType) {
|
|
case 'source':
|
|
gutters = [
|
|
'debugger-fnview-gutter-icon',
|
|
'debugger-fnview-gutter-addr',
|
|
'debugger-fnview-gutter-code'
|
|
];
|
|
break;
|
|
default:
|
|
gutters = [
|
|
'debugger-fnview-gutter-icon',
|
|
'debugger-fnview-gutter-addr'
|
|
];
|
|
break;
|
|
}
|
|
cm.setOption('gutters', gutters);
|
|
|
|
cm.clearGutter('debugger-fnview-gutter-icon');
|
|
cm.clearGutter('debugger-fnview-gutter-addr');
|
|
cm.clearGutter('debugger-fnview-gutter-code');
|
|
|
|
// Set last to make all option changes stick.
|
|
switch (codeType) {
|
|
case 'source':
|
|
cm.operation(function() {
|
|
updateSourceCode(fn);
|
|
});
|
|
break;
|
|
default:
|
|
var value = fn ? fn.disasm[codeType] : null;
|
|
cm.setValue(value || '');
|
|
break;
|
|
}
|
|
};
|
|
$scope.$watch('codeType', updateCode);
|
|
|
|
function updateLine(line) {
|
|
var sourceLine = $scope.sourceLines[line];
|
|
var cm = $scope.codeMirror;
|
|
if (sourceLine[0] != 'i') {
|
|
return;
|
|
}
|
|
var address = sourceLine[1];
|
|
var breakpoint = app.session.breakpoints[address];
|
|
var el;
|
|
if (breakpoint && breakpoint.type == 'code') {
|
|
el = document.createElement('span');
|
|
el.classList.add('debugger-fnview-gutter-icon-el');
|
|
if (breakpoint.enabled) {
|
|
el.innerHTML = '●';
|
|
} else {
|
|
el.innerHTML = '◌';
|
|
}
|
|
} else {
|
|
el = null;
|
|
}
|
|
cm.setGutterMarker(line, 'debugger-fnview-gutter-icon', el);
|
|
|
|
var highlightInfo = $scope.highlightInfo;
|
|
if (highlightInfo && highlightInfo.line == line) {
|
|
/*
|
|
if (!highlightInfo.widget) {
|
|
el = document.createElement('div');
|
|
el.style.width = '100%';
|
|
el.style.height = '20px';
|
|
el.style.backgroundColor = 'red';
|
|
el.innerHTML = 'hi!';
|
|
highlightInfo.widget = cm.addLineWidget(line, el, {
|
|
coverGutter: false
|
|
});
|
|
cm.scrollIntoView(line, 50);
|
|
}
|
|
*/
|
|
cm.addLineClass(line, 'background', 'debugger-fnview-line-highlight-bg');
|
|
} else {
|
|
cm.removeLineClass(line, 'background');
|
|
}
|
|
};
|
|
|
|
function toggleBreakpoint(line, sourceLine, shiftKey) {
|
|
var address = sourceLine[1];
|
|
var breakpoint = app.session.breakpoints[address];
|
|
if (breakpoint) {
|
|
// Existing breakpoint - toggle or remove.
|
|
if (shiftKey || !breakpoint.enabled) {
|
|
app.session.toggleBreakpoint(breakpoint, !breakpoint.enabled);
|
|
} else {
|
|
app.session.removeBreakpoint(breakpoint);
|
|
}
|
|
} else {
|
|
// New breakpoint needed.
|
|
breakpoint = app.session.addCodeBreakpoint(
|
|
$scope.functionAddress, address);
|
|
}
|
|
|
|
updateLine(line);
|
|
};
|
|
|
|
$scope.codeMirror.on('gutterClick', function(
|
|
instance, line, gutterClass, e) {
|
|
if (e.which == 1) {
|
|
if (gutterClass == 'debugger-fnview-gutter-icon' ||
|
|
gutterClass == 'debugger-fnview-gutter-addr') {
|
|
var sourceLine = $scope.sourceLines[line];
|
|
if (!sourceLine || sourceLine[0] != 'i') {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
toggleBreakpoint(line, sourceLine, e.shiftKey);
|
|
}
|
|
}
|
|
});
|
|
// $scope.codeMirror.on('gutterContextMenu', function(
|
|
// instance, line, gutterClass, e) {
|
|
// console.log('context menu');
|
|
// e.preventDefault();
|
|
// });
|
|
// $scope.codeMirror.on('contextmenu', function(
|
|
// instance, e) {
|
|
// console.log('context menu main');
|
|
// e.preventDefault();
|
|
// });
|
|
});
|