From a0b0ead21c4ba24514f464e215e01932f1216133 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 10 Dec 2024 12:45:30 +0530 Subject: [PATCH] feat: inline color preview --- src/editor/Editor.js | 26 +++++- .../CSSColorPreview/main.js | 84 +++++++++++++++++-- src/nls/root/strings.js | 3 +- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index ebf427909..74ef85855 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -1491,19 +1491,41 @@ define(function (require, exports, module) { }; /** - * Clears all mark of the given type. If nothing is given, clears all marks(Don't use this API without types!). + * Clears all marks of the given type. If a lineNumbers array is given, only clears marks on those lines. + * If no markType or lineNumbers are given, clears all marks (use cautiously). * @param {string} [markType] - Optional, if given will only delete marks of that type. Else delete everything. + * @param {number[]} [lineNumbers] - Optional, array of line numbers where marks should be cleared. */ - Editor.prototype.clearAllMarks = function (markType) { + Editor.prototype.clearAllMarks = function (markType, lineNumbers) { const self = this; + self._codeMirror.operation(function () { let marks = self.getAllMarks(markType); + + if (lineNumbers && Array.isArray(lineNumbers)) { + // Filter marks to only those within the specified line numbers + marks = marks.filter(function (mark) { + const range = mark.find(); // Get the range of the mark + if (!range) { + return false; + } + + const startLine = range.from.line; + const endLine = range.to.line; + + // Check if the mark overlaps with any of the specified lines + return lineNumbers.some(line => line >= startLine && line <= endLine); + }); + } + + // Clear the filtered marks for (let mark of marks) { mark.clear(); } }); }; + /** * Checks if two positions in the editor are the same. * diff --git a/src/extensionsIntegrated/CSSColorPreview/main.js b/src/extensionsIntegrated/CSSColorPreview/main.js index 4920f55c9..6f8512d37 100644 --- a/src/extensionsIntegrated/CSSColorPreview/main.js +++ b/src/extensionsIntegrated/CSSColorPreview/main.js @@ -40,6 +40,7 @@ define(function (require, exports, module) { // Extension variables. const COLOR_REGEX = ColorUtils.COLOR_REGEX, // used to match color GUTTER_NAME = "CodeMirror-colorGutter", + COLOR_MARK_NAME = "colorMarker", DUMMY_GUTTER_CLASS = "CodeMirror-colorGutter-none", SINGLE_COLOR_PREVIEW_CLASS = "ico-cssColorPreview", MULTI_COLOR_PREVIEW_CLASS = "ico-multiple-cssColorPreview", @@ -50,12 +51,18 @@ define(function (require, exports, module) { // For preferences settings, to toggle this feature on/off const PREFERENCES_CSS_COLOR_PREVIEW = "colorPreview"; - let enabled = true; // by default:- on + const PREFERENCES_INLINE_COLOR_PREVIEW = "colorPreviewInline"; + let enabled = true, // by default:- on + inlinePreviewEnabled = false; PreferencesManager.definePreference(PREFERENCES_CSS_COLOR_PREVIEW, "boolean", enabled, { description: Strings.DESCRIPTION_CSS_COLOR_PREVIEW }); + PreferencesManager.definePreference(PREFERENCES_INLINE_COLOR_PREVIEW, "boolean", enabled, { + description: Strings.DESCRIPTION_CSS_COLOR_PREVIEW_INLINE + }); + /** * Gets all the colors that are to be displayed @@ -114,6 +121,16 @@ define(function (require, exports, module) { }, 50); } + function _colorMark(editor, from, to, color) { + editor.markText(COLOR_MARK_NAME, from, to, { + css: ` + --bg-color-mark: ${color}; + background: var(--bg-color-mark); + color: lch(from var(--bg-color-mark) calc((50 - l) * infinity) 0 0); + ` + }); + } + /** * To display the color marks on the gutter * @@ -143,13 +160,13 @@ define(function (require, exports, module) { // Single color preview $marker = $("") .addClass(SINGLE_COLOR_PREVIEW_CLASS) - .css('background-color', obj.colorValues[0]); + .css('background-color', obj.colorValues[0].color); editor.setGutterMarker(obj.lineNumber, GUTTER_NAME, $marker[0]); $marker.click((event)=>{ event.preventDefault(); event.stopPropagation(); - _colorIconClicked(editor, obj.lineNumber, obj.colorValues[0]); + _colorIconClicked(editor, obj.lineNumber, obj.colorValues[0].color); }); } else { // Multiple colors preview @@ -168,13 +185,13 @@ define(function (require, exports, module) { const $colorBox = $("
") .addClass("color-box") .css({ - 'background-color': color, + 'background-color': color.color, ...positions[index] }); $colorBox.click((event)=>{ event.preventDefault(); event.stopPropagation(); - _colorIconClicked(editor, obj.lineNumber, color); + _colorIconClicked(editor, obj.lineNumber, color.color); }); $marker.append($colorBox); } @@ -197,9 +214,36 @@ define(function (require, exports, module) { } } + function _applyInlineColor(editor, line) { + editor._currentlyColorMarkedLine = line; + editor.clearAllMarks(COLOR_MARK_NAME); + const colors = detectValidColorsInLine(editor, line); + for(let color of colors){ + _colorMark(editor, {line, ch: color.index}, {line, ch: color.index + color.color.length}, + color.color); + } + } + function _cursorActivity(_evt, editor){ // this is to prevent a gutter gap in the active line if there is no color on this line. - _addDummyGutterMarkerIfNotExist(editor, editor.getCursorPos().line); + const line = editor.getCursorPos().line; + if(enabled){ + _addDummyGutterMarkerIfNotExist(editor, line); + } + if(!inlinePreviewEnabled){ + return; + } + if(editor.hasSelection()){ + if(editor._currentlyColorMarkedLine === line){ + editor._currentlyColorMarkedLine = null; + editor.clearAllMarks(COLOR_MARK_NAME); + } + return; + } + if(editor._currentlyColorMarkedLine === line) { + return; + } + _applyInlineColor(editor, line); } /** @@ -320,8 +364,7 @@ define(function (require, exports, module) { }); } - // Return up to 4 colors - return validColors.slice(0, 4).map(item => item.color); + return validColors; } /** @@ -392,10 +435,14 @@ define(function (require, exports, module) { */ function onChanged(_evt, instance, changeList) { // for insertion and deletion, update the changed lines - if(!changeList || !changeList.length) { + if(!changeList || !changeList.length || !enabled) { return; } const changeObj = changeList[0]; + instance._currentlyColorMarkedLine = null; + if(inlinePreviewEnabled && changeObj.origin && changeObj.origin.startsWith("+InlineColorEditor")){ + _applyInlineColor(instance, instance.getCursorPos().line); + } if(changeList.length === 1 && changeObj.origin === '+input' || changeObj.origin === '+delete') { // we only do the diff updates on single key type input/delete and not bulk changes // somehow the performance degrades if we do the diff logic on large blocks. @@ -415,10 +462,29 @@ define(function (require, exports, module) { } } + function inlinePreferenceChanged() { + inlinePreviewEnabled = PreferencesManager.get(PREFERENCES_INLINE_COLOR_PREVIEW); + const allActiveEditors = MainViewManager.getAllViewedEditors(); + + allActiveEditors.forEach((activeEditor) => { + const currEditor = activeEditor.editor; + if(!currEditor){ + return; + } + if(!inlinePreviewEnabled) { + currEditor.clearAllMarks(COLOR_MARK_NAME); + } else { + _applyInlineColor(currEditor, currEditor.getCursorPos().line); + } + }); + } + // init after appReady AppInit.appReady(function () { PreferencesManager.on("change", PREFERENCES_CSS_COLOR_PREVIEW, preferenceChanged); + PreferencesManager.on("change", PREFERENCES_INLINE_COLOR_PREVIEW, inlinePreferenceChanged); preferenceChanged(); + inlinePreferenceChanged(); registerHandlers(); }); }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c9f8554f6..49ba9e744 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1251,5 +1251,6 @@ define({ // indent guides extension "DESCRIPTION_INDENT_GUIDES_ENABLED": "true to show indent guide lines, else false.", "DESCRIPTION_HIDE_FIRST": "true to show the first Indent Guide line else false.", - "DESCRIPTION_CSS_COLOR_PREVIEW": "true to display color previews in the gutter, else false." + "DESCRIPTION_CSS_COLOR_PREVIEW": "true to display color previews in the gutter, else false.", + "DESCRIPTION_CSS_COLOR_PREVIEW_INLINE": "When true, color codes in the editor will display a colored background behind them, making it easy to visualize colors while editing CSS, else false." });