Configurable shortcuts in the Debugger. Fixes #2901

pull/8/head
Murtuza Zabuawala 2018-02-09 12:43:27 +00:00 committed by Dave Page
parent 258b064963
commit 942ac733a4
8 changed files with 341 additions and 59 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

View File

@ -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:
+--------------------------+--------------------+-----------------------------------+
| <accesskey> + s | <accesskey> + 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 |
+--------------------------+--------------------+-----------------------------------+

View File

@ -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.

View File

@ -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,24 +48,27 @@ function _stopEventPropagation(event) {
event.stopImmediatePropagation();
}
/* Debugger: Keyboard Shortcuts handling */
function keyboardShortcutsDebugger($el, event) {
let keyCode = event.which || event.keyCode;
/* Function use to validate shortcut keys */
function validateShortcutKeys(user_defined_shortcut, event) {
if(!user_defined_shortcut) {
return false;
}
// To handle debugger's internal tab navigation like Parameters/Messages...
if (this.isAltShiftBoth(event)) {
// Get the active wcDocker panel from DOM element
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;
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:
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")'
@ -77,11 +78,14 @@ function keyboardShortcutsDebugger($el, event) {
if($input.length)
$input.click();
}
break;
} 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');
}
// Actual panel starts with 1 in wcDocker
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,

View File

@ -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/<node_type>/<int:sid>/<int:did>/<int:scid>/<int:fid>',
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()
)

View File

@ -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,6 +1210,12 @@ 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
@ -1212,7 +1223,6 @@ define([
.$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);

View File

@ -41,19 +41,19 @@ try {
<div class="btn-group" role="group" aria-label="">
<button type="button" class="btn btn-default btn-step-into"
title="{{ _('Step into') }}"
accesskey="i"
accesskey="{{ accesskey.step_into }}"
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"
accesskey="{{ accesskey.step_over }}"
tabindex="0">
<i class="fa fa-outdent"></i>
</button>
<button type="button" class="btn btn-default btn-continue"
title="{{ _('Continue/Start') }}"
accesskey="c"
accesskey="{{ accesskey.toggle_breakpoint }}"
tabindex="0">
<i class="fa fa-play-circle"></i>
</button>
@ -61,20 +61,20 @@ try {
<div class="btn-group" role="group" aria-label="">
<button type="button" class="btn btn-default btn-toggle-breakpoint"
title="{{ _('Toggle breakpoint') }}"
accesskey="t"
accesskey="{{ accesskey.toggle_breakpoint }}"
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"
accesskey="{{ accesskey.clear_breakpoints }}"
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"
accesskey="{{ accesskey.stop }}"
title="{{ _('Stop') }}"
tabindex="0">
<i class="fa fa-stop-circle"></i>

View File

@ -16,7 +16,14 @@ describe('the keyboard shortcuts', () => {
RIGHT_ARROW_KEY = 39,
MOVE_NEXT = 'right';
let debuggerElementSpy, event;
let debuggerElementSpy, event, debuggerUserShortcutSpy;
debuggerUserShortcutSpy = jasmine.createSpyObj(
'userDefinedShortcuts', [
{ 'edit_grid_keys': null },
{ 'next_panel_keys': null },
{ 'previous_panel_keys': null }
]
);
beforeEach(() => {
event = {
shift: false,
@ -31,7 +38,9 @@ describe('the keyboard shortcuts', () => {
describe('when the key is not handled by the function', function () {
beforeEach(() => {
event.which = F1_KEY;
keyboardShortcuts.processEventDebugger(debuggerElementSpy, event);
keyboardShortcuts.processEventDebugger(
debuggerElementSpy, event, debuggerUserShortcutSpy
);
});
it('should allow event to propagate', () => {