Hi Dave, Please find updated patch with docs and JS tests.
-- Regards, Murtuza Zabuawala EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company On Mon, Jan 22, 2018 at 4:02 PM, Dave Page <dp...@pgadmin.org> wrote: > Hi > > This seems to be missing documentation updates. I would also expect to see > some Jasmine unit tests for some/all of the new JS functions. > > On Tue, Jan 16, 2018 at 1:29 PM, Murtuza Zabuawala <murtuza.zabuawala@ > enterprisedb.com> wrote: > >> Hi, >> >> PFA patch to add keyboard navigation in Debugger module via >> Tab/Shift-Tab key. >> RM#2897 >> >> Also fixed the issue where user was not able to update values in >> variable's panel while debugging. >> RM#2981 >> >> Currrently we have execution related shortcuts using accesskey, >> Shortcuts (Execution related) >> ---------------------------------- >> <accesskey> + i = Step in >> <accesskey> + o = Step over >> <accesskey> + c = Continue/Restart >> <accesskey> + t = Toggle breakpoint >> <accesskey> + x = Clear all breakpoints >> <accesskey> + s = Stop >> >> Shortcuts (Panel navigation related) >> ---------------------------------- >> Alt + Shift + Right Arrow >> Alt + Shift + Left Arrow >> >> Edit/Enter values in Grid (Parameter & Local variables panel's) >> ------------------------------------------------------------- >> Alt + Shift + g >> >> Please review. >> >> *Note:* As of now inner panel's are not getting focused on Tab/Shift-Tab >> keys but once RM#2895 <https://redmine.postgresql.org/issues/2895> patch >> gets committed it will start working automatically as it's inherited code >> which will add tabindex tag automatically on each newly created wcDocker >> panel. >> >> -- >> Regards, >> Murtuza Zabuawala >> EnterpriseDB: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company >> >> > > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EnterpriseDB UK: http://www.enterprisedb.com > The Enterprise PostgreSQL Company >
diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index de67f3e..119bd79 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -89,3 +89,32 @@ When using the Query Tool, the following shortcuts are available: +--------------------------+--------------------+-----------------------------------+ | Ctrl+Shift+F | Cmd+Shift+F | Replace | +--------------------------+--------------------+-----------------------------------+ + +**Debugger** + +When using the Debugger, the following shortcuts are available: + ++--------------------------+---------------------------+------------------------------+ +| Shortcut (Windows/Linux) | Shortcut (Mac) | Function | ++==========================+===========================+==============================+ +| <accesskey> + i | <accesskey> + i | Step in | ++--------------------------+---------------------------+------------------------------+ +| <accesskey> + o | <accesskey> + o | Step over | ++--------------------------+---------------------------+------------------------------+ +| <accesskey> + c | <accesskey> + c | Continue/Restart | ++--------------------------+---------------------------+------------------------------+ +| <accesskey> + t | <accesskey> + t | Toggle breakpoint | ++--------------------------+---------------------------+------------------------------+ +| <accesskey> + x | <accesskey> + x | Clear all breakpoints | ++--------------------------+---------------------------+------------------------------+ +| <accesskey> + s | <accesskey> + s | Stop | ++--------------------------+---------------------------+------------------------------+ +| Alt + Shift + Right Arrow| Alt + Shift + Right Arrow | Move to next inner panel | ++--------------------------+---------------------------+------------------------------+ +| Alt + Shift + Left Arrow | Alt + Shift + Left Arrow | Move to previous inner panel | ++--------------------------+---------------------------+------------------------------+ +| Alt + Shift + g | Alt + Shift + g | Enter or Edit values in Grid | ++--------------------------+---------------------------+------------------------------+ + +.. note:: <accesskey> can be browser and platform dependant please `click here <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey>`_ for more information. + diff --git a/web/pgadmin/static/js/keyboard_shortcuts.js b/web/pgadmin/static/js/keyboard_shortcuts.js new file mode 100644 index 0000000..780e089 --- /dev/null +++ b/web/pgadmin/static/js/keyboard_shortcuts.js @@ -0,0 +1,119 @@ +import $ from 'jquery'; + +const EDIT_KEY = 71, // Key: G -> Grid values + LEFT_ARROW_KEY = 37, + RIGHT_ARROW_KEY = 39; + +function isMac() { + return window.navigator.platform.search('Mac') != -1; +} + +function isKeyCtrlAlt(event) { + return event.ctrlKey || event.altKey; +} + +function isKeyAltShift(event) { + return event.altKey || event.shiftKey; +} + +function isKeyCtrlShift(event) { + return event.ctrlKey || event.shiftKey; +} + +function isKeyCtrlAltShift(event) { + return event.ctrlKey || event.altKey || event.shiftKey; +} + +function isAltShiftBoth(event) { + return event.altKey && event.shiftKey && !event.ctrlKey; +} + +function isCtrlShiftBoth(event) { + return event.ctrlKey && event.shiftKey && !event.altKey; +} + +function isCtrlAltBoth(event) { + return event.ctrlKey && event.altKey && !event.shiftKey; +} + +function _stopEventPropagation(event) { + event.cancelBubble = true; + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); +} + +/* Debugger: Keyboard Shortcuts handling */ +function keyboardShortcutsDebugger($el, event) { + let keyCode = event.which || event.keyCode; + + // To handle debugger's internal tab navigation like Parameters/Messages... + if (this.isAltShiftBoth(event)) { + // Get the active wcDocker panel from DOM element + let panel_id, panel_content, $input; + switch(keyCode) { + case LEFT_ARROW_KEY: + this._stopEventPropagation(event); + panel_id = this.getInnerPanel($el, 'left'); + break; + case RIGHT_ARROW_KEY: + this._stopEventPropagation(event); + panel_id = this.getInnerPanel($el, 'right'); + break; + case EDIT_KEY: + this._stopEventPropagation(event); + panel_content = $el.find( + 'div.wcPanelTabContent:not(".wcPanelTabContentHidden")' + ); + if(panel_content.length) { + $input = $(panel_content).find('td.editable:first'); + if($input.length) + $input.click(); + } + break; + } + // Actual panel starts with 1 in wcDocker + return panel_id; + } +} + +// Finds the desired panel on which user wants to navigate to +function getInnerPanel($el, direction) { + if(!$el || !$el.length) + return false; + + let total_panels = $el.find('.wcPanelTab'); + // If no panels found OR if single panel + if (!total_panels.length || total_panels.length == 1) + return false; + + let active_panel = $(total_panels).filter('.wcPanelTabActive'), + id = parseInt($(active_panel).attr('id')), + fist_panel = 0, + last_panel = total_panels.length - 1; + + // Find desired panel + if (direction == 'left') { + if(id > fist_panel) + id--; + } else { + if (id < last_panel) + id++; + } + return id; +} + +module.exports = { + processEventDebugger: keyboardShortcutsDebugger, + getInnerPanel: getInnerPanel, + // misc functions + _stopEventPropagation: _stopEventPropagation, + isMac: isMac, + isKeyCtrlAlt: isKeyCtrlAlt, + isKeyAltShift: isKeyAltShift, + isKeyCtrlShift: isKeyCtrlShift, + isKeyCtrlAltShift: isKeyCtrlAltShift, + isAltShiftBoth: isAltShiftBoth, + isCtrlShiftBoth: isCtrlShiftBoth, + isCtrlAltBoth: isCtrlAltBoth, +}; diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index a173c8e..099adb4 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -17,6 +17,8 @@ import random from flask import url_for, Response, render_template, request, session, current_app from flask_babel import gettext from flask_security import login_required +from werkzeug.useragents import UserAgent + from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import bad_request from pgadmin.utils.ajax import make_json_response, \ @@ -346,6 +348,9 @@ def direct_new(trans_id): if "linux" in _platform: is_linux_platform = True + # We need client OS information to render correct Keyboard shortcuts + user_agent = UserAgent(request.headers.get('User-Agent')) + return render_template( "debugger/direct.html", _=gettext, @@ -354,6 +359,7 @@ def direct_new(trans_id): debug_type=debug_type, is_desktop_mode=current_app.PGADMIN_RUNTIME, is_linux=is_linux_platform, + client_platform=user_agent.platform, stylesheets=[url_for('debugger.static', filename='css/debugger.css')] ) diff --git a/web/pgadmin/tools/debugger/static/js/debugger_ui.js b/web/pgadmin/tools/debugger/static/js/debugger_ui.js index 3a35ceb..f9049d8 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_ui.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_ui.js @@ -132,7 +132,8 @@ define([ // Variables to store the data sent from sqlite database var func_args_data = this.func_args_data = []; - // As we are not getting pgBrowser.tree when we debug again so tree info will be updated from the server data + // As we are not getting pgBrowser.tree when we debug again + // so tree info will be updated from the server data if (restart_debug == 0) { var t = pgBrowser.tree, i = t.selected(), @@ -501,7 +502,8 @@ define([ } } - // Check if the arguments already available in the sqlite database then we should use the existing arguments + // Check if the arguments already available in the sqlite database + // then we should use the existing arguments if (func_args_data.length == 0) { this.debuggerInputArgsColl = new DebuggerInputArgCollections(my_obj); @@ -537,12 +539,12 @@ define([ setup: function() { return { buttons: [{ - text: 'Debug', + text: gettext('Debug'), key: 13, className: 'btn btn-primary', }, { - text: 'Cancel', + text: gettext('Cancel'), key: 27, className: 'btn btn-primary', }, @@ -563,13 +565,14 @@ define([ }, // Callback functions when click on the buttons of the Alertify dialogs callback: function(e) { - if (e.button.text === 'Debug') { + if (e.button.text === gettext('Debug')) { // Initialize the target once the debug button is clicked and // create asynchronous connection and unique transaction ID var self = this; - // If the debugging is started again then treeInfo is already stored in this.data so we can use the same. + // If the debugging is started again then treeInfo is already + // stored in this.data so we can use the same. if (self.restart_debug == 0) { var t = pgBrowser.tree, i = t.selected(), @@ -791,7 +794,8 @@ define([ }, }); } else { - // If the debugging is started again then we should only set the arguments and start the listener again + // If the debugging is started again then we should only set the + // arguments and start the listener again baseUrl = url_for('debugger.start_listener', { 'trans_id': self.data.trans_id, }); @@ -838,7 +842,7 @@ define([ return true; } - if (e.button.text === 'Cancel') { + if (e.button.text === gettext('Cancel')) { //close the dialog... return false; } @@ -853,8 +857,8 @@ define([ ); /* - If we already have data available in sqlite database then we should enable the debug button otherwise - disable the debug button. + If we already have data available in sqlite database then we should + enable the debug button otherwise disable the debug button. */ if (this.func_args_data.length == 0) { this.__internal.buttons[0].element.disabled = true; @@ -875,7 +879,8 @@ define([ for (var i = 0; i < this.collection.length; i++) { - // TODO: Need to check the "NULL" and "Expression" column value to enable/disable the "Debug" button + // TODO: Need to check the "NULL" and "Expression" column value to + // enable/disable the "Debug" button if (this.collection.models[i].get('value') == '' || this.collection.models[i].get('value') == null || this.collection.models[i].get('value') == undefined) { @@ -899,9 +904,11 @@ define([ }); } - Alertify.debuggerInputArgsDialog('Debugger', args, restart_debug).resizeTo('60%', '60%'); + Alertify.debuggerInputArgsDialog( + gettext('Debugger'), args, restart_debug + ).resizeTo('60%', '60%'); }; return res; -}); \ No newline at end of file +}); diff --git a/web/pgadmin/tools/debugger/static/js/direct.js b/web/pgadmin/tools/debugger/static/js/direct.js index d0dbad7..b601841 100644 --- a/web/pgadmin/tools/debugger/static/js/direct.js +++ b/web/pgadmin/tools/debugger/static/js/direct.js @@ -2,10 +2,10 @@ define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone', 'pgadmin.backgrid', 'pgadmin.backform', 'sources/../bundle/codemirror', - 'pgadmin.tools.debugger.ui', 'wcdocker', + 'pgadmin.tools.debugger.ui', 'sources/keyboard_shortcuts', 'wcdocker', ], function( gettext, url_for, $, _, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, - Backform, codemirror, debug_function_again + Backform, codemirror, debug_function_again, keyboardShortcuts ) { var CodeMirror = codemirror.default, @@ -31,7 +31,8 @@ define([ /* Function to set the breakpoint and send the line no. which is set to server - trans_id :- Unique Transaction ID, line_no - line no. to set the breakpoint, set_type = 0 - clear , 1 - set + trans_id :- Unique Transaction ID, line_no - line no. to set the breakpoint, + set_type = 0 - clear , 1 - set */ set_breakpoint: function(trans_id, line_no, set_type) { // Make ajax call to set/clear the break point by user @@ -57,7 +58,8 @@ define([ }); }, - // Function to get the latest breakpoint information and update the gutters of codemirror + // Function to get the latest breakpoint information and update the + // gutters of codemirror UpdateBreakpoint: function(trans_id) { var self = this; @@ -223,7 +225,8 @@ define([ // Call function to create and update local variables self.AddLocalVariables(res.data.result); self.AddParameters(res.data.result); - // If debug function is restarted then again start listener to read the updated messages. + // If debug function is restarted then again start listener to + // read the updated messages. if (pgTools.DirectDebug.debug_restarted) { if (pgTools.DirectDebug.debug_type) { self.poll_end_execution_result(trans_id); @@ -281,8 +284,8 @@ define([ }, /* - poll the actual result after user has executed the "continue", "step-into", "step-over" actions and get the - other updated information from the server. + poll the actual result after user has executed the "continue", "step-into", + "step-over" actions and get the other updated information from the server. */ poll_result: function(trans_id) { var self = this; @@ -299,8 +302,9 @@ define([ poll_timeout; /* - During the execution we should poll the result in minimum seconds but once the execution is completed - and wait for the another debugging session then we should decrease the polling frequency. + During the execution we should poll the result in minimum seconds but + once the execution is completed and wait for the another debugging + session then we should decrease the polling frequency. */ if (pgTools.DirectDebug.polling_timeout_idle) { // Poll the result after 1 second @@ -333,8 +337,13 @@ define([ pgTools.DirectDebug.docker.finishLoading(50); pgTools.DirectDebug.editor.setValue(res.data.result[0].src); self.UpdateBreakpoint(trans_id); - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); - pgTools.DirectDebug.editor.addLineClass((res.data.result[0].linenumber - 2), 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); + pgTools.DirectDebug.editor.addLineClass( + (res.data.result[0].linenumber - 2), + 'wrap', 'CodeMirror-activeline-background' + ); self.active_line_no = (res.data.result[0].linenumber - 2); // Update the stack, local variables and parameters information @@ -343,7 +352,9 @@ define([ } else if (!pgTools.DirectDebug.debug_type && !pgTools.DirectDebug.first_time_indirect_debug) { pgTools.DirectDebug.docker.finishLoading(50); if (self.active_line_no != undefined) { - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); } self.clear_all_breakpoint(trans_id); self.execute_query(trans_id); @@ -358,8 +369,13 @@ define([ self.UpdateBreakpoint(trans_id); } - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); - pgTools.DirectDebug.editor.addLineClass((res.data.result[0].linenumber - 2), 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); + pgTools.DirectDebug.editor.addLineClass( + (res.data.result[0].linenumber - 2), + 'wrap', 'CodeMirror-activeline-background' + ); self.active_line_no = (res.data.result[0].linenumber - 2); // Update the stack, local variables and parameters information @@ -378,7 +394,9 @@ define([ pgTools.DirectDebug.polling_timeout_idle = true; // If status is Busy then poll the result by recursive call to the poll function if (!pgTools.DirectDebug.debug_type) { - pgTools.DirectDebug.docker.startLoading(gettext('Waiting for another session to invoke the target...')); + pgTools.DirectDebug.docker.startLoading( + gettext('Waiting for another session to invoke the target...') + ); // As we are waiting for another session to invoke the target,disable all the buttons self.enable('stop', false); @@ -429,8 +447,9 @@ define([ }, /* - For the direct debugging, we need to check weather the functions execution is completed or not. After completion - of the debugging, we will stop polling the result until new execution starts. + For the direct debugging, we need to check weather the functions execution + is completed or not. After completion of the debugging, we will stop polling + the result until new execution starts. */ poll_end_execution_result: function(trans_id) { var self = this; @@ -470,10 +489,13 @@ define([ if (res.data.status === 'Success') { if (res.data.result == undefined) { /* - "result" is undefined only in case of EDB procedure. As Once the EDB procedure execution is completed - then we are not getting any result so we need ignore the result. + "result" is undefined only in case of EDB procedure. + As Once the EDB procedure execution is completed then we are + not getting any result so we need ignore the result. */ - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); pgTools.DirectDebug.direct_execution_completed = true; pgTools.DirectDebug.polling_timeout_idle = true; @@ -488,7 +510,8 @@ define([ // remove progress cursor $('.debugger-container').removeClass('show_progress'); - // Execution completed so disable the buttons other than "Continue/Start" button because user can still + // Execution completed so disable the buttons other than + // "Continue/Start" button because user can still // start the same execution again. self.enable('stop', false); self.enable('step_over', false); @@ -501,7 +524,9 @@ define([ } else { // Call function to create and update local variables .... if (res.data.result != null) { - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); self.AddResults(res.data.col_info, res.data.result); pgTools.DirectDebug.results_panel.focus(); pgTools.DirectDebug.direct_execution_completed = true; @@ -518,7 +543,8 @@ define([ // remove progress cursor $('.debugger-container').removeClass('show_progress'); - // Execution completed so disable the buttons other than "Continue/Start" button because user can still + // Execution completed so disable the buttons other than + // "Continue/Start" button because user can still // start the same execution again. self.enable('stop', false); self.enable('step_over', false); @@ -532,7 +558,8 @@ define([ } } } else if (res.data.status === 'Busy') { - // If status is Busy then poll the result by recursive call to the poll function + // If status is Busy then poll the result by recursive call to + // the poll function self.poll_end_execution_result(trans_id); // Update the message tab of the debugger if (res.data.status_message) { @@ -545,9 +572,12 @@ define([ ); } else if (res.data.status === 'ERROR') { pgTools.DirectDebug.direct_execution_completed = true; - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); - //Set the Alertify message to inform the user that execution is completed with error. + //Set the Alertify message to inform the user that execution is + // completed with error. if (!pgTools.DirectDebug.is_user_aborted_debugging) { Alertify.error(res.info, 3); } @@ -625,14 +655,16 @@ define([ } /* - Need to check if restart debugging really require to open the input dialog ? - If yes then we will get the previous arguments from database and populate the input dialog - If no then we should directly start the listener. + Need to check if restart debugging really require to open the input + dialog? If yes then we will get the previous arguments from database + and populate the input dialog, If no then we should directly start the + listener. */ if (res.data.result.require_input) { debug_function_again(res.data.result, restart_dbg); } else { - // Debugging of void function is started again so we need to start the listener again + // Debugging of void function is started again so we need to start + // the listener again var baseUrl = url_for('debugger.start_listener', { 'trans_id': trans_id, }); @@ -803,7 +835,9 @@ define([ success: function(res) { if (res.data.status) { // Call function to create and update local variables .... - pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background'); + pgTools.DirectDebug.editor.removeLineClass( + self.active_line_no, 'wrap', 'CodeMirror-activeline-background' + ); pgTools.DirectDebug.direct_execution_completed = true; pgTools.DirectDebug.is_user_aborted_debugging = true; @@ -1099,8 +1133,10 @@ define([ result_grid.render(); // Render the result grid into result panel - pgTools.DirectDebug.results_panel.$container.find('.debug_results').append(result_grid.el); - + pgTools.DirectDebug.results_panel + .$container + .find('.debug_results') + .append(result_grid.el); }, AddLocalVariables: function(result) { @@ -1120,11 +1156,13 @@ define([ }, }); - // Collection which contains the model for function informations. + // Collection which contains the model for function information. var VariablesCollection = Backbone.Collection.extend({ model: DebuggerVariablesModel, }); + VariablesCollection.prototype.on('change', self.deposit_parameter_value, self); + var gridCols = [{ name: 'name', label: gettext('Name'), @@ -1170,7 +1208,10 @@ define([ variable_grid.render(); // Render the variables grid into local variables panel - pgTools.DirectDebug.local_variables_panel.$container.find('.local_variables').append(variable_grid.el); + pgTools.DirectDebug.local_variables_panel + .$container + .find('.local_variables') + .append(variable_grid.el); }, @@ -1331,7 +1372,7 @@ define([ controller about the click and controller will take the action for the specified button click. */ var DebuggerToolbarView = Backbone.View.extend({ - el: '#btn-toolbar', + el: '.dubugger_main_container', initialize: function() { controller.on('pgDebugger:button:state:stop', this.enable_stop, this); controller.on('pgDebugger:button:state:step_over', this.enable_step_over, this); @@ -1347,6 +1388,7 @@ define([ 'click .btn-continue': 'on_continue', 'click .btn-step-over': 'on_step_over', 'click .btn-step-into': 'on_step_into', + 'keydown': 'keyAction', }, enable_stop: function(enable) { var $btn = this.$el.find('.btn-stop'); @@ -1414,7 +1456,6 @@ define([ $btn.attr('disabled', 'disabled'); } }, - on_stop: function() { controller.Stop(pgTools.DirectDebug.trans_id); }, @@ -1433,19 +1474,28 @@ define([ on_step_into: function() { controller.Step_into(pgTools.DirectDebug.trans_id); }, + keyAction: function (event) { + var $el = this.$el, panel_id, actual_panel; + panel_id = keyboardShortcuts.processEventDebugger($el, event); + // Panel navigation + if(!_.isUndefined(panel_id) && !_.isNull(panel_id)) { + actual_panel = panel_id + 1; + pgTools.DirectDebug.docker.findPanels()[actual_panel].focus(); + } + }, }); /* - Function is responsible to create the new wcDocker instance for debugger and initialize the debugger panel inside - the docker instance. + Function is responsible to create the new wcDocker instance for debugger and + initialize the debugger panel inside the docker instance. */ var DirectDebug = function() {}; _.extend(DirectDebug.prototype, { - init: function(trans_id, debug_type) { /* We should get the transaction id from the server during initialization here */ + /* We should get the transaction id from the server during initialization here */ + init: function(trans_id, debug_type) { // We do not want to initialize the module multiple times. - var self = this; _.bindAll(pgTools.DirectDebug, 'messages'); @@ -1524,7 +1574,8 @@ define([ this.intializePanels(); }, - // Read the messages of the database server and get the port ID and attach the executer to that port. + // Read the messages of the database server and get the port ID and attach + // the executer to that port. messages: function(trans_id) { var self = this; // Make ajax call to listen the database message @@ -1615,7 +1666,7 @@ define([ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id ="parameters" class="parameters"></div>', + content: '<div id ="parameters" class="parameters" tabindex="0"></div>', }); // Create the Local variables panel to display the local variables of the function. @@ -1626,7 +1677,7 @@ define([ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id ="local_variables" class="local_variables"></div>', + content: '<div id ="local_variables" class="local_variables" tabindex="0"></div>', }); // Create the messages panel to display the message returned from the database server @@ -1637,7 +1688,7 @@ define([ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id="messages" class="messages"></div>', + content: '<div id="messages" class="messages" tabindex="0"></div>', }); // Create the result panel to display the result after debugging the function @@ -1648,7 +1699,7 @@ define([ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id="debug_results" class="debug_results"></div>', + content: '<div id="debug_results" class="debug_results" tabindex="0"></div>', }); // Create the stack pane panel to display the debugging stack information. @@ -1659,7 +1710,7 @@ define([ height: '100%', isCloseable: false, isPrivate: true, - content: '<div id="stack_pane" class="stack_pane"></div>', + content: '<div id="stack_pane" class="stack_pane" tabindex="0"></div>', }); // Load all the created panels @@ -1671,34 +1722,48 @@ define([ }); self.code_editor_panel = self.docker.addPanel('code', wcDocker.DOCK.TOP); - self.parameters_panel = self.docker.addPanel( - 'parameters', wcDocker.DOCK.BOTTOM, self.code_editor_panel); - self.local_variables_panel = self.docker.addPanel('local_variables', wcDocker.DOCK.STACKED, self.parameters_panel, { - tabOrientation: wcDocker.TAB.TOP, - }); - self.messages_panel = self.docker.addPanel('messages', wcDocker.DOCK.STACKED, self.parameters_panel); + 'parameters', wcDocker.DOCK.BOTTOM, self.code_editor_panel + ); + self.local_variables_panel = self.docker.addPanel( + 'local_variables', + wcDocker.DOCK.STACKED, + self.parameters_panel, { + tabOrientation: wcDocker.TAB.TOP, + } + ); + self.messages_panel = self.docker.addPanel( + 'messages', wcDocker.DOCK.STACKED, self.parameters_panel); self.results_panel = self.docker.addPanel( - 'results', wcDocker.DOCK.STACKED, self.parameters_panel); + 'results', wcDocker.DOCK.STACKED, self.parameters_panel); self.stack_pane_panel = self.docker.addPanel( - 'stack_pane', wcDocker.DOCK.STACKED, self.parameters_panel); + 'stack_pane', wcDocker.DOCK.STACKED, self.parameters_panel); - var editor_pane = $('<div id="stack_editor_pane" class="full-container-pane info"></div>'); - var code_editor_area = $('<textarea id="debugger-editor-textarea"></textarea>').append(editor_pane); + var editor_pane = $('<div id="stack_editor_pane" ' + + 'class="full-container-pane info"></div>'); + var code_editor_area = $('<textarea id="debugger-editor-textarea">' + + '</textarea>').append(editor_pane); self.code_editor_panel.layout().addItem(code_editor_area); // To show the line-number and set breakpoint marker details by user. self.editor = CodeMirror.fromTextArea( code_editor_area.get(0), { + tabindex: 0, lineNumbers: true, foldOptions: { widget: '\u2026', }, foldGutter: { - rangeFinder: CodeMirror.fold.combine(CodeMirror.pgadminBeginRangeFinder, CodeMirror.pgadminIfRangeFinder, - CodeMirror.pgadminLoopRangeFinder, CodeMirror.pgadminCaseRangeFinder), + rangeFinder: CodeMirror.fold.combine( + CodeMirror.pgadminBeginRangeFinder, + CodeMirror.pgadminIfRangeFinder, + CodeMirror.pgadminLoopRangeFinder, + CodeMirror.pgadminCaseRangeFinder + ), }, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints'], + gutters: [ + 'CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints', + ], mode: 'text/x-pgsql', readOnly: true, extraKeys: pgAdmin.Browser.editor_shortcut_keys, @@ -1741,7 +1806,7 @@ define([ panel.title(title); panel.closeable(false); panel.layout().addItem( - $('<div>', { + $('<div tabindex="0">', { 'class': 'pg-debugger-panel', }) ); diff --git a/web/pgadmin/tools/debugger/templates/debugger/direct.html b/web/pgadmin/tools/debugger/templates/debugger/direct.html index 29bb729..8ab50cb 100644 --- a/web/pgadmin/tools/debugger/templates/debugger/direct.html +++ b/web/pgadmin/tools/debugger/templates/debugger/direct.html @@ -35,44 +35,57 @@ try { .debugger-container .wcLoadingIcon.fa-pulse{-webkit-animation: none;} </style> {% endif %} -<nav class="navbar-inverse navbar-fixed-top"> - <div id="btn-toolbar" class="btn-toolbar pg-prop-btn-group bg-gray-2 border-gray-3" role="toolbar" aria-label=""> - <div class="btn-group" role="group" aria-label=""> - <button type="button" class="btn btn-default btn-step-into" title="{{ _('Step into') }}" - tabindex="1"> - <i class="fa fa-indent"></i> - </button> - <button type="button" class="btn btn-default btn-step-over" title="{{ _('Step over') }}" - tabindex="2"> - <i class="fa fa-outdent"></i> - </button> - <button type="button" class="btn btn-default btn-continue" title="{{ _('Continue/Start') }}" - tabindex="3"> - <i class="fa fa-play-circle"></i> - </button> +<div class="dubugger_main_container" tabindex="0"> + <nav class="navbar-inverse navbar-fixed-top"> + <div id="btn-toolbar" class="btn-toolbar pg-prop-btn-group bg-gray-2 border-gray-3" role="toolbar" aria-label=""> + <div class="btn-group" role="group" aria-label=""> + <button type="button" class="btn btn-default btn-step-into" + title="{{ _('Step into') }}" + accesskey="i" + tabindex="0" autofocus="autofocus"> + <i class="fa fa-indent"></i> + </button> + <button type="button" class="btn btn-default btn-step-over" + title="{{ _('Step over') }}" + accesskey="o" + tabindex="0"> + <i class="fa fa-outdent"></i> + </button> + <button type="button" class="btn btn-default btn-continue" + title="{{ _('Continue/Start') }}" + accesskey="c" + tabindex="0"> + <i class="fa fa-play-circle"></i> + </button> + </div> + <div class="btn-group" role="group" aria-label=""> + <button type="button" class="btn btn-default btn-toggle-breakpoint" + title="{{ _('Toggle breakpoint') }}" + accesskey="t" + tabindex="0"> + <i class="fa fa-circle"></i> + </button> + <button type="button" class="btn btn-default btn-clear-breakpoint" + title="{{ _('Clear all breakpoints') }}" + accesskey="x" + tabindex="0"> + <i class="fa fa-ban"></i> + </button> + </div> + <div class="btn-group" role="group" aria-label=""> + <button type="button" class="btn btn-default btn-stop" + accesskey="s" + title="{{ _('Stop') }}" + tabindex="0"> + <i class="fa fa-stop-circle"></i> + </button> + </div> </div> - <div class="btn-group" role="group" aria-label=""> - <button type="button" class="btn btn-default btn-toggle-breakpoint" title="{{ _('Toggle breakpoint') }}" - tabindex="4"> - <i class="fa fa-circle"></i> - </button> - <button type="button" class="btn btn-default btn-clear-breakpoint" title="{{ _('Clear all breakpoints') }}" - tabindex="5"> - <i class="fa fa-ban"></i> - </button> - </div> - <div class="btn-group" role="group" aria-label=""> - <button type="button" class="btn btn-default btn-stop" title="{{ _('Stop') }}" - tabindex="6"> - <i class="fa fa-stop-circle"></i> - </button> - </div> - </div> -</nav> -<div id="container" class="debugger-container"></div> + </nav> + <div id="container" class="debugger-container" tabindex="0"></div> +</div> {% endblock %} - {% block css_link %} {% for stylesheet in stylesheets %} <link type="text/css" rel="stylesheet" href="{{ stylesheet }}"/> diff --git a/web/regression/javascript/common_keyboard_shortcuts_spec.js b/web/regression/javascript/common_keyboard_shortcuts_spec.js new file mode 100644 index 0000000..8887dc6 --- /dev/null +++ b/web/regression/javascript/common_keyboard_shortcuts_spec.js @@ -0,0 +1,50 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import keyboardShortcuts from 'sources/keyboard_shortcuts'; + +describe('the keyboard shortcuts', () => { + const F1_KEY = 112, + EDIT_KEY = 71, // Key: G -> Grid values + LEFT_ARROW_KEY = 37, + RIGHT_ARROW_KEY = 39, + MOVE_NEXT = 'right'; + + let debuggerElementSpy, event; + beforeEach(() => { + event = { + shift: false, + which: undefined, + preventDefault: jasmine.createSpy('preventDefault'), + cancelBubble: false, + stopPropagation: jasmine.createSpy('stopPropagation'), + stopImmediatePropagation: jasmine.createSpy('stopImmediatePropagation'), + }; + }); + + describe('when the key is not handled by the function', function () { + beforeEach(() => { + event.which = F1_KEY; + keyboardShortcuts.processEventDebugger(debuggerElementSpy, event); + }); + + it('should allow event to propagate', () => { + expect(event.preventDefault).not.toHaveBeenCalled(); + }); + }); + + describe('when user wants to goto next panel', function () { + + it('returns panel id', function () { + expect(keyboardShortcuts.getInnerPanel(debuggerElementSpy, 'right')).toEqual(false); + }); + }); + + +});