Add an option to auto-complete keywords in upper case. Fixes #2686

This commit is contained in:
Akshay Joshi 2018-06-15 10:18:56 +01:00 committed by Dave Page
parent df7b4d55c6
commit b48145f01f
9 changed files with 165 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@ -127,6 +127,13 @@ Please note: the default help paths include the *VERSION* placeholder; the $VERS
Expand the *SQL Editor* node to access panels that allow you to specify your preferences for the SQL Editor tool. Expand the *SQL Editor* node to access panels that allow you to specify your preferences for the SQL Editor tool.
.. image:: images/preferences_sql_auto_completion.png
:alt: Preferences dialog sqleditor auto completion option
Use the fields on the *Auto Completion* panel to set the auto completion options.
* When the *Keywords in uppercase* switch is set to *True* then keywords are shown in upper case.
.. image:: images/preferences_sql_csv_output.png .. image:: images/preferences_sql_csv_output.png
:alt: Preferences dialog sqleditor csv output option :alt: Preferences dialog sqleditor csv output option

View File

@ -11,6 +11,7 @@ Features
******** ********
| `Feature #1447 <https://redmine.postgresql.org/issues/1447>`_ - Add support for SSH tunneled connections | `Feature #1447 <https://redmine.postgresql.org/issues/1447>`_ - Add support for SSH tunneled connections
| `Feature #2686 <https://redmine.postgresql.org/issues/2686>`_ - Add an option to auto-complete keywords in upper case
| `Feature #3204 <https://redmine.postgresql.org/issues/3204>`_ - Add support for LISTEN/NOTIFY in the query tool | `Feature #3204 <https://redmine.postgresql.org/issues/3204>`_ - Add support for LISTEN/NOTIFY in the query tool
| `Feature #3362 <https://redmine.postgresql.org/issues/3362>`_ - Function and procedure support for PG11 | `Feature #3362 <https://redmine.postgresql.org/issues/3362>`_ - Function and procedure support for PG11
@ -36,5 +37,5 @@ Bug fixes
| `Bug #3342 <https://redmine.postgresql.org/issues/3342>`_ - Set SESSION_COOKIE_SAMESITE='Lax' per Flask recommendation to prevents sending cookies with CSRF-prone requests from external sites, such as submitting a form | `Bug #3342 <https://redmine.postgresql.org/issues/3342>`_ - Set SESSION_COOKIE_SAMESITE='Lax' per Flask recommendation to prevents sending cookies with CSRF-prone requests from external sites, such as submitting a form
| `Bug #3353 <https://redmine.postgresql.org/issues/3353>`_ - Handle errors properly if they occur when renaming a database | `Bug #3353 <https://redmine.postgresql.org/issues/3353>`_ - Handle errors properly if they occur when renaming a database
| `Bug #3374 <https://redmine.postgresql.org/issues/3374>`_ - Fix autocomplete | `Bug #3374 <https://redmine.postgresql.org/issues/3374>`_ - Fix autocomplete
| `Bug #3392 <https://redmine.postgresql.org/issues/3392>`_ - Fix IPv6 support in the container build. | `Bug #3392 <https://redmine.postgresql.org/issues/3392>`_ - Fix IPv6 support in the container build
| `Bug #3409 <https://redmine.postgresql.org/issues/3409>`_ - Avoid an exception on GreenPlum when retrieving RE-SQL on a table | `Bug #3409 <https://redmine.postgresql.org/issues/3409>`_ - Avoid an exception on GreenPlum when retrieving RE-SQL on a table

View File

