/** ****************************************************************************** * 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) { $scope.fn = null; return; } app.session.state.fetchFunction($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(); // }); });