♻️ Rework utils and color

This commit is contained in:
Robin van der Linde 2025-01-04 14:49:18 +01:00 committed by Robin van der Linde
parent 6457bfa63d
commit 0f01b73c22
12 changed files with 1367 additions and 269 deletions

2
package-lock.json generated
View file

@ -667,7 +667,7 @@
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"node_modules/color-convert/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",

View file

@ -51,4 +51,4 @@
"colortranslator": "^4.1.0",
"jsonc-parser": "^3.3.1"
}
}
}

View file

@ -3,15 +3,11 @@
*/
import * as vscode from "vscode";
import {
getCursorPath,
getRawCursorPath,
getStartEnd,
getValueFromPath,
pathToJSONPath,
} from "./utils";
import { getCursorPath, getRawCursorPath, getStartEnd } from "./utils/cursor";
import { getValueFromPath, pathToJSONPath } from "./utils/json";
import { ColorTranslator } from "colortranslator";
import * as path from "path";
import { findHexColor } from "./utils/color";
/**
* Icon definition provider
@ -111,22 +107,46 @@ export const colorProvider = vscode.languages.registerColorProvider(
provideColorPresentations(color, _context, _token) {
console.log("colorProvider.provideColorPresentations");
const colorHex = new ColorTranslator(
let outputColor: string;
// Convert the color to a hex string
outputColor = 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;
console.log(`First output color: ${outputColor}`);
// TODO: Add color names, maybe convert to short-hand hex
// If the color is fully opaque (AKA it ends with FF), we can remove the alpha channel
outputColor = outputColor.endsWith("FF")
? outputColor.substring(0, 7)
: outputColor;
console.log(`Output color after alpha check: ${outputColor}`);
// See if we can find a color name
const colorName = findHexColor(outputColor);
if (colorName) {
outputColor = colorName.name;
}
console.log(`Output color after color name check: ${outputColor}`);
// If we have a pair like #AABBCC, we can shorten that to #ABC
if (
outputColor[1] === outputColor[2] &&
outputColor[3] === outputColor[4] &&
outputColor[5] === outputColor[6]
) {
outputColor = `#${outputColor[1]}${outputColor[3]}${outputColor[5]}`;
}
console.log(`Color after shortening: ${outputColor}`);
return [
{
label: colorHexString,
label: outputColor,
},
];
},

View file

@ -9,14 +9,9 @@
*/
import * as vscode from "vscode";
import {
getCursorPath,
getFilters,
getRawCursorPath,
getStartEnd,
getTagRenderings,
getValueFromPath,
} from "./utils";
import { getCursorPath, getRawCursorPath, getStartEnd } from "./utils/cursor";
import { getFilters, getTagRenderings } from "./utils/mapcomplete";
import { getValueFromPath } from "./utils/json";
import { JSONPath } from "jsonc-parser";
/**

View file

@ -8,12 +8,8 @@
import * as vscode from "vscode";
import * as path from "path";
import {
getCursorPath,
getRawCursorPath,
getStartEnd,
getValueFromPath,
} from "./utils";
import { getCursorPath, getRawCursorPath, getStartEnd } from "./utils/cursor";
import { getValueFromPath } from "./utils/json";
/**
* Path definition provider

View file

@ -10,12 +10,9 @@
import * as vscode from "vscode";
import * as path from "path";
import {
getAvailableLayers,
getCursorPath,
getRawCursorPath,
getValueFromPath,
} from "./utils";
import { getCursorPath, getRawCursorPath } from "./utils/cursor";
import { getAvailableLayers } from "./utils/mapcomplete";
import { getValueFromPath } from "./utils/json";
/**
* Layer completion provider

View file

@ -1,233 +0,0 @@
import * as vscode from "vscode";
import * as path from "path";
import {
findNodeAtLocation,
getLocation,
JSONPath,
parseTree,
} from "jsonc-parser";
/**
* Utility function to get the JSON path at the cursor position, separated by dots
*
* @param jsonText Original unparsed JSON text
* @param position VScode cursor position
* @returns JSON path as a string, separated by dots
*/
export function getCursorPath(
jsonText: string,
position: vscode.Position
): string {
return getRawCursorPath(jsonText, position).join(".");
}
/**
* Utility function to get the JSON path at the cursor position
*
* @param jsonText Original unparsed JSON text
* @param position VScode cursor position
* @returns JSON path as an array of strings
*/
export function getRawCursorPath(
jsonText: string,
position: vscode.Position
): JSONPath {
const offset = positionToOffset(jsonText, position);
const location = getLocation(jsonText, offset);
return location.path;
}
/**
* Utility function to convert a VScode position to a numeric offset
*
* @param text Original text content
* @param position VScode cursor position
* @returns Offset
*/
function positionToOffset(text: string, position: vscode.Position): number {
const lines = text.split("\n");
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += lines[i].length + 1; // +1 for the newline character
}
offset += position.character;
return offset;
}
/**
* Function to get all available layers on disk
*
* In essence, we look for folders under the assets/layers directory
* and return the names of the folders
* @returns List of layer names
*/
export async function getAvailableLayers(): Promise<string[]> {
const layers: string[] = [];
let files = await vscode.workspace.findFiles(
"assets/layers/**/*.json",
"**/node_modules/**"
);
files = files.filter((file) => {
return !file.fsPath.includes("license_info");
});
for (const file of files) {
const layerName = path.basename(path.dirname(file.fsPath));
layers.push(layerName);
}
return layers;
}
/**
* Function to get a range of a JSON path
* Useful for creating document links
*
* @param json file content
* @param path JSON path
* @returns Range of the path
*/
export function getStartEnd(json: string, path: JSONPath): vscode.Range {
const rootNode = parseTree(json);
if (!rootNode) {
return new vscode.Range(0, 0, 0, 0);
} else {
const node = findNodeAtLocation(rootNode, path);
if (!node) {
return new vscode.Range(0, 0, 0, 0);
} else {
return new vscode.Range(
offsetToPosition(json, node.offset + 1),
offsetToPosition(json, node.offset + node.length - 1)
);
}
}
}
/**
* Utility function to convert an offset to a position
*
* @param text Text content
* @param offset Offset
* @returns Position
*/
function offsetToPosition(text: string, offset: number): vscode.Position {
const lines = text.split("\n");
let currentOffset = 0;
for (let i = 0; i < lines.length; i++) {
if (currentOffset + lines[i].length + 1 >= offset) {
return new vscode.Position(i, offset - currentOffset);
}
currentOffset += lines[i].length + 1;
}
return new vscode.Position(0, 0);
}
/**
* Utility function to get the value of a JSON path
*
* @param json Original JSON content
* @param path JSON path
* @returns Value of the path
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getValueFromPath(json: string, path: JSONPath): any {
console.log("getValueFromPath", path);
const rootNode = parseTree(json);
if (!rootNode) {
console.log("Root node not found");
return undefined;
} else {
console.log("Root node found");
const node = findNodeAtLocation(rootNode, path);
if (!node) {
return undefined;
} else {
console.log(
"Substring",
json.substring(node.offset, node.offset + node.length)
);
return JSON.parse(json.substring(node.offset, node.offset + node.length));
}
}
}
/**
* Utility function to get the tagRenderings from the questions layer
* //TODO: This should get ALL tagRenderings, not just from the questions layer
*
* @returns List of CompletionItems for tagRenderings
*/
export async function getTagRenderings(): Promise<vscode.CompletionItem[]> {
const tagRenderings: vscode.CompletionItem[] = [];
// Open the questions layer file
const questionsFile = await vscode.workspace.findFiles(
"assets/layers/questions/questions.json",
"**/node_modules/**"
);
if (questionsFile.length === 0) {
console.error("questions.json not found");
return [];
}
const content = await vscode.workspace.fs.readFile(questionsFile[0]);
const questions = JSON.parse(new TextDecoder().decode(content));
for (const tagRendering of questions.tagRenderings) {
tagRenderings.push(
new vscode.CompletionItem(
tagRendering.id,
vscode.CompletionItemKind.Value
)
);
}
return tagRenderings;
}
/**
* Utility function to get the filters from the filters layer
* //TODO: This should get ALL filters, not just from the filters layer
*
* @returns List of CompletionItems for tagRenderings
*/
export async function getFilters(): Promise<vscode.CompletionItem[]> {
const filtersList: vscode.CompletionItem[] = [];
// Open the filters layer file
const filtersFile = await vscode.workspace.findFiles(
"assets/layers/filters/filters.json",
"**/node_modules/**"
);
if (filtersFile.length === 0) {
console.error("filters.json not found");
return [];
}
const content = await vscode.workspace.fs.readFile(filtersFile[0]);
const filters = JSON.parse(new TextDecoder().decode(content));
for (const filter of filters.filter) {
filtersList.push(
new vscode.CompletionItem(filter.id, vscode.CompletionItemKind.Value)
);
}
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);
});
}

