Support keyboard navigation in the debugger. Fixes #2897

In passing, fix injection of variable values. Fixes #2981
This commit is contained in:
Murtuza Zabuawala 2018-01-23 11:58:10 +00:00 committed by Dave Page
parent 65337daeba
commit 0e41b3364b
7 changed files with 409 additions and 109 deletions

View File

@ -89,3 +89,43 @@ 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> is browser and platform dependant. The following table lists the default access keys for supported browsers.
+-------------------+------------+------------+------------+
| | Windows | Linux | Mac |
+===================+============+============+============+
| Internet Explorer | Alt | Alt | |
+-------------------+------------+------------+------------+
| Chrome | Alt | Alt | Ctrl+Alt |
+-------------------+------------+------------+------------+
| Firefox | Alt+Shift | Alt+Shift | Ctrl+Alt |
+-------------------+------------+------------+------------+
| Safari | Alt | | Ctrl+Alt |
+-------------------+------------+------------+------------+

View File

@ -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,
};

View File

@ -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')]
)

View File

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

View File

@ -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',
})
);

View File

@ -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 }}"/>

View File

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