From d927d393eeecd825189d6364af5ffbefcc912341 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 17 Apr 2021 23:09:16 +0530 Subject: [PATCH 1/5] fix: Add autocompletion items in Server Script - API to add autocompletion items in Code field --- frappe/cache_manager.py | 2 +- .../doctype/server_script/server_script.js | 6 +++ .../doctype/server_script/server_script.py | 23 ++++++++- frappe/public/js/frappe/form/controls/code.js | 50 +++++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index bad879d2fa..4e0fe0cf44 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -18,7 +18,7 @@ global_cache_keys = ("app_hooks", "installed_apps", 'all_apps', 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version', 'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts', - 'sitemap_routes', 'db_tables') + doctype_map_keys + 'sitemap_routes', 'db_tables', 'server_script_autocompletion_items') + doctype_map_keys user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", "defaults", "user_permissions", "home_page", "linked_with", diff --git a/frappe/core/doctype/server_script/server_script.js b/frappe/core/doctype/server_script/server_script.js index 95a63780f8..e12200b6fc 100644 --- a/frappe/core/doctype/server_script/server_script.js +++ b/frappe/core/doctype/server_script/server_script.js @@ -9,6 +9,12 @@ frappe.ui.form.on('Server Script', { if (frm.doc.script_type != 'Scheduler Event') { frm.dashboard.hide(); } + + frm.call('get_autocompletion_items') + .then(r => r.message) + .then(items => { + frm.set_df_property('script', 'autocompletions', items) + }); }, setup_help(frm) { diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 8838d9e954..6a8eb59c3a 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -5,11 +5,12 @@ from __future__ import unicode_literals import ast +from types import FunctionType, ModuleType from typing import Dict, List import frappe from frappe.model.document import Document -from frappe.utils.safe_exec import safe_exec +from frappe.utils.safe_exec import get_safe_globals, safe_exec, NamespaceDict from frappe import _ @@ -122,6 +123,26 @@ class ServerScript(Document): if locals["conditions"]: return locals["conditions"] + @frappe.whitelist() + def get_autocompletion_items(self): + def get_keys(obj): + out = [] + for key in obj: + if key.startswith('_'): + continue + value = obj[key] + if isinstance(value, (FunctionType, ModuleType)): + out.append(key) + elif isinstance(value, (NamespaceDict, dict)): + out += [f'{key}.{subkey}' for subkey in get_keys(value)] + return out + + items = frappe.cache().get_value('server_script_autocompletion_items') + if not items: + items = get_keys(get_safe_globals()) + frappe.cache().set_value('server_script_autocompletion_items', items) + return items + @frappe.whitelist() def setup_scheduler_events(script_name, frequency): diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index eec450b390..8d2609b836 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -31,6 +31,56 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ const input_value = this.get_input_value(); this.parse_validate_and_set_in_model(input_value); }, 300)); + + // setup autocompletion when it is set the first time + Object.defineProperty(this.df, 'autocompletions', { + get() { + return this._autocompletions || []; + }, + set: (value) => { + this.setup_autocompletion(); + this.df._autocompletions = value; + } + }); + }, + + setup_autocompletion() { + if (this._autocompletion_setup) return; + + const ace = window.ace; + const get_autocompletions = () => this.df.autocompletions; + + ace.config.loadModule("ace/ext/language_tools", langTools => { + this.editor.setOptions({ + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true + }); + + let completer = { + getCompletions: function(editor, session, pos, prefix, callback) { + if (prefix.length === 0) { + callback(null, []); + return; + } + let autocompletions = get_autocompletions(); + if (autocompletions.length) { + callback( + null, + autocompletions.map(a => ({ + name: 'frappe', + value: a, + score: 100, + meta: 'Frappe API' + })) + ); + } + } + } + langTools.addCompleter(completer); + }); + + this._autocompletion_setup = true; }, refresh_height() { From 8aa26c728609f7e6277b5abf9f8d1592f288f320 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 20 Apr 2021 16:01:56 +0530 Subject: [PATCH 2/5] fix: Include all keys and sort by score --- .../doctype/server_script/server_script.js | 2 +- .../doctype/server_script/server_script.py | 40 +++++++++++++++---- frappe/public/js/frappe/form/controls/code.js | 7 +--- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.js b/frappe/core/doctype/server_script/server_script.js index e12200b6fc..dda39115bf 100644 --- a/frappe/core/doctype/server_script/server_script.js +++ b/frappe/core/doctype/server_script/server_script.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Server Script', { frm.call('get_autocompletion_items') .then(r => r.message) .then(items => { - frm.set_df_property('script', 'autocompletions', items) + frm.set_df_property('script', 'autocompletions', items); }); }, diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 6a8eb59c3a..ea27a2ac83 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import ast -from types import FunctionType, ModuleType +from types import FunctionType, MethodType, ModuleType from typing import Dict, List import frappe @@ -125,21 +125,47 @@ class ServerScript(Document): @frappe.whitelist() def get_autocompletion_items(self): + """Generates a list of a autocompletion strings from the context dict + that is used while executing a Server Script. + + Returns: + list: Returns list of autocompletion items. + For e.g., ["frappe.utils.cint", "frappe.db.get_all", ...] + """ def get_keys(obj): out = [] for key in obj: if key.startswith('_'): continue value = obj[key] - if isinstance(value, (FunctionType, ModuleType)): - out.append(key) - elif isinstance(value, (NamespaceDict, dict)): - out += [f'{key}.{subkey}' for subkey in get_keys(value)] + if isinstance(value, (NamespaceDict, dict)) and value: + if key == 'form_dict': + out.append(['form_dict', 3]) + continue + for subkey, score in get_keys(value): + fullkey = f'{key}.{subkey}' + out.append([fullkey, score]) + else: + if isinstance(value, ModuleType): + score = 0 + elif isinstance(value, (FunctionType, MethodType)): + score = 1 + elif isinstance(value, type) and issubclass(value, Exception): + score = 9 + elif isinstance(value, type): + score = 2 + elif isinstance(value, dict): + score = 3 + else: + score = 4 + out.append([key, score]) return out items = frappe.cache().get_value('server_script_autocompletion_items') - if not items: - items = get_keys(get_safe_globals()) + if not items or True: + unsorted_items = get_keys(get_safe_globals()) + sorted_items = sorted(unsorted_items, key=lambda k: k[1]) + items = [d[0] for d in sorted_items] frappe.cache().set_value('server_script_autocompletion_items', items) return items diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index 8d2609b836..635146563e 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -53,11 +53,10 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ ace.config.loadModule("ace/ext/language_tools", langTools => { this.editor.setOptions({ enableBasicAutocompletion: true, - enableSnippets: true, enableLiveAutocompletion: true }); - let completer = { + langTools.addCompleter({ getCompletions: function(editor, session, pos, prefix, callback) { if (prefix.length === 0) { callback(null, []); @@ -76,10 +75,8 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ ); } } - } - langTools.addCompleter(completer); + }); }); - this._autocompletion_setup = true; }, From 8ca7ba6b5619df286609624f94923a38ae9ca1dd Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 20 Apr 2021 16:08:11 +0530 Subject: [PATCH 3/5] fix: remove hardcoded value --- frappe/core/doctype/server_script/server_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index ea27a2ac83..b791997a8b 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -162,7 +162,7 @@ class ServerScript(Document): return out items = frappe.cache().get_value('server_script_autocompletion_items') - if not items or True: + if not items: unsorted_items = get_keys(get_safe_globals()) sorted_items = sorted(unsorted_items, key=lambda k: k[1]) items = [d[0] for d in sorted_items] From c309cb2d29d1dd791632d37cf5d5952728440703 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 20 Apr 2021 16:23:20 +0530 Subject: [PATCH 4/5] fix: Pass score to ace to let it handle sorting --- .../doctype/server_script/server_script.py | 19 +++++++++---------- frappe/public/js/frappe/form/controls/code.js | 16 ++++++++++------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index b791997a8b..f80a067cf1 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -140,32 +140,31 @@ class ServerScript(Document): value = obj[key] if isinstance(value, (NamespaceDict, dict)) and value: if key == 'form_dict': - out.append(['form_dict', 3]) + out.append(['form_dict', 7]) continue for subkey, score in get_keys(value): fullkey = f'{key}.{subkey}' out.append([fullkey, score]) else: - if isinstance(value, ModuleType): + if isinstance(value, type) and issubclass(value, Exception): score = 0 + elif isinstance(value, ModuleType): + score = 10 elif isinstance(value, (FunctionType, MethodType)): - score = 1 - elif isinstance(value, type) and issubclass(value, Exception): score = 9 elif isinstance(value, type): - score = 2 + score = 8 elif isinstance(value, dict): - score = 3 + score = 7 else: - score = 4 + score = 6 out.append([key, score]) return out items = frappe.cache().get_value('server_script_autocompletion_items') if not items: - unsorted_items = get_keys(get_safe_globals()) - sorted_items = sorted(unsorted_items, key=lambda k: k[1]) - items = [d[0] for d in sorted_items] + items = get_keys(get_safe_globals()) + items = [{'value': d[0], 'score': d[1]} for d in items] frappe.cache().set_value('server_script_autocompletion_items', items) return items diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index 635146563e..33579b3b88 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -66,12 +66,16 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ if (autocompletions.length) { callback( null, - autocompletions.map(a => ({ - name: 'frappe', - value: a, - score: 100, - meta: 'Frappe API' - })) + autocompletions.map(a => { + if (typeof a === 'string') { + a = { value: a }; + } + return { + name: 'frappe', + value: a.value, + score: a.score + } + }) ); } } From 273e6b01db9f4c62e6f71b582da8eaaa97ca52b8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 20 Apr 2021 21:46:20 +0530 Subject: [PATCH 5/5] style: missing semicolon --- frappe/public/js/frappe/form/controls/code.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index 33579b3b88..9600763588 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -74,7 +74,7 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ name: 'frappe', value: a.value, score: a.score - } + }; }) ); }