From f262bbbe92d8843ccd66daa528f6111a2123dded Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Wed, 12 Aug 2020 18:45:36 +0100 Subject: [PATCH 1/8] Add initial color decorators --- package.json | 15 +++ src/extension.ts | 2 + src/lib/registerColorDecorator.ts | 125 +++++++++++++++++++++ src/lsp/providers/documentColorProvider.ts | 63 +++++++++++ src/lsp/server.ts | 3 + src/lsp/util/find.ts | 66 ++++++++++- src/lsp/util/resolveRange.ts | 18 +++ src/lsp/util/state.ts | 9 ++ 8 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/lib/registerColorDecorator.ts create mode 100644 src/lsp/providers/documentColorProvider.ts create mode 100644 src/lsp/util/resolveRange.ts diff --git a/package.json b/package.json index 0cdb709..13c2d39 100755 --- a/package.json +++ b/package.json @@ -71,6 +71,21 @@ "default": {}, "markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`" }, + "tailwindCSS.colorDecorators.enabled": { + "type": "boolean", + "default": true, + "scope": "language-overridable" + }, + "tailwindCSS.colorDecorators.classes": { + "type": "boolean", + "default": true, + "scope": "language-overridable" + }, + "tailwindCSS.colorDecorators.cssHelpers": { + "type": "boolean", + "default": true, + "scope": "language-overridable" + }, "tailwindCSS.validate": { "type": "boolean", "default": true, diff --git a/src/extension.ts b/src/extension.ts index 279ec96..fe982e9 100755 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import isObject from './util/isObject' import { dedupe, equal } from './util/array' import { createEmitter } from './lib/emitter' import { onMessage } from './lsp/notifications' +import { registerColorDecorator } from './lib/registerColorDecorator' const CLIENT_ID = 'tailwindcss-intellisense' const CLIENT_NAME = 'Tailwind CSS IntelliSense' @@ -152,6 +153,7 @@ export function activate(context: ExtensionContext) { client.onReady().then(() => { let emitter = createEmitter(client) registerConfigErrorHandler(emitter) + registerColorDecorator(client, context, emitter) onMessage(client, 'getConfiguration', async (scope) => { return Workspace.getConfiguration('tailwindCSS', scope) }) diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts new file mode 100644 index 0000000..cb01a7b --- /dev/null +++ b/src/lib/registerColorDecorator.ts @@ -0,0 +1,125 @@ +import { window, workspace, ExtensionContext, TextEditor } from 'vscode' +import { NotificationEmitter } from './emitter' +import { LanguageClient } from 'vscode-languageclient' + +const colorDecorationType = window.createTextEditorDecorationType({ + before: { + width: '0.8em', + height: '0.8em', + contentText: ' ', + border: '0.1em solid', + margin: '0.1em 0.2em 0', + }, + dark: { + before: { + borderColor: '#eeeeee', + }, + }, + light: { + before: { + borderColor: '#000000', + }, + }, +}) + +export function registerColorDecorator( + client: LanguageClient, + context: ExtensionContext, + emitter: NotificationEmitter +) { + let activeEditor = window.activeTextEditor + let timeout: NodeJS.Timer | undefined = undefined + + async function updateDecorations() { + return updateDecorationsInEditor(activeEditor) + } + + async function updateDecorationsInEditor(editor: TextEditor) { + if (!editor) return + if (editor.document.uri.scheme !== 'file') return + + let workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri) + if ( + !workspaceFolder || + workspaceFolder.uri.toString() !== + client.clientOptions.workspaceFolder.uri.toString() + ) { + return + } + + let settings = workspace.getConfiguration( + 'tailwindCSS.colorDecorators', + editor.document + ) + + if (settings.enabled !== true) { + editor.setDecorations(colorDecorationType, []) + return + } + + let { colors } = await emitter.emit('getDocumentColors', { + document: editor.document.uri.toString(), + classes: settings.classes, + cssHelpers: settings.cssHelpers, + }) + + editor.setDecorations( + colorDecorationType, + colors + .filter(({ color }) => color !== 'rgba(0, 0, 0, 0.01)') + .map(({ range, color }) => ({ + range, + renderOptions: { before: { backgroundColor: color } }, + })) + ) + } + + function triggerUpdateDecorations() { + if (timeout) { + clearTimeout(timeout) + timeout = undefined + } + timeout = setTimeout(updateDecorations, 500) + } + + if (activeEditor) { + triggerUpdateDecorations() + } + + window.onDidChangeActiveTextEditor( + (editor) => { + activeEditor = editor + if (editor) { + triggerUpdateDecorations() + } + }, + null, + context.subscriptions + ) + + workspace.onDidChangeTextDocument( + (event) => { + if (activeEditor && event.document === activeEditor.document) { + triggerUpdateDecorations() + } + }, + null, + context.subscriptions + ) + + workspace.onDidOpenTextDocument( + (document) => { + if (activeEditor && document === activeEditor.document) { + triggerUpdateDecorations() + } + }, + null, + context.subscriptions + ) + + workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('tailwindCSS.colorDecorators')) { + window.visibleTextEditors.forEach(updateDecorationsInEditor) + } + }) +} diff --git a/src/lsp/providers/documentColorProvider.ts b/src/lsp/providers/documentColorProvider.ts new file mode 100644 index 0000000..5c9e9e3 --- /dev/null +++ b/src/lsp/providers/documentColorProvider.ts @@ -0,0 +1,63 @@ +import { onMessage } from '../notifications' +import { State } from '../util/state' +import { + findClassListsInDocument, + getClassNamesInClassList, + findHelperFunctionsInDocument, +} from '../util/find' +import { getClassNameParts } from '../util/getClassNameAtPosition' +import { getColor, getColorFromValue } from '../util/color' +import { logFull } from '../util/logFull' +import { stringToPath } from '../util/stringToPath' +const dlv = require('dlv') + +export function registerDocumentColorProvider(state: State) { + onMessage( + state.editor.connection, + 'getDocumentColors', + async ({ document, classes, cssHelpers }) => { + let colors = [] + let doc = state.editor.documents.get(document) + if (!doc) return { colors } + + if (classes) { + let classLists = findClassListsInDocument(state, doc) + classLists.forEach((classList) => { + let classNames = getClassNamesInClassList(classList) + classNames.forEach((className) => { + let parts = getClassNameParts(state, className.className) + if (!parts) return + let color = getColor(state, parts) + if (!color) return + colors.push({ range: className.range, color: color.documentation }) + }) + }) + } + + if (cssHelpers) { + let helperFns = findHelperFunctionsInDocument(state, doc) + helperFns.forEach((fn) => { + let keys = stringToPath(fn.value) + let base = fn.helper === 'theme' ? ['theme'] : [] + let value = dlv(state.config, [...base, ...keys]) + let color = getColorFromValue(value) + if (color) { + // colors.push({ + // range: { + // start: { + // line: fn.valueRange.start.line, + // character: fn.valueRange.start.character + 1, + // }, + // end: fn.valueRange.end, + // }, + // color, + // }) + colors.push({ range: fn.valueRange, color }) + } + }) + } + + return { colors } + } + ) +} diff --git a/src/lsp/server.ts b/src/lsp/server.ts index 4b149f8..bd7cc22 100644 --- a/src/lsp/server.ts +++ b/src/lsp/server.ts @@ -35,6 +35,7 @@ import { } from './providers/diagnostics/diagnosticsProvider' import { createEmitter } from '../lib/emitter' import { provideCodeActions } from './providers/codeActions/codeActionProvider' +import { registerDocumentColorProvider } from './providers/documentColorProvider' let connection = createConnection(ProposedFeatures.all) let state: State = { enabled: false, emitter: createEmitter(connection) } @@ -195,6 +196,8 @@ connection.onInitialized && state.config, state.plugins, ]) + + registerDocumentColorProvider(state) }) connection.onDidChangeConfiguration((change) => { diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts index db5609e..8ff4ded 100644 --- a/src/lsp/util/find.ts +++ b/src/lsp/util/find.ts @@ -1,5 +1,10 @@ import { TextDocument, Range, Position } from 'vscode-languageserver' -import { DocumentClassName, DocumentClassList, State } from './state' +import { + DocumentClassName, + DocumentClassList, + State, + DocumentHelperFunction, +} from './state' import lineColumn from 'line-column' import { isCssContext, isCssDoc } from './css' import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html' @@ -11,6 +16,7 @@ import { getComputedClassAttributeLexer, } from './lexers' import { getLanguageBoundaries } from './getLanguageBoundaries' +import { resolveRange } from './resolveRange' export function findAll(re: RegExp, str: string): RegExpMatchArray[] { let match: RegExpMatchArray @@ -254,6 +260,64 @@ export function findClassListsInDocument( ]) } +export function findHelperFunctionsInDocument( + state: State, + doc: TextDocument +): DocumentHelperFunction[] { + if (isCssDoc(state, doc)) { + return findHelperFunctionsInRange(doc) + } + + let boundaries = getLanguageBoundaries(state, doc) + if (!boundaries) return [] + + return flatten( + boundaries.css.map((range) => findHelperFunctionsInRange(doc, range)) + ) +} + +export function findHelperFunctionsInRange( + doc: TextDocument, + range?: Range +): DocumentHelperFunction[] { + const text = doc.getText(range) + const matches = findAll( + /(?^|\s)(?theme|config)\((?:(?')([^']+)'|(?")([^"]+)")\)/gm, + text + ) + + return matches.map((match) => { + let value = match[4] || match[6] + let startIndex = match.index + match.groups.before.length + return { + full: match[0].substr(match.groups.before.length), + value, + helper: match.groups.helper === 'theme' ? 'theme' : 'config', + quotes: match.groups.single ? "'" : '"', + range: resolveRange( + { + start: indexToPosition(text, startIndex), + end: indexToPosition(text, match.index + match[0].length), + }, + range + ), + valueRange: resolveRange( + { + start: indexToPosition( + text, + startIndex + match.groups.helper.length + 1 + ), + end: indexToPosition( + text, + startIndex + match.groups.helper.length + 1 + 1 + value.length + 1 + ), + }, + range + ), + } + }) +} + export function indexToPosition(str: string, index: number): Position { const { line, col } = lineColumn(str + '\n', index) return { line: line - 1, character: col - 1 } diff --git a/src/lsp/util/resolveRange.ts b/src/lsp/util/resolveRange.ts new file mode 100644 index 0000000..96fe343 --- /dev/null +++ b/src/lsp/util/resolveRange.ts @@ -0,0 +1,18 @@ +import { Range } from 'vscode-languageserver' + +export function resolveRange(range: Range, relativeTo?: Range) { + return { + start: { + line: (relativeTo?.start.line || 0) + range.start.line, + character: + (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) + + range.start.character, + }, + end: { + line: (relativeTo?.start.line || 0) + range.end.line, + character: + (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) + + range.end.character, + }, + } +} diff --git a/src/lsp/util/state.ts b/src/lsp/util/state.ts index 09a0200..77afdfa 100644 --- a/src/lsp/util/state.ts +++ b/src/lsp/util/state.ts @@ -74,6 +74,15 @@ export type DocumentClassName = { classList: DocumentClassList } +export type DocumentHelperFunction = { + full: string + helper: 'theme' | 'config' + value: string + quotes: '"' | "'" + range: Range + valueRange: Range +} + export type ClassNameMeta = { source: 'base' | 'components' | 'utilities' pseudo: string[] From 85ba6a42cb3f01cd52bbd85a17697c23edbd0acf Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 14 Aug 2020 16:21:49 +0100 Subject: [PATCH 2/8] Update settings --- package.json | 27 ++++----- src/lib/registerColorDecorator.ts | 16 +++--- src/lsp/providers/documentColorProvider.ts | 67 ++++++++++------------ 3 files changed, 54 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 13c2d39..af3094d 100755 --- a/package.json +++ b/package.json @@ -71,19 +71,20 @@ "default": {}, "markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`" }, - "tailwindCSS.colorDecorators.enabled": { - "type": "boolean", - "default": true, - "scope": "language-overridable" - }, - "tailwindCSS.colorDecorators.classes": { - "type": "boolean", - "default": true, - "scope": "language-overridable" - }, - "tailwindCSS.colorDecorators.cssHelpers": { - "type": "boolean", - "default": true, + "tailwindCSS.colorDecorators": { + "type": "string", + "enum": [ + "inherit", + "on", + "off" + ], + "markdownEnumDescriptions": [ + "Color decorators are rendered if `editor.colorDecorators` is `true`.", + "Color decorators are rendered.", + "Color decorators are not rendered." + ], + "default": "inherit", + "markdownDescription": "Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions.", "scope": "language-overridable" }, "tailwindCSS.validate": { diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts index cb01a7b..5d3aae4 100644 --- a/src/lib/registerColorDecorator.ts +++ b/src/lib/registerColorDecorator.ts @@ -47,20 +47,22 @@ export function registerColorDecorator( return } - let settings = workspace.getConfiguration( - 'tailwindCSS.colorDecorators', - editor.document - ) + let preference = + workspace.getConfiguration('tailwindCSS', editor.document) + .colorDecorators || 'inherit' - if (settings.enabled !== true) { + let enabled = + preference === 'inherit' + ? workspace.getConfiguration('editor').colorDecorators + : preference === 'on' + + if (enabled !== true) { editor.setDecorations(colorDecorationType, []) return } let { colors } = await emitter.emit('getDocumentColors', { document: editor.document.uri.toString(), - classes: settings.classes, - cssHelpers: settings.cssHelpers, }) editor.setDecorations( diff --git a/src/lsp/providers/documentColorProvider.ts b/src/lsp/providers/documentColorProvider.ts index 5c9e9e3..d9ea6a6 100644 --- a/src/lsp/providers/documentColorProvider.ts +++ b/src/lsp/providers/documentColorProvider.ts @@ -7,7 +7,6 @@ import { } from '../util/find' import { getClassNameParts } from '../util/getClassNameAtPosition' import { getColor, getColorFromValue } from '../util/color' -import { logFull } from '../util/logFull' import { stringToPath } from '../util/stringToPath' const dlv = require('dlv') @@ -15,47 +14,43 @@ export function registerDocumentColorProvider(state: State) { onMessage( state.editor.connection, 'getDocumentColors', - async ({ document, classes, cssHelpers }) => { + async ({ document }) => { let colors = [] let doc = state.editor.documents.get(document) if (!doc) return { colors } - if (classes) { - let classLists = findClassListsInDocument(state, doc) - classLists.forEach((classList) => { - let classNames = getClassNamesInClassList(classList) - classNames.forEach((className) => { - let parts = getClassNameParts(state, className.className) - if (!parts) return - let color = getColor(state, parts) - if (!color) return - colors.push({ range: className.range, color: color.documentation }) - }) + let classLists = findClassListsInDocument(state, doc) + classLists.forEach((classList) => { + let classNames = getClassNamesInClassList(classList) + classNames.forEach((className) => { + let parts = getClassNameParts(state, className.className) + if (!parts) return + let color = getColor(state, parts) + if (!color) return + colors.push({ range: className.range, color: color.documentation }) }) - } + }) - if (cssHelpers) { - let helperFns = findHelperFunctionsInDocument(state, doc) - helperFns.forEach((fn) => { - let keys = stringToPath(fn.value) - let base = fn.helper === 'theme' ? ['theme'] : [] - let value = dlv(state.config, [...base, ...keys]) - let color = getColorFromValue(value) - if (color) { - // colors.push({ - // range: { - // start: { - // line: fn.valueRange.start.line, - // character: fn.valueRange.start.character + 1, - // }, - // end: fn.valueRange.end, - // }, - // color, - // }) - colors.push({ range: fn.valueRange, color }) - } - }) - } + let helperFns = findHelperFunctionsInDocument(state, doc) + helperFns.forEach((fn) => { + let keys = stringToPath(fn.value) + let base = fn.helper === 'theme' ? ['theme'] : [] + let value = dlv(state.config, [...base, ...keys]) + let color = getColorFromValue(value) + if (color) { + // colors.push({ + // range: { + // start: { + // line: fn.valueRange.start.line, + // character: fn.valueRange.start.character + 1, + // }, + // end: fn.valueRange.end, + // }, + // color, + // }) + colors.push({ range: fn.valueRange, color }) + } + }) return { colors } } From e3c3ead8eada53bbb3ce9079fb66fd3a9e923cd9 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 14 Aug 2020 16:27:38 +0100 Subject: [PATCH 3/8] Settings tweak --- src/lib/registerColorDecorator.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts index 5d3aae4..7ae0d7d 100644 --- a/src/lib/registerColorDecorator.ts +++ b/src/lib/registerColorDecorator.ts @@ -51,12 +51,12 @@ export function registerColorDecorator( workspace.getConfiguration('tailwindCSS', editor.document) .colorDecorators || 'inherit' - let enabled = + let enabled: boolean = preference === 'inherit' - ? workspace.getConfiguration('editor').colorDecorators + ? Boolean(workspace.getConfiguration('editor').colorDecorators) : preference === 'on' - if (enabled !== true) { + if (!enabled) { editor.setDecorations(colorDecorationType, []) return } @@ -120,7 +120,10 @@ export function registerColorDecorator( ) workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('tailwindCSS.colorDecorators')) { + if ( + e.affectsConfiguration('editor.colorDecorators') || + e.affectsConfiguration('tailwindCSS.colorDecorators') + ) { window.visibleTextEditors.forEach(updateDecorationsInEditor) } }) From d63a97ab155b21fc4ada9646893d413f85763e4a Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Mon, 17 Aug 2020 14:20:31 +0100 Subject: [PATCH 4/8] Fix color decorators not updating when config changes or errors --- src/lib/registerColorDecorator.ts | 8 +++++++ src/lsp/providers/documentColorProvider.ts | 1 + src/lsp/server.ts | 26 +++++----------------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts index 7ae0d7d..b365b8f 100644 --- a/src/lib/registerColorDecorator.ts +++ b/src/lib/registerColorDecorator.ts @@ -127,4 +127,12 @@ export function registerColorDecorator( window.visibleTextEditors.forEach(updateDecorationsInEditor) } }) + + emitter.on('configUpdated', () => { + window.visibleTextEditors.forEach(updateDecorationsInEditor) + }) + + emitter.on('configError', () => { + window.visibleTextEditors.forEach(updateDecorationsInEditor) + }) } diff --git a/src/lsp/providers/documentColorProvider.ts b/src/lsp/providers/documentColorProvider.ts index d9ea6a6..95321aa 100644 --- a/src/lsp/providers/documentColorProvider.ts +++ b/src/lsp/providers/documentColorProvider.ts @@ -16,6 +16,7 @@ export function registerDocumentColorProvider(state: State) { 'getDocumentColors', async ({ document }) => { let colors = [] + if (!state.enabled) return { colors } let doc = state.editor.documents.get(document) if (!doc) return { colors } diff --git a/src/lsp/server.ts b/src/lsp/server.ts index bd7cc22..c4cb2d1 100644 --- a/src/lsp/server.ts +++ b/src/lsp/server.ts @@ -38,7 +38,7 @@ import { provideCodeActions } from './providers/codeActions/codeActionProvider' import { registerDocumentColorProvider } from './providers/documentColorProvider' let connection = createConnection(ProposedFeatures.all) -let state: State = { enabled: false, emitter: createEmitter(connection) } +const state: State = { enabled: false, emitter: createEmitter(connection) } let documents = new TextDocuments() let workspaceFolder: string | null @@ -74,7 +74,7 @@ connection.onInitialize( async (params: InitializeParams): Promise => { const capabilities = params.capabilities - const editorState: EditorState = { + state.editor = { connection, documents, documentSettings, @@ -100,12 +100,7 @@ connection.onInitialize( // @ts-ignore onChange: (newState: State): void => { if (newState && !newState.error) { - state = { - ...newState, - enabled: true, - emitter: state.emitter, - editor: editorState, - } + Object.assign(state, newState, { enabled: true }) connection.sendNotification('tailwindcss/configUpdated', [ state.configPath, state.config, @@ -113,11 +108,7 @@ connection.onInitialize( ]) updateAllDiagnostics(state) } else { - state = { - enabled: false, - emitter: state.emitter, - editor: editorState, - } + state.enabled = false if (newState && newState.error) { const payload: { message: string @@ -141,14 +132,9 @@ connection.onInitialize( ) if (tailwindState) { - state = { - enabled: true, - emitter: state.emitter, - editor: editorState, - ...tailwindState, - } + Object.assign(state, tailwindState, { enabled: true }) } else { - state = { enabled: false, emitter: state.emitter, editor: editorState } + state.enabled = false } return { From 9994965ef694de1f4cceccfbeb1034c325155613 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Tue, 18 Aug 2020 15:50:16 +0100 Subject: [PATCH 5/8] Update setting description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af3094d..f7d84e0 100755 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "off" ], "markdownEnumDescriptions": [ - "Color decorators are rendered if `editor.colorDecorators` is `true`.", + "Color decorators are rendered if `editor.colorDecorators` is enabled.", "Color decorators are rendered.", "Color decorators are not rendered." ], From 5b7f050df039a4385046b48b3ebb15295b33b998 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 21 Aug 2020 11:08:32 +0100 Subject: [PATCH 6/8] Remove unused code --- src/lsp/providers/documentColorProvider.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lsp/providers/documentColorProvider.ts b/src/lsp/providers/documentColorProvider.ts index 95321aa..688ee74 100644 --- a/src/lsp/providers/documentColorProvider.ts +++ b/src/lsp/providers/documentColorProvider.ts @@ -39,16 +39,6 @@ export function registerDocumentColorProvider(state: State) { let value = dlv(state.config, [...base, ...keys]) let color = getColorFromValue(value) if (color) { - // colors.push({ - // range: { - // start: { - // line: fn.valueRange.start.line, - // character: fn.valueRange.start.character + 1, - // }, - // end: fn.valueRange.end, - // }, - // color, - // }) colors.push({ range: fn.valueRange, color }) } }) From 8b03e8b9849f1d7de41e72d118ce72be905e41b0 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 21 Aug 2020 13:35:23 +0100 Subject: [PATCH 7/8] Update color extraction --- src/lsp/util/color.ts | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/lsp/util/color.ts b/src/lsp/util/color.ts index 95d5417..31d56ec 100644 --- a/src/lsp/util/color.ts +++ b/src/lsp/util/color.ts @@ -48,17 +48,43 @@ export function getColor( ) // check that all of the values are valid colors - if (colors.some((color) => !color.isValid)) { + if (colors.some((color) => color !== 'transparent' && !color.isValid)) { return null } - // check that all of the values are the same color - const colorStrings = colors.map((color) => color.toRgbString()) - if (dedupe(colorStrings).length !== 1) { + // check that all of the values are the same color, ignoring alpha + const colorStrings = dedupe( + colors.map((color) => + color === 'transparent' + ? 'transparent' + : `${color.r}-${color.g}-${color.b}` + ) + ) + if (colorStrings.length !== 1) { return null } - return { documentation: colorStrings[0] } + if (colorStrings[0] === 'transparent') { + return { + documentation: 'rgba(0, 0, 0, 0.01)', + } + } + + const nonTransparentColors = colors.filter( + (color): color is TinyColor => color !== 'transparent' + ) + + const alphas = dedupe(nonTransparentColors.map((color) => color.a)) + + if (alphas.length === 1 || (alphas.length === 2 && alphas.includes(0))) { + return { + documentation: nonTransparentColors + .find((color) => color.a !== 0) + .toRgbString(), + } + } + + return null } export function getColorFromValue(value: unknown): string { @@ -73,9 +99,9 @@ export function getColorFromValue(value: unknown): string { return null } -function createColor(str: string): TinyColor { +function createColor(str: string): TinyColor | 'transparent' { if (str === 'transparent') { - return new TinyColor({ r: 0, g: 0, b: 0, a: 0.01 }) + return 'transparent' } // matches: rgba(, , , var(--bg-opacity)) From d95533428f2ec19c98c979bb92f49ff082391cd5 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 21 Aug 2020 13:44:24 +0100 Subject: [PATCH 8/8] Update color decorator debounce --- package-lock.json | 12 ++++++++++++ package.json | 2 ++ src/lib/registerColorDecorator.ts | 10 ++-------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3617ee8..f79d333 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1019,6 +1019,12 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==", + "dev": true + }, "@types/graceful-fs": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", @@ -1960,6 +1966,12 @@ "integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==", "dev": true }, + "debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", diff --git a/package.json b/package.json index f7d84e0..7815ce1 100755 --- a/package.json +++ b/package.json @@ -173,6 +173,7 @@ }, "devDependencies": { "@ctrl/tinycolor": "^3.1.0", + "@types/debounce": "^1.2.0", "@types/mocha": "^5.2.0", "@types/moo": "^0.5.3", "@types/node": "^13.9.3", @@ -182,6 +183,7 @@ "chokidar": "^3.3.1", "concurrently": "^5.1.0", "css.escape": "^1.5.1", + "debounce": "^1.2.0", "detect-indent": "^6.0.0", "dlv": "^1.1.3", "dset": "^2.0.1", diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts index b365b8f..66d6767 100644 --- a/src/lib/registerColorDecorator.ts +++ b/src/lib/registerColorDecorator.ts @@ -1,6 +1,7 @@ import { window, workspace, ExtensionContext, TextEditor } from 'vscode' import { NotificationEmitter } from './emitter' import { LanguageClient } from 'vscode-languageclient' +import debounce from 'debounce' const colorDecorationType = window.createTextEditorDecorationType({ before: { @@ -28,7 +29,6 @@ export function registerColorDecorator( emitter: NotificationEmitter ) { let activeEditor = window.activeTextEditor - let timeout: NodeJS.Timer | undefined = undefined async function updateDecorations() { return updateDecorationsInEditor(activeEditor) @@ -76,13 +76,7 @@ export function registerColorDecorator( ) } - function triggerUpdateDecorations() { - if (timeout) { - clearTimeout(timeout) - timeout = undefined - } - timeout = setTimeout(updateDecorations, 500) - } + const triggerUpdateDecorations = debounce(updateDecorations, 200) if (activeEditor) { triggerUpdateDecorations()