@ -1,2 +1,7 @@
{# SQL query for getting keywords #} {# SQL query for getting keywords #}
{% if upper_case %}
SELECT upper(word) as word FROM pg_get_keywords() SELECT upper(word) as word FROM pg_get_keywords()
{% else %}
SELECT word FROM pg_get_keywords()
{% endif %}

View File

@ -136,6 +136,7 @@ function keyboardShortcutsQueryTool(
let downloadCsvKeys = keyboardShortcutConfig['download_csv']; let downloadCsvKeys = keyboardShortcutConfig['download_csv'];
let nextPanelKeys = keyboardShortcutConfig['move_next']; let nextPanelKeys = keyboardShortcutConfig['move_next'];
let previousPanelKeys = keyboardShortcutConfig['move_previous']; let previousPanelKeys = keyboardShortcutConfig['move_previous'];
let toggleCaseKeys = keyboardShortcutConfig['toggle_case'];
if (this.validateShortcutKeys(executeKeys, event)) { if (this.validateShortcutKeys(executeKeys, event)) {
this._stopEventPropagation(event); this._stopEventPropagation(event);
@ -149,6 +150,9 @@ function keyboardShortcutsQueryTool(
} else if (this.validateShortcutKeys(downloadCsvKeys, event)) { } else if (this.validateShortcutKeys(downloadCsvKeys, event)) {
this._stopEventPropagation(event); this._stopEventPropagation(event);
queryToolActions.download(sqlEditorController); queryToolActions.download(sqlEditorController);
} else if (this.validateShortcutKeys(toggleCaseKeys, event)) {
this._stopEventPropagation(event);
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
} else if (( } else if ((
(this.isMac() && event.metaKey) || (this.isMac() && event.metaKey) ||
(!this.isMac() && event.ctrlKey) (!this.isMac() && event.ctrlKey)

View File

@ -132,6 +132,8 @@ let queryToolActions = {
'sqleditor', 'move_next'); 'sqleditor', 'move_next');
let previousPanelPerf = window.top.pgAdmin.Browser.get_preference( let previousPanelPerf = window.top.pgAdmin.Browser.get_preference(
'sqleditor', 'move_previous'); 'sqleditor', 'move_previous');
let toggleCasePerf = window.top.pgAdmin.Browser.get_preference(
'sqleditor', 'toggle_case');
if(!executeQueryPref && sqlEditorController.handler.is_new_browser_tab) { if(!executeQueryPref && sqlEditorController.handler.is_new_browser_tab) {
executeQueryPref = window.opener.pgAdmin.Browser.get_preference( executeQueryPref = window.opener.pgAdmin.Browser.get_preference(
@ -151,6 +153,9 @@ let queryToolActions = {
), ),
previousPanelPerf = window.opener.pgAdmin.Browser.get_preference( previousPanelPerf = window.opener.pgAdmin.Browser.get_preference(
'sqleditor', 'move_previous' 'sqleditor', 'move_previous'
),
toggleCasePerf = window.opener.pgAdmin.Browser.get_preference(
'sqleditor', 'toggle_case'
); );
} }
@ -161,9 +166,23 @@ let queryToolActions = {
'download_csv': downloadCsvPref.value, 'download_csv': downloadCsvPref.value,
'move_next': nextPanelPerf.value, 'move_next': nextPanelPerf.value,
'move_previous': previousPanelPerf.value, 'move_previous': previousPanelPerf.value,
'toggle_case': toggleCasePerf.value,
}; };
}, },
toggleCaseOfSelectedText: function (sqlEditorController) {
let codeMirrorObj = sqlEditorController.gridView.query_tool_obj;
let selectedText = codeMirrorObj.getSelection();
if (!selectedText) return;
if (selectedText === selectedText.toUpperCase()) {
codeMirrorObj.replaceSelection(selectedText.toLowerCase());
} else {
codeMirrorObj.replaceSelection(selectedText.toUpperCase());
}
},
}; };
module.exports = queryToolActions; module.exports = queryToolActions;

View File

@ -535,6 +535,32 @@ def RegisterQueryToolPreferences(self):
fields=accesskey_fields fields=accesskey_fields
) )
self.preference.register(
'keyboard_shortcuts',
'toggle_case',
gettext('Toggle case of selected text'),
'keyboardshortcut',
{
'alt': False,
'shift': True,
'control': True,
'key': {
'key_code': 91,
'char': 'u'
}
},
category_label=gettext('Keyboard shortcuts'),
fields=shortcut_fields
)
self.preference.register(
'auto_completion', 'keywords_in_uppercase',
gettext("Keywords in uppercase"), 'boolean', True,
category_label=gettext('Auto completion'),
help_str=gettext('If set to True, Keywords will be displayed '
'in upper case for auto completion.')
)
def get_query_tool_keyboard_shortcuts(): def get_query_tool_keyboard_shortcuts():
@ -564,6 +590,7 @@ def get_query_tool_keyboard_shortcuts():
explain_query = qt_perf.preference('explain_query').get() explain_query = qt_perf.preference('explain_query').get()
explain_analyze_query = qt_perf.preference('explain_analyze_query').get() explain_analyze_query = qt_perf.preference('explain_analyze_query').get()
find_options = qt_perf.preference('btn_find_options').get() find_options = qt_perf.preference('btn_find_options').get()
toggle_case = qt_perf.preference('toggle_case').get()
return { return {
'keys': { 'keys': {
@ -587,7 +614,8 @@ def get_query_tool_keyboard_shortcuts():
'explain_analyze_query': explain_analyze_query.get('key').get( 'explain_analyze_query': explain_analyze_query.get('key').get(
'char' 'char'
), ),
'find_options': find_options.get('key').get('char') 'find_options': find_options.get('key').get('char'),
'toggle_case': toggle_case.get('key').get('char')
}, },
'shortcuts': { 'shortcuts': {
'conn_status': conn_status, 'conn_status': conn_status,
@ -608,7 +636,8 @@ def get_query_tool_keyboard_shortcuts():
'execute_query': execute_query, 'execute_query': execute_query,
'explain_query': explain_query, 'explain_query': explain_query,
'explain_analyze_query': explain_analyze_query, 'explain_analyze_query': explain_analyze_query,
'find_options': find_options 'find_options': find_options,
'toggle_case': toggle_case
}, },
} }

