From d4679e547cb64c778800550236617168edfd3188 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Tue, 6 Dec 2016 14:00:20 -0800 Subject: [PATCH 1/7] use mi commands instead of regular gdb commands --- gdbgui/static/js/gdbgui.js | 2 +- gdbgui/templates/gdbgui.jade | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gdbgui/static/js/gdbgui.js b/gdbgui/static/js/gdbgui.js index 3c267154..68ccb7c3 100644 --- a/gdbgui/static/js/gdbgui.js +++ b/gdbgui/static/js/gdbgui.js @@ -137,7 +137,7 @@ let App = { }, click_set_target_app_button: function(e){ var binary = Consts.jq_binary.val(); - App.run_gdb_command(`file ${binary}`); + App.run_gdb_command(`-file-exec-and-symbols ${binary}`); App.enable_gdb_controls(); }, keydown_on_binary_input: function(e){ diff --git a/gdbgui/templates/gdbgui.jade b/gdbgui/templates/gdbgui.jade index 704fab2e..da070405 100644 --- a/gdbgui/templates/gdbgui.jade +++ b/gdbgui/templates/gdbgui.jade @@ -30,15 +30,15 @@ body //- premade buttons div.btn-group.btn-group-xs(role="group") - button.btn.btn-default.gdb_cmd(type='button', data-cmd='run', title='Run the loaded program') Run + button.btn.btn-default.gdb_cmd(type='button', data-cmd='-exec-run', title='Run the loaded program') Run span.glyphicon.glyphicon-off.padding_left - button.btn.btn-default.gdb_cmd(type='button', data-cmd='continue', title='Continue running program') Continue + button.btn.btn-default.gdb_cmd(type='button', data-cmd='-exec-continue', title='Continue running program') Continue span.glyphicon.glyphicon-play.padding_left - button.btn.btn-default.gdb_cmd(type='button', data-cmd='next', title='Step to next line (will not enter function)') Next + button.btn.btn-default.gdb_cmd(type='button', data-cmd='-exec-next', title='Step to next line (will not enter function)') Next span.glyphicon.glyphicon-step-forward.padding_left - button.btn.btn-default.gdb_cmd(type='button', data-cmd='step', title='Step into next function call or to next line') Step + button.btn.btn-default.gdb_cmd(type='button', data-cmd='-exec-step', title='Step into next function call or to next line') Step span.glyphicon.glyphicon-arrow-down.padding_left - button.btn.btn-default.gdb_cmd(type='button', data-cmd='return', title='Return to calling function') Return + button.btn.btn-default.gdb_cmd(type='button', data-cmd='-exec-return', title='Return to calling function') Return span.glyphicon.glyphicon-arrow-up.padding_left button.btn.btn-default.gdb_cmd(type='button', data-cmd='-break-insert main', title="Add breakpoint to the programs entry point") Add Breakpoint to Main span.glyphicon.glyphicon-flag.padding_left @@ -64,7 +64,7 @@ body span#source_code_heading Source Code span.glyphicon.glyphicon-plus.resizer.pointer(data-target_selector="#code_container" data-resize_type="enlarge") span.glyphicon.glyphicon-minus.resizer.pointer(data-target_selector="#code_container" data-resize_type="shrink") - + button.btn.btn-default.gdb_cmd.btn-xs(type="button", data-cmd='-file-list-exec-source-files') Fetch Source File List div#code_container.gdb_content_div table#code_table.code tr @@ -89,7 +89,7 @@ body div.col-md-6.no_padding stdout/stderr span.glyphicon.glyphicon-plus.resizer.pointer(data-target_selector="#stdout" data-resize_type="enlarge") span.glyphicon.glyphicon-minus.resizer.pointer(data-target_selector="#stdout" data-resize_type="shrink") - button.btn.btn-default.btn-xs.gdb_cmd(type='button', data-cmd='call fflush(0)') Refresh + button.btn.btn-default.btn-xs.gdb_cmd(type='button', data-cmd='-data-evaluate-expression fflush(0)') Refresh span.glyphicon.glyphicon-refresh.padding_left div.gdb_content_div#stdout From a205c963c0bebf8ba01add1c327722e4488f4081 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 7 Dec 2016 09:17:46 -0800 Subject: [PATCH 2/7] ensure file exists and better error handling --- gdbgui/backend.py | 5 ++--- gdbgui/static/js/gdbgui.js | 9 ++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gdbgui/backend.py b/gdbgui/backend.py index 8e1f6203..643199a3 100755 --- a/gdbgui/backend.py +++ b/gdbgui/backend.py @@ -59,7 +59,7 @@ def run_gdb_command(): def read_file(): """Used to get contents of source files that are being debugged""" path = request.args.get('path') - if os.path.isfile(path): + if path and os.path.isfile(path): try: with open(path, 'r') as f: return jsonify({'source_code': f.read().splitlines(), @@ -85,7 +85,6 @@ def signal_handler(signal, frame): def quit_backend(): - global app, gdb gdb.exit() func = request.environ.get('werkzeug.server.shutdown') if func is None: @@ -102,7 +101,7 @@ def setup_backend(serve=True, port=5000, debug=False): app.debug = debug app.config['TEMPLATES_AUTO_RELOAD'] = True if serve: - extra_files=[] + extra_files = [] for dirname, dirs, files in os.walk(TEMPLATE_DIR): for filename in files: filename = os.path.join(dirname, filename) diff --git a/gdbgui/static/js/gdbgui.js b/gdbgui/static/js/gdbgui.js index 68ccb7c3..5010e36b 100644 --- a/gdbgui/static/js/gdbgui.js +++ b/gdbgui/static/js/gdbgui.js @@ -24,8 +24,11 @@ const Util = { return result.join('\n'); }, post_msg: function(data){ - App.set_status(_.escape(data.responseJSON.message)) - // Messenger().post(_.escape(data.responseJSON.message)) + if (data.responseJSON && data.responseJSON.message){ + App.set_status(_.escape(data.responseJSON.message)) + }else{ + App.set_status(`${data.statusText} (${data.status} error)`) + } }, escape: function(s){ return s.replace(/([^\\]\\n)/g, '
') @@ -342,7 +345,7 @@ let App = { Consts.jq_registers.html(Util.get_table(thead, register_array)); }, read_and_render_file: function(fullname, highlight_line=0){ - if (fullname === null){ + if (fullname === null || fullname === undefined){ return } From 24430555f3a3e4e2d89a8b1a1721fc22ba765270 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 7 Dec 2016 09:18:08 -0800 Subject: [PATCH 3/7] add moment file --- gdbgui/static/vendor/js/moment.min.js | 551 ++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 gdbgui/static/vendor/js/moment.min.js diff --git a/gdbgui/static/vendor/js/moment.min.js b/gdbgui/static/vendor/js/moment.min.js new file mode 100644 index 00000000..fe68e0db --- /dev/null +++ b/gdbgui/static/vendor/js/moment.min.js @@ -0,0 +1,551 @@ +//! moment.js +//! version : 2.17.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return od.apply(null,arguments)} +// This is done to register the method called with moment() +// without creating circular dependencies. +function b(a){od=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){ +// IE8 will treat undefined and null as object if it wasn't for +// input != null +return null!=a&&"[object Object]"===Object.prototype.toString.call(a)}function e(a){var b;for(b in a) +// even if its not own property I'd still call it non-empty +return!1;return!0}function f(a){return"number"==typeof a||"[object Number]"===Object.prototype.toString.call(a)}function g(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function h(a,b){var c,d=[];for(c=0;c0)for(c in rd)d=rd[c],e=b[d],p(e)||(a[d]=e);return a} +// Moment prototype object +function r(b){q(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)), +// Prevent infinite loop in case updateOffset creates new moment +// objects. +sd===!1&&(sd=!0,a.updateOffset(this),sd=!1)}function s(a){return a instanceof r||null!=a&&null!=a._isAMomentObject}function t(a){return a<0?Math.ceil(a)||0:Math.floor(a)}function u(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=t(b)),c} +// compare two arrays, return the number of differences +function v(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;d0?"future":"past"];return z(c)?c(b):c.replace(/%s/i,b)}function J(a,b){var c=a.toLowerCase();Dd[c]=Dd[c+"s"]=Dd[b]=a}function K(a){return"string"==typeof a?Dd[a]||Dd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)i(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(a,b){Ed[a]=b}function N(a){var b=[];for(var c in a)b.push({unit:c,priority:Ed[c]});return b.sort(function(a,b){return a.priority-b.priority}),b}function O(b,c){return function(d){return null!=d?(Q(this,b,d),a.updateOffset(this,c),this):P(this,b)}}function P(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function Q(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)} +// MOMENTS +function R(a){return a=K(a),z(this[a])?this[a]():this}function S(a,b){if("object"==typeof a){a=L(a);for(var c=N(a),d=0;d=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d} +// token: 'M' +// padded: ['MM', 2] +// ordinal: 'Mo' +// callback: function () { this.month() + 1 } +function U(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Id[a]=e),b&&(Id[b[0]]=function(){return T(e.apply(this,arguments),b[1],b[2])}),c&&(Id[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function V(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function W(a){var b,c,d=a.match(Fd);for(b=0,c=d.length;b=0&&Gd.test(a);)a=a.replace(Gd,c),Gd.lastIndex=0,d-=1;return a}function Z(a,b,c){$d[a]=z(b)?b:function(a,d){return a&&c?c:b}}function $(a,b){return i($d,a)?$d[a](b._strict,b._locale):new RegExp(_(a))} +// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript +function _(a){return aa(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function aa(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function ba(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),f(b)&&(d=function(a,c){c[b]=u(a)}),c=0;c=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ta(a){var b=new Date(Date.UTC.apply(null,arguments)); +//the Date.UTC function remaps years 0-99 to 1900-1999 +return a<100&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b} +// start-of-first-week - start-of-year +function ua(a,b,c){var// first-week day -- which january is always in the first week (4 for iso, 1 for other) +d=7+b-c, +// first-week day local weekday -- which local weekday is fwd +e=(7+ta(a,0,d).getUTCDay()-b)%7;return-e+d-1} +//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday +function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return j<=0?(f=a-1,g=pa(f)+j):j>pa(a)?(f=a+1,g=j-pa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return g<1?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(pa(a)-d+e)/7} +// HELPERS +// LOCALES +function ya(a){return wa(a,this._week.dow,this._week.doy).week}function za(){return this._week.dow}function Aa(){return this._week.doy} +// MOMENTS +function Ba(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ca(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")} +// HELPERS +function Da(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Ea(a,b){return"string"==typeof a?b.weekdaysParse(a)%7||7:isNaN(a)?null:a}function Fa(a,b){return a?c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]:this._weekdays}function Ga(a){return a?this._weekdaysShort[a.day()]:this._weekdaysShort}function Ha(a){return a?this._weekdaysMin[a.day()]:this._weekdaysMin}function Ia(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;d<7;++d)f=k([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=je.call(this._weekdaysParse,g),e!==-1?e:null):"ddd"===b?(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:null):(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null):"dddd"===b?(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null))):"ddd"===b?(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null))):(e=je.call(this._minWeekdaysParse,g),e!==-1?e:(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:null)))}function Ja(a,b,c){var d,e,f;if(this._weekdaysParseExact)return Ia.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;d<7;d++){ +// test the regex +if( +// make the regex if we don't have it already +e=k([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}} +// MOMENTS +function Ka(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Da(a,this.localeData()),this.add(a-b,"d")):b}function La(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ma(a){if(!this.isValid())return null!=a?this:NaN; +// behaves the same as moment#day except +// as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) +// as a setter, sunday should belong to the previous week. +if(null!=a){var b=Ea(a,this.localeData());return this.day(this.day()%7?b:b-7)}return this.day()||7}function Na(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):(i(this,"_weekdaysRegex")||(this._weekdaysRegex=ue),this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex)}function Oa(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(i(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ve),this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Pa(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(i(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=we),this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qa(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],h=[],i=[],j=[];for(b=0;b<7;b++) +// make the regex if we don't have it already +c=k([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),h.push(e),i.push(f),j.push(d),j.push(e),j.push(f);for( +// Sorting makes sure if one weekday (or abbr) is a prefix of another it +// will match the longer piece. +g.sort(a),h.sort(a),i.sort(a),j.sort(a),b=0;b<7;b++)h[b]=aa(h[b]),i[b]=aa(i[b]),j[b]=aa(j[b]);this._weekdaysRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")} +// FORMATTING +function Ra(){return this.hours()%12||12}function Sa(){return this.hours()||24}function Ta(a,b){U(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})} +// PARSING +function Ua(a,b){return b._meridiemParse} +// LOCALES +function Va(a){ +// IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays +// Using charAt should be more compatible. +return"p"===(a+"").toLowerCase().charAt(0)}function Wa(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Xa(a){return a?a.toLowerCase().replace("_","-"):a} +// pick the locale from the array +// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each +// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root +function Ya(a){for(var b,c,d,e,f=0;f0;){if(d=Za(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&v(e,c,!0)>=b-1) +//the next array item is better than a shallower substring of this one +break;b--}f++}return null}function Za(a){var b=null; +// TODO: Find a better way to register and load all the locales in Node +if(!Be[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=xe._abbr,require("./locale/"+a), +// because defineLocale currently also sets the global locale, we +// want to undo that for lazy loaded locales +$a(b)}catch(a){}return Be[a]} +// This function will load locale and then set the global locale. If +// no arguments are passed in, it will simply return the current global +// locale key. +function $a(a,b){var c; +// moment.duration._locale = moment._locale = data; +return a&&(c=p(b)?bb(a):_a(a,b),c&&(xe=c)),xe._abbr}function _a(a,b){if(null!==b){var c=Ae;if(b.abbr=a,null!=Be[a])y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),c=Be[a]._config;else if(null!=b.parentLocale){if(null==Be[b.parentLocale])return Ce[b.parentLocale]||(Ce[b.parentLocale]=[]),Ce[b.parentLocale].push({name:a,config:b}),null;c=Be[b.parentLocale]._config} +// backwards compat for now: also set the locale +// make sure we set the locale AFTER all child locales have been +// created, so we won't end up with the child locale set. +return Be[a]=new C(B(c,b)),Ce[a]&&Ce[a].forEach(function(a){_a(a.name,a.config)}),$a(a),Be[a]} +// useful for testing +return delete Be[a],null}function ab(a,b){if(null!=b){var c,d=Ae; +// MERGE +null!=Be[a]&&(d=Be[a]._config),b=B(d,b),c=new C(b),c.parentLocale=Be[a],Be[a]=c, +// backwards compat for now: also set the locale +$a(a)}else +// pass null for config to unupdate, useful for tests +null!=Be[a]&&(null!=Be[a].parentLocale?Be[a]=Be[a].parentLocale:null!=Be[a]&&delete Be[a]);return Be[a]} +// returns locale data +function bb(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return xe;if(!c(a)){if( +//short-circuit everything else +b=Za(a))return b;a=[a]}return Ya(a)}function cb(){return wd(Be)}function db(a){var b,c=a._a;return c&&m(a).overflow===-2&&(b=c[be]<0||c[be]>11?be:c[ce]<1||c[ce]>ea(c[ae],c[be])?ce:c[de]<0||c[de]>24||24===c[de]&&(0!==c[ee]||0!==c[fe]||0!==c[ge])?de:c[ee]<0||c[ee]>59?ee:c[fe]<0||c[fe]>59?fe:c[ge]<0||c[ge]>999?ge:-1,m(a)._overflowDayOfYear&&(bce)&&(b=ce),m(a)._overflowWeeks&&b===-1&&(b=he),m(a)._overflowWeekday&&b===-1&&(b=ie),m(a).overflow=b),a} +// date from iso format +function eb(a){var b,c,d,e,f,g,h=a._i,i=De.exec(h)||Ee.exec(h);if(i){for(m(a).iso=!0,b=0,c=Ge.length;bpa(e)&&(m(a)._overflowDayOfYear=!0),c=ta(e,0,a._dayOfYear),a._a[be]=c.getUTCMonth(),a._a[ce]=c.getUTCDate()),b=0;b<3&&null==a._a[b];++b)a._a[b]=f[b]=d[b]; +// Zero out whatever was not defaulted, including time +for(;b<7;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b]; +// Check for 24:00:00.000 +24===a._a[de]&&0===a._a[ee]&&0===a._a[fe]&&0===a._a[ge]&&(a._nextDay=!0,a._a[de]=0),a._d=(a._useUTC?ta:sa).apply(null,f), +// Apply timezone offset from input. The actual utcOffset can be changed +// with parseZone. +null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[de]=24)}}function jb(a){var b,c,d,e,f,g,h,i;if(b=a._w,null!=b.GG||null!=b.W||null!=b.E)f=1,g=4, +// TODO: We need to take the current isoWeekYear, but that depends on +// how we interpret now (local, utc, fixed offset). So create +// a now version of current config (take local/utc/offset flags, and +// create now). +c=gb(b.GG,a._a[ae],wa(sb(),1,4).year),d=gb(b.W,1),e=gb(b.E,1),(e<1||e>7)&&(i=!0);else{f=a._locale._week.dow,g=a._locale._week.doy;var j=wa(sb(),f,g);c=gb(b.gg,a._a[ae],j.year), +// Default to current week. +d=gb(b.w,j.week),null!=b.d?( +// weekday -- low day numbers are considered next week +e=b.d,(e<0||e>6)&&(i=!0)):null!=b.e?( +// local weekday -- counting starts from begining of week +e=b.e+f,(b.e<0||b.e>6)&&(i=!0)): +// default to begining of week +e=f}d<1||d>xa(c,f,g)?m(a)._overflowWeeks=!0:null!=i?m(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[ae]=h.year,a._dayOfYear=h.dayOfYear)} +// date from string and format string +function kb(b){ +// TODO: Move this to another part of the creation flow to prevent circular deps +if(b._f===a.ISO_8601)return void eb(b);b._a=[],m(b).empty=!0; +// This array is used to make a Date, either with `new Date` or `Date.UTC` +var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Y(b._f,b._locale).match(Fd)||[],c=0;c0&&m(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length), +// don't parse if it's not a known token +Id[f]?(d?m(b).empty=!1:m(b).unusedTokens.push(f),da(f,d,b)):b._strict&&!d&&m(b).unusedTokens.push(f); +// add remaining unparsed input length to the string +m(b).charsLeftOver=i-j,h.length>0&&m(b).unusedInput.push(h), +// clear _12h flag if hour is <= 12 +b._a[de]<=12&&m(b).bigHour===!0&&b._a[de]>0&&(m(b).bigHour=void 0),m(b).parsedDateParts=b._a.slice(0),m(b).meridiem=b._meridiem, +// handle meridiem +b._a[de]=lb(b._locale,b._a[de],b._meridiem),ib(b),db(b)}function lb(a,b,c){var d; +// Fallback +return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&b<12&&(b+=12),d||12!==b||(b=0),b):b} +// date from string and array of format strings +function mb(a){var b,c,d,e,f;if(0===a._f.length)return m(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e +// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset +// +0200, so we adjust the time as needed, to be valid. +// +// Keeping the time actually adds/subtracts (one hour) +// from the actual represented time. That is why we call updateOffset +// a second time. In case it wants us to change the offset again +// _changeInProgress == true case, then we have to adjust, because +// there is no such time in the given timezone. +function Db(b,c){var d,e=this._offset||0;if(!this.isValid())return null!=b?this:NaN;if(null!=b){if("string"==typeof b){if(b=Ab(Xd,b),null===b)return this}else Math.abs(b)<16&&(b=60*b);return!this._isUTC&&c&&(d=Cb(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Tb(this,Ob(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?e:Cb(this)}function Eb(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Fb(a){return this.utcOffset(0,a)}function Gb(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Cb(this),"m")),this}function Hb(){if(null!=this._tzm)this.utcOffset(this._tzm);else if("string"==typeof this._i){var a=Ab(Wd,this._i);null!=a?this.utcOffset(a):this.utcOffset(0,!0)}return this}function Ib(a){return!!this.isValid()&&(a=a?sb(a).utcOffset():0,(this.utcOffset()-a)%60===0)}function Jb(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Kb(){if(!p(this._isDSTShifted))return this._isDSTShifted;var a={};if(q(a,this),a=pb(a),a._a){var b=a._isUTC?k(a._a):sb(a._a);this._isDSTShifted=this.isValid()&&v(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Lb(){return!!this.isValid()&&!this._isUTC}function Mb(){return!!this.isValid()&&this._isUTC}function Nb(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Ob(a,b){var c,d,e,g=a, +// matching against regexp is expensive, do it on demand +h=null;// checks for null or undefined +return xb(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:f(a)?(g={},b?g[b]=a:g.milliseconds=a):(h=Ne.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:u(h[ce])*c,h:u(h[de])*c,m:u(h[ee])*c,s:u(h[fe])*c,ms:u(yb(1e3*h[ge]))*c}):(h=Oe.exec(a))?(c="-"===h[1]?-1:1,g={y:Pb(h[2],c),M:Pb(h[3],c),w:Pb(h[4],c),d:Pb(h[5],c),h:Pb(h[6],c),m:Pb(h[7],c),s:Pb(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=Rb(sb(g.from),sb(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new wb(g),xb(a)&&i(a,"_locale")&&(d._locale=a._locale),d}function Pb(a,b){ +// We'd normally use ~~inp for this, but unfortunately it also +// converts floats to ints. +// inp may be undefined, so careful calling replace on it. +var c=a&&parseFloat(a.replace(",",".")); +// apply sign while we're at it +return(isNaN(c)?0:c)*b}function Qb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Rb(a,b){var c;return a.isValid()&&b.isValid()?(b=Bb(b,a),a.isBefore(b)?c=Qb(a,b):(c=Qb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}} +// TODO: remove 'name' arg after deprecation is removed +function Sb(a,b){return function(c,d){var e,f; +//invert the arguments, but complain about it +return null===d||isNaN(+d)||(y(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ob(c,d),Tb(this,e,a),this}}function Tb(b,c,d,e){var f=c._milliseconds,g=yb(c._days),h=yb(c._months);b.isValid()&&(e=null==e||e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&Q(b,"Date",P(b,"Date")+g*d),h&&ja(b,P(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function Ub(a,b){var c=a.diff(b,"days",!0);return c<-6?"sameElse":c<-1?"lastWeek":c<0?"lastDay":c<1?"sameDay":c<2?"nextDay":c<7?"nextWeek":"sameElse"}function Vb(b,c){ +// We want to compare the start of today, vs this. +// Getting start-of-today depends on whether we're local/utc/offset or not. +var d=b||sb(),e=Bb(d,this).startOf("day"),f=a.calendarFormat(this,e)||"sameElse",g=c&&(z(c[f])?c[f].call(this,d):c[f]);return this.format(g||this.localeData().calendar(f,this,sb(d)))}function Wb(){return new r(this)}function Xb(a,b){var c=s(a)?a:sb(a);return!(!this.isValid()||!c.isValid())&&(b=K(p(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()f&&(b=f),Fc.call(this,a,b,c,d,e))}function Fc(a,b,c,d,e){var f=va(a,b,c,d,e),g=ta(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this} +// MOMENTS +function Gc(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)} +// HELPERS +// MOMENTS +function Hc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function Ic(a,b){b[ge]=u(1e3*("0."+a))} +// MOMENTS +function Jc(){return this._isUTC?"UTC":""}function Kc(){return this._isUTC?"Coordinated Universal Time":""}function Lc(a){return sb(1e3*a)}function Mc(){return sb.apply(null,arguments).parseZone()}function Nc(a){return a}function Oc(a,b,c,d){var e=bb(),f=k().set(d,b);return e[c](f,a)}function Pc(a,b,c){if(f(a)&&(b=a,a=void 0),a=a||"",null!=b)return Oc(a,b,c,"month");var d,e=[];for(d=0;d<12;d++)e[d]=Oc(a,d,c,"month");return e} +// () +// (5) +// (fmt, 5) +// (fmt) +// (true) +// (true, 5) +// (true, fmt, 5) +// (true, fmt) +function Qc(a,b,c,d){"boolean"==typeof a?(f(b)&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,f(b)&&(c=b,b=void 0),b=b||"");var e=bb(),g=a?e._week.dow:0;if(null!=c)return Oc(b,(c+g)%7,d,"day");var h,i=[];for(h=0;h<7;h++)i[h]=Oc(b,(h+g)%7,d,"day");return i}function Rc(a,b){return Pc(a,b,"months")}function Sc(a,b){return Pc(a,b,"monthsShort")}function Tc(a,b,c){return Qc(a,b,c,"weekdays")}function Uc(a,b,c){return Qc(a,b,c,"weekdaysShort")}function Vc(a,b,c){return Qc(a,b,c,"weekdaysMin")}function Wc(){var a=this._data;return this._milliseconds=Ze(this._milliseconds),this._days=Ze(this._days),this._months=Ze(this._months),a.milliseconds=Ze(a.milliseconds),a.seconds=Ze(a.seconds),a.minutes=Ze(a.minutes),a.hours=Ze(a.hours),a.months=Ze(a.months),a.years=Ze(a.years),this}function Xc(a,b,c,d){var e=Ob(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()} +// supports only 2.0-style add(1, 's') or add(duration) +function Yc(a,b){return Xc(this,a,b,1)} +// supports only 2.0-style subtract(1, 's') or subtract(duration) +function Zc(a,b){return Xc(this,a,b,-1)}function $c(a){return a<0?Math.floor(a):Math.ceil(a)}function _c(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data; +// if we have a mix of positive and negative values, bubble down first +// check: https://github.com/moment/moment/issues/2166 +// The following code bubbles up values, see the tests for +// examples of what that means. +// convert days to months +// 12 months -> 1 year +return f>=0&&g>=0&&h>=0||f<=0&&g<=0&&h<=0||(f+=864e5*$c(bd(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=t(f/1e3),i.seconds=a%60,b=t(a/60),i.minutes=b%60,c=t(b/60),i.hours=c%24,g+=t(c/24),e=t(ad(g)),h+=e,g-=$c(bd(e)),d=t(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function ad(a){ +// 400 years have 146097 days (taking into account leap year rules) +// 400 years have 12 months === 4800 +return 4800*a/146097}function bd(a){ +// the reverse of daysToMonths +return 146097*a/4800}function cd(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+ad(b),"month"===a?c:c/12;switch( +// handle milliseconds separately because of floating point math errors (issue #1867) +b=this._days+Math.round(bd(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3; +// Math.floor prevents floating point math errors here +case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}} +// TODO: Use this.as('ms')? +function dd(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*u(this._months/12)}function ed(a){return function(){return this.as(a)}}function fd(a){return a=K(a),this[a+"s"]()}function gd(a){return function(){return this._data[a]}}function hd(){return t(this.days()/7)} +// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize +function id(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function jd(a,b,c){var d=Ob(a).abs(),e=of(d.as("s")),f=of(d.as("m")),g=of(d.as("h")),h=of(d.as("d")),i=of(d.as("M")),j=of(d.as("y")),k=e0,k[4]=c,id.apply(null,k)} +// This function allows you to set the rounding function for relative time strings +function kd(a){return void 0===a?of:"function"==typeof a&&(of=a,!0)} +// This function allows you to set a threshold for relative time strings +function ld(a,b){return void 0!==pf[a]&&(void 0===b?pf[a]:(pf[a]=b,!0))}function md(a){var b=this.localeData(),c=jd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function nd(){ +// for ISO strings we do not use the normal bubbling rules: +// * milliseconds bubble up until they become hours +// * days do not bubble at all +// * months bubble up until they become years +// This is because there is no context-free conversion between hours and days +// (think of clock changes) +// and also not between days and months (28-31 days per month) +var a,b,c,d=qf(this._milliseconds)/1e3,e=qf(this._days),f=qf(this._months); +// 3600 seconds -> 60 minutes -> 1 hour +a=t(d/60),b=t(a/60),d%=60,a%=60, +// 12 months -> 1 year +c=t(f/12),f%=12; +// inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js +var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(m<0?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var od,pd;pd=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;d68?1900:2e3)}; +// MOMENTS +var pe=O("FullYear",!0); +// FORMATTING +U("w",["ww",2],"wo","week"),U("W",["WW",2],"Wo","isoWeek"), +// ALIASES +J("week","w"),J("isoWeek","W"), +// PRIORITIES +M("week",5),M("isoWeek",5), +// PARSING +Z("w",Od),Z("ww",Od,Kd),Z("W",Od),Z("WW",Od,Kd),ca(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=u(a)});var qe={dow:0,// Sunday is the first day of the week. +doy:6}; +// FORMATTING +U("d",0,"do","day"),U("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),U("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),U("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),U("e",0,0,"weekday"),U("E",0,0,"isoWeekday"), +// ALIASES +J("day","d"),J("weekday","e"),J("isoWeekday","E"), +// PRIORITY +M("day",11),M("weekday",11),M("isoWeekday",11), +// PARSING +Z("d",Od),Z("e",Od),Z("E",Od),Z("dd",function(a,b){return b.weekdaysMinRegex(a)}),Z("ddd",function(a,b){return b.weekdaysShortRegex(a)}),Z("dddd",function(a,b){return b.weekdaysRegex(a)}),ca(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict); +// if we didn't get a weekday name, mark the date as invalid +null!=e?b.d=e:m(c).invalidWeekday=a}),ca(["d","e","E"],function(a,b,c,d){b[d]=u(a)}); +// LOCALES +var re="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),se="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),te="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ue=Zd,ve=Zd,we=Zd;U("H",["HH",2],0,"hour"),U("h",["hh",2],0,Ra),U("k",["kk",2],0,Sa),U("hmm",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)}),U("hmmss",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),U("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),U("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ta("a",!0),Ta("A",!1), +// ALIASES +J("hour","h"), +// PRIORITY +M("hour",13),Z("a",Ua),Z("A",Ua),Z("H",Od),Z("h",Od),Z("HH",Od,Kd),Z("hh",Od,Kd),Z("hmm",Pd),Z("hmmss",Qd),Z("Hmm",Pd),Z("Hmmss",Qd),ba(["H","HH"],de),ba(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),ba(["h","hh"],function(a,b,c){b[de]=u(a),m(c).bigHour=!0}),ba("hmm",function(a,b,c){var d=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d)),m(c).bigHour=!0}),ba("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d,2)),b[fe]=u(a.substr(e)),m(c).bigHour=!0}),ba("Hmm",function(a,b,c){var d=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d))}),ba("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d,2)),b[fe]=u(a.substr(e))});var xe,ye=/[ap]\.?m?\.?/i,ze=O("Hours",!0),Ae={calendar:xd,longDateFormat:yd,invalidDate:zd,ordinal:Ad,ordinalParse:Bd,relativeTime:Cd,months:le,monthsShort:me,week:qe,weekdays:re,weekdaysMin:te,weekdaysShort:se,meridiemParse:ye},Be={},Ce={},De=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ee=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Fe=/Z|[+-]\d\d(?::?\d\d)?/,Ge=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/], +// YYYYMM is NOT allowed by the standard +["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],He=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ie=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=x("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}), +// constant that refers to the ISO standard +a.ISO_8601=function(){};var Je=x("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=sb.apply(null,arguments);return this.isValid()&&a.isValid()?athis?this:a:o()}),Le=function(){return Date.now?Date.now():+new Date};zb("Z",":"),zb("ZZ",""), +// PARSING +Z("Z",Xd),Z("ZZ",Xd),ba(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ab(Xd,a)}); +// HELPERS +// timezone chunker +// '+10:00' > ['10', '00'] +// '-1530' > ['-15', '30'] +var Me=/([\+\-]|\d\d)/gi; +// HOOKS +// This function will be called whenever a moment is mutated. +// It is intended to keep the offset in sync with the timezone. +a.updateOffset=function(){}; +// ASP.NET json date format regex +var Ne=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Oe=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;Ob.fn=wb.prototype;var Pe=Sb(1,"add"),Qe=Sb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Re=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)}); +// FORMATTING +U(0,["gg",2],0,function(){return this.weekYear()%100}),U(0,["GG",2],0,function(){return this.isoWeekYear()%100}),zc("gggg","weekYear"),zc("ggggg","weekYear"),zc("GGGG","isoWeekYear"),zc("GGGGG","isoWeekYear"), +// ALIASES +J("weekYear","gg"),J("isoWeekYear","GG"), +// PRIORITY +M("weekYear",1),M("isoWeekYear",1), +// PARSING +Z("G",Vd),Z("g",Vd),Z("GG",Od,Kd),Z("gg",Od,Kd),Z("GGGG",Sd,Md),Z("gggg",Sd,Md),Z("GGGGG",Td,Nd),Z("ggggg",Td,Nd),ca(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=u(a)}),ca(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}), +// FORMATTING +U("Q",0,"Qo","quarter"), +// ALIASES +J("quarter","Q"), +// PRIORITY +M("quarter",7), +// PARSING +Z("Q",Jd),ba("Q",function(a,b){b[be]=3*(u(a)-1)}), +// FORMATTING +U("D",["DD",2],"Do","date"), +// ALIASES +J("date","D"), +// PRIOROITY +M("date",9), +// PARSING +Z("D",Od),Z("DD",Od,Kd),Z("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),ba(["D","DD"],ce),ba("Do",function(a,b){b[ce]=u(a.match(Od)[0],10)}); +// MOMENTS +var Se=O("Date",!0); +// FORMATTING +U("DDD",["DDDD",3],"DDDo","dayOfYear"), +// ALIASES +J("dayOfYear","DDD"), +// PRIORITY +M("dayOfYear",4), +// PARSING +Z("DDD",Rd),Z("DDDD",Ld),ba(["DDD","DDDD"],function(a,b,c){c._dayOfYear=u(a)}), +// FORMATTING +U("m",["mm",2],0,"minute"), +// ALIASES +J("minute","m"), +// PRIORITY +M("minute",14), +// PARSING +Z("m",Od),Z("mm",Od,Kd),ba(["m","mm"],ee); +// MOMENTS +var Te=O("Minutes",!1); +// FORMATTING +U("s",["ss",2],0,"second"), +// ALIASES +J("second","s"), +// PRIORITY +M("second",15), +// PARSING +Z("s",Od),Z("ss",Od,Kd),ba(["s","ss"],fe); +// MOMENTS +var Ue=O("Seconds",!1); +// FORMATTING +U("S",0,0,function(){return~~(this.millisecond()/100)}),U(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),U(0,["SSS",3],0,"millisecond"),U(0,["SSSS",4],0,function(){return 10*this.millisecond()}),U(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),U(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),U(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),U(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),U(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}), +// ALIASES +J("millisecond","ms"), +// PRIORITY +M("millisecond",16), +// PARSING +Z("S",Rd,Jd),Z("SS",Rd,Kd),Z("SSS",Rd,Ld);var Ve;for(Ve="SSSS";Ve.length<=9;Ve+="S")Z(Ve,Ud);for(Ve="S";Ve.length<=9;Ve+="S")ba(Ve,Ic); +// MOMENTS +var We=O("Milliseconds",!1); +// FORMATTING +U("z",0,0,"zoneAbbr"),U("zz",0,0,"zoneName");var Xe=r.prototype;Xe.add=Pe,Xe.calendar=Vb,Xe.clone=Wb,Xe.diff=bc,Xe.endOf=oc,Xe.format=gc,Xe.from=hc,Xe.fromNow=ic,Xe.to=jc,Xe.toNow=kc,Xe.get=R,Xe.invalidAt=xc,Xe.isAfter=Xb,Xe.isBefore=Yb,Xe.isBetween=Zb,Xe.isSame=$b,Xe.isSameOrAfter=_b,Xe.isSameOrBefore=ac,Xe.isValid=vc,Xe.lang=Re,Xe.locale=lc,Xe.localeData=mc,Xe.max=Ke,Xe.min=Je,Xe.parsingFlags=wc,Xe.set=S,Xe.startOf=nc,Xe.subtract=Qe,Xe.toArray=sc,Xe.toObject=tc,Xe.toDate=rc,Xe.toISOString=ec,Xe.inspect=fc,Xe.toJSON=uc,Xe.toString=dc,Xe.unix=qc,Xe.valueOf=pc,Xe.creationData=yc, +// Year +Xe.year=pe,Xe.isLeapYear=ra, +// Week Year +Xe.weekYear=Ac,Xe.isoWeekYear=Bc, +// Quarter +Xe.quarter=Xe.quarters=Gc, +// Month +Xe.month=ka,Xe.daysInMonth=la, +// Week +Xe.week=Xe.weeks=Ba,Xe.isoWeek=Xe.isoWeeks=Ca,Xe.weeksInYear=Dc,Xe.isoWeeksInYear=Cc, +// Day +Xe.date=Se,Xe.day=Xe.days=Ka,Xe.weekday=La,Xe.isoWeekday=Ma,Xe.dayOfYear=Hc, +// Hour +Xe.hour=Xe.hours=ze, +// Minute +Xe.minute=Xe.minutes=Te, +// Second +Xe.second=Xe.seconds=Ue, +// Millisecond +Xe.millisecond=Xe.milliseconds=We, +// Offset +Xe.utcOffset=Db,Xe.utc=Fb,Xe.local=Gb,Xe.parseZone=Hb,Xe.hasAlignedHourOffset=Ib,Xe.isDST=Jb,Xe.isLocal=Lb,Xe.isUtcOffset=Mb,Xe.isUtc=Nb,Xe.isUTC=Nb, +// Timezone +Xe.zoneAbbr=Jc,Xe.zoneName=Kc, +// Deprecations +Xe.dates=x("dates accessor is deprecated. Use date instead.",Se),Xe.months=x("months accessor is deprecated. Use month instead",ka),Xe.years=x("years accessor is deprecated. Use year instead",pe),Xe.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Eb),Xe.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Kb);var Ye=C.prototype;Ye.calendar=D,Ye.longDateFormat=E,Ye.invalidDate=F,Ye.ordinal=G,Ye.preparse=Nc,Ye.postformat=Nc,Ye.relativeTime=H,Ye.pastFuture=I,Ye.set=A, +// Month +Ye.months=fa,Ye.monthsShort=ga,Ye.monthsParse=ia,Ye.monthsRegex=na,Ye.monthsShortRegex=ma, +// Week +Ye.week=ya,Ye.firstDayOfYear=Aa,Ye.firstDayOfWeek=za, +// Day of Week +Ye.weekdays=Fa,Ye.weekdaysMin=Ha,Ye.weekdaysShort=Ga,Ye.weekdaysParse=Ja,Ye.weekdaysRegex=Na,Ye.weekdaysShortRegex=Oa,Ye.weekdaysMinRegex=Pa, +// Hours +Ye.isPM=Va,Ye.meridiem=Wa,$a("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===u(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}), +// Side effect imports +a.lang=x("moment.lang is deprecated. Use moment.locale instead.",$a),a.langData=x("moment.langData is deprecated. Use moment.localeData instead.",bb);var Ze=Math.abs,$e=ed("ms"),_e=ed("s"),af=ed("m"),bf=ed("h"),cf=ed("d"),df=ed("w"),ef=ed("M"),ff=ed("y"),gf=gd("milliseconds"),hf=gd("seconds"),jf=gd("minutes"),kf=gd("hours"),lf=gd("days"),mf=gd("months"),nf=gd("years"),of=Math.round,pf={s:45,// seconds to minute +m:45,// minutes to hour +h:22,// hours to day +d:26,// days to month +M:11},qf=Math.abs,rf=wb.prototype; +// Deprecations +// Side effect imports +// FORMATTING +// PARSING +// Side effect imports +return rf.abs=Wc,rf.add=Yc,rf.subtract=Zc,rf.as=cd,rf.asMilliseconds=$e,rf.asSeconds=_e,rf.asMinutes=af,rf.asHours=bf,rf.asDays=cf,rf.asWeeks=df,rf.asMonths=ef,rf.asYears=ff,rf.valueOf=dd,rf._bubble=_c,rf.get=fd,rf.milliseconds=gf,rf.seconds=hf,rf.minutes=jf,rf.hours=kf,rf.days=lf,rf.weeks=hd,rf.months=mf,rf.years=nf,rf.humanize=md,rf.toISOString=nd,rf.toString=nd,rf.toJSON=nd,rf.locale=lc,rf.localeData=mc,rf.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",nd),rf.lang=Re,U("X",0,0,"unix"),U("x",0,0,"valueOf"),Z("x",Vd),Z("X",Yd),ba("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),ba("x",function(a,b,c){c._d=new Date(u(a))}),a.version="2.17.1",b(sb),a.fn=Xe,a.min=ub,a.max=vb,a.now=Le,a.utc=k,a.unix=Lc,a.months=Rc,a.isDate=g,a.locale=$a,a.invalid=o,a.duration=Ob,a.isMoment=s,a.weekdays=Tc,a.parseZone=Mc,a.localeData=bb,a.isDuration=xb,a.monthsShort=Sc,a.weekdaysMin=Vc,a.defineLocale=_a,a.updateLocale=ab,a.locales=cb,a.weekdaysShort=Uc,a.normalizeUnits=K,a.relativeTimeRounding=kd,a.relativeTimeThreshold=ld,a.calendarFormat=Ub,a.prototype=Xe,a}); From afc283a05c9ed203a5321d46fa9121827c75fc7c Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 7 Dec 2016 10:17:27 -0800 Subject: [PATCH 4/7] handle tables with similar data but non-overlapping columns --- gdbgui/static/js/gdbgui.js | 39 +++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/gdbgui/static/js/gdbgui.js b/gdbgui/static/js/gdbgui.js index 5010e36b..d63b7767 100644 --- a/gdbgui/static/js/gdbgui.js +++ b/gdbgui/static/js/gdbgui.js @@ -2,11 +2,11 @@ "use strict"; const Util = { - get_table: function(thead, data) { + get_table: function(columns, data) { var result = [""]; result.push(""); result.push(""); - for (let h of thead){ + for (let h of columns){ result.push(``); } result.push(""); @@ -23,6 +23,21 @@ const Util = { result.push("
${h}
"); return result.join('\n'); }, + get_table_data: function(objs){ + // put keys of all objects into array + let all_keys = _.flatten(objs.map(i => _.keys(i))) + let columns = _.uniq(_.flatten(all_keys)).sort() + + let data = [] + for (let s of objs){ + let row = [] + for (let k of columns){ + row.push(k in s ? s[k] : '') + } + data.push(row) + } + return [columns, data] + }, post_msg: function(data){ if (data.responseJSON && data.responseJSON.message){ App.set_status(_.escape(data.responseJSON.message)) @@ -76,9 +91,6 @@ let App = { } }, onclose: function(){ - console.log(JSON.stringify(App.state.history)) - console.log(JSON.stringify(App.state.history)) - console.log(JSON.stringify(App.state.history)) localStorage.setItem('last_binary', Consts.jq_binary.val()) localStorage.setItem('history', JSON.stringify(App.state.history)) return null @@ -166,7 +178,6 @@ let App = { } }, click_resizer_button: function(e){ - console.log(e) let jq_selection = $(e.currentTarget.dataset['target_selector']) let cur_height = jq_selection.height() if (e.currentTarget.dataset['resize_type'] === 'enlarge'){ @@ -208,7 +219,7 @@ let App = { return } - App.set_status('') + App.set_status(`running command "${cmd}"`) App.save_to_history(cmd) App.show_in_history_table(cmd) $.ajax({ @@ -335,14 +346,13 @@ let App = { Consts.jq_disassembly_heading.html(asm_insns['fullname']) }, render_stack: function(stack){ - let thead = _.keys(stack[0]) - let stack_array = stack.map(b => _.values(b)); - Consts.jq_stack.html(Util.get_table(thead, stack_array)); + let [columns, data] = Util.get_table_data(stack) + Consts.jq_stack.html(Util.get_table(columns, data)); }, render_registers(names, values){ - let thead = ['name', 'value'] + let columns = ['name', 'value'] let register_array = values.map(v => [names[v['number']], v['value']]); - Consts.jq_registers.html(Util.get_table(thead, register_array)); + Consts.jq_registers.html(Util.get_table(columns, register_array)); }, read_and_render_file: function(fullname, highlight_line=0){ if (fullname === null || fullname === undefined){ @@ -410,9 +420,8 @@ let App = { App.state.breakpoints.push(breakpoint); }, render_breakpoint_table: function(){ - const thead = _.keys(App.state.breakpoints[0]); - let bkpt_array = App.state.breakpoints.map(b => _.values(b)); - Consts.jq_breakpoints.html(Util.get_table(thead, bkpt_array)); + let [columns, data] = Util.get_table_data(App.state.breakpoints) + Consts.jq_breakpoints.html(Util.get_table(columns, data)) }, enable_gdb_controls: function(){ Consts.js_gdb_controls.removeClass('disabled'); From fbd0c21ce9681a423bd527f3cfca8b7b9b7d6e49 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 7 Dec 2016 10:52:19 -0800 Subject: [PATCH 5/7] add button to get delayed gdb response; add fixed top status bar --- gdbgui/backend.py | 11 +++++++++++ gdbgui/static/js/gdbgui.js | 10 ++++++++++ gdbgui/templates/gdbgui.jade | 28 ++++++++++++++++++---------- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/gdbgui/backend.py b/gdbgui/backend.py index 643199a3..47b07484 100755 --- a/gdbgui/backend.py +++ b/gdbgui/backend.py @@ -54,6 +54,17 @@ def run_gdb_command(): else: return client_error({'message': 'gdb is not running'}) +@app.route('/get_gdb_response') +def get_gdb_response(): + if gdb is not None: + try: + response = gdb.get_gdb_response() + return jsonify(response) + except Exception as e: + return server_error({'message': str(e)}) + else: + return client_error({'message': 'gdb is not running'}) + @app.route('/read_file') def read_file(): diff --git a/gdbgui/static/js/gdbgui.js b/gdbgui/static/js/gdbgui.js index d63b7767..4163aca7 100644 --- a/gdbgui/static/js/gdbgui.js +++ b/gdbgui/static/js/gdbgui.js @@ -131,6 +131,7 @@ let App = { $('.gdb_cmd').click(function(e){App.click_gdb_cmd_button(e)}); $('.clear_history').click(function(e){App.clear_history(e)}); $('.clear_console').click(function(e){App.clear_console(e)}); + $('.get_gdb_response').click(function(e){App.get_gdb_response(e)}); $("body").on("click", ".breakpoint", App.click_breakpoint); $("body").on("click", ".no_breakpoint", App.click_source_file_gutter_with_no_breakpoint); $("body").on("click", ".sent_command", App.click_sent_command); @@ -231,6 +232,15 @@ let App = { error: Util.post_msg }) }, + get_gdb_response: function(){ + App.set_status(`Getting GDB response`) + $.ajax({ + url: "/get_gdb_response", + cache: false, + success: App.receive_gdb_response, + error: Util.post_msg + }) + }, clear_history: function(){ App.state.history = [] Consts.jq_command_history.html('') diff --git a/gdbgui/templates/gdbgui.jade b/gdbgui/templates/gdbgui.jade index da070405..af5ef2ff 100644 --- a/gdbgui/templates/gdbgui.jade +++ b/gdbgui/templates/gdbgui.jade @@ -15,10 +15,15 @@ head script(type="text/javascript" src='static/vendor/js/moment.min.js') body - div.container-fluid + div.container-fluid(style="position: fixed; top: 0; margin-top: 0; + height: 30px; width: 100%; + border-bottom: black; border-style: solid; border-width: 0px; border-bottom-width: 1px; + z-index: 1000; background: #6dc6ff") div.row span Status:   span#status(style="height: 20px") GDB is running on the backend. Choose a binary to debug. + + div.container-fluid(style="margin-top: 50px") div.row div.col-md-6 //- binary selection @@ -45,27 +50,23 @@ body button.btn.btn-default.gdb_cmd(type="button", data-cmd='-exec-interrupt') Kill Target span.glyphicon.glyphicon-ban-circle.padding_left + button.btn.btn-default.get_gdb_response(type="button") Get GDB Response + + //- freeform input form(role=form) div.input-group.input-group-sm span.input-group-btn button.btn.btn-success.run_gdb_command(type="button") Send command input.form-control.dropdown-input#gdb_command(type="text", placeholder="gdb command", autocomplete="on", list="gdb_command_reference") - //- history - div.col-md-6(style="overflow: auto; height: 100px; ") History - button.btn.btn-default.btn-xs.clear_history(type='button') Clear - - div.gdb_content_div - table.table.table-condensed - tbody#command_history //- source - div.no_padding.col-md-6#source_div + div.no_padding.col-md-12#source_div span#source_code_heading Source Code span.glyphicon.glyphicon-plus.resizer.pointer(data-target_selector="#code_container" data-resize_type="enlarge") span.glyphicon.glyphicon-minus.resizer.pointer(data-target_selector="#code_container" data-resize_type="shrink") button.btn.btn-default.gdb_cmd.btn-xs(type="button", data-cmd='-file-list-exec-source-files') Fetch Source File List - div#code_container.gdb_content_div + div#code_container.gdb_content_div(style="height: 300px") table#code_table.code tr td Source code will be displayed here when applicable @@ -93,6 +94,13 @@ body span.glyphicon.glyphicon-refresh.padding_left div.gdb_content_div#stdout + //- history + div.col-md-6.no_padding History + button.btn.btn-default.btn-xs.clear_history(type='button') Clear + + div.gdb_content_div + table.table.table-condensed + tbody#command_history div.col-md-6.no_padding stack span.glyphicon.glyphicon-plus.resizer.pointer(data-target_selector="#stack" data-resize_type="enlarge") From fc86a631c4a69e1ea37fd1a2ce0dbe375f1700d5 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 7 Dec 2016 15:27:51 -0800 Subject: [PATCH 6/7] better history saving; choose from list of all source files --- gdbgui/static/js/gdbgui.js | 69 ++++++++++++++++++++++++++---------- gdbgui/templates/gdbgui.jade | 15 +++++--- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/gdbgui/static/js/gdbgui.js b/gdbgui/static/js/gdbgui.js index 4163aca7..e06d5ba1 100644 --- a/gdbgui/static/js/gdbgui.js +++ b/gdbgui/static/js/gdbgui.js @@ -60,6 +60,7 @@ const Consts = { jq_gdb_command_input: $('#gdb_command'), jq_binary: $('#binary'), + jq_source_file_list: $('#source_file_list'), jq_code: $('#code_table'), jq_code_container: $('#code_container'), js_gdb_controls: $('.gdb_controls'), @@ -80,19 +81,24 @@ let App = { init: function(){ App.register_events(); - Consts.jq_binary.val(localStorage.getItem('last_binary')) try{ - App.state.history = JSON.parse(localStorage.getItem('history')) + App.state.past_binaries = _.uniq(JSON.parse(localStorage.getItem('past_binaries'))) + Consts.jq_binary.val(App.state.past_binaries[0]) + } catch(err){ + App.state.past_binaries = [] + } + App.render_past_binary_options_datalist() + + try{ + App.state.history = _.uniq(JSON.parse(localStorage.getItem('history'))) }catch(err){ App.state.history = [] } - if (_.isArray(App.state.history)){ - App.state.history.map(App.show_in_history_table) - } + App.render_history_table() }, onclose: function(){ - localStorage.setItem('last_binary', Consts.jq_binary.val()) - localStorage.setItem('history', JSON.stringify(App.state.history)) + localStorage.setItem('past_binaries', JSON.stringify(App.state.past_binaries) || []) + localStorage.setItem('history', JSON.stringify(App.state.history) || []) return null }, set_status: function(status){ @@ -102,7 +108,8 @@ let App = { 'source_files': [], // list of absolute paths, and their contents 'frame': {}, // current "frame" in gdb. Has keys: line, fullname (path to file), among others. 'rendered_source_file': {'fullname': null, 'line': null}, // current source file displayed - 'history': [] + 'history': [], + 'past_binaries': [] }, clear_state: function(){ App.state = { @@ -136,6 +143,7 @@ let App = { $("body").on("click", ".no_breakpoint", App.click_source_file_gutter_with_no_breakpoint); $("body").on("click", ".sent_command", App.click_sent_command); $("body").on("click", ".resizer", App.click_resizer_button); + $("body").on("change", "#source_file_list", App.select_file_from_file_list); Consts.jq_refresh_disassembly_button.click(App.refresh_disassembly); }, @@ -153,8 +161,13 @@ let App = { }, click_set_target_app_button: function(e){ var binary = Consts.jq_binary.val(); + _.remove(App.state.past_binaries, i => i === binary) + App.state.past_binaries.unshift(binary) + App.render_past_binary_options_datalist() App.run_gdb_command(`-file-exec-and-symbols ${binary}`); - App.enable_gdb_controls(); + }, + render_past_binary_options_datalist: function(){ + $('#past_binaries').html(App.state.past_binaries.map(b => ``)) + }, + select_file_from_file_list: function(e){ + App.read_and_render_file($(e.currentTarget).val()) + }, render_disasembly: function(asm_insns){ let thead = [ 'line', 'function+offset address instruction'] let data = [] diff --git a/gdbgui/templates/gdbgui.jade b/gdbgui/templates/gdbgui.jade index af5ef2ff..0ae45aed 100644 --- a/gdbgui/templates/gdbgui.jade +++ b/gdbgui/templates/gdbgui.jade @@ -18,10 +18,10 @@ body div.container-fluid(style="position: fixed; top: 0; margin-top: 0; height: 30px; width: 100%; border-bottom: black; border-style: solid; border-width: 0px; border-bottom-width: 1px; - z-index: 1000; background: #6dc6ff") + z-index: 1000; background: #ccc") div.row - span Status:   - span#status(style="height: 20px") GDB is running on the backend. Choose a binary to debug. + span Status:  + span#status(style="height: 20px") GDB is waiting to run a command div.container-fluid(style="margin-top: 50px") div.row @@ -31,7 +31,8 @@ body div.input-group.input-group-sm span.input-group-btn button.btn.btn-primary#set_target_app(type="button") Load Binary - input.form-control#binary(type="text", placeholder="Path to target executable") + input.form-control#binary(type="text", placeholder="Path to target executable", list='past_binaries') + datalist#past_binaries //- premade buttons div.btn-group.btn-group-xs(role="group") @@ -65,7 +66,11 @@ body span#source_code_heading Source Code span.glyphicon.glyphicon-plus.resizer.pointer(data-target_selector="#code_container" data-resize_type="enlarge") span.glyphicon.glyphicon-minus.resizer.pointer(data-target_selector="#code_container" data-resize_type="shrink") - button.btn.btn-default.gdb_cmd.btn-xs(type="button", data-cmd='-file-list-exec-source-files') Fetch Source File List + div + select#source_file_list + option Load a binary, then click Refresh List + button.btn.btn-default.gdb_cmd.btn-xs(type="button", data-cmd='-file-list-exec-source-files') Refresh List + div#code_container.gdb_content_div(style="height: 300px") table#code_table.code tr From 238f4af5f2e2e7298170eab4f2d714aea6273533 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 7 Dec 2016 17:03:47 -0800 Subject: [PATCH 7/7] improve source file selection --- gdbgui/static/css/gdbgui.css | 10 ++ gdbgui/static/js/gdbgui.js | 59 ++++++++--- gdbgui/static/vendor/css/awesomeplete.css | 104 ++++++++++++++++++++ gdbgui/static/vendor/js/awesomeplete.min.js | 3 + gdbgui/templates/gdbgui.jade | 84 +++------------- setup.py | 2 +- 6 files changed, 175 insertions(+), 87 deletions(-) create mode 100644 gdbgui/static/vendor/css/awesomeplete.css create mode 100644 gdbgui/static/vendor/js/awesomeplete.min.js diff --git a/gdbgui/static/css/gdbgui.css b/gdbgui/static/css/gdbgui.css index ea5e0655..b8badf9d 100644 --- a/gdbgui/static/css/gdbgui.css +++ b/gdbgui/static/css/gdbgui.css @@ -97,3 +97,13 @@ pre{ border-width: 1px; border-radius: 2px; } +.dropdown-btn { + vertical-align: top; + height: 30px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.awesomplete ul { + overflow: auto; + max-height: 200px; +} diff --git a/gdbgui/static/js/gdbgui.js b/gdbgui/static/js/gdbgui.js index e06d5ba1..b58b83c2 100644 --- a/gdbgui/static/js/gdbgui.js +++ b/gdbgui/static/js/gdbgui.js @@ -60,7 +60,8 @@ const Consts = { jq_gdb_command_input: $('#gdb_command'), jq_binary: $('#binary'), - jq_source_file_list: $('#source_file_list'), + jq_source_file_input: $('#source_file_input'), + jq_source_files_datalist: $('#source_files_datalist'), jq_code: $('#code_table'), jq_code_container: $('#code_container'), js_gdb_controls: $('.gdb_controls'), @@ -105,11 +106,12 @@ let App = { Consts.jq_status.text(status) }, state: {'breakpoints': [], // list of breakpoints - 'source_files': [], // list of absolute paths, and their contents + 'source_files': [], // list of absolute paths + 'cached_source_files': [], // list of absolute paths, and their source code 'frame': {}, // current "frame" in gdb. Has keys: line, fullname (path to file), among others. 'rendered_source_file': {'fullname': null, 'line': null}, // current source file displayed 'history': [], - 'past_binaries': [] + 'past_binaries': [], }, clear_state: function(){ App.state = { @@ -136,6 +138,7 @@ let App = { } ); $('.gdb_cmd').click(function(e){App.click_gdb_cmd_button(e)}); + Consts.jq_source_file_input.keyup(function(e){App.keyup_source_file_input(e)}); $('.clear_history').click(function(e){App.clear_history(e)}); $('.clear_console').click(function(e){App.clear_console(e)}); $('.get_gdb_response').click(function(e){App.get_gdb_response(e)}); @@ -143,8 +146,37 @@ let App = { $("body").on("click", ".no_breakpoint", App.click_source_file_gutter_with_no_breakpoint); $("body").on("click", ".sent_command", App.click_sent_command); $("body").on("click", ".resizer", App.click_resizer_button); - $("body").on("change", "#source_file_list", App.select_file_from_file_list); + Consts.jq_refresh_disassembly_button.click(App.refresh_disassembly); + App.init_autocomplete() + + + }, + init_autocomplete: function(){ + + App.autocomplete_source_file_input = new Awesomplete('#source_file_input', { + minChars: 0, + maxItems: 10000, + list: [], + sort: (a,b ) => {return a < b ? -1 : 1;} + }); + + Awesomplete.$('.dropdown-btn').addEventListener("click", function() { + if (App.autocomplete_source_file_input.ul.childNodes.length === 0) { + App.autocomplete_source_file_input.minChars = 0; + App.autocomplete_source_file_input.evaluate(); + } + else if (App.autocomplete_source_file_input.ul.hasAttribute('hidden')) { + App.autocomplete_source_file_input.open(); + } + else { + App.autocomplete_source_file_input.close(); + } + }) + + Awesomplete.$('#source_file_input').addEventListener('awesomplete-selectcomplete', function(e){ + App.read_and_render_file(e.currentTarget.value) + }); }, refresh_disassembly: function(e){ @@ -325,7 +357,9 @@ let App = { App.render_disasembly(r.payload.asm_insns) } else if ('files' in r.payload){ - App.render_file_list(r.payload.files) + App.source_files = _.uniq(r.payload.files.map(f => f.fullname)).sort() + App.autocomplete_source_file_input.list = App.source_files + App.autocomplete_source_file_input.evaluate() } } else if (r.payload && typeof r.payload.frame !== 'undefined') { @@ -370,11 +404,10 @@ let App = { Consts.jq_gdb_mi_output.animate({'scrollTop': Consts.jq_gdb_mi_output.prop('scrollHeight')}) Consts.jq_console.animate({'scrollTop': Consts.jq_console.prop('scrollHeight')}) }, - render_file_list: function(files){ - Consts.jq_source_file_list.html(files.map(f => ``)) - }, - select_file_from_file_list: function(e){ - App.read_and_render_file($(e.currentTarget).val()) + keyup_source_file_input: function(e){ + if (e.keyCode === 13){ + App.read_and_render_file(e.currentTarget.value) + } }, render_disasembly: function(asm_insns){ let thead = [ 'line', 'function+offset address instruction'] @@ -400,9 +433,9 @@ let App = { return } - if (App.state.source_files.some(f => f.fullname === fullname)){ + if (App.state.cached_source_files.some(f => f.fullname === fullname)){ // We have this cached locally, just use it! - let f = _.find(App.state.source_files, i => i.fullname === fullname); + let f = _.find(App.state.cached_source_files, i => i.fullname === fullname); App.render_source_file(fullname, f.source_code, highlight_line); return } @@ -413,7 +446,7 @@ let App = { type: 'GET', data: {path: fullname}, success: function(response){ - App.state.source_files.push({'fullname': fullname, 'source_code': response.source_code}) + App.state.cached_source_files.push({'fullname': fullname, 'source_code': response.source_code}) App.render_source_file(fullname, response.source_code, highlight_line); }, error: Util.post_msg diff --git a/gdbgui/static/vendor/css/awesomeplete.css b/gdbgui/static/vendor/css/awesomeplete.css new file mode 100644 index 00000000..26fcd910 --- /dev/null +++ b/gdbgui/static/vendor/css/awesomeplete.css @@ -0,0 +1,104 @@ +.awesomplete [hidden] { + display: none; +} + +.awesomplete .visually-hidden { + position: absolute; + clip: rect(0, 0, 0, 0); +} + +.awesomplete { + display: inline-block; + position: relative; +} + +.awesomplete > input { + display: block; +} + +.awesomplete > ul { + position: absolute; + left: 0; + z-index: 1; + min-width: 100%; + box-sizing: border-box; + list-style: none; + padding: 0; + margin: 0; + background: #fff; +} + +.awesomplete > ul:empty { + display: none; +} + +.awesomplete > ul { + border-radius: .3em; + margin: .2em 0 0; + background: hsla(0,0%,100%,.9); + background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); + border: 1px solid rgba(0,0,0,.3); + box-shadow: .05em .2em .6em rgba(0,0,0,.2); + text-shadow: none; +} + +@supports (transform: scale(0)) { + .awesomplete > ul { + transition: .3s cubic-bezier(.4,.2,.5,1.4); + transform-origin: 1.43em -.43em; + } + + .awesomplete > ul[hidden], + .awesomplete > ul:empty { + opacity: 0; + transform: scale(0); + display: block; + transition-timing-function: ease; + } +} + + /* Pointer */ + .awesomplete > ul:before { + content: ""; + position: absolute; + top: -.43em; + left: 1em; + width: 0; height: 0; + padding: .4em; + background: white; + border: inherit; + border-right: 0; + border-bottom: 0; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + } + + .awesomplete > ul > li { + position: relative; + padding: .2em .5em; + cursor: pointer; + } + + .awesomplete > ul > li:hover { + background: hsl(200, 40%, 80%); + color: black; + } + + .awesomplete > ul > li[aria-selected="true"] { + background: hsl(205, 40%, 40%); + color: white; + } + + .awesomplete mark { + background: hsl(65, 100%, 50%); + } + + .awesomplete li:hover mark { + background: hsl(68, 100%, 41%); + } + + .awesomplete li[aria-selected="true"] mark { + background: hsl(86, 100%, 21%); + color: inherit; + } +/*# sourceMappingURL=awesomplete.css.map */ diff --git a/gdbgui/static/vendor/js/awesomeplete.min.js b/gdbgui/static/vendor/js/awesomeplete.min.js new file mode 100644 index 00000000..c0faecf6 --- /dev/null +++ b/gdbgui/static/vendor/js/awesomeplete.min.js @@ -0,0 +1,3 @@ +// Awesomplete - Lea Verou - MIT license +!function(){function t(t){var e=Array.isArray(t)?{label:t[0],value:t[1]}:"object"==typeof t&&"label"in t&&"value"in t?t:{label:t,value:t};this.label=e.label||e.value,this.value=e.value}function e(t,e,i){for(var n in e){var s=e[n],r=t.input.getAttribute("data-"+n.toLowerCase());"number"==typeof s?t[n]=parseInt(r):s===!1?t[n]=null!==r:s instanceof Function?t[n]=null:t[n]=r,t[n]||0===t[n]||(t[n]=n in i?i[n]:s)}}function i(t,e){return"string"==typeof t?(e||document).querySelector(t):t||null}function n(t,e){return o.call((e||document).querySelectorAll(t))}function s(){n("input.awesomplete").forEach(function(t){new r(t)})}var r=function(t,n){var s=this;this.isOpened=!1,this.input=i(t),this.input.setAttribute("autocomplete","off"),this.input.setAttribute("aria-autocomplete","list"),n=n||{},e(this,{minChars:2,maxItems:10,autoFirst:!1,data:r.DATA,filter:r.FILTER_CONTAINS,sort:r.SORT_BYLENGTH,item:r.ITEM,replace:r.REPLACE},n),this.index=-1,this.container=i.create("div",{className:"awesomplete",around:t}),this.ul=i.create("ul",{hidden:"hidden",inside:this.container}),this.status=i.create("span",{className:"visually-hidden",role:"status","aria-live":"assertive","aria-relevant":"additions",inside:this.container}),i.bind(this.input,{input:this.evaluate.bind(this),blur:this.close.bind(this,{reason:"blur"}),keydown:function(t){var e=t.keyCode;s.opened&&(13===e&&s.selected?(t.preventDefault(),s.select()):27===e?s.close({reason:"esc"}):38!==e&&40!==e||(t.preventDefault(),s[38===e?"previous":"next"]()))}}),i.bind(this.input.form,{submit:this.close.bind(this,{reason:"submit"})}),i.bind(this.ul,{mousedown:function(t){var e=t.target;if(e!==this){for(;e&&!/li/i.test(e.nodeName);)e=e.parentNode;e&&0===t.button&&(t.preventDefault(),s.select(e,t.target))}}}),this.input.hasAttribute("list")?(this.list="#"+this.input.getAttribute("list"),this.input.removeAttribute("list")):this.list=this.input.getAttribute("data-list")||n.list||[],r.all.push(this)};r.prototype={set list(t){if(Array.isArray(t))this._list=t;else if("string"==typeof t&&t.indexOf(",")>-1)this._list=t.split(/\s*,\s*/);else if(t=i(t),t&&t.children){var e=[];o.apply(t.children).forEach(function(t){if(!t.disabled){var i=t.textContent.trim(),n=t.value||i,s=t.label||i;""!==n&&e.push({label:s,value:n})}}),this._list=e}document.activeElement===this.input&&this.evaluate()},get selected(){return this.index>-1},get opened(){return this.isOpened},close:function(t){this.opened&&(this.ul.setAttribute("hidden",""),this.isOpened=!1,this.index=-1,i.fire(this.input,"awesomplete-close",t||{}))},open:function(){this.ul.removeAttribute("hidden"),this.isOpened=!0,this.autoFirst&&this.index===-1&&this.goto(0),i.fire(this.input,"awesomplete-open")},next:function(){var t=this.ul.children.length;this.goto(this.index-1&&e.length>0&&(e[t].setAttribute("aria-selected","true"),this.status.textContent=e[t].textContent,i.fire(this.input,"awesomplete-highlight",{text:this.suggestions[this.index]}))},select:function(t,e){if(t?this.index=i.siblingIndex(t):t=this.ul.children[this.index],t){var n=this.suggestions[this.index],s=i.fire(this.input,"awesomplete-select",{text:n,origin:e||t});s&&(this.replace(n),this.close({reason:"select"}),i.fire(this.input,"awesomplete-selectcomplete",{text:n}))}},evaluate:function(){var e=this,i=this.input.value;i.length>=this.minChars&&this._list.length>0?(this.index=-1,this.ul.innerHTML="",this.suggestions=this._list.map(function(n){return new t(e.data(n,i))}).filter(function(t){return e.filter(t,i)}).sort(this.sort).slice(0,this.maxItems),this.suggestions.forEach(function(t){e.ul.appendChild(e.item(t,i))}),0===this.ul.children.length?this.close({reason:"nomatches"}):this.open()):this.close({reason:"nomatches"})}},r.all=[],r.FILTER_CONTAINS=function(t,e){return RegExp(i.regExpEscape(e.trim()),"i").test(t)},r.FILTER_STARTSWITH=function(t,e){return RegExp("^"+i.regExpEscape(e.trim()),"i").test(t)},r.SORT_BYLENGTH=function(t,e){return t.length!==e.length?t.length-e.length:t$&");return i.create("li",{innerHTML:n,"aria-selected":"false"})},r.REPLACE=function(t){this.input.value=t.value},r.DATA=function(t){return t},Object.defineProperty(t.prototype=Object.create(String.prototype),"length",{get:function(){return this.label.length}}),t.prototype.toString=t.prototype.valueOf=function(){return""+this.label};var o=Array.prototype.slice;return i.create=function(t,e){var n=document.createElement(t);for(var s in e){var r=e[s];if("inside"===s)i(r).appendChild(n);else if("around"===s){var o=i(r);o.parentNode.insertBefore(n,o),n.appendChild(o)}else s in n?n[s]=r:n.setAttribute(s,r)}return n},i.bind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.addEventListener(e,n)})}},i.fire=function(t,e,i){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0);for(var s in i)n[s]=i[s];return t.dispatchEvent(n)},i.regExpEscape=function(t){return t.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&")},i.siblingIndex=function(t){for(var e=0;t=t.previousElementSibling;e++);return e},"undefined"!=typeof Document&&("loading"!==document.readyState?s():document.addEventListener("DOMContentLoaded",s)),r.$=i,r.$$=n,"undefined"!=typeof self&&(self.Awesomplete=r),"object"==typeof module&&module.exports&&(module.exports=r),r}(); +//# sourceMappingURL=awesomplete.min.js.map diff --git a/gdbgui/templates/gdbgui.jade b/gdbgui/templates/gdbgui.jade index 0ae45aed..4752c52d 100644 --- a/gdbgui/templates/gdbgui.jade +++ b/gdbgui/templates/gdbgui.jade @@ -7,12 +7,14 @@ head link(href='static/css/gdbgui.css', rel='stylesheet') link(href='static/vendor/css/bootstrap.min.css', rel='stylesheet') link(href='static/vendor/css/messenger.css', rel='stylesheet') + link(href='static/vendor/css/awesomeplete.css', rel='stylesheet') script(type="text/javascript" src='static/vendor/js/jquery.min.js') script(type="text/javascript" src='static/vendor/js/bootstrap.min.js') script(type="text/javascript" src='static/vendor/js/messenger.min.js') script(type="text/javascript" src='static/vendor/js/lodash.min.js') script(type="text/javascript" src='static/vendor/js/moment.min.js') + script(type="text/javascript" src='static/vendor/js/awesomeplete.min.js') body div.container-fluid(style="position: fixed; top: 0; margin-top: 0; @@ -59,16 +61,20 @@ body div.input-group.input-group-sm span.input-group-btn button.btn.btn-success.run_gdb_command(type="button") Send command - input.form-control.dropdown-input#gdb_command(type="text", placeholder="gdb command", autocomplete="on", list="gdb_command_reference") + input.form-control.dropdown-input#gdb_command(type="text", placeholder="gdb command", autocomplete="on", data-list="#gdb_command_reference", style="width: 300px") //- source div.no_padding.col-md-12#source_div span#source_code_heading Source Code span.glyphicon.glyphicon-plus.resizer.pointer(data-target_selector="#code_container" data-resize_type="enlarge") span.glyphicon.glyphicon-minus.resizer.pointer(data-target_selector="#code_container" data-resize_type="shrink") + div - select#source_file_list - option Load a binary, then click Refresh List + input#source_file_input.dropdown-input(autocomplete='off', style="width: 500px") + + button.dropdown-btn(type="button") + span.caret + button.btn.btn-default.gdb_cmd.btn-xs(type="button", data-cmd='-file-list-exec-source-files') Refresh List div#code_container.gdb_content_div(style="height: 300px") @@ -137,6 +143,7 @@ body datalist(id="gdb_command_reference") + //- TODO complete this list option(value="Command Description") option(value="help List gdb command topics.") option(value="help topic-classes List gdb command within class.") @@ -229,75 +236,6 @@ datalist(id="gdb_command_reference") option(value="list filename:function List source code.") option(value="set listsize count") option(value="show listsize Number of lines listed when list command given.") - //- option(value="directory directory-name") - //- option(value="dir directory-name") - //- option(value="show directories Add specified directory to front of source code path.") - //- option(value="directory Clear sourcepath when nothing specified.") - //- option(value="Machine Language ") - //- option(value="info line") - //- option(value="info line number Displays the start and end position in object code for the current line in source.") - //- option(value="Display position in object code for a specified line in source.") - //- option(value="disassemble 0xstart 0xend Displays machine code for positions in object code specified (can use start and end hex memory values") - //- option(value="given by the info line command.") - //- option(value="stepi") - //- option(value="si") - //- option(value="nexti") - //- option(value="ni step/next assembly/processor instruction.") - //- option(value="x 0xaddress") - //- option(value="x/nfu 0xaddress Examine the contents of memory.") - //- option(value="Examine the contents of memory and specify formatting.") - //- option(value="n: number of display items to print") - //- option(value="f: specify the format for the output") - //- option(value="u: specify the size of the data unit (eg. byte, word, ...)") - //- option(value="Example: x/4dw var") - //- option(value="Examine Variables ") - //- option(value="print variable-name") - //- option(value="p variable-name") - //- option(value="p file-name::variable-name") - //- option(value="p 'file-name'::variable-name Print value stored in variable.") - //- option(value="p *array-variable@length Print first # values of array specified by length. Good for pointers to dynamicaly allocated memory.") - //- option(value="p/x variable Print as integer variable in hex.") - //- option(value="p/d variable Print variable as a signed integer.") - //- option(value="p/u variable Print variable as a un-signed integer.") - //- option(value="p/o variable Print variable as a octal.") - //- option(value="p/t variable") - //- option(value="x/b address") - //- option(value="x/b &variable Print as integer value in binary. (1 byte/8bits)") - //- option(value="p/c variable Print integer as character.") - //- option(value="p/f variable Print variable as floating point number.") - //- option(value="p/a variable Print as a hex address.") - //- option(value="x/w address") - //- option(value="x/4b &variable Print binary representation of 4 bytes (1 32 bit word) of memory pointed to by address.") - //- option(value="ptype variable") - //- option(value="ptype data-type Prints type definition of the variable or declared variable type. Helpful for viewing class or struct definitions option(value=while debugging.") - //- option(value="GDB Modes ") - //- option(value="set gdb-option value Set a GDB option") - //- option(value="set logging on") - //- option(value="set logging off") - //- option(value="show logging") - //- option(value="set logging file log-file Turn on/off logging. Default name of file is gdb.txt") - //- option(value="set print array on") - //- option(value="set print array off") - //- option(value="show print array Default is off. Convient readable format for arrays turned on/off.") - //- option(value="set print array-indexes on") - //- option(value="set print array-indexes off") - //- option(value="show print array-indexes Default off. Print index of array elements.") - //- option(value="set print pretty on") - //- option(value="set print pretty off") - //- option(value="show print pretty Format printing of C structures.") - //- option(value="set print union on") - //- option(value="set print union off") - //- option(value="show print union Default is on. Print C unions.") - //- option(value="set print demangle on") - //- option(value="set print demangle off") - //- option(value="show print demangle Default on. Controls printing of C++ names.") - //- option(value="Start and Stop ") - //- option(value="run") - //- option(value="r") - //- option(value="run command-line-arguments") - //- option(value="c Continue execution to next break point.") - //- option(value="kill Stop program execution.") - //- option(value="quit") - //- option(value="q Exit GDB debugger.") + script(type="text/javascript" src='static/js/gdbgui.js') diff --git a/setup.py b/setup.py index 39b66454..91158d17 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ # from distutils.core import setup, EXCLUDE_FROM_PACKAGES = [] -version = '0.0.0.2' +version = '0.0.0.3' class TestCommand (Command):