From 31a8ae6a202614046739331b91b3769d248cf25e Mon Sep 17 00:00:00 2001 From: Elazar Date: Wed, 12 Jan 2022 23:07:40 +0200 Subject: [PATCH] initial implementation --- package.json | 304 ++++++++++++++++++++++++++++++++- src/editorContextApplyStyle.js | 80 +++++++++ src/extension.js | 29 +++- src/util.js | 42 ++++- 4 files changed, 441 insertions(+), 14 deletions(-) create mode 100644 src/editorContextApplyStyle.js diff --git a/package.json b/package.json index 206c668..aea8ff4 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,61 @@ "title": "List highlighted annotations", "category": "TODO-Highlight", "command": "todohighlight.listAnnotations" + }, + { + "title": "Style 1", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle1" + }, + { + "title": "Style 2", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle2" + }, + { + "title": "Style 3", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle3" + }, + { + "title": "Style 4", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle4" + }, + { + "title": "Style 5", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle5" + }, + { + "title": "Style 6", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle6" + }, + { + "title": "Style 7", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle7" + }, + { + "title": "Style 8", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle8" + }, + { + "title": "Style 9", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle9" + }, + { + "title": "Style 10", + "category": "TODO-Highlight", + "command": "todohighlight.applyStyle10" + }, + { + "title": "Clear Style", + "category": "TODO-Highlight", + "command": "todohighlight.clearStyle" } ], "configuration": { @@ -109,7 +164,7 @@ "scope": "language-overridable", "markdownDescription": "An array of keywords, and their CSS to customise how they look. See all available properties in the [VSCode doc on DecorationRenderOptions](https://code.visualstudio.com/api/references/vscode-api#DecorationRenderOptions) section.", "items": { - "anyOf": [ + "oneOf": [ { "type": "string" }, @@ -158,7 +213,34 @@ }, "required": [ "text" - ] + ], + "not": { + "required": [ + "styleId" + ] + } + }, + { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Custom text to be highlighted." + }, + "styleId": { + "type": "number", + "markdownDescription": "The style ID from the `#todohighlight.styles#`." + } + }, + "required": [ + "text", + "styleId" + ], + "not": { + "required": [ + "regex" + ] + } } ] }, @@ -265,9 +347,223 @@ "type": "number", "default": 5120, "description": "Max files for searching" + }, + "todohighlight.styles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Enable or disable this style from the UI." + }, + "id": { + "type": "number", + "minimum": 1, + "maximum": 10 + }, + "backgroundColor": { + "type": "string", + "description": "The text background color." + }, + "border": { + "type": "string", + "description": "The border style for the highlight, as a CSS string." + }, + "color": { + "type": "string", + "description": "The text color. " + }, + "cursor": { + "type": "string", + "description": "The style for the cursor shown over the highlight, as a CSS property." + }, + "isWholeLine": { + "type": "boolean", + "default": false, + "description": "If true, then the whole line is highlighted, not just the matching text." + }, + "overviewRulerColor": { + "type": "string", + "description": "The color of the ruler mark on the scroll bar." + } + }, + "required": [ + "id" + ] + }, + "default": [ + { + "id": 1, + "enabled": true, + "backgroundColor": "#ffeb3b" + }, + { + "id": 2, + "enabled": true, + "backgroundColor": "#2196f3" + }, + { + "id": 3, + "enabled": true, + "backgroundColor": "#ff9800" + }, + { + "id": 4, + "enabled": false, + "backgroundColor": "#9c27b0" + }, + { + "id": 5, + "enabled": false, + "backgroundColor": "#2e7d32" + }, + { + "id": 6, + "enabled": false, + "backgroundColor": "#ff5722" + }, + { + "id": 7, + "enabled": false, + "backgroundColor": "#607d8b" + }, + { + "id": 8, + "enabled": false, + "backgroundColor": "#795548" + }, + { + "id": 9, + "enabled": false, + "backgroundColor": "#ffeb3b" + }, + { + "id": 10, + "enabled": false, + "backgroundColor": "#2196f3" + } + ] } } - } + }, + "menus": { + "commandPalette": [ + { + "command": "todohighlight.applyStyle1", + "when": "false" + }, + { + "command": "todohighlight.applyStyle2", + "when": "false" + }, + { + "command": "todohighlight.applyStyle3", + "when": "false" + }, + { + "command": "todohighlight.applyStyle4", + "when": "false" + }, + { + "command": "todohighlight.applyStyle5", + "when": "false" + }, + { + "command": "todohighlight.applyStyle6", + "when": "false" + }, + { + "command": "todohighlight.applyStyle7", + "when": "false" + }, + { + "command": "todohighlight.applyStyle8", + "when": "false" + }, + { + "command": "todohighlight.applyStyle9", + "when": "false" + }, + { + "command": "todohighlight.applyStyle10", + "when": "false" + }, + { + "command": "todohighlight.clearStyle", + "when": "false" + } + ], + "editor/context": [ + { + "submenu": "todohighlight.editorMenu" + } + ], + "todohighlight.editorMenu": [ + { + "command": "todohighlight.toggleHighlight" + }, + { + "submenu": "todohighlight.applyStyleMenu" + }, + { + "command": "todohighlight.clearStyle" + } + ], + "todohighlight.applyStyleMenu": [ + { + "command": "todohighlight.applyStyle1", + "when": "todohighlight.styleContextKey1" + }, + { + "command": "todohighlight.applyStyle2", + "when": "todohighlight.styleContextKey2" + }, + { + "command": "todohighlight.applyStyle3", + "when": "todohighlight.styleContextKey3" + }, + { + "command": "todohighlight.applyStyle4", + "when": "todohighlight.styleContextKey4" + }, + { + "command": "todohighlight.applyStyle5", + "when": "todohighlight.styleContextKey5" + }, + { + "command": "todohighlight.applyStyle6", + "when": "todohighlight.styleContextKey6" + }, + { + "command": "todohighlight.applyStyle7", + "when": "todohighlight.styleContextKey7" + }, + { + "command": "todohighlight.applyStyle8", + "when": "todohighlight.styleContextKey8" + }, + { + "command": "todohighlight.applyStyle9", + "when": "todohighlight.styleContextKey9" + }, + { + "command": "todohighlight.applyStyle10", + "when": "todohighlight.styleContextKey10" + } + ] + }, + "submenus": [ + { + "id": "todohighlight.editorMenu", + "label": "Todo Highlight" + }, + { + "id": "todohighlight.applyStyleMenu", + "label": "Apply Style" + } + ] }, "devDependencies": { "@types/mocha": "^2.2.32", @@ -289,4 +585,4 @@ "vsce": "^1.77.0", "webpack": "^5.52.0" } -} +} \ No newline at end of file diff --git a/src/editorContextApplyStyle.js b/src/editorContextApplyStyle.js new file mode 100644 index 0000000..661cbdd --- /dev/null +++ b/src/editorContextApplyStyle.js @@ -0,0 +1,80 @@ +const vscode = require('vscode'); +const util = require('./util'); + +function styleContextKey(id) { + return `todohighlight.styleContextKey${id}`; +} + +function applyStyleCommand(id) { + return `todohighlight.applyStyle${id}`; +} + +function getSelectionOrWord(editor) { + return editor.selection.isEmpty + ? editor.document.getText( + editor.document.getWordRangeAtPosition(editor.selection.start) + ) + : editor.document.getText(editor.selection); +} + +const disposables = []; + +function init(settings) { + // first, clean up all registered commands + disposables.forEach((d) => d.dispose()); + disposables.length = 0; + + const styles = settings.get('styles'); + for (const style of styles) { + vscode.commands.executeCommand( + 'setContext', + styleContextKey(style.id), + style.enabled === true + ); + + disposables.push( + vscode.commands.registerTextEditorCommand( + applyStyleCommand(style.id), + // get selection + async (editor, _) => { + const text = getSelectionOrWord(editor); + // add corresponding highlight entry to workspace setting + const newStyleObject = { + styleId: style.id, + text: text, + }; + + const currentSettings = settings.get('keywords'); + const idx = currentSettings.findIndex( + (item) => item.text === text + ); + if (idx !== -1) { + currentSettings[idx] = newStyleObject; + } else { + currentSettings.push(newStyleObject); + } + + settings.update('keywords', currentSettings); + } + ) + ); + } +} + +function clearStyle(editor, settings) { + const currentSettings = settings.get('keywords'); + const text = getSelectionOrWord(editor); + const idx = currentSettings.findIndex( + (item) => util.isReferencedStyleKeyword(item) && item.text === text + ); + if (idx !== -1) { + currentSettings.splice(idx, 1); + } + settings.update('keywords', currentSettings); +} + +module.exports = { + init, + clearStyle, + disposables, +}; diff --git a/src/extension.js b/src/extension.js index b79bdec..3a1b665 100644 --- a/src/extension.js +++ b/src/extension.js @@ -6,6 +6,7 @@ var vscode = require('vscode'); var util = require('./util'); +var editorContextApplyStyle = require('./editorContextApplyStyle'); var window = vscode.window; var workspace = vscode.workspace; @@ -50,6 +51,13 @@ function activate(context) { util.showOutputChannel(annotationList); })); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'todohighlight.clearStyle', + (editor) => editorContextApplyStyle.clearStyle(editor, settings) + ) + ); + if (activeEditor) { triggerUpdateDecorations(); } @@ -131,6 +139,9 @@ function activate(context) { window.outputChannel = window.createOutputChannel('TodoHighlight'); } + // setup style from editor context + editorContextApplyStyle.init(settings); + decorationTypes = {}; if (keywordsPattern.trim()) { @@ -140,7 +151,12 @@ function activate(context) { pattern = keywordsPattern; } else { - assembledData = util.getAssembledData(settings.get('keywords'), customDefaultStyle, isCaseSensitive); + assembledData = util.getAssembledData( + settings.get('keywords'), + customDefaultStyle, + isCaseSensitive, + settings.get('styles', []) + ); Object.keys(assembledData).forEach((v) => { if (!isCaseSensitive) { v = v.toUpperCase() @@ -183,4 +199,13 @@ function activate(context) { } } -exports.activate = activate; +function deactivate() { + editorContextApplyStyle.disposables.forEach((disposable) => + disposable.dispose() + ); +} + +module.exports = { + activate, + deactivate +} diff --git a/src/util.js b/src/util.js index c1418c9..92e532e 100644 --- a/src/util.js +++ b/src/util.js @@ -27,26 +27,51 @@ var DEFAULT_STYLE = { backgroundColor: "#ffeb3b", }; -function getAssembledData(keywords, customDefaultStyle, isCaseSensitive) { - var result = {}, regex = [], reg; +function isReferencedStyleKeyword(obj) { + return obj.hasOwnProperty('styleId'); +} + +function getAssembledData( + keywords, + customDefaultStyle, + isCaseSensitive, + styles +) { + var result = {}, + regex = [], + reg; keywords.forEach((v) => { v = typeof v == 'string' ? { text: v } : v; - var text = v.text; - if (!text) return;//NOTE: in case of the text is empty + let text = v.text; + if (!text) return; //NOTE: in case of the text is empty if (!isCaseSensitive) { text = text.toUpperCase(); } + if (isReferencedStyleKeyword(v)) { + result[text] = Object.assign( + {}, + DEFAULT_STYLE, + customDefaultStyle, + styles.find((s) => s.enabled && s.id === v.styleId) ?? {} + ); + } else { if (text == 'TODO:' || text == 'FIXME:') { v = Object.assign({}, DEFAULT_KEYWORDS[text], v); } - result[text] = Object.assign({}, DEFAULT_STYLE, customDefaultStyle, v); + result[text] = Object.assign( + {}, + DEFAULT_STYLE, + customDefaultStyle, + v + ); if (v.regex) { - regex.push(regex.pattern||text); + regex.push(regex.pattern || text); } - }) + } + }); if (regex) { reg = regex.join('|'); @@ -292,5 +317,6 @@ module.exports = { showOutputChannel, escapeRegExp, escapeRegExpGroups, - escapeRegExpGroupsLegacy + escapeRegExpGroupsLegacy, + isReferencedStyleKeyword, };