Added option to trigger autocomplete on key press in the query tool. Fixes #4488

This commit is contained in:
Akshay Joshi 2022-07-19 11:55:37 +05:30
parent 8f73956d1e
commit 4585597388
12 changed files with 171 additions and 70 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -290,6 +290,9 @@ preferences for the Query Editor tool.
Use the fields on the *Auto Completion* panel to set the auto completion options.
* When the *Autocomplete on key press* switch is set to *True* then autocomplete
will be available on key press along with CTRL/CMD + Space. If it is set to
*False* then autocomplete is only activated when CTRL/CMD + Space is pressed.
* When the *Keywords in uppercase* switch is set to *True* then keywords are
shown in upper case.

View File

@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #4488 <https://redmine.postgresql.org/issues/4488>`_ - Added option to trigger autocomplete on key press in the query tool.
| `Issue #7486 <https://redmine.postgresql.org/issues/7486>`_ - Added support for visualizing the graphs using Stacked Line, Bar, and Stacked Bar charts in the query tool.
| `Issue #7487 <https://redmine.postgresql.org/issues/7487>`_ - Added support for visualise the graph using a Pie chart in the query tool.

View File

@ -16,3 +16,39 @@
background-size: 20px !important;
background-position: center left;
}
.icon-at {
background-image: url('../../../../static/img/at.svg') !important;
background-repeat: no-repeat;
background-size: 15px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
.icon-key {
background-image: url('../../../../static/img/key.svg') !important;
background-repeat: no-repeat;
background-size: 15px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
.icon-join {
background-image: url('../../../../static/img/join.svg') !important;
background-repeat: no-repeat;
background-size: 20px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
.icon-spinner {
background-image: url('../../../../static/img/spinner.svg') !important;
background-repeat: no-repeat;
background-size: 15px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="150px" height="150px" viewBox="0 0 128 128" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 64 2 C 29.734375 2 2 29.730469 2 64 C 2 98.265625 29.730469 126 64 126 C 76.039062 126 87.835938 122.464844 97.851562 115.945312 C 100.851562 113.992188 101.507812 109.871094 99.238281 107.101562 L 96.695312 103.992188 C 94.777344 101.648438 91.402344 101.078125 88.851562 102.710938 C 81.480469 107.4375 72.828125 110 64 110 C 38.636719 110 18 89.363281 18 64 C 18 38.636719 38.636719 18 64 18 C 89.035156 18 110 32.40625 110 58 C 110 67.695312 104.726562 77.933594 95.457031 78.921875 C 91.121094 78.808594 91.230469 75.710938 92.089844 71.417969 L 97.945312 41.140625 C 98.664062 37.4375 95.828125 34 92.054688 34 L 80.8125 34 C 79.09375 34 77.648438 35.289062 77.453125 37 L 77.449219 37.019531 C 73.777344 32.546875 67.339844 31.578125 62.457031 31.578125 C 43.8125 31.578125 28 47.136719 28 69.441406 C 28 85.769531 37.195312 95.910156 52 95.910156 C 58.746094 95.910156 66.34375 92 70.746094 86.328125 C 73.128906 94.851562 80.902344 94.851562 88.425781 94.851562 C 115.652344 94.851562 126 76.949219 126 58 C 126 23.914062 98.503906 2 64 2 Z M 58.578125 78.109375 C 53.019531 78.109375 49.5625 74.203125 49.5625 67.914062 C 49.5625 56.667969 57.257812 49.734375 64.21875 49.734375 C 69.792969 49.734375 73.121094 53.542969 73.121094 59.925781 C 73.121094 71.191406 64.652344 78.109375 58.578125 78.109375 Z M 58.578125 78.109375 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg id="_1" data-name="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.cls-1{fill:#34495e;stroke:#528d19;stroke-miterlimit:10;}.cls-2{fill:#f0ecb6;}.cls-3{fill:#c18f35;}.cls-4{fill:#d3d4f3;}.cls-5{fill:#666bd1;}.cls-6{fill:#addff3;}.cls-7{fill:#2980b9;}.cls-8,.cls-9{fill:none;stroke-linejoin:round;opacity:0.8;}.cls-8{stroke:#666bd1;}.cls-9{stroke:#c18f35;}</style></defs><title>ex_join</title><path class="cls-1" d="M30.27,41.36a4.42,4.42,0,0,0,2.05-.51,2.81,2.81,0,0,0,1.06-1.08,3.59,3.59,0,0,0,.4-1.4q.06-.75.06-1.46v.35c0-.68,0-1.25,0-1.72a9.28,9.28,0,0,1,.11-1.2,2.76,2.76,0,0,1,.26-.83,2.37,2.37,0,0,1,.44-.6,2.53,2.53,0,0,1,.62-.46,4,4,0,0,1,.64-.27,3.5,3.5,0,0,1,.6-.13c.19,0,.19-.1,0-.12a3.52,3.52,0,0,1-.6-.13,3.65,3.65,0,0,1-.64-.27,2.52,2.52,0,0,1-.62-.46,2.41,2.41,0,0,1-.44-.6,2.77,2.77,0,0,1-.26-.83,9.31,9.31,0,0,1-.11-1.2q0-.71,0-1.73v.36c0-.47,0-1-.06-1.46a3.58,3.58,0,0,0-.4-1.4,2.82,2.82,0,0,0-1.06-1.08,4.47,4.47,0,0,0-2.05-.51V22.2l.53,0a4.11,4.11,0,0,1,.67.11,5.12,5.12,0,0,1,.72.23,2.89,2.89,0,0,1,.7.41,3.34,3.34,0,0,1,.83.9A3.66,3.66,0,0,1,34.15,25a7,7,0,0,1,.16,1.29c0,.47,0,1.88,0,1.88a7.9,7.9,0,0,0,.12,1.5,2.37,2.37,0,0,0,.47,1.06,2.65,2.65,0,0,0,1,.73.69.69,0,0,1,0,1.17,2.66,2.66,0,0,0-1,.73,2.39,2.39,0,0,0-.47,1.06,7.9,7.9,0,0,0-.12,1.5s-.09,2.78-.18,3.17a3.65,3.65,0,0,1-.43,1.07,3.31,3.31,0,0,1-.83.9,2.9,2.9,0,0,1-.7.41,5.39,5.39,0,0,1-.72.23,4.34,4.34,0,0,1-.67.11l-.53,0Z"/><path class="cls-2" d="M41.14,49.88c-1.37,0-1.5-.13-1.5-1.5V19.88h7.28v30Z"/><path class="cls-3" d="M46.42,20.38v29H41.14a3.25,3.25,0,0,1-.9-.08h0a2.18,2.18,0,0,1-.1-.92v-28h6.28m1-1H39.14v29c0,1.65.35,2,2,2h6.28v-31Z"/><path class="cls-4" d="M46.9,49.88v-30h7.28v28.5c0,1.37-.13,1.5-1.5,1.5Z"/><path class="cls-5" d="M53.69,20.38v28a3.28,3.28,0,0,1-.08.9,2.18,2.18,0,0,1-.92.1H47.4v-29h6.28m1-1H46.4v31h6.28c1.65,0,2-.35,2-2v-29Z"/><path class="cls-6" d="M39.64,19.88v-4.5c0-1.37.13-1.5,1.5-1.5h5.78v6Z"/><path class="cls-7" d="M46.42,14.38v5H40.14v-4a3.28,3.28,0,0,1,.08-.9,2.18,2.18,0,0,1,.92-.1h5.28m1-1H41.14c-1.65,0-2,.35-2,2v5h8.28v-7Z"/><path class="cls-6" d="M46.91,19.88v-6h5.78c1.37,0,1.5.13,1.5,1.5v4.5Z"/><path class="cls-7" d="M52.69,14.38a3.25,3.25,0,0,1,.9.08h0a2.18,2.18,0,0,1,.1.92v4H47.41v-5h5.28m0-1H46.41v7h8.28v-5c0-1.65-.35-2-2-2Z"/><line class="cls-8" x1="46.67" y1="26.23" x2="54.16" y2="26.23"/><line class="cls-9" x1="39.67" y1="26.23" x2="46.4" y2="26.23"/><line class="cls-8" x1="46.67" y1="32.23" x2="54.16" y2="32.23"/><line class="cls-9" x1="39.67" y1="32.23" x2="46.4" y2="32.23"/><line class="cls-8" x1="46.67" y1="38.23" x2="54.16" y2="38.23"/><line class="cls-9" x1="39.67" y1="38.23" x2="46.4" y2="38.23"/><line class="cls-8" x1="46.67" y1="44.23" x2="54.16" y2="44.23"/><line class="cls-9" x1="39.67" y1="44.23" x2="46.4" y2="44.23"/><path class="cls-4" d="M11.34,29.31c-1.37,0-1.5-.13-1.5-1.5V16.31h7.28v13Z"/><path class="cls-5" d="M16.62,16.81v12H11.34a3.25,3.25,0,0,1-.9-.08h0a2.18,2.18,0,0,1-.1-.92v-11h6.28m1-1H9.34v12c0,1.65.35,2,2,2h6.28v-14Z"/><path class="cls-4" d="M17.11,29.31v-13h7.28v11.5c0,1.37-.13,1.5-1.5,1.5Z"/><path class="cls-5" d="M23.89,16.81v11a3.28,3.28,0,0,1-.08.9,2.18,2.18,0,0,1-.92.1H17.61v-12h6.28m1-1H16.61v14h6.28c1.65,0,2-.35,2-2v-12Z"/><path class="cls-6" d="M9.84,16.31v-4.5c0-1.37.13-1.5,1.5-1.5h5.78v6Z"/><path class="cls-7" d="M16.62,10.81v5H10.34v-4a3.28,3.28,0,0,1,.08-.9,2.18,2.18,0,0,1,.92-.1h5.28m1-1H11.34c-1.65,0-2,.35-2,2v5h8.28v-7Z"/><path class="cls-6" d="M17.11,16.31v-6h5.78c1.37,0,1.5.13,1.5,1.5v4.5Z"/><path class="cls-7" d="M22.89,10.81a3.25,3.25,0,0,1,.9.08h0a2.18,2.18,0,0,1,.1.92v4H17.61v-5h5.28m0-1H16.61v7h8.28v-5c0-1.65-.35-2-2-2Z"/><line class="cls-8" x1="16.87" y1="22.66" x2="24.37" y2="22.66"/><line class="cls-8" x1="9.87" y1="22.66" x2="16.61" y2="22.66"/><path class="cls-2" d="M11.34,53.46c-1.37,0-1.5-.13-1.5-1.5V40.46h7.28v13Z"/><path class="cls-3" d="M16.62,41V53H11.34a3.25,3.25,0,0,1-.9-.08h0a2.18,2.18,0,0,1-.1-.92V41h6.28m1-1H9.34V52c0,1.65.35,2,2,2h6.28V40Z"/><path class="cls-2" d="M17.11,53.46v-13h7.28V52c0,1.37-.13,1.5-1.5,1.5Z"/><path class="cls-3" d="M23.89,41V52a3.28,3.28,0,0,1-.08.9,2.18,2.18,0,0,1-.92.1H17.61V41h6.28m1-1H16.61V54h6.28c1.65,0,2-.35,2-2V40Z"/><path class="cls-6" d="M9.84,40.46V36c0-1.37.13-1.5,1.5-1.5h5.78v6Z"/><path class="cls-7" d="M16.62,35v5H10.34V36a3.28,3.28,0,0,1,.08-.9,2.18,2.18,0,0,1,.92-.1h5.28m1-1H11.34c-1.65,0-2,.35-2,2v5h8.28V34Z"/><path class="cls-6" d="M17.11,40.46v-6h5.78c1.37,0,1.5.13,1.5,1.5v4.5Z"/><path class="cls-7" d="M22.89,35a3.25,3.25,0,0,1,.9.08h0a2.18,2.18,0,0,1,.1.92v4H17.61V35h5.28m0-1H16.61v7h8.28V36c0-1.65-.35-2-2-2Z"/><line class="cls-9" x1="16.87" y1="46.81" x2="24.37" y2="46.81"/><line class="cls-9" x1="9.87" y1="46.81" x2="16.61" y2="46.81"/></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px" height="100px" viewBox="0 0 25 25" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 25 8.59375 C 25 13.339844 21.152344 17.1875 16.40625 17.1875 C 15.859375 17.1875 15.320312 17.136719 14.804688 17.039062 L 13.632812 18.355469 C 13.40625 18.605469 13.089844 18.75 12.753906 18.75 L 10.9375 18.75 L 10.9375 20.703125 C 10.9375 21.351562 10.414062 21.875 9.765625 21.875 L 7.8125 21.875 L 7.8125 23.828125 C 7.8125 24.476562 7.289062 25 6.640625 25 L 1.171875 25 C 0.523438 25 0 24.476562 0 23.828125 L 0 20.015625 C 0 19.707031 0.125 19.40625 0.34375 19.1875 L 8.242188 11.289062 C 7.964844 10.441406 7.8125 9.535156 7.8125 8.59375 C 7.8125 3.847656 11.660156 0 16.40625 0 C 21.164062 0 25 3.835938 25 8.59375 Z M 16.40625 6.25 C 16.40625 7.546875 17.453125 8.59375 18.75 8.59375 C 20.046875 8.59375 21.09375 7.546875 21.09375 6.25 C 21.09375 4.953125 20.046875 3.90625 18.75 3.90625 C 17.453125 3.90625 16.40625 4.953125 16.40625 6.25 Z M 16.40625 6.25 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z"/></svg>

After

Width:  |  Height:  |  Size: 839 B

View File

@ -418,6 +418,17 @@ export default function CodeMirror({currEditor, name, value, options, events, re
Object.keys(events||{}).forEach((eventName)=>{
editor.current.on(eventName, events[eventName]);
});
// Register keyup event if autocomplete is true
let pref = pgWindow?.pgAdmin?.Browser?.get_preferences_for_module('sqleditor') || {};
if (autocomplete && pref.autocomplete_on_key_press) {
editor.current.on('keyup', (cm, event)=>{
if (!cm.state.completionActive && event.key != 'Enter' && event.key != 'Escape') {
OrigCodeMirror.commands.autocomplete(cm, null, {completeSingle: false});
}
});
}
editor.current.on('drop', handleDrop);
editor.current.on('paste', handlePaste);
initPreferences();

View File

@ -61,6 +61,7 @@ MODULE_NAME = 'sqleditor'
TRANSACTION_STATUS_CHECK_FAILED = gettext("Transaction status check failed.")
_NODES_SQL = 'nodes.sql'
sqleditor_close_session_lock = Lock()
auto_complete_objects = dict()
class SqlEditorModule(PgAdminModule):
@ -559,6 +560,10 @@ def close(trans_id):
trans_id: unique transaction id
"""
with sqleditor_close_session_lock:
# delete the SQLAutoComplete object
if trans_id in auto_complete_objects:
del auto_complete_objects[trans_id]
if 'gridData' not in session:
return make_json_response(data={'status': True})
@ -1746,10 +1751,13 @@ def auto_complete(trans_id):
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
# Create object of SQLAutoComplete class and pass connection object
auto_complete_obj = SQLAutoComplete(
sid=trans_obj.sid, did=trans_obj.did, conn=conn)
if trans_id not in auto_complete_objects:
# Create object of SQLAutoComplete class and pass connection object
auto_complete_objects[trans_id] = \
SQLAutoComplete(sid=trans_obj.sid, did=trans_obj.did,
conn=conn)
auto_complete_obj = auto_complete_objects[trans_id]
# Get the auto completion suggestions.
res = auto_complete_obj.get_completions(full_sql, text_before_cursor)
else:

View File

@ -27,6 +27,7 @@ const useStyles = makeStyles(()=>({
}));
function registerAutocomplete(api, transId, onFailure) {
let timeoutId;
OrigCodeMirror.registerHelper('hint', 'sql', function (editor) {
var data = [],
doc = editor.getDoc(),
@ -49,7 +50,6 @@ function registerAutocomplete(api, transId, onFailure) {
*/
hint_render: function (elt, data_arg, cur_arg) {
var el = document.createElement('span');
switch (cur_arg.type) {
case 'database':
el.className = 'sqleditor-hint pg-icon-' + cur_arg.type;
@ -58,10 +58,14 @@ function registerAutocomplete(api, transId, onFailure) {
el.className = 'sqleditor-hint icon-type';
break;
case 'keyword':
el.className = 'fa fa-key';
el.className = 'sqleditor-hint icon-key';
break;
case 'table alias':
el.className = 'fa fa-at';
el.className = 'sqleditor-hint icon-at';
break;
case 'join':
case 'fk join':
el.className = 'sqleditor-hint icon-join';
break;
default:
el.className = 'sqleditor-hint icon-' + cur_arg.type;
@ -87,73 +91,87 @@ function registerAutocomplete(api, transId, onFailure) {
return {
then: function (cb) {
var self_local = this;
// Make ajax call to find the autocomplete data
api.post(self_local.url, JSON.stringify(self_local.data))
.then((res) => {
var result = [];
_.each(res.data.data.result, function (obj, key) {
result.push({
text: key,
type: obj.object_type,
render: self_local.hint_render,
/*
* Below logic find the start and end point
* to replace the selected auto complete suggestion.
*/
var token = self_local.editor.getTokenAt(cur),
start, end, search;
if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
if (token.string.match(/^[.`\w@]\w*$/)) {
search = token.string;
start = token.start;
end = token.end;
} else {
start = end = cur.ch;
search = '';
}
/*
* Added 1 in the start position if search string
* started with "." or "`" else auto complete of code mirror
* will remove the "." when user select any suggestion.
*/
if (search.charAt(0) == '.' || search.charAt(0) == '``')
start += 1;
cb({
list: [{
text: '',
render: (elt)=>{
var el = document.createElement('span');
el.className = 'sqleditor-hint icon-spinner';
el.appendChild(document.createTextNode(gettext('Loading...')));
elt.appendChild(el);
},
}, {text: ''}],
from: {
line: self_local.current_line,
ch: start,
},
to: {
line: self_local.current_line,
ch: end,
},
});
timeoutId && clearTimeout(timeoutId);
timeoutId = setTimeout(()=> {
timeoutId = null;
// Make ajax call to find the autocomplete data
api.post(self_local.url, JSON.stringify(self_local.data))
.then((res) => {
var result = [];
_.each(res.data.data.result, function (obj, key) {
result.push({
text: key,
type: obj.object_type,
render: self_local.hint_render,
});
});
cb({
list: result,
from: {
line: self_local.current_line,
ch: start,
},
to: {
line: self_local.current_line,
ch: end,
},
});
})
.catch((err) => {
onFailure?.(err);
});
// Sort function to sort the suggestion's alphabetically.
result.sort(function (a, b) {
var textA = a.text.toLowerCase(),
textB = b.text.toLowerCase();
if (textA < textB) //sort string ascending
return -1;
if (textA > textB)
return 1;
return 0; //default return value (no sorting)
});
/*
* Below logic find the start and end point
* to replace the selected auto complete suggestion.
*/
var token = self_local.editor.getTokenAt(cur),
start, end, search;
if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
if (token.string.match(/^[.`\w@]\w*$/)) {
search = token.string;
start = token.start;
end = token.end;
} else {
start = end = cur.ch;
search = '';
}
/*
* Added 1 in the start position if search string
* started with "." or "`" else auto complete of code mirror
* will remove the "." when user select any suggestion.
*/
if (search.charAt(0) == '.' || search.charAt(0) == '``')
start += 1;
cb({
list: result,
from: {
line: self_local.current_line,
ch: start,
},
to: {
line: self_local.current_line,
ch: end,
},
});
})
.catch((err) => {
onFailure?.(err);
});
}, 300);
}.bind(ctx),
};
});

View File

@ -677,6 +677,16 @@ def register_query_tool_preferences(self):
'in upper case for auto completion.')
)
self.preference.register(
'auto_completion', 'autocomplete_on_key_press',
gettext("Autocomplete on key press"), 'boolean', True,
category_label=gettext('Auto completion'),
help_str=gettext('If set to True, autocomplete will be available on '
'key press along with CTRL/CMD + Space. If set to '
'False, autocomplete is only activated when CTRL/CMD '
'+ Space is pressed.')
)
self.preference.register(
'keyboard_shortcuts',
'commit_transaction',