Add support for filters and license info

This commit is contained in:
Robin van der Linde 2025-01-01 20:34:35 +01:00
parent 61e61a4e2c
commit 4542a9f6b2
Signed by: Robin-van-der-Linde
GPG key ID: 53956B3252478F0D
5 changed files with 280 additions and 3 deletions

View file

@ -8,4 +8,6 @@ Not everything is supported yet, but currently the following features are suppor
- Definition support for the layer names - Definition support for the layer names
- Definintion support for icons - Definintion support for icons
- Autocompletion for tagRenderings in questions.json - 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

View file

@ -1,14 +1,28 @@
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 { 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) { export function activate(context: vscode.ExtensionContext) {
// Activate all theme related features // Activate all theme related features
context.subscriptions.push(layerCompletionProvider, layerDefinitionProvider); context.subscriptions.push(layerCompletionProvider, layerDefinitionProvider);
// Activate all layer related features // 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 // Activate all generic features
context.subscriptions.push(iconDefinitionProvider); context.subscriptions.push(iconDefinitionProvider);

View file

@ -11,6 +11,7 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { import {
getCursorPath, getCursorPath,
getFilters,
getRawCursorPath, getRawCursorPath,
getStartEnd, getStartEnd,
getTagRenderings, getTagRenderings,
@ -18,6 +19,14 @@ import {
} from "./utils"; } from "./utils";
import { JSONPath } from "jsonc-parser"; 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 = export const tagRenderingCompletionProvider =
vscode.languages.registerCompletionItemProvider( vscode.languages.registerCompletionItemProvider(
{ {
@ -55,6 +64,11 @@ export const tagRenderingCompletionProvider =
/** /**
* Tag rendering definition provider * 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 = export const tagRenderingDefinitionProvider =
vscode.languages.registerDefinitionProvider( 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;
},
}
);

63
src/license_info.ts Normal file
View file

@ -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];
}
},
}
);

View file

@ -187,3 +187,35 @@ export async function getTagRenderings(): Promise<vscode.CompletionItem[]> {
return tagRenderings; 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;
}