From aa47eda0872cd585e6b72579f942d6731d04a726 Mon Sep 17 00:00:00 2001 From: Gorash Date: Tue, 6 Apr 2021 12:11:46 +0000 Subject: [PATCH] [IMP] web: add mode qweb in ace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new parser in the ace for the qweb highlight. This makes it possible to visualize the qweb tags and the part of code. Used in the website and in xml fields. closes odoo/odoo#69609 Signed-off-by: Géry Debongnie (ged) --- addons/web/__manifest__.py | 1 + addons/web/static/lib/ace/mode-qweb.js | 143 ++++++++++++++++++ .../web/static/src/js/fields/basic_fields.js | 6 +- addons/web/static/src/scss/ace.scss | 15 ++ addons/web_editor/static/src/js/common/ace.js | 4 +- addons/website/__manifest__.py | 1 + .../static/src/js/editor/snippets.options.js | 3 +- 7 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 addons/web/static/lib/ace/mode-qweb.js create mode 100644 addons/web/static/src/scss/ace.scss diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index d375d6ff965..48b396a8946 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -95,6 +95,7 @@ This module provides the core of the Odoo Web Client. 'web/static/src/scss/tooltip.scss', 'web/static/src/scss/switch_company_menu.scss', 'web/static/src/scss/debug_manager.scss', + 'web/static/src/scss/ace.scss', 'web/static/src/scss/control_panel.scss', 'web/static/src/scss/fields.scss', 'web/static/src/scss/file_upload.scss', diff --git a/addons/web/static/lib/ace/mode-qweb.js b/addons/web/static/lib/ace/mode-qweb.js new file mode 100644 index 00000000000..d9372fac4cf --- /dev/null +++ b/addons/web/static/lib/ace/mode-qweb.js @@ -0,0 +1,143 @@ +define("ace/mode/qweb_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/xml_highlight_rules"], function (require, exports, module) { + "use strict"; + + var oop = require("../lib/oop"); + var XmlHighlightRules = require("./xml_highlight_rules").XmlHighlightRules; + + var QWebHighlightRules = function () { + XmlHighlightRules.call(this); + const xmlRules = this.$rules; + + const tagRegex = xmlRules.attributes[0].regex; + + this.$rules = Object.assign({}, + xmlRules, + { + attributes: [{ + include: "attributes_odoo", + }, { + include: "attributes_qweb", + }, { + include: "attributes_groups", + }, { + include: "attributes_sample", + }], + + attributes_odoo: [{ + token: ["entity.other.attribute-name.xml.odoo", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml.code", "string.attribute-value.xml.end"], + regex: '(domain|attrs|options)(=)(\\s*)(")([^"]*)(")', + }, { + token: ["entity.other.attribute-name.xml.odoo", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml.code", "string.attribute-value.xml.end"], + regex: "(domain|attrs|options)(=)(\\s*)(')([^']*)(')", + }], + + attributes_qweb: [{ + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml", "string.attribute-value.xml.end"], + regex: '(t-name|t-call-assets|t-js|t-css)(=)(\\s*)(")([^"]*)(")', + }, { + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml", "string.attribute-value.xml.end"], + regex: "(t-name|t-call-assets|t-js|t-css)(=)(\\s*)(')([^']*)(')", + }, { + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml", "string.attribute-value.xml", "string.attribute-value.xml.code", "string.attribute-value.xml", "string.attribute-value.xml", "string.attribute-value.xml.end"], + regex: '(t-call|t-attf-(?:' + tagRegex + '))(=)(\\s*)(")([^"#{]*)(?:([#{]\\{)([^"}]+)(\\}[}]?))?([^"]*)(")', + }, { + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml", "string.attribute-value.xml", "string.attribute-value.xml.code", "string.attribute-value.xml", "string.attribute-value.xml", "string.attribute-value.xml.end"], + regex: "(t-call|t-attf-(?:" + tagRegex + "))(=)(\\s*)(')([^'#{]*)(?:([#{]\\{)([^'}]+)(\\}[}]?))?([^']*)(')", + }, { + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml.code", "string.attribute-value.xml.end"], + regex: '(t-(?:' + tagRegex + '))(=)(\\s*)(")([^"]*)(")', + }, { + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml.code", "string.attribute-value.xml.end"], + regex: "(t-(?:" + tagRegex + "))(=)(\\s*)(')([^']*)(')", + }], + + attributes_groups: [{ + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml", "string.attribute-value.xml.end"], + regex: '(groups)(=)(\\s*)(")([\\s\\na-zA-Z0-9,_-]*)(")', + }, { + token: ["entity.other.attribute-name.xml.qweb", "keyword.operator.attribute-equals.xml", "text", "string.attribute-value.xml.start", "string.attribute-value.xml", "string.attribute-value.xml.end"], + regex: "(groups)(=)(\\s*)(')((?:[\\s\\na-zA-Z0-9,_-]*)*)(')", + }], + + attributes_sample: [{ + token: "entity.other.attribute-name.xml", + regex: tagRegex, + }, { + token: "keyword.operator.attribute-equals.xml", + regex: "=", + }, { + include: "tag_whitespace", + }, { + include: "attribute_value", + }], + + tag: [{ + token: ["meta.tag.punctuation.tag-open.xml", "meta.tag.punctuation.end-tag-open.xml", "meta.tag.tag-name.xml"], + regex: "(?:(<)|()|(>)", + next: "start", + }, + ], + }], + }, + ); + + if (this.constructor === QWebHighlightRules) { + this.normalizeRules(); + } + }; + + oop.inherits(QWebHighlightRules, XmlHighlightRules); + + exports.QWebHighlightRules = QWebHighlightRules; +}); + +define("ace/mode/qweb",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text","ace/mode/qweb_highlight_rules","ace/mode/behaviour/xml","ace/mode/folding/xml","ace/worker/worker_client"], function(require, exports, module) { + "use strict"; + + var oop = require("../lib/oop"); + var lang = require("../lib/lang"); + var TextMode = require("./text").Mode; + var QWebHighlightRules = require("./qweb_highlight_rules").QWebHighlightRules; + var XmlBehaviour = require("./behaviour/xml").XmlBehaviour; + var XmlFoldMode = require("./folding/xml").FoldMode; + var WorkerClient = require("../worker/worker_client").WorkerClient; + + var Mode = function() { + this.HighlightRules = QWebHighlightRules; + this.$behaviour = new XmlBehaviour(); + this.foldingRules = new XmlFoldMode(); + }; + + oop.inherits(Mode, TextMode); + + (function() { + + this.voidElements = lang.arrayToMap([]); + + this.blockComment = {start: ""}; + + this.createWorker = function(session) { + var worker = new WorkerClient(["ace"], "ace/mode/xml_worker", "Worker"); + worker.attachToDocument(session.getDocument()); + + worker.on("error", function(e) { + session.setAnnotations(e.data); + }); + + worker.on("terminate", function() { + session.clearAnnotations(); + }); + + return worker; + }; + + this.$id = "ace/mode/xml"; + }).call(Mode.prototype); + + exports.Mode = Mode; +}); diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 77d52fd61b5..de48b193490 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -3520,7 +3520,8 @@ var AceEditor = DebouncedField.extend({ '/web/static/lib/ace/ace.js', [ '/web/static/lib/ace/mode-python.js', - '/web/static/lib/ace/mode-xml.js' + '/web/static/lib/ace/mode-xml.js', + '/web/static/lib/ace/mode-qweb.js' ] ], events: {}, // events are triggered manually for this debounced widget @@ -3613,9 +3614,10 @@ var AceEditor = DebouncedField.extend({ } this.aceEditor.$blockScrolling = true; this.aceSession = this.aceEditor.getSession(); + const mode = this.nodeOptions.mode || 'qweb'; this.aceSession.setOptions({ useWorker: false, - mode: "ace/mode/" + (this.nodeOptions.mode || 'xml'), + mode: "ace/mode/" + (mode === 'xml' ? 'qweb' : mode), tabSize: 2, useSoftTabs: true, }); diff --git a/addons/web/static/src/scss/ace.scss b/addons/web/static/src/scss/ace.scss new file mode 100644 index 00000000000..95adacf494e --- /dev/null +++ b/addons/web/static/src/scss/ace.scss @@ -0,0 +1,15 @@ +.ace_editor > .ace_gutter { + display: block !important; // display even with aria-hidden +} +.ace_editor .ace_qweb { + color: orange !important; +} +.ace_editor .ace_code { + color: fuchsia !important; +} +.ace_editor.ace-monokai .ace_qweb { + color: lightblue !important; +} +.ace_editor.ace-monokai .ace_code { + color: #c94bc9 !important; +} diff --git a/addons/web_editor/static/src/js/common/ace.js b/addons/web_editor/static/src/js/common/ace.js index 0f353f31681..caa219f5103 100644 --- a/addons/web_editor/static/src/js/common/ace.js +++ b/addons/web_editor/static/src/js/common/ace.js @@ -122,6 +122,7 @@ var ViewEditor = Widget.extend({ [ '/web/static/lib/ace/javascript_highlight_rules.js', '/web/static/lib/ace/mode-xml.js', + '/web/static/lib/ace/mode-qweb.js', '/web/static/lib/ace/mode-scss.js', '/web/static/lib/ace/mode-js.js', '/web/static/lib/ace/theme-monokai.js' @@ -349,7 +350,8 @@ var ViewEditor = Widget.extend({ type = type || this.currentType; var editingSession = new window.ace.EditSession(this.resources[type][resID].arch); editingSession.setUseWorker(false); - editingSession.setMode('ace/mode/' + (type || this.currentType)); + let mode = (type || this.currentType); + editingSession.setMode('ace/mode/' + (mode === 'xml' ? 'qweb' : mode)); editingSession.setUndoManager(new window.ace.UndoManager()); editingSession.on('change', function () { _.defer(function () { diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py index ea675051beb..07c5248bbe9 100644 --- a/addons/website/__manifest__.py +++ b/addons/website/__manifest__.py @@ -183,6 +183,7 @@ 'website.assets_editor': [ ('include', 'web._assets_helpers'), 'web/static/lib/bootstrap/scss/_variables.scss', + 'web/static/src/scss/ace.scss', 'website/static/src/scss/website.editor.ui.scss', 'website/static/src/scss/website.theme_install.scss', 'website/static/src/js/menu/content.js', diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index 569d2b94891..7647e621fa2 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -965,6 +965,7 @@ options.registry.OptionsTab = options.Class.extend({ jsLibs: [ '/web/static/lib/ace/ace.js', '/web/static/lib/ace/mode-xml.js', + '/web/static/lib/ace/mode-qweb.js', ], }); @@ -1163,7 +1164,7 @@ options.registry.OptionsTab = options.Class.extend({ const aceSession = aceEditor.getSession(); aceSession.setOptions({ - mode: "ace/mode/xml", + mode: "ace/mode/qweb", useWorker: false, }); return aceEditor;