🚧 Colour picker

This commit is contained in:
Robin van der Linde 2025-01-03 01:57:50 +01:00 committed by Robin van der Linde
parent 8bdc9f16d8
commit 6457bfa63d
5 changed files with 138 additions and 5 deletions

11
package-lock.json generated
View file

@ -1,14 +1,15 @@
{ {
"name": "mapcompletevscode", "name": "mapcompletevscode",
"version": "0.0.1", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcompletevscode", "name": "mapcompletevscode",
"version": "0.0.1", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"colortranslator": "^4.1.0",
"jsonc-parser": "^3.3.1" "jsonc-parser": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {
@ -673,6 +674,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/colortranslator": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/colortranslator/-/colortranslator-4.1.0.tgz",
"integrity": "sha512-bwa5awaMnQ6dpm9D3nbsFwUr6x6FrTKmxPdolNtSYfxCNR7ZM93GG1OF5Y3Sy1LvYdalb3riKC9uTn0X5NB36g==",
"license": "Apache-2.0"
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",

View file

@ -48,6 +48,7 @@
"typescript-eslint": "^8.16.0" "typescript-eslint": "^8.16.0"
}, },
"dependencies": { "dependencies": {
"colortranslator": "^4.1.0",
"jsonc-parser": "^3.3.1" "jsonc-parser": "^3.3.1"
} }
} }

View file

@ -1,6 +1,6 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { layerCompletionProvider, layerDefinitionProvider } from "./theme"; import { layerCompletionProvider, layerDefinitionProvider } from "./theme";
import { iconDefinitionProvider } from "./generic"; import { colorProvider, iconDefinitionProvider } from "./generic";
import { import {
filterCompletionProvider, filterCompletionProvider,
filterDefinitionProvider, filterDefinitionProvider,
@ -25,5 +25,5 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(pathDefinitionProvider); context.subscriptions.push(pathDefinitionProvider);
// Activate all generic features // Activate all generic features
context.subscriptions.push(iconDefinitionProvider); context.subscriptions.push(iconDefinitionProvider, colorProvider);
} }

View file

@ -8,7 +8,9 @@ import {
getRawCursorPath, getRawCursorPath,
getStartEnd, getStartEnd,
getValueFromPath, getValueFromPath,
pathToJSONPath,
} from "./utils"; } from "./utils";
import { ColorTranslator } from "colortranslator";
import * as path from "path"; import * as path from "path";
/** /**
@ -78,3 +80,114 @@ export const iconDefinitionProvider =
}, },
} }
); );
/**
* Color provider
*
* Some fields in MapComplete actually represent colors, this provider will provide a color presentation for these fields
* Colours can be represented in two ways:
* - As a hex color, like #ff0000ff or #ff0000
* - As a color name, like red
*
* JSON paths:
* - (layers.{index}.)lineRendering.{index}.color
* - (layers.{index}.)lineRendering.{index}.color.render
* - (layers.{index}.)lineRendering.{index}.color.mappings.{index}.then
* - (layers.{index}.)lineRendering.{index}.fillColor
* - (layers.{index}.)lineRendering.{index}.fillColor.render
* - (layers.{index}.)lineRendering.{index}.fillColor.mappings.{index}.then
* - (layers.{index}.)pointRendering.{index}.marker.{index}.color
* - (layers.{index}.)pointRendering.{index}.marker.{index}.color.render
* - (layers.{index}.)pointRendering.{index}.marker.{index}.color.mappings.{index}.then
*
*/
export const colorProvider = vscode.languages.registerColorProvider(
{
language: "json",
scheme: "file",
pattern: "**/assets/*/*/*.json",
},
{
provideColorPresentations(color, _context, _token) {
console.log("colorProvider.provideColorPresentations");
const colorHex = new ColorTranslator(
`rgba(${color.red * 255}, ${color.green * 255}, ${color.blue * 255}, ${
color.alpha
})`
).HEXA;
// If the color is fully opaque (AKA it ends with FF), we can remove the alpha channel
const colorHexString = colorHex.endsWith("FF")
? colorHex.substring(0, 7)
: colorHex;
// TODO: Add color names, maybe convert to short-hand hex
return [
{
label: colorHexString,
},
];
},
provideDocumentColors(document, _token) {
console.log("colorProvider.provideDocumentColors");
const text = document.getText();
const colors: vscode.ColorInformation[] = [];
const jsonParsed = JSON.parse(text);
const allPaths: string[] = [];
// Find all paths in the JSON
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const findPaths = (obj: any, path = "") => {
for (const key in obj) {
if (typeof obj[key] === "object") {
findPaths(obj[key], path + key + ".");
} else {
allPaths.push(path + key);
}
}
};
findPaths(jsonParsed);
const regexes = [
/^(layers.\d+.)?lineRendering.\d+.color((.render)|(.mappings.\d+.then))?$/,
/^(layers.\d+.)?lineRendering.\d+.fillColor((.render)|(.mappings.\d+.then))?$/,
/^(layers.\d+.)?pointRendering.\d+.marker.\d+.color((.render)|(.mappings.\d+.then))?$/,
];
regexes.forEach((regex) => {
allPaths.forEach((path) => {
if (regex.exec(path)) {
const colorValue = getValueFromPath(text, pathToJSONPath(path));
let colorRgba;
try {
colorRgba = new ColorTranslator(colorValue).RGBAObject;
} catch (error) {
console.error("Error translating color value:", error);
return;
}
console.log(
`Output color: ${colorRgba.R}, ${colorRgba.G}, ${colorRgba.B}, ${colorRgba.A}`
);
colors.push({
color: new vscode.Color(
colorRgba.R / 255,
colorRgba.G / 255,
colorRgba.B / 255,
colorRgba.A ?? 1
),
range: getStartEnd(text, pathToJSONPath(path)),
});
}
});
});
return colors;
},
}
);

View file

@ -219,3 +219,15 @@ export async function getFilters(): Promise<vscode.CompletionItem[]> {
return filtersList; return filtersList;
} }
/**
* Utility function to convert a string path to a JSON path
*
* @param path String path, separated by dots
* @returns JSON path
*/
export function pathToJSONPath(path: string): JSONPath {
return path.split(".").map((str) => {
return isNaN(parseInt(str)) ? str : parseInt(str);
});
}