View File

@ -26,6 +26,7 @@ from .function_metadata import FunctionMetadata
from .parseutils import ( from .parseutils import (
last_word, extract_tables, find_prev_keyword, parse_partial_identifier) last_word, extract_tables, find_prev_keyword, parse_partial_identifier)
from .prioritization import PrevalenceCounter from .prioritization import PrevalenceCounter
from pgadmin.utils.preferences import Preferences
PY2 = sys.version_info[0] == 2 PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3 PY3 = sys.version_info[0] == 3
@ -101,8 +102,17 @@ class SQLAutoComplete(object):
for record in res['rows']: for record in res['rows']:
self.search_path.append(record['schema']) self.search_path.append(record['schema'])
pref = Preferences.module('sqleditor')
keywords_in_uppercase = \
pref.preference('keywords_in_uppercase').get()
# Fetch the keywords # Fetch the keywords
query = render_template("/".join([self.sql_path, 'keywords.sql'])) query = render_template("/".join([self.sql_path, 'keywords.sql']))
# If setting 'Keywords in uppercase' is set to True in
# Preferences then fetch the keywords in upper case.
if keywords_in_uppercase:
query = render_template(
"/".join([self.sql_path, 'keywords.sql']), upper_case=True)
status, res = self.conn.execute_dict(query) status, res = self.conn.execute_dict(query)
if status: if status:
for record in res['rows']: for record in res['rows']:

View File

