Skip to content

Commit

Permalink
feat: add color picker support (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
iChenLei authored Nov 22, 2021
1 parent 198a851 commit e650d8b
Show file tree
Hide file tree
Showing 8 changed files with 492 additions and 165 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- `var()` and `calc()` css builtin function autocomplete support
- color picker support, to fix [#51](https://github.com/d4rkr00t/language-stylus/issues/51)

### Changed
- Change `css function` automplete behavior, set cursor into the position between parens
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# vscode-stylus

[![VSCode Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/sysoev.language-stylus?label=VSCode%20Marketplace)](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus)
[![VSCode Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/i/sysoev.language-stylus)](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus)
[![VSCode Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/sysoev.language-stylus)](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus)
[![VSCode Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/sysoev.language-stylus)](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus)
[![VSCode Marketplace Stars](https://img.shields.io/visual-studio-marketplace/r/sysoev.language-stylus)](https://marketplace.visualstudio.com/items?itemName=sysoev.language-stylus)
[![github-issues](https://img.shields.io/github/issues/d4rkr00t/language-stylus.svg)](https://github.com/d4rkr00t/language-stylus/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/d4rkr00t/language-stylus/pulls)
Expand All @@ -27,8 +28,6 @@ Adds syntax highlighting and code completion to `Stylus` files in Visual Studio
"languageStylus.useSeparator": true, // default value
// Toggle matches for Stylus Builtin Functions on autocomplete
"languageStylus.useBuiltinFunctions": true, // default value
// Toggle colors preview
"editor.colorDecorators": true // default value
}
```

Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@
"main": "./out/src/extension",
"icon": "assets/icon.png",
"keywords": [
"vscode",
"stylus",
"styl",
"stylus",
"language-stylus",
"vscode-stylus",
"language-support",
"stylus-intellisense"
],
"activationEvents": [
Expand Down
154 changes: 0 additions & 154 deletions src/color-decorators.ts

This file was deleted.

192 changes: 192 additions & 0 deletions src/color-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import {
CancellationToken,
Color,
ColorInformation,
ColorPresentation,
DocumentColorProvider,
Position,
ProviderResult,
Range,
TextDocument,
TextEdit
} from 'vscode';

import {
StylusNode,
StylusValue,
buildAst, flattenAndFilterAst,
isColor
} from './parser';
import {
colors,
colorFromHex,
getNumericValue,
getAngle,
toTwoDigitHex,
colorFromHSL,
hslFromColor
} from './colors';

export const buildCallValueFromArgs = args =>
args.nodes.map(node => node.nodes[0].val).join(', ');

export const getRealColumn = (textToSearch: string, text: string[], lineno: number) =>
Math.max(text[lineno].indexOf(textToSearch), 0);

export const getRealCallColumn = (textToSearch: string, text: string[], lineno: number) => {
const startPos = Math.max(text[lineno].indexOf(textToSearch), 0);
const searchStr = text[lineno].slice(startPos);
return Math.max(text[lineno].indexOf(textToSearch), 0) + searchStr.indexOf(")") + 1;
}

export function normalizeColors(colorsNode: StylusNode[], text: string[]): ColorInformation[] {
const colorsInformation = [];
colorsNode.forEach(color => {
if (color.nodeName === 'ident' && colors[color.name]) {
try {
const colorObj = colorFromHex(colors[color.name]);
colorsInformation.push(new ColorInformation(
new Range(
new Position(color.lineno - 1, getRealColumn(color.name, text, color.lineno - 1)),
new Position(color.lineno - 1, getRealColumn(color.name, text, color.lineno - 1) + color.name?.length || 0)
),
new Color(colorObj.red, colorObj.green, colorObj.blue, colorObj.alpha)
));
} catch (_) {
// do nothing
}
} else if (color.nodeName === 'rgba') {
try {
colorsInformation.push(new ColorInformation(
new Range(
new Position(color.lineno - 1, getRealColumn((color as any).raw, text, color.lineno - 1)),
new Position(color.lineno - 1, getRealColumn((color as any).raw, text, color.lineno - 1) + (color as any).raw?.length || 0)
),
// @ts-ignore
new Color(color.r, color.g, color.b, color.a)
));
} catch (_) {
// do nothing
}
} else if (color.nodeName === 'call') {
try {
// @ts-ignore
const colorValues = color?.args?.nodes?.map?.(node => node.nodes[0].val);
if (!colorValues || colorValues.length < 3 || colorValues.length > 4) {
return;
}
const alpha = colorValues.length === 4 ? getNumericValue(colorValues[3], 1) : 1;
const funcName = color.name as string;
if (funcName === 'rgb' || funcName === 'rgba') {
colorsInformation.push(new ColorInformation(
new Range(
new Position(color.lineno - 1, getRealColumn(color.name, text, color.lineno - 1)),
new Position(color.lineno - 1, getRealCallColumn(color.name, text, color.lineno - 1))
),
// @ts-ignore
new Color(
getNumericValue(colorValues[0], 255.0),
getNumericValue(colorValues[1], 255.0),
getNumericValue(colorValues[2], 255.0),
alpha
)
));
} else if (funcName === 'hsl' || funcName === 'hsla') {
const h = getAngle(colorValues[0]);
const s = getNumericValue(colorValues[1], 100.0);
const l = getNumericValue(colorValues[2], 100.0);
const colorRes = colorFromHSL(h, s, l, alpha);
colorsInformation.push(new ColorInformation(
new Range(
new Position(color.lineno - 1, getRealColumn(color.name, text, color.lineno - 1)),
new Position(color.lineno - 1, getRealCallColumn(color.name, text, color.lineno - 1))
),
new Color(colorRes.red, colorRes.green, colorRes.blue, colorRes.alpha)
));
}
} catch (_) {
_;
// do nothing
}
}
});
return colorsInformation;
}

export function extractColorsFromExpression(node:StylusValue) {
let result = [];

if (node.nodeName === 'expression') {
node.nodes.forEach(valNode => {
if (isColor(valNode)) {
result.push(valNode);
} else if (valNode.nodeName === 'object') {
Object.keys(valNode.vals).forEach(subValNode => {
result = result.concat(extractColorsFromExpression(valNode.vals[subValNode]));
});
}
})
}

return result;
}

export function getColors(ast) {
return (ast.nodes || ast || []).reduce((acc, node) => {
if (node.nodeName === 'ident') {
acc = acc.concat(extractColorsFromExpression(node.val));
}

if (node.nodeName === 'property' && node.expr) {
acc = acc.concat(extractColorsFromExpression(node.expr));
}

return acc;
}, []);
}

export class StylusColorProvider implements DocumentColorProvider {

provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult<ColorInformation[]> {
if (token.isCancellationRequested) {
return [];
}
const documentTxt = document.getText();
const ast = flattenAndFilterAst(buildAst(documentTxt));
const list = normalizeColors(getColors(ast), documentTxt.split('\n'));
return list;
}

provideColorPresentations(color: Color, context: { document: TextDocument; range: Range; }, token: CancellationToken): ProviderResult<ColorPresentation[]> {
if (token.isCancellationRequested) {
return [];
}
const result: ColorPresentation[] = [];
const red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255);

let label;
if (color.alpha === 1) {
label = `rgb(${red256}, ${green256}, ${blue256})`;
} else {
label = `rgba(${red256}, ${green256}, ${blue256}, ${color.alpha})`;
}
result.push({ label: label, textEdit: TextEdit.replace(context.range, label) });

if (color.alpha === 1) {
label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}`;
} else {
label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}${toTwoDigitHex(Math.round(color.alpha * 255))}`;
}
result.push({ label: label, textEdit: TextEdit.replace(context.range, label) });

const hsl = hslFromColor(color);
if (hsl.a === 1) {
label = `hsl(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%)`;
} else {
label = `hsla(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%, ${hsl.a})`;
}
result.push({ label: label, textEdit: TextEdit.replace(context.range, label) });

return result;
}
}
Loading

0 comments on commit e650d8b

Please sign in to comment.