diff --git a/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png b/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png new file mode 100644 index 000000000..96786c596 Binary files /dev/null and b/docs/en_US/images/preferences_debugger_keyboard_shortcuts.png differ diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index 1f0ee04c9..114297502 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -4,7 +4,7 @@ Keyboard Shortcuts Keyboard shortcuts are provided in pgAdmin to allow easy access to specific functions. Alternate shortcuts can be configured through File > Preferences if -desired. +desired.˝ **Main Browser Window** @@ -130,7 +130,7 @@ When using the Debugger, the following shortcuts are available: +--------------------------+--------------------+-----------------------------------+ | + s | + s | Stop | +--------------------------+--------------------+-----------------------------------+ -| Alt + Shift + g | Alt + Shift + g | Enter or Edit values in Grid | +| Alt + Shift + q | Alt + Shift + q | Enter or Edit values in Grid | +--------------------------+--------------------+-----------------------------------+ diff --git a/docs/en_US/preferences.rst b/docs/en_US/preferences.rst index 6211edc5b..2492628b9 100644 --- a/docs/en_US/preferences.rst +++ b/docs/en_US/preferences.rst @@ -69,6 +69,11 @@ Expand the *Debugger* node to specify your debugger display preferences. * When the *Open in new browser tab* switch is set to *True*, the Debugger will open in a new browser tab when invoked. +Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the debugger window navigation: + +.. image:: images/preferences_debugger_keyboard_shortcuts.png + :alt: Preferences dialog debugger keyboard shortcuts section + **The Miscellaneous Node** Expand the *Miscellaneous* node to specify miscellaneous display preferences. diff --git a/web/pgadmin/static/js/keyboard_shortcuts.js b/web/pgadmin/static/js/keyboard_shortcuts.js index c1a827d32..68b71c374 100644 --- a/web/pgadmin/static/js/keyboard_shortcuts.js +++ b/web/pgadmin/static/js/keyboard_shortcuts.js @@ -1,8 +1,6 @@ import $ from 'jquery'; -// Debugger -const EDIT_KEY = 71, // Key: G -> Grid values - LEFT_ARROW_KEY = 37, +const LEFT_ARROW_KEY = 37, RIGHT_ARROW_KEY = 39, F5_KEY = 116, F7_KEY = 118, @@ -50,38 +48,44 @@ function _stopEventPropagation(event) { 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; +/* Function use to validate shortcut keys */ +function validateShortcutKeys(user_defined_shortcut, event) { + if(!user_defined_shortcut) { + return false; } + + let keyCode = event.which || event.keyCode; + return user_defined_shortcut.alt == event.altKey && + user_defined_shortcut.shift == event.shiftKey && + user_defined_shortcut.control == event.ctrlKey && + user_defined_shortcut.key.key_code == keyCode; +} + +/* Debugger: Keyboard Shortcuts handling */ +function keyboardShortcutsDebugger($el, event, user_defined_shortcuts) { + let panel_id, panel_content, $input; + let edit_grid_keys = user_defined_shortcuts.edit_grid_keys, + next_panel_keys = user_defined_shortcuts.next_panel_keys, + previous_panel_keys = user_defined_shortcuts.previous_panel_keys; + + if(this.validateShortcutKeys(edit_grid_keys, event)) { + 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(); + } + } else if(this.validateShortcutKeys(next_panel_keys, event)) { + this._stopEventPropagation(event); + panel_id = this.getInnerPanel($el, 'right'); + } else if(this.validateShortcutKeys(previous_panel_keys, event)) { + this._stopEventPropagation(event); + panel_id = this.getInnerPanel($el, 'left'); + } + return panel_id; } // Finds the desired panel on which user wants to navigate to @@ -169,6 +173,7 @@ module.exports = { processEventDebugger: keyboardShortcutsDebugger, processEventQueryTool: keyboardShortcutsQueryTool, getInnerPanel: getInnerPanel, + validateShortcutKeys: validateShortcutKeys, // misc functions _stopEventPropagation: _stopEventPropagation, isMac: isMac, diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index b4ee8250a..e2703eeec 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -73,6 +73,172 @@ class DebuggerModule(PgAdminModule): 'will be opened in a new browser tab.') ) + # Shortcut configuration for Accesskey + accesskey_fields = [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': gettext('Key') + } + ] + + shortcut_fields = [ + { + 'name': 'alt', + 'type': 'checkbox', + 'label': gettext('Alt/Option') + }, + { + 'name': 'shift', + 'type': 'checkbox', + 'label': gettext('Shift') + }, + + { + 'name': 'control', + 'type': 'checkbox', + 'label': gettext('Ctrl') + }, + { + 'name': 'key', + 'type': 'keyCode', + 'label': gettext('Key') + } + ] + + self.preference.register( + 'keyboard_shortcuts', 'btn_start', + gettext('Accesskey (Continue/Start)'), 'keyboardshortcut', + { + 'key': { + 'key_code': 67, + 'char': 'c' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=accesskey_fields + ) + + self.preference.register( + 'keyboard_shortcuts', 'btn_stop', + gettext('Accesskey (Stop)'), 'keyboardshortcut', + { + 'key': { + 'key_code': 83, + 'char': 's' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=accesskey_fields + ) + + self.preference.register( + 'keyboard_shortcuts', 'btn_step_into', + gettext('Accesskey (Step into)'), 'keyboardshortcut', + { + 'key': { + 'key_code': 73, + 'char': 'i' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=accesskey_fields + ) + + self.preference.register( + 'keyboard_shortcuts', 'btn_step_over', + gettext('Accesskey (Step over)'), 'keyboardshortcut', + { + 'key': { + 'key_code': 79, + 'char': 'o' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=accesskey_fields + ) + + self.preference.register( + 'keyboard_shortcuts', 'btn_toggle_breakpoint', + gettext('Accesskey (Toggle breakpoint)'), 'keyboardshortcut', + { + 'key': { + 'key_code': 84, + 'char': 't' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=accesskey_fields + ) + + self.preference.register( + 'keyboard_shortcuts', 'btn_clear_breakpoints', + gettext('Accesskey (Clear all breakpoints)'), 'keyboardshortcut', + { + 'key': { + 'key_code': 88, + 'char': 'x' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=accesskey_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'edit_grid_values', + gettext('Edit grid values'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': True, + 'control': False, + 'key': { + 'key_code': 81, + 'char': 'q' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'move_previous', + gettext('Previous tab'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': True, + 'control': False, + 'key': { + 'key_code': 37, + 'char': 'ArrowLeft' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'move_next', + gettext('Next tab'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': True, + 'control': False, + 'key': { + 'key_code': 39, + 'char': 'ArrowRight' + } + }, + category_label=gettext('Keyboard shortcuts'), + fields=shortcut_fields + ) + + def get_exposed_url_endpoints(self): """ Returns the list of URLs exposed to the client. @@ -159,6 +325,33 @@ def update_session_function_transaction(trans_id, data): session['functionData'] = function_data +def get_shortcuts_for_accesskey(): + """ + This function will fetch and return accesskey shortcuts for debugger + toolbar buttons + + Returns: + Dict of shortcut keys + """ + # Fetch debugger preferences + dp = Preferences.module('debugger') + btn_start = dp.preference('btn_start').get() + btn_stop = dp.preference('btn_stop').get() + btn_step_into = dp.preference('btn_step_into').get() + btn_step_over = dp.preference('btn_step_over').get() + btn_toggle_breakpoint = dp.preference('btn_toggle_breakpoint').get() + btn_clear_breakpoints = dp.preference('btn_clear_breakpoints').get() + + return { + 'start': btn_start.get('key').get('char'), + 'stop': btn_stop.get('key').get('char'), + 'step_into': btn_step_into.get('key').get('char'), + 'step_over': btn_step_over.get('key').get('char'), + 'toggle_breakpoint': btn_toggle_breakpoint.get('key').get('char'), + 'clear_breakpoints': btn_clear_breakpoints.get('key').get('char') + } + + @blueprint.route( '/init/////', methods=['GET'], endpoint='init_for_function' @@ -411,6 +604,8 @@ def direct_new(trans_id): # 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, @@ -420,7 +615,8 @@ def direct_new(trans_id): 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')] + stylesheets=[url_for('debugger.static', filename='css/debugger.css')], + accesskey=get_shortcuts_for_accesskey() ) diff --git a/web/pgadmin/tools/debugger/static/js/direct.js b/web/pgadmin/tools/debugger/static/js/direct.js index b60184160..62cc0ba76 100644 --- a/web/pgadmin/tools/debugger/static/js/direct.js +++ b/web/pgadmin/tools/debugger/static/js/direct.js @@ -625,9 +625,8 @@ define([ Restart: function(trans_id) { var self = this, - baseUrl = url_for('debugger.restart', { - 'trans_id': trans_id, - }); + baseUrl = url_for('debugger.restart', {'trans_id': trans_id}); + self.enable('stop', false); self.enable('step_over', false); self.enable('step_into', false); @@ -636,7 +635,11 @@ define([ self.enable('continue', false); // Clear msg tab - pgTools.DirectDebug.messages_panel.$container.find('.messages').html(''); + pgTools.DirectDebug + .messages_panel + .$container + .find('.messages') + .html(''); $.ajax({ url: baseUrl, @@ -1161,7 +1164,9 @@ define([ model: DebuggerVariablesModel, }); - VariablesCollection.prototype.on('change', self.deposit_parameter_value, self); + VariablesCollection.prototype.on( + 'change', self.deposit_parameter_value, self + ); var gridCols = [{ name: 'name', @@ -1205,14 +1210,19 @@ define([ className: 'backgrid table-bordered', }); + variable_grid.collection.on( + 'backgrid:edited', () => { + pgTools.DirectDebug.editor.focus(); + } + ); + variable_grid.render(); // Render the variables grid into local variables panel pgTools.DirectDebug.local_variables_panel - .$container - .find('.local_variables') - .append(variable_grid.el); - + .$container + .find('.local_variables') + .append(variable_grid.el); }, AddParameters: function(result) { @@ -1237,7 +1247,9 @@ define([ model: DebuggerParametersModel, }); - self.ParametersCollection.prototype.on('change', self.deposit_parameter_value, self); + ParametersCollection.prototype.on( + 'change', self.deposit_parameter_value, self + ); var paramGridCols = [{ name: 'name', @@ -1281,12 +1293,20 @@ define([ className: 'backgrid table-bordered', }); + param_grid.collection.on( + 'backgrid:edited', () => { + pgTools.DirectDebug.editor.focus(); + } + ); + param_grid.render(); // Render the parameters grid into parameter panel - pgTools.DirectDebug.parameters_panel.$container.find('.parameters').append(param_grid.el); + pgTools.DirectDebug.parameters_panel + .$container + .find('.parameters') + .append(param_grid.el); }, - deposit_parameter_value: function(model) { var self = this; @@ -1476,7 +1496,45 @@ define([ }, keyAction: function (event) { var $el = this.$el, panel_id, actual_panel; - panel_id = keyboardShortcuts.processEventDebugger($el, event); + + // If already fetched earlier then don't do it again + if(_.size(pgTools.DirectDebug.debugger_keyboard_shortcuts) == 0) { + // Fetch keyboard shortcut keys + var edit_grid_shortcut_perf, next_panel_perf, previous_panel_perf; + edit_grid_shortcut_perf = window.top.pgAdmin.Browser.get_preference( + 'debugger', 'edit_grid_values' + ); + next_panel_perf = window.top.pgAdmin.Browser.get_preference( + 'debugger', 'move_next' + ); + previous_panel_perf = window.top.pgAdmin.Browser.get_preference( + 'debugger', 'move_previous' + ); + + // If debugger opened in new Tab then window.top won't be available + if(!edit_grid_shortcut_perf || !next_panel_perf || !previous_panel_perf) { + edit_grid_shortcut_perf = window.opener.pgAdmin.Browser.get_preference( + 'debugger', 'edit_grid_values' + ); + next_panel_perf = window.opener.pgAdmin.Browser.get_preference( + 'debugger', 'move_next' + ); + previous_panel_perf = window.opener.pgAdmin.Browser.get_preference( + 'debugger', 'move_previous' + ); + } + + pgTools.DirectDebug.debugger_keyboard_shortcuts = { + 'edit_grid_keys': edit_grid_shortcut_perf.value, + 'next_panel_keys': next_panel_perf.value, + 'previous_panel_keys': previous_panel_perf.value, + }; + } + + panel_id = keyboardShortcuts.processEventDebugger( + $el, event, pgTools.DirectDebug.debugger_keyboard_shortcuts + ); + // Panel navigation if(!_.isUndefined(panel_id) && !_.isNull(panel_id)) { actual_panel = panel_id + 1; @@ -1513,6 +1571,7 @@ define([ this.debug_restarted = false; this.is_user_aborted_debugging = false; this.is_polling_required = true; // Flag to stop unwanted ajax calls + this.debugger_keyboard_shortcuts = {}; this.docker = new wcDocker( '#container', { @@ -1748,7 +1807,7 @@ define([ // To show the line-number and set breakpoint marker details by user. self.editor = CodeMirror.fromTextArea( code_editor_area.get(0), { - tabindex: 0, + tabindex: -1, lineNumbers: true, foldOptions: { widget: '\u2026', @@ -1775,6 +1834,14 @@ define([ matchBrackets: pgAdmin.Browser.editor_options.brace_matching, }); + // Useful for keyboard navigation, when user presses escape key we will + // defocus from the codemirror editor allow user to navigate further + CodeMirror.on(self.editor, 'keydown', function(cm,event) { + if(event.keyCode==27){ + document.activeElement.blur(); + } + }); + // On loading the docker, register the callbacks var onLoad = function() { self.docker.finishLoading(100); diff --git a/web/pgadmin/tools/debugger/templates/debugger/direct.html b/web/pgadmin/tools/debugger/templates/debugger/direct.html index 8ab50cbed..c2a23a517 100644 --- a/web/pgadmin/tools/debugger/templates/debugger/direct.html +++ b/web/pgadmin/tools/debugger/templates/debugger/direct.html @@ -41,19 +41,19 @@ try {
@@ -61,20 +61,20 @@ try {