1082
src/utils/color.ts Normal file

File diff suppressed because it is too large Load diff

102
src/utils/cursor.ts Normal file
View file

@ -0,0 +1,102 @@
/**
* Utility functions related to vscode cursor position and JSON paths
*/
import * as vscode from "vscode";
import {
findNodeAtLocation,
getLocation,
JSONPath,
parseTree,
} from "jsonc-parser";
/**
* Utility function to get the JSON path at the cursor position, separated by dots
*
* @param jsonText Original unparsed JSON text
* @param position VScode cursor position
* @returns JSON path as a string, separated by dots
*/
export function getCursorPath(
jsonText: string,
position: vscode.Position
): string {
return getRawCursorPath(jsonText, position).join(".");
}
/**
* Utility function to get the JSON path at the cursor position
*
* @param jsonText Original unparsed JSON text
* @param position VScode cursor position
* @returns JSON path as an array of strings
*/
export function getRawCursorPath(
jsonText: string,
position: vscode.Position
): JSONPath {
const offset = positionToOffset(jsonText, position);
const location = getLocation(jsonText, offset);
return location.path;
}
/**
* Utility function to convert a VScode position to a numeric offset
*
* @param text Original text content
* @param position VScode cursor position
* @returns Offset
*/
function positionToOffset(text: string, position: vscode.Position): number {
const lines = text.split("\n");
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += lines[i].length + 1; // +1 for the newline character
}
offset += position.character;
return offset;
}
/**
* Utility function to convert an offset to a position
*
* @param text Text content
* @param offset Offset
* @returns Position
*/
function offsetToPosition(text: string, offset: number): vscode.Position {
const lines = text.split("\n");
let currentOffset = 0;
for (let i = 0; i < lines.length; i++) {
if (currentOffset + lines[i].length + 1 >= offset) {
return new vscode.Position(i, offset - currentOffset);
}
currentOffset += lines[i].length + 1;
}
return new vscode.Position(0, 0);
}
/**
* Function to get a range of a JSON path
* Useful for creating document links
*
* @param json file content
* @param path JSON path
* @returns Range of the path
*/
export function getStartEnd(json: string, path: JSONPath): vscode.Range {
const rootNode = parseTree(json);
if (!rootNode) {
return new vscode.Range(0, 0, 0, 0);
} else {
const node = findNodeAtLocation(rootNode, path);
if (!node) {
return new vscode.Range(0, 0, 0, 0);
} else {
return new vscode.Range(
offsetToPosition(json, node.offset + 1),
offsetToPosition(json, node.offset + node.length - 1)
);
}
}
}

