├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build
├── web-extension.webpack.config.js
└── webpack.config.js
├── media
├── excluded.png
├── fields.png
├── logo.png
├── logo.svg
└── starter-example.png
├── package-lock.json
├── package.json
├── src
├── color_extractor.ts
├── color_info_display.ts
├── configuration.ts
├── displays
│ ├── alpha_display.ts
│ ├── cmyk_display.ts
│ ├── display_helper.ts
│ ├── hex_display.ts
│ ├── hsl_display.ts
│ ├── hsv_display.ts
│ ├── lab_display.ts
│ ├── lch_display.ts
│ ├── name_display.ts
│ ├── preview_display.ts
│ └── rgb_display.ts
├── extension.ts
└── hover_provider.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.js]
4 | indent_style = space
5 | indent_size = 4
6 |
7 | [*.json]
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | /**@type {import('eslint').Linter.Config} */
4 | // eslint-disable-next-line no-undef
5 | module.exports = {
6 | root: true,
7 | parser: '@typescript-eslint/parser',
8 | plugins: [
9 | '@typescript-eslint',
10 | ],
11 | extends: [
12 | 'eslint:recommended',
13 | 'plugin:@typescript-eslint/recommended',
14 | ],
15 | rules: {
16 | 'semi': [2, "always"],
17 | '@typescript-eslint/no-unused-vars': 0,
18 | '@typescript-eslint/no-explicit-any': 0,
19 | '@typescript-eslint/explicit-module-boundary-types': 0,
20 | '@typescript-eslint/no-non-null-assertion': 0,
21 | }
22 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS Files #
2 | ############
3 | .DS_Store*
4 | ehthumbs.db
5 | Icon?
6 | Thumbs.db
7 |
8 | out
9 | node_modules
10 | example
11 | *.vsix
12 | dist
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | script: tsc -p ./
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.1.0",
4 | "configurations": [
5 | {
6 | "name": "Launch Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
11 | "stopOnEntry": false,
12 | "sourceMaps": true,
13 | "outDir": "${workspaceRoot}/out/src",
14 | "preLaunchTask": "npm"
15 | },
16 | {
17 | "name": "Launch Tests",
18 | "type": "extensionHost",
19 | "request": "launch",
20 | "runtimeExecutable": "${execPath}",
21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
22 | "stopOnEntry": false,
23 | "sourceMaps": true,
24 | "outDir": "${workspaceRoot}/out/test",
25 | "preLaunchTask": "npm"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
10 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // Available variables which can be used inside of strings.
2 | // ${workspaceRoot}: the root folder of the team
3 | // ${file}: the current opened file
4 | // ${fileBasename}: the current opened file's basename
5 | // ${fileDirname}: the current opened file's dirname
6 | // ${fileExtname}: the current opened file's extension
7 | // ${cwd}: the current working directory of the spawned process
8 |
9 | // A task runner that calls a custom npm script that compiles the extension.
10 | {
11 | "version": "2.0.0",
12 |
13 | // we want to run npm
14 | "command": "npm",
15 |
16 | // we run the custom script "compile" as defined in package.json
17 | "args": ["run", "compile", "--loglevel", "silent"],
18 |
19 | // The tsc compiler is started in watching mode
20 | "isWatching": true,
21 |
22 | // use the standard tsc in watch mode problem matcher to find compile problems in the output.
23 | "problemMatcher": "$tsc-watch",
24 | "tasks": [
25 | {
26 | "label": "npm",
27 | "type": "shell",
28 | "command": "npm",
29 | "args": [
30 | "run",
31 | "compile",
32 | "--loglevel",
33 | "silent"
34 | ],
35 | "isBackground": true,
36 | "problemMatcher": "$tsc-watch",
37 | "group": "build"
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | build/**
4 | src/**
5 | test/**
6 | node_modules
7 | **/*.map
8 | .gitignore
9 | tsconfig.json
10 | **/*.vsix
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 0.7.2 - January 18, 2023
4 | * Optimize image files to further reduce download and install size.
5 |
6 | ## 0.7.1 - January 18, 2023
7 | * Reduce install size.
8 | * Switch to `onStartupFinished` to defer extension activation.
9 |
10 | ## 0.7.0 - September 8, 2021
11 | * Webpack extension to improve performance and reduce install size.
12 | * Add support for running in browsers.
13 |
14 | ## 0.6.1 - July 5, 2021
15 | * Update extension kind to support workspace if requested. Thanks @bamurtaugh!
16 |
17 | ## 0.6.0 - April 26, 2021
18 | * Add LCH color display. Thanks @mgcrea!
19 |
20 | ## 0.5.1 - April 24, 2019
21 | * Mark explicit extension kind for VS Code compatibility.
22 |
23 | ## 0.5.0 - June 13, 2017
24 | * Add support for hex colors with alpha: `#f0f7` and `#ff00ff77`
25 |
26 | ## 0.4.1 - April 17, 2017
27 | * Fix hex not detecting in strings such as `"#f0f"`
28 |
29 | ## 0.4.0 - April 3, 2017
30 | * Provide color info in scss by default. Thanks @smlombardi!
31 |
32 | ## 0.3.0 - March 16, 2017
33 | * Added `css-color-name` display field to show color name.
34 |
35 | ## 0.2.1 - December 30, 2016
36 | * Fixed some false positive color name matches.
37 |
38 | ## 0.2.0 - December 14, 2016
39 | * Added `colorInfo.languages` setting to allow using the extension for any file type and language.
40 | * Fixed a few false positive matches.
41 |
42 | ## 0.1.1 – Nov 28, 2016
43 | * Fix alpha in preview display not showing.
44 |
45 | ## 0.1.0 – Nov 28, 2016
46 | * Added color preview displays.
47 | * Made settings use enum values for better intellisense.
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (C) 2016 Matt Bierner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VSCode Color Info
2 |
3 | [Visual Studio Code](https://code.visualstudio.com) extension that provides quick information css colors.
4 |
5 | 
6 |
7 |
8 | ## About
9 | *Color Info* adds additional information when you hover over a css color, including:
10 |
11 | * `rgb` – RGB color values. Enabled by default
12 | * `hsl` – HSL color values. Enabled by default
13 | * `css-color-name` - Name of the color (`red`, `blue`, ...).
14 | * `hsv` – HSV color values
15 | * `lab` – LAB color values
16 | * `lch` – LCHab color values
17 | * `cmyk` – CMYK color values. Enabled by default
18 | * `hex` – Hex value. Enabled by default
19 | * `alpha` – Alpha value. Enabled by default
20 | * `preview` – Preview of the color. Displays color with alpha in lower left corner and color without alpha in upper right corner. Enabled by default
21 | * `preview-xl` – Larger preview of the color
22 | * `preview-square` – Square preview of the color
23 | * `preview-square-xl` – Larger square preview of the color
24 |
25 | You can customize which of these fields are displayed and the order they are displayed in using the settings described below.
26 |
27 | By default, the extension works with any css, sass, scss, or less document and recognizes all basic css color formats, including named colors. You can also enable Color Info in additional file types using the `colorInfo.languages` setting
28 |
29 |
30 | ## Configuration
31 |
32 | #### `colorInfo.fields`
33 | Ordered array of color fields to display.
34 |
35 | 
36 |
37 |
38 | #### `colorInfo.excludedFields`
39 | Array of fields not to display. Overrides `colorInfo.fields`. If you only need to exclude one or two of the default fields, using `colorInfo.excludedFields` is the preferred approach.
40 |
41 | 
42 |
43 |
44 | #### `colorInfo.languages`
45 | Defines which files and languages have color info enabled, and which color types are supported. The default language setting enables all css color values for `css`, `sass`, and `less` documents:
46 |
47 | ```json
48 | "colorInfo.languages": [
49 | {
50 | "selector": "css",
51 | "colors": "css"
52 | }, {
53 | "selector": "sass",
54 | "colors": "css"
55 | }, {
56 | "selector": "scss",
57 | "colors": "css"
58 | }, {
59 | "selector": "less",
60 | "colors": "css"
61 | }
62 | ]
63 | ```
64 |
65 | Each element consists of:
66 |
67 | * `"selector"` - [VSCode document selector](https://code.visualstudio.com/Docs/extensionAPI/vscode-api#DocumentSelector).
68 | * `"colors"` - String or array of strings defining the types of colors to display information for. Valid values color types are:
69 |
70 | * `css` - All css color value types
71 | * `hex` - Css hex (`#ff00ff` or `#f0f`)
72 | * `hex+alpha` - Css hex plus alpha (`#ff00ff77` or `#f0f7`)
73 | * `rgb` - Css rgb or rgba (`rgb(1, 2, 3)` or `rgba(1, 2, 3, 0.5)`) c
74 | * `hsl` - Css hsl or hsla (`hsl(1, 2, 3)` or `hsla(1, 2, 3, 0.5)`)
75 | * `css-colors-names` - Css color names (`red`, `blue`)
76 |
77 | VSCode does not currently support nested languages, so to enable Color Info in an `html` file, you must add:
78 |
79 | ```json
80 | {
81 | "selector": "html",
82 | "colors": "css"
83 | }
84 | ```
85 |
86 | To your `colorInfo.languages` setting. Make you add this setting to the end of the default `colorInfo.languages` setting so that you do not disable Color Info for the standard languages.
87 |
--------------------------------------------------------------------------------
/build/web-extension.webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = /** @type WebpackConfig */ {
5 | context: path.dirname(__dirname),
6 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
7 | target: 'webworker', // extensions run in a webworker context
8 | entry: {
9 | 'extension': './src/extension.ts', // source of the web extension main file
10 | },
11 | output: {
12 | filename: '[name].js',
13 | path: path.join(__dirname, '../dist/web'),
14 | libraryTarget: 'commonjs'
15 | },
16 | resolve: {
17 | mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
18 | extensions: ['.ts', '.js'], // support ts-files and js-files
19 | },
20 | module: {
21 | rules: [{
22 | test: /\.ts$/,
23 | exclude: /node_modules/,
24 | use: [{
25 | loader: 'ts-loader'
26 | }]
27 | }]
28 | },
29 | externals: {
30 | 'vscode': 'commonjs vscode', // ignored because it doesn't exist
31 | },
32 | performance: {
33 | hints: false
34 | },
35 | devtool: 'nosources-source-map' // create a source map that points to the original source file
36 | };
--------------------------------------------------------------------------------
/build/webpack.config.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | //@ts-check
7 |
8 | 'use strict';
9 |
10 | const path = require('path');
11 |
12 | /**@type {import('webpack').Configuration}*/
13 | const config = {
14 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
15 |
16 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
17 | output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
18 | path: path.resolve(__dirname, '..', 'dist'),
19 | filename: 'extension.js',
20 | libraryTarget: "commonjs2",
21 | devtoolModuleFilenameTemplate: "../[resource-path]",
22 | },
23 | devtool: 'source-map',
24 | externals: {
25 | vscode: "commonjs vscode" // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
26 | },
27 | resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
28 | extensions: ['.ts', '.js']
29 | },
30 | module: {
31 | rules: [{
32 | test: /\.ts$/,
33 | exclude: /node_modules/,
34 | use: [{
35 | loader: 'ts-loader',
36 | options: {
37 | compilerOptions: {
38 | "module": "es6" // override `tsconfig.json` so that TypeScript emits native JavaScript modules.
39 | }
40 | }
41 | }]
42 | }]
43 | },
44 | }
45 |
46 | module.exports = config;
--------------------------------------------------------------------------------
/media/excluded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattbierner/vscode-color-info/cf3b476920ec9bf3bee061c4901f352010afcc89/media/excluded.png
--------------------------------------------------------------------------------
/media/fields.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattbierner/vscode-color-info/cf3b476920ec9bf3bee061c4901f352010afcc89/media/fields.png
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattbierner/vscode-color-info/cf3b476920ec9bf3bee061c4901f352010afcc89/media/logo.png
--------------------------------------------------------------------------------
/media/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/media/starter-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattbierner/vscode-color-info/cf3b476920ec9bf3bee061c4901f352010afcc89/media/starter-example.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "color-info",
3 | "displayName": "Color Info",
4 | "description": "Provides quick information about css colors",
5 | "version": "0.7.2",
6 | "publisher": "bierner",
7 | "license": "MIT",
8 | "extensionKind": [
9 | "ui",
10 | "workspace"
11 | ],
12 | "keywords": [
13 | "color",
14 | "rgb",
15 | "css",
16 | "hsl",
17 | "cmyk"
18 | ],
19 | "icon": "media/logo.png",
20 | "galleryBanner": {
21 | "color": "#fefefe",
22 | "theme": "light"
23 | },
24 | "engines": {
25 | "vscode": "^1.60.0"
26 | },
27 | "categories": [
28 | "Other"
29 | ],
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/mattbierner/vscode-color-info.git"
33 | },
34 | "activationEvents": [
35 | "onStartupFinished",
36 | "onLanguage:css",
37 | "onLanguage:less",
38 | "onLanguage:sass"
39 | ],
40 | "main": "./dist/extension.js",
41 | "browser": "./dist/web/extension.js",
42 | "contributes": {
43 | "configuration": {
44 | "type": "object",
45 | "title": "Color-Info configuration",
46 | "properties": {
47 | "colorInfo.fields": {
48 | "type": "array",
49 | "items": {
50 | "type": "string",
51 | "enum": [
52 | "alpha",
53 | "cmyk",
54 | "css-color-name",
55 | "hex",
56 | "hsl",
57 | "hsv",
58 | "lab",
59 | "lch",
60 | "preview-square-xl",
61 | "preview-square",
62 | "preview-xl",
63 | "preview",
64 | "rgb"
65 | ],
66 | "description": "Color field id."
67 | },
68 | "default": null,
69 | "description": "Ordered list of color fields to display."
70 | },
71 | "colorInfo.excludedFields": {
72 | "type": "array",
73 | "items": {
74 | "type": "string",
75 | "enum": [
76 | "alpha",
77 | "cmyk",
78 | "css-color-name",
79 | "hex",
80 | "hsl",
81 | "hsv",
82 | "lab",
83 | "lch",
84 | "preview-square-xl",
85 | "preview-square",
86 | "preview-xl",
87 | "preview",
88 | "rgb"
89 | ],
90 | "description": "Color field id"
91 | },
92 | "default": null,
93 | "markdownDescription": "Fields not to display. Overrides `#colorInfo.fields#`."
94 | },
95 | "colorInfo.languages": {
96 | "type": "array",
97 | "default": [
98 | {
99 | "selector": "css",
100 | "colors": "css"
101 | },
102 | {
103 | "selector": "sass",
104 | "colors": "css"
105 | },
106 | {
107 | "selector": "scss",
108 | "colors": "css"
109 | },
110 | {
111 | "selector": "less",
112 | "colors": "css"
113 | }
114 | ],
115 | "items": {
116 | "type": "object",
117 | "properties": {
118 | "selector": {
119 | "type": [
120 | "string",
121 | "object"
122 | ],
123 | "markdownDescription": "Document selector for language or file. See https://code.visualstudio.com/Docs/extensionAPI/vscode-api#DocumentSelector"
124 | },
125 | "colors": {
126 | "type": [
127 | "array",
128 | "string"
129 | ],
130 | "default": "css",
131 | "description": "Types of colors to detect",
132 | "items": {
133 | "enum": [
134 | "css",
135 | "hex",
136 | "hex+alpha",
137 | "rgb",
138 | "hsl",
139 | "css-color-names"
140 | ]
141 | }
142 | }
143 | }
144 | }
145 | }
146 | }
147 | }
148 | },
149 | "scripts": {
150 | "compile": "webpack --config ./build/webpack.config.js",
151 | "watch": "webpack --watch --config ./build/webpack.config.js",
152 | "package": "webpack --mode production --config ./build/webpack.config.js",
153 | "lint": "eslint -c .eslintrc.js --ext .ts src",
154 | "vscode:prepublish": "npm run package ; npm run package-web",
155 | "watch-web": "webpack --watch --config ./build/web-extension.webpack.config.js",
156 | "package-web": "webpack --mode production --devtool hidden-source-map --config ./build/web-extension.webpack.config.js"
157 | },
158 | "devDependencies": {
159 | "@types/base-64": "^1.0.0",
160 | "@types/color-convert": "^2.0.0",
161 | "@types/tinycolor2": "^1.4.3",
162 | "@types/utf8": "^3.0.0",
163 | "@types/vscode": "^1.60.0",
164 | "@typescript-eslint/eslint-plugin": "^4.31.0",
165 | "@typescript-eslint/parser": "^4.31.0",
166 | "eslint": "^7.32.0",
167 | "ts-loader": "^9.2.5",
168 | "typescript": "^4.9.4",
169 | "webpack": "^5.52.0",
170 | "webpack-cli": "^4.8.0"
171 | },
172 | "dependencies": {
173 | "base-64": "^1.0.0",
174 | "color-convert": "^2.0.1",
175 | "tinycolor2": "^1.4.1",
176 | "utf8": "^3.0.0"
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/color_extractor.ts:
--------------------------------------------------------------------------------
1 | import * as tinycolor from 'tinycolor2';
2 | import * as vscode from 'vscode';
3 |
4 | /**
5 | * A found color value in some text
6 | */
7 | export interface ColorMatch {
8 | value: string;
9 | color: tinycolor.Instance;
10 | span: vscode.Range;
11 | }
12 |
13 | export type ColorValueExtractorType = 'hex' | 'hex+alpha' | 'rgb' | 'hsl' | 'css-color-names';
14 |
15 | /**
16 | * Extracts colors from lines of text.
17 | */
18 | interface ColorValueExtractor {
19 | type: ColorValueExtractorType;
20 | getColors(input: string, position: vscode.Position): ColorMatch[];
21 | }
22 |
23 | /**
24 | * Extract all matches for a given regular expression on a line
25 | */
26 | const getRegExForLine = (re: RegExp, line: string, lineNumber: number): ColorMatch[] => {
27 | const matches: ColorMatch[] = [];
28 | let match: RegExpExecArray | null;
29 | while ((match = re.exec(line))) {
30 | const color = tinycolor(match[1]);
31 | if (color && color.isValid()) {
32 | const span = new vscode.Range(
33 | new vscode.Position(lineNumber, match.index),
34 | new vscode.Position(lineNumber, match.index + match[0].length));
35 |
36 | matches.push({
37 | value: match[0],
38 | color,
39 | span,
40 | });
41 | }
42 | }
43 | return matches;
44 | };
45 |
46 | /**
47 | * Get all `rgb(...)` colors in a line of text.
48 | */
49 | const rgbExtractor: ColorValueExtractor = {
50 | type: 'rgb',
51 | getColors(line: string, position: vscode.Position) {
52 | return getRegExForLine(/(?:\b|^)(rgba?\(.+?\))/g, line, position.line);
53 | },
54 | };
55 |
56 | /**
57 | * Get all `hsl(...)` colors in a line of text.
58 | */
59 | const hslExtractor: ColorValueExtractor = {
60 | type: 'hsl',
61 | getColors(line: string, position: vscode.Position) {
62 | return getRegExForLine(/(?:\b|^)(hsla?\(.+?\))/g, line, position.line);
63 | },
64 | };
65 |
66 | /**
67 | * Get all hex colors in a line of text.
68 | */
69 | const hexExtractor: ColorValueExtractor = {
70 | type: 'hex',
71 | getColors(line: string, position: vscode.Position) {
72 | return getRegExForLine(/(?:^|\s|\W)(#(?:[0-9a-fA-F]{3}){1,2})(\b|$)/g, line, position.line);
73 | },
74 | };
75 |
76 | /**
77 | * Get all hex colors with alpha in a line of text.
78 | */
79 | const hexaExtractor: ColorValueExtractor = {
80 | type: 'hex+alpha',
81 | getColors(line: string, position: vscode.Position) {
82 | return getRegExForLine(/(?:^|\s|\W)(#(?:[0-9a-fA-F]{4}){1,2})(\b|$)/g, line, position.line);
83 | },
84 | };
85 |
86 | /**
87 | * Extracts named css colors
88 | */
89 | const cssNameExtractor: ColorValueExtractor = {
90 | type: 'css-color-names',
91 | getColors(line: string, position: vscode.Position) {
92 | const colorNameCharacter = /[a-z]/i;
93 | let right = '';
94 | for (let i = position.character; i < line.length; ++i) {
95 | const char = line[i];
96 | if (!char.match(colorNameCharacter)) {
97 | break;
98 | }
99 | right += char;
100 | }
101 |
102 | let left = '';
103 | let leftHead = position.character - 1;
104 | for (; leftHead >= 0; --leftHead) {
105 | const char = line[leftHead];
106 | if (!char.match(colorNameCharacter)) {
107 | break;
108 | }
109 | left = char + left;
110 | }
111 |
112 | // Make sure not to grab potential hex strings
113 | if (leftHead > 0 && line[leftHead] === '#') {
114 | return [];
115 | }
116 |
117 | leftHead = Math.max(leftHead, 0);
118 |
119 | const word = left + right;
120 | const color = tinycolor(word);
121 | if (color && color.isValid()) {
122 | const span = new vscode.Range(
123 | new vscode.Position(position.line, leftHead),
124 | new vscode.Position(position.line, leftHead + word.length));
125 |
126 | return [{
127 | value: word,
128 | color,
129 | span,
130 | }];
131 | }
132 | return [];
133 | },
134 | };
135 |
136 | interface ExtractorRegistry {
137 | [kind: string]: ColorValueExtractor;
138 | }
139 |
140 | const valueExtractorRegistry = [
141 | rgbExtractor,
142 | hslExtractor,
143 | hexExtractor,
144 | hexaExtractor,
145 | cssNameExtractor,
146 | ].reduce((registry, extractor) => {
147 | registry[extractor.type] = extractor;
148 | return registry;
149 | }, {} as ExtractorRegistry);
150 |
151 | /**
152 | * Extracts color values from text
153 | */
154 | export class ColorExtractor {
155 | private _valueExtractors: Set;
156 |
157 | constructor(valueExtractorTypes: Set) {
158 | this._valueExtractors = new Set();
159 | for (const t of valueExtractorTypes) {
160 | const extractor = valueExtractorRegistry[t];
161 | if (extractor) {
162 | this._valueExtractors.add(extractor);
163 | }
164 | }
165 | }
166 |
167 | public getColorAtPosition(line: string, position: vscode.Position): ColorMatch | undefined {
168 | const allColors = this.getColorsForLine(position, line)
169 | .filter((x) => x.span.contains(position));
170 | return allColors[0];
171 | }
172 |
173 | /**
174 | * Get all color values in a line of text.
175 | */
176 | private getColorsForLine(position: vscode.Position, line: string): ColorMatch[] {
177 | const matches: ColorMatch[] = [];
178 | for (const valueExtractor of this._valueExtractors) {
179 | matches.push(...valueExtractor.getColors(line, position));
180 | }
181 | return matches;
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/color_info_display.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { ColorMatch } from './color_extractor';
4 |
5 | import AlphaDisplay from './displays/alpha_display';
6 | import CmykDisplay from './displays/cmyk_display';
7 | import HexDisplay from './displays/hex_display';
8 | import HslDisplay from './displays/hsl_display';
9 | import HsvDisplay from './displays/hsv_display';
10 | import LabDisplay from './displays/lab_display';
11 | import LchDisplay from './displays/lch_display';
12 | import NameDisplay from './displays/name_display';
13 | import { Preview, PreviewSquare, PreviewSquareXL, PreviewXL } from './displays/preview_display';
14 | import RgbDisplay from './displays/rgb_display';
15 |
16 | export interface ColorValueDisplay {
17 | name: string;
18 | display(colorMatch: ColorMatch): string | null;
19 | }
20 |
21 | type DisplayRegistry = Map;
22 |
23 | const allFields: DisplayRegistry = [
24 | Preview, PreviewXL, PreviewSquare, PreviewSquareXL,
25 | RgbDisplay,
26 | HslDisplay,
27 | HsvDisplay,
28 | CmykDisplay,
29 | LabDisplay,
30 | LchDisplay,
31 | AlphaDisplay,
32 | HexDisplay,
33 | NameDisplay,
34 | ].reduce((p, display) => {
35 | p.set(display.name, display);
36 | return p;
37 | }, new Map());
38 |
39 | const defaultFields = [
40 | Preview.name,
41 | RgbDisplay.name,
42 | HslDisplay.name,
43 | CmykDisplay.name,
44 | HexDisplay.name,
45 | AlphaDisplay.name,
46 | ];
47 |
48 | /**
49 | * Normalize the name of a color field
50 | */
51 | const normalizeFieldName = (name: string) =>
52 | ('' + name).toLowerCase();
53 |
54 | export class ColorDisplay {
55 | private _config: vscode.WorkspaceConfiguration;
56 |
57 | constructor(config: vscode.WorkspaceConfiguration) {
58 | this._config = config;
59 | }
60 |
61 | public display(colorMatch: ColorMatch): string | null {
62 | const display = this.getDisplay(colorMatch);
63 | if (display && display.length) {
64 | return display;
65 | }
66 | return null;
67 | }
68 |
69 | private getDisplays(): ColorValueDisplay[] {
70 | let fields = (this._config.get('fields') || defaultFields).map(normalizeFieldName);
71 | const excluded = (this._config.get('excludedFields') || []).map(normalizeFieldName);
72 |
73 | fields = fields.filter((name) => excluded.indexOf(name) === -1);
74 |
75 | return fields
76 | .map((x) => allFields.get(x))
77 | .filter((x) => x) as ColorValueDisplay[];
78 | }
79 |
80 | private getDisplay(match: ColorMatch): string {
81 | return this.getDisplays().map((x) => x.display(match))
82 | .filter((x) => x && x.length > 0)
83 | .join('\n\n');
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/configuration.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { ColorValueExtractorType } from './color_extractor';
4 |
5 | interface UserLanguageConfiguration {
6 | selector: string | Record;
7 | colors: string | string[];
8 | }
9 |
10 | class LanguageConfiguration {
11 | public static fromUser(user: UserLanguageConfiguration): LanguageConfiguration {
12 | const types: Set = new Set();
13 | const userColors = Array.isArray(user.colors) ? user.colors : [user.colors];
14 | for (const t of userColors.map((x) => ('' + x).toLowerCase())) {
15 | switch (t) {
16 | case 'css':
17 | types.add('rgb');
18 | types.add('hsl');
19 | types.add('hex');
20 | types.add('hex+alpha');
21 | types.add('css-color-names');
22 | break;
23 | case 'rgb':
24 | case 'hsl':
25 | case 'hex':
26 | case 'hex+alpha':
27 | case 'css-color-names':
28 | types.add(t);
29 | break;
30 | }
31 | }
32 | const selector: vscode.DocumentSelector = typeof user.selector === 'string'
33 | ? [{ language: user.selector }]
34 | : user.selector;
35 | return new LanguageConfiguration(selector, types);
36 | }
37 |
38 | private constructor(
39 | public readonly selector: vscode.DocumentSelector,
40 | public readonly colorExtractors: Set,
41 | ) { }
42 | }
43 |
44 | /**
45 | * Configures
46 | */
47 | export class LanguagesConfiguration {
48 | public static load(config: vscode.WorkspaceConfiguration): LanguagesConfiguration {
49 | const user = config.get('languages') || [];
50 | const lang = new LanguagesConfiguration(
51 | user.map((x) => LanguageConfiguration.fromUser(x)).filter((x) => x !== null));
52 | return lang;
53 | }
54 |
55 | public languages: LanguageConfiguration[];
56 |
57 | private constructor(languages: LanguageConfiguration[]) {
58 | this.languages = languages;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/displays/alpha_display.ts:
--------------------------------------------------------------------------------
1 | import { ColorMatch } from '../color_extractor';
2 | import { ColorValueDisplay } from '../color_info_display';
3 | import { decimalPercent, func } from './display_helper';
4 |
5 | /**
6 | * Displays the alpha value
7 | */
8 | export default new class AlphaDisplay implements ColorValueDisplay {
9 | public name = 'alpha';
10 |
11 | public display(match: ColorMatch) {
12 | const { a } = match.color.toRgb();
13 | return func('alpha', decimalPercent(a, 0));
14 | }
15 | }();
16 |
--------------------------------------------------------------------------------
/src/displays/cmyk_display.ts:
--------------------------------------------------------------------------------
1 | import * as convert from 'color-convert';
2 |
3 | import { ColorMatch } from '../color_extractor';
4 | import { ColorValueDisplay } from '../color_info_display';
5 | import { func, percent } from './display_helper';
6 |
7 | /**
8 | * Displays the CMYK value of a color.
9 | */
10 | export default new class CmykDisplay implements ColorValueDisplay {
11 | public name = 'cmyk';
12 |
13 | public display(match: ColorMatch) {
14 | const { h, s, l } = match.color.toHsl();
15 | const [c, m, y, k] = convert.hsl.cmyk([h, s * 100, l * 100]);
16 | return func('cmyk', percent(c, 5), percent(m, 5), percent(y, 5), percent(k, 5));
17 | }
18 | }();
19 |
--------------------------------------------------------------------------------
/src/displays/display_helper.ts:
--------------------------------------------------------------------------------
1 | export const clamp = (val: number, min: number, max: number): number =>
2 | Math.min(max, Math.max(min, val));
3 |
4 | export const pad = (val: string, count: number): string =>
5 | ('' + val).padStart(count, '\u00A0');
6 |
7 | export const number = (val: string, padding: number) =>
8 | '`\u200B' + pad(val, padding + 2) + '`';
9 |
10 | export const unit = (unit: string, val: number, padding: number): string =>
11 | number(val + unit, padding);
12 |
13 | export const percent = (val: number, padding: number): string =>
14 | unit('%', +(val).toFixed(2), padding);
15 |
16 | export const decimalPercent = (val: number, padding: number): string =>
17 | percent(val * 100, padding);
18 |
19 | export const deg = (val: number, padding: number): string =>
20 | unit('\u00B0', +(val).toFixed(2), padding);
21 |
22 | export const func = (name: string, ...keys: string[]): string =>
23 | `**${name}(**${keys.join(', ')}**)**`;
24 |
--------------------------------------------------------------------------------
/src/displays/hex_display.ts:
--------------------------------------------------------------------------------
1 | import {ColorMatch} from '../color_extractor';
2 | import {ColorValueDisplay} from '../color_info_display';
3 | import {number} from './display_helper';
4 |
5 | /**
6 | * Displays the hex value of the color.
7 | */
8 | export default new class HexDisplay implements ColorValueDisplay {
9 | public name = 'hex';
10 |
11 | public display(match: ColorMatch) {
12 | const hex = match.color.toHexString();
13 | return number(hex, 0);
14 | }
15 | }();
16 |
--------------------------------------------------------------------------------
/src/displays/hsl_display.ts:
--------------------------------------------------------------------------------
1 | import { ColorMatch } from '../color_extractor';
2 | import { ColorValueDisplay } from '../color_info_display';
3 | import { decimalPercent, deg, func } from './display_helper';
4 |
5 | /**
6 | * Displays the HSL value of a color.
7 | */
8 | export default new class HslDisplay implements ColorValueDisplay {
9 | public name = 'hsl';
10 |
11 | public display(match: ColorMatch) {
12 | const { h, s, l } = match.color.toHsl();
13 | return func('hsl', deg(h, 5), decimalPercent(s, 5), decimalPercent(l, 5));
14 | }
15 | }();
16 |
--------------------------------------------------------------------------------
/src/displays/hsv_display.ts:
--------------------------------------------------------------------------------
1 | import { ColorMatch } from '../color_extractor';
2 | import { ColorValueDisplay } from '../color_info_display';
3 | import { decimalPercent, deg, func } from './display_helper';
4 |
5 | /**
6 | * Displays the HSV value of a color.
7 | */
8 | export default new class HsvDisplay implements ColorValueDisplay {
9 | public name = 'hsv';
10 |
11 | public display(match: ColorMatch) {
12 | const { h, s, v } = match.color.toHsv();
13 | return func('hsv', deg(h, 5), decimalPercent(s, 5), decimalPercent(v, 5));
14 | }
15 | }();
16 |
--------------------------------------------------------------------------------
/src/displays/lab_display.ts:
--------------------------------------------------------------------------------
1 |
2 | import { hsl } from 'color-convert';
3 | import { ColorMatch } from '../color_extractor';
4 | import { ColorValueDisplay } from '../color_info_display';
5 | import { func, number } from './display_helper';
6 |
7 | /**
8 | * Displays the LAB value of a color.
9 | */
10 | export default new class LabDisplay implements ColorValueDisplay {
11 | public name = 'lab';
12 |
13 | public display(match: ColorMatch) {
14 | const { h, s, l } = match.color.toHsl();
15 | const lab = hsl.lab.raw([h, s * 100, l * 100]);
16 | return func('lab', number(lab[0].toFixed(2), 5), number(lab[1].toFixed(2), 5), number(lab[2].toFixed(2), 5));
17 | }
18 | }();
19 |
--------------------------------------------------------------------------------
/src/displays/lch_display.ts:
--------------------------------------------------------------------------------
1 | import { hsl } from 'color-convert';
2 | import { ColorMatch } from '../color_extractor';
3 | import { ColorValueDisplay } from '../color_info_display';
4 | import { func, number, deg} from './display_helper';
5 |
6 | /**
7 | * Displays the LCHab value of a color.
8 | */
9 | export default new class LchDisplay implements ColorValueDisplay {
10 | public name = 'lch';
11 |
12 | public display(match: ColorMatch) {
13 | const { h, s, l } = match.color.toHsl();
14 | const lch = hsl.lch.raw([h, s * 100, l * 100]);
15 | return func('lch', number(lch[0].toFixed(2), 5), number(lch[1].toFixed(2), 5), deg(lch[2], 5));
16 | }
17 | }();
18 |
--------------------------------------------------------------------------------
/src/displays/name_display.ts:
--------------------------------------------------------------------------------
1 | import { ColorMatch } from '../color_extractor';
2 | import { ColorValueDisplay } from '../color_info_display';
3 |
4 | /**
5 | * Displays the name of a color.
6 | */
7 | export default new class NameDisplay implements ColorValueDisplay {
8 | public name = 'css-color-name';
9 |
10 | public display(match: ColorMatch) {
11 | const name = match.color.toName();
12 | return name ? name : null;
13 | }
14 | }();
15 |
--------------------------------------------------------------------------------
/src/displays/preview_display.ts:
--------------------------------------------------------------------------------
1 | import { ColorMatch } from '../color_extractor';
2 | import { ColorValueDisplay } from '../color_info_display';
3 | import * as base64 from 'base-64';
4 | import * as utf8 from 'utf8';
5 |
6 | /**
7 | * Displays a preview of a color.
8 | */
9 | class PreviewDisplay implements ColorValueDisplay {
10 | constructor(
11 | public name: string,
12 | private width: number,
13 | private height: number,
14 | ) { }
15 |
16 | public display(match: ColorMatch) {
17 | const hex = match.color.toHexString();
18 |
19 | const src = `
20 |
21 | `;
28 | const dataUri = 'data:image/svg+xml;charset=UTF-8;base64,' + base64.encode(utf8.encode(src));
29 | return ``;
30 | }
31 | }
32 |
33 | export const Preview = new PreviewDisplay('preview', 256, 64);
34 | export const PreviewXL = new PreviewDisplay('preview-xl', 400, 128);
35 |
36 | export const PreviewSquare = new PreviewDisplay('preview-square', 128, 128);
37 | export const PreviewSquareXL = new PreviewDisplay('preview-square-xl', 256, 256);
38 |
--------------------------------------------------------------------------------
/src/displays/rgb_display.ts:
--------------------------------------------------------------------------------
1 | import {ColorMatch} from '../color_extractor';
2 | import {ColorValueDisplay} from '../color_info_display';
3 | import {clamp, func, number} from './display_helper';
4 |
5 | const formatNumber = (val: number) =>
6 | number(clamp(val, 0, 255) + '', 3);
7 |
8 | /**
9 | * Displays the RGB value of a color.
10 | */
11 | export default new class RgbDisplay implements ColorValueDisplay {
12 | public name = 'rgb';
13 |
14 | public display(match: ColorMatch) {
15 | const {r, g, b} = match.color.toRgb();
16 | return func('rgb', formatNumber(r), formatNumber(g), formatNumber(b));
17 | }
18 | }();
19 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { ColorExtractor } from './color_extractor';
4 | import { ColorDisplay } from './color_info_display';
5 | import { LanguagesConfiguration } from './configuration';
6 | import ColorInfoHoverProvider from './hover_provider';
7 |
8 | /**
9 | * Main extension activation.
10 | */
11 | export function activate(context: vscode.ExtensionContext) {
12 | function reload() {
13 | for (const existing of providerRegistrations) {
14 | existing.dispose();
15 | }
16 |
17 | providerRegistrations = [];
18 | const workspaceConfig = vscode.workspace.getConfiguration('colorInfo');
19 | const display = new ColorDisplay(workspaceConfig);
20 |
21 | const languageConfig = LanguagesConfiguration.load(workspaceConfig);
22 | for (const lang of languageConfig.languages) {
23 | const hoverProvider = new ColorInfoHoverProvider(new ColorExtractor(lang.colorExtractors), display);
24 | const registration = vscode.languages.registerHoverProvider(lang.selector, hoverProvider);
25 | providerRegistrations.push(registration);
26 | }
27 | }
28 |
29 | let providerRegistrations: vscode.Disposable[] = [];
30 | context.subscriptions.push(new vscode.Disposable(() => {
31 | vscode.Disposable.from(...providerRegistrations).dispose();
32 | providerRegistrations = [];
33 | }));
34 |
35 | vscode.workspace.onDidChangeConfiguration(() => {
36 | reload();
37 | });
38 | reload();
39 | }
40 |
--------------------------------------------------------------------------------
/src/hover_provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { ColorExtractor } from './color_extractor';
4 | import { ColorDisplay } from './color_info_display';
5 |
6 | /**
7 | * Color info hover provider
8 | */
9 | export default class ColorInfoHoverProvider implements vscode.HoverProvider {
10 | private _extractor: ColorExtractor;
11 | private _display: ColorDisplay;
12 |
13 | constructor(extractor: ColorExtractor, display: ColorDisplay) {
14 | this._extractor = extractor;
15 | this._display = display;
16 | }
17 |
18 | public provideHover(
19 | document: vscode.TextDocument,
20 | position: vscode.Position,
21 | _token: vscode.CancellationToken,
22 | ): vscode.Hover | null {
23 | const line = document.lineAt(position.line);
24 | const match = this._extractor.getColorAtPosition(line.text, position);
25 | if (match) {
26 | const display = this._display.display(match);
27 | if (display) {
28 | return new vscode.Hover(display);
29 | }
30 | }
31 | return null;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "target": "es2020",
6 | "outDir": "out",
7 | "lib": [
8 | "es2020"
9 | ],
10 | "sourceMap": true,
11 | "rootDir": ".",
12 | "strict": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitAny": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "noImplicitReturns": true,
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "exclude": [
21 | "node_modules",
22 | ".vscode-test"
23 | ]
24 | }
--------------------------------------------------------------------------------