From 4542a9f6b2a6b24bee6bde426cbd1d01ed168b35 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Wed, 1 Jan 2025 20:34:35 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20filters=20and?= =?UTF-8?q?=20license=20info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/extension.ts | 18 ++++- src/layers.ts | 166 ++++++++++++++++++++++++++++++++++++++++++++ src/license_info.ts | 63 +++++++++++++++++ src/utils.ts | 32 +++++++++ 5 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 src/license_info.ts diff --git a/README.md b/README.md index aaad0ef..186693e 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,6 @@ Not everything is supported yet, but currently the following features are suppor - Definition support for the layer names - Definintion support for icons - Autocompletion for tagRenderings in questions.json -- Definition support for tagRenderings in questions.json +- Definition support for tagRenderings +- Autocompletion for filter keys in questions.json +- Definition support for filter keys diff --git a/src/extension.ts b/src/extension.ts index 628df51..235d6d3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,14 +1,28 @@ import * as vscode from "vscode"; import { layerCompletionProvider, layerDefinitionProvider } from "./theme"; import { iconDefinitionProvider } from "./generic"; -import { tagRenderingCompletionProvider } from "./layers"; +import { + filterCompletionProvider, + filterDefinitionProvider, + tagRenderingCompletionProvider, + tagRenderingDefinitionProvider, +} from "./layers"; +import { pathDefinitionProvider } from "./license_info"; export function activate(context: vscode.ExtensionContext) { // Activate all theme related features context.subscriptions.push(layerCompletionProvider, layerDefinitionProvider); // Activate all layer related features - context.subscriptions.push(tagRenderingCompletionProvider); + context.subscriptions.push( + tagRenderingCompletionProvider, + tagRenderingDefinitionProvider, + filterCompletionProvider, + filterDefinitionProvider + ); + + // Activate all license info related features + context.subscriptions.push(pathDefinitionProvider); // Activate all generic features context.subscriptions.push(iconDefinitionProvider); diff --git a/src/layers.ts b/src/layers.ts index 168f5a5..b30b537 100644 --- a/src/layers.ts +++ b/src/layers.ts @@ -11,6 +11,7 @@ import * as vscode from "vscode"; import { getCursorPath, + getFilters, getRawCursorPath, getStartEnd, getTagRenderings, @@ -18,6 +19,14 @@ import { } from "./utils"; import { JSONPath } from "jsonc-parser"; +/** + * Tag rendering completion provider + * + * This provider will provide a list of existing tagRenderings for autocompletion + * + * JSON path: + * - (layers.{index}.)tagRenderings.{index}(.builtin) + */ export const tagRenderingCompletionProvider = vscode.languages.registerCompletionItemProvider( { @@ -55,6 +64,11 @@ export const tagRenderingCompletionProvider = /** * Tag rendering definition provider + * + * This provider will provide a definition for tagRenderings, allowing users to jump to the tagRendering definition + * + * JSON path: + * - (layers.{index}.)tagRenderings.{index}(.builtin) */ export const tagRenderingDefinitionProvider = vscode.languages.registerDefinitionProvider( @@ -154,3 +168,155 @@ export const tagRenderingDefinitionProvider = }, } ); + +/** + * Filter completion provider + * + * This provider will provide a list of existing filters for autocompletion + * + * JSON path: + * - (layers.{index}.)filter.{index} + */ +export const filterCompletionProvider = + vscode.languages.registerCompletionItemProvider( + { + language: "json", + scheme: "file", + pattern: "**/assets/layers/*/*.json", + }, + { + async provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position + ) { + // Stop running if the file is called license_info.json + if (document.fileName.includes("license_info")) { + return []; + } + + console.log("filterCompletionProvider"); + const text = document.getText(); + const jsonPath = getCursorPath(text, position); + + console.log(jsonPath); + + const regex = /^(layers.\d+.)?filter\.\d+$/; + if (regex.exec(jsonPath)) { + const filters = await getFilters(); + console.log(`Got ${filters.length} filters`); + + // Now we need to return the completion items + return filters; + } + + return []; + }, + } + ); + +/** + * Filter definition provider + * + * This provider will provide a definition for filters, allowing users to jump to the filter definition + * + * JSON path: + * - (layers.{index}.)filter.{index} + */ +export const filterDefinitionProvider = + vscode.languages.registerDefinitionProvider( + { + language: "json", + scheme: "file", + pattern: "**/assets/layers/*/*.json", + }, + { + async provideDefinition( + document: vscode.TextDocument, + position: vscode.Position + ) { + console.log("filterDefinitionProvider"); + const text = document.getText(); + const jsonPath = getCursorPath(text, position); + const rawJsonPath = getRawCursorPath(text, position); + + const regex = /^(layers.\d.)?filter.\d*$/; + + if (regex.exec(jsonPath)) { + const filter = getValueFromPath(text, rawJsonPath); + + if (typeof filter === "string") { + console.log("Found reference to filter", filter); + if (filter.indexOf(".") === -1) { + console.log("This is a built-in filter"); + // This is a built-in filter + // Read the built-in filters file + const layerFile = await vscode.workspace.findFiles( + "assets/layers/filters/filters.json" + ); + if (layerFile.length === 0) { + return null; + } + + const layerText = await vscode.workspace.fs.readFile( + layerFile[0] + ); + const layerTextString = new TextDecoder().decode(layerText); + const layer = JSON.parse(layerTextString); + + const filterIndex = layer.filter.findIndex( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (f: any) => f.id === filter + ); + + const path: JSONPath = ["filter", filterIndex]; + const startEnd = getStartEnd(layerTextString, path); + + const link: vscode.DefinitionLink = { + targetUri: layerFile[0], + targetRange: startEnd, + originSelectionRange: getStartEnd(text, rawJsonPath), + }; + + return [link]; + } else { + // This is a reference to a filter in another layer + // We need to find the layer and the filter + const layerName = filter.split(".")[0]; + const filterName = filter.split(".")[1]; + + const layerFile = await vscode.workspace.findFiles( + `assets/layers/${layerName}/${layerName}.json` + ); + if (layerFile.length === 0) { + return null; + } + + const layerText = await vscode.workspace.fs.readFile( + layerFile[0] + ); + const layerTextString = new TextDecoder().decode(layerText); + const layer = JSON.parse(layerTextString); + + const filterIndex = layer.filter.findIndex( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (f: any) => f.id === filterName + ); + + const path: JSONPath = ["filter", filterIndex]; + const startEnd = getStartEnd(layerTextString, path); + + const link: vscode.DefinitionLink = { + targetUri: layerFile[0], + targetRange: startEnd, + originSelectionRange: getStartEnd(text, rawJsonPath), + }; + + return [link]; + } + } + } + + return null; + }, + } + ); diff --git a/src/license_info.ts b/src/license_info.ts new file mode 100644 index 0000000..870f8a7 --- /dev/null +++ b/src/license_info.ts @@ -0,0 +1,63 @@ +/** + * This file contains all functions that should be used when editing license_info files + * License info files are located in the /assets/{themes/layers}/{name}/license_info.json file + * + * This consists of the following functions: + * - pathDefinitionProvider: Provides a definition for paths, allowing users to jump to the image file + */ + +import * as vscode from "vscode"; +import * as path from "path"; +import { + getCursorPath, + getRawCursorPath, + getStartEnd, + getValueFromPath, +} from "./utils"; + +/** + * Path definition provider + * + * This provider will provide a definition for paths, allowing users to jump to the image file + * + * JSON path: + * - {index}.path + */ +export const pathDefinitionProvider = + vscode.languages.registerDefinitionProvider( + { + language: "json", + scheme: "file", + pattern: "**/assets/*/*/license_info.json", + }, + { + provideDefinition( + document: vscode.TextDocument, + position: vscode.Position + ) { + console.log("pathDefinitionProvider"); + const text = document.getText(); + const jsonPath = getCursorPath(text, position); + const rawPath = getRawCursorPath(text, position); + + const regex = /^\d+.path$/; + if (regex.exec(jsonPath)) { + const imageFile = getValueFromPath(text, rawPath); + console.log("Found reference to filw", imageFile); + + const imagePath = path.join( + path.dirname(document.fileName), + imageFile + ); + + const link: vscode.LocationLink = { + originSelectionRange: getStartEnd(text, rawPath), + targetUri: vscode.Uri.file(imagePath), + targetRange: new vscode.Range(0, 0, 0, 0), + }; + + return [link]; + } + }, + } + ); diff --git a/src/utils.ts b/src/utils.ts index 31e8107..15c1346 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -187,3 +187,35 @@ export async function getTagRenderings(): Promise { 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 { + 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; +}