40
src/utils/json.ts Normal file
View file

@ -0,0 +1,40 @@
/**
* Utility functions to work with JSON content and paths
*/
import { JSONPath, findNodeAtLocation, parseTree } from "jsonc-parser";
/**
* Utility function to get the value of a JSON path
*
* @param json Original JSON content
* @param path JSON path
* @returns Value of the path
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getValueFromPath(json: string, path: JSONPath): any {
console.log("getValueFromPath", path);
const rootNode = parseTree(json);
if (!rootNode) {
return undefined;
} else {
const node = findNodeAtLocation(rootNode, path);
if (!node) {
return undefined;
} else {
return JSON.parse(json.substring(node.offset, node.offset + node.length));
}
}
}
/**
* 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);
});
}

99
src/utils/mapcomplete.ts Normal file
View file

@ -0,0 +1,99 @@
/**
* This file contains some function related to interaction with MapComplete data in the editor
*/
import * as vscode from "vscode";
import * as path from "path";
/**
* Function to get all available layers on disk
*
* In essence, we look for folders under the assets/layers directory
* and return the names of the folders
* @returns List of layer names
*/
export async function getAvailableLayers(): Promise<string[]> {
const layers: string[] = [];
let files = await vscode.workspace.findFiles(
"assets/layers/**/*.json",
"**/node_modules/**"
);
files = files.filter((file) => {
return !file.fsPath.includes("license_info");
});
for (const file of files) {
const layerName = path.basename(path.dirname(file.fsPath));
layers.push(layerName);
}
return layers;
}
/**
* Utility function to get the tagRenderings from the questions layer
* //TODO: This should get ALL tagRenderings, not just from the questions layer
*
* @returns List of CompletionItems for tagRenderings
*/
export async function getTagRenderings(): Promise<vscode.CompletionItem[]> {
const tagRenderings: vscode.CompletionItem[] = [];
// Open the questions layer file
const questionsFile = await vscode.workspace.findFiles(
"assets/layers/questions/questions.json",
"**/node_modules/**"
);
if (questionsFile.length === 0) {
console.error("questions.json not found");
return [];
}
const content = await vscode.workspace.fs.readFile(questionsFile[0]);
const questions = JSON.parse(new TextDecoder().decode(content));
for (const tagRendering of questions.tagRenderings) {
tagRenderings.push(
new vscode.CompletionItem(
tagRendering.id,
vscode.CompletionItemKind.Value
)
);
}
return tagRenderings;
}
/**
* Utility function to get the filters from the filters layer
* //TODO: This should get ALL filters, not just from the filters layer
*
* @returns List of CompletionItems for tagRenderings
*/
export async function getFilters(): Promise<vscode.CompletionItem[]> {
const filtersList: vscode.CompletionItem[] = [];
// Open the filters layer file
const filtersFile = await vscode.workspace.findFiles(
"assets/layers/filters/filters.json",
"**/node_modules/**"
);
if (filtersFile.length === 0) {
console.error("filters.json not found");
return [];
}
const content = await vscode.workspace.fs.readFile(filtersFile[0]);
const filters = JSON.parse(new TextDecoder().decode(content));
for (const filter of filters.filter) {
filtersList.push(
new vscode.CompletionItem(filter.id, vscode.CompletionItemKind.Value)
);
}
return filtersList;
}

0
types/named-web-colors.d.ts vendored Normal file
View file