@ -11,7 +11,8 @@ import queryToolActions from 'sources/sqleditor/query_tool_actions';
describe('queryToolActions', () => { describe('queryToolActions', () => {
let sqlEditorController, let sqlEditorController,
getSelectionSpy, getValueSpy, getSelectionSpy, getValueSpy,
selectedQueryString, entireQueryString; selectedQueryString, entireQueryString,
replaceSelectionSpy;
describe('executeQuery', () => { describe('executeQuery', () => {
describe('when the command is being run from the query tool', () => { describe('when the command is being run from the query tool', () => {
@ -437,9 +438,93 @@ describe('queryToolActions', () => {
}); });
}); });
describe('toggleCaseOfSelectedText', () => {
describe('when there is no query text', () => {
beforeEach(() => {
setUpSpies('', '');
});
it('does nothing', () => {
expect(
queryToolActions.toggleCaseOfSelectedText(sqlEditorController)
).not.toBeDefined();
});
});
describe('when there is empty selection', () => {
beforeEach(() => {
setUpSpies('', 'a string\nddd\nsss');
sqlEditorController.gridView.query_tool_obj.getCursor = (isFrom) => {
return isFrom ? 3 : 3;
};
});
it('does nothing', () => {
expect(
queryToolActions.toggleCaseOfSelectedText(sqlEditorController)
).not.toBeDefined();
});
});
describe('when selected query is in lower case', () => {
beforeEach(() => {
setUpSpies('string', 'a string\nddd\nsss');
});
it('toggle the selection and string should be in upper case', () => {
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
expect(replaceSelectionSpy
).toHaveBeenCalledWith('STRING');
});
it('(negative scenario toggle the selection and string should be in upper case', () => {
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
expect(replaceSelectionSpy
).not.toHaveBeenCalledWith('string');
});
});
describe('when selected query is in upper case', () => {
beforeEach(() => {
setUpSpies('STRING', 'a string\nddd\nsss');
});
it('toggle the selection and string should be in lower case', () => {
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
expect(replaceSelectionSpy
).toHaveBeenCalledWith('string');
});
it('(negative scenario toggle the selection and string should be in lower case', () => {
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
expect(replaceSelectionSpy
).not.toHaveBeenCalledWith('STRING');
});
});
describe('when selected query is in mixed case', () => {
beforeEach(() => {
setUpSpies('sTRIng', 'a string\nddd\nsss');
});
it('toggle the selection and string should be in upper case', () => {
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
expect(replaceSelectionSpy
).toHaveBeenCalledWith('STRING');
});
it('(negative scenario toggle the selection and string should be in upper case', () => {
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
expect(replaceSelectionSpy
).not.toHaveBeenCalledWith('sTRIng');
});
});
});
function setUpSpies(selectedQueryString, entireQueryString) { function setUpSpies(selectedQueryString, entireQueryString) {
getValueSpy = jasmine.createSpy('getValueSpy').and.returnValue(entireQueryString); getValueSpy = jasmine.createSpy('getValueSpy').and.returnValue(entireQueryString);
getSelectionSpy = jasmine.createSpy('getSelectionSpy').and.returnValue(selectedQueryString); getSelectionSpy = jasmine.createSpy('getSelectionSpy').and.returnValue(selectedQueryString);
replaceSelectionSpy = jasmine.createSpy('replaceSelectionSpy');
sqlEditorController = { sqlEditorController = {
gridView: { gridView: {
@ -449,6 +534,7 @@ describe('queryToolActions', () => {
toggleComment: jasmine.createSpy('toggleCommentSpy'), toggleComment: jasmine.createSpy('toggleCommentSpy'),
lineComment: jasmine.createSpy('lineCommentSpy'), lineComment: jasmine.createSpy('lineCommentSpy'),
uncomment: jasmine.createSpy('uncommentSpy'), uncomment: jasmine.createSpy('uncommentSpy'),
replaceSelection: replaceSelectionSpy,
getCursor: (isFrom) => { getCursor: (isFrom) => {
return entireQueryString.indexOf(selectedQueryString) + (isFrom ? 0 : selectedQueryString.length); return entireQueryString.indexOf(selectedQueryString) + (isFrom ? 0 : selectedQueryString.length);
}, },