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);
+    });
+  });
+
+
+});

Reply via email to