Add some layer related providers

This commit is contained in:
Robin van der Linde 2025-01-01 04:44:34 +01:00
parent 10822c6686
commit 61e61a4e2c
Signed by: Robin-van-der-Linde
GPG key ID: 53956B3252478F0D
6 changed files with 262 additions and 62 deletions

View file

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

View file

@ -1,11 +1,15 @@
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";
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
context.subscriptions.push(tagRenderingCompletionProvider);
// Activate all generic features // Activate all generic features
context.subscriptions.push(iconDefinitionProvider); context.subscriptions.push(iconDefinitionProvider);
} }

View file

@ -35,54 +35,41 @@ export const iconDefinitionProvider =
const regexes = [/icon$/, /icon.render/, /icon.mappings.\d+.then$/]; const regexes = [/icon$/, /icon.render/, /icon.mappings.\d+.then$/];
for (const regex of regexes) { if (regexes.some((regex) => regex.exec(jsonPath))) {
if (regex.exec(jsonPath)) { const iconPath = getValueFromPath(text, rawJsonPath);
const iconPath = getValueFromPath(text, rawJsonPath); console.log("Found reference to icon", iconPath);
console.log("Found reference to icon", iconPath);
let fullIconPath: string; let fullIconPath: string;
// Check if the path starts with a dot, if so, it's a relative path // Check if the path starts with a dot, if so, it's a relative path
// if not, it's a built-in icon // if not, it's a built-in icon
if (!iconPath.startsWith(".")) { if (!iconPath.startsWith(".")) {
fullIconPath = path.join( fullIconPath = path.join(
(vscode.workspace.workspaceFolders (vscode.workspace.workspaceFolders
? vscode.workspace.workspaceFolders[0].uri.fsPath ? vscode.workspace.workspaceFolders[0].uri.fsPath
: "") || "", : "") || "",
"assets", "assets",
"svg", "svg",
iconPath + ".svg" iconPath + ".svg"
);
} else {
fullIconPath = path.join(
(vscode.workspace.workspaceFolders
? vscode.workspace.workspaceFolders[0].uri.fsPath
: "") || "",
iconPath
);
}
const startEnd = getStartEnd(text, rawJsonPath);
console.log("fullIconPath", fullIconPath);
console.log(
"startEndLines",
startEnd.start.line,
startEnd.end.line
); );
console.log( } else {
"startEndChars", fullIconPath = path.join(
startEnd.start.character, (vscode.workspace.workspaceFolders
startEnd.end.character ? vscode.workspace.workspaceFolders[0].uri.fsPath
: "") || "",
iconPath
); );
const link: vscode.DefinitionLink = {
targetUri: vscode.Uri.file(fullIconPath),
targetRange: new vscode.Range(0, 0, 0, 0),
originSelectionRange: startEnd,
};
return [link];
} }
const startEnd = getStartEnd(text, rawJsonPath);
const link: vscode.DefinitionLink = {
targetUri: vscode.Uri.file(fullIconPath),
targetRange: new vscode.Range(0, 0, 0, 0),
originSelectionRange: startEnd,
};
return [link];
} }
return []; return [];

156
src/layers.ts Normal file
View file

@ -0,0 +1,156 @@
/**
* This file contains all functions that should be used when editing layers
* Layer files are located in the /assets/layers/{LAYER_NAME}.json file
* This is also loaded when editing the theme files, as layers can be inline in the theme files
*
* This consists of the following functions:
* - tagRenderingCompletionProvider: Provides a list of existing tag renderings for autocompletion
* - tagRenderingDefinitionProvider: Provides a definition for tag renderings, allowing users to jump to the tag rendering definition
*/
import * as vscode from "vscode";
import {
getCursorPath,
getRawCursorPath,
getStartEnd,
getTagRenderings,
getValueFromPath,
} from "./utils";
import { JSONPath } from "jsonc-parser";
export const tagRenderingCompletionProvider =
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("tagRenderingCompletionProvider");
const text = document.getText();
const jsonPath = getCursorPath(text, position);
const regex = /^(layers.\d+.)?tagRenderings\.\d+(.builtin)?$/;
if (regex.exec(jsonPath)) {
const tagRenderings = await getTagRenderings();
console.log(`Got ${tagRenderings.length} tagRenderings`);
// Now we need to return the completion items
return tagRenderings;
}
return [];
},
}
);
/**
* Tag rendering definition provider
*/
export const tagRenderingDefinitionProvider =
vscode.languages.registerDefinitionProvider(
{
language: "json",
scheme: "file",
pattern: "**/assets/layers/*/*.json",
},
{
async provideDefinition(
document: vscode.TextDocument,
position: vscode.Position
) {
console.log("tagRenderingDefinitionProvider");
const text = document.getText();
const jsonPath = getCursorPath(text, position);
const rawJsonPath = getRawCursorPath(text, position);
const regex = /^(layers.\d.)?tagRenderings.\d*(.builtin)?$/;
if (regex.exec(jsonPath)) {
const tagRendering = getValueFromPath(text, rawJsonPath);
if (typeof tagRendering === "string") {
console.log("Found reference to tagRendering", tagRendering);
if (tagRendering.indexOf(".") === -1) {
console.log("This is a built-in tag rendering");
// This is a built-in tag rendering
// Read the built-in tag renderings file
const layerFile = await vscode.workspace.findFiles(
"assets/layers/questions/questions.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 tagRenderingIndex = layer.tagRenderings.findIndex(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tr: any) => tr.id === tagRendering
);
const path: JSONPath = ["tagRenderings", tagRenderingIndex];
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 tag rendering in another layer
// We need to find the layer and the tag rendering
const layerName = tagRendering.split(".")[0];
const tagRenderingName = tagRendering.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 tagRenderingIndex = layer.tagRenderings.findIndex(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tr: any) => tr.id === tagRenderingName
);
const path: JSONPath = ["tagRenderings", tagRenderingIndex];
const startEnd = getStartEnd(layerTextString, path);
const link: vscode.DefinitionLink = {
targetUri: layerFile[0],
targetRange: startEnd,
originSelectionRange: getStartEnd(text, rawJsonPath),
};
return [link];
}
}
}
return null;
},
}
);

View file

@ -1,5 +1,6 @@
/** /**
* This file contains all functions that should be used when editing theme files * This file contains all functions that should mainly be used when editing theme files
* All of these are related to looking for layer references in the theme files, but some layers also reference other layers
* Theme files are located in the /assets/themes/{THEME_NAME}/{THEME_NAME}.json file * Theme files are located in the /assets/themes/{THEME_NAME}/{THEME_NAME}.json file
* *
* This consists of the following functions: * This consists of the following functions:
@ -9,7 +10,12 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import * as path from "path"; import * as path from "path";
import { getAvailableLayers, getCursorPath } from "./utils"; import {
getAvailableLayers,
getCursorPath,
getRawCursorPath,
getValueFromPath,
} from "./utils";
/** /**
* Layer completion provider * Layer completion provider
@ -25,22 +31,27 @@ export const layerCompletionProvider =
{ {
language: "json", language: "json",
scheme: "file", scheme: "file",
pattern: "**/assets/themes/*/*.json", pattern: "**/assets/*/*/*.json",
}, },
{ {
async provideCompletionItems( async provideCompletionItems(
document: vscode.TextDocument, document: vscode.TextDocument,
position: vscode.Position position: vscode.Position
) { ) {
console.log("layerCompletionProvider"); // Stop running if the file is called license_info.json
// Now we'll need to try and get the current path for the cursor if (document.fileName.includes("license_info")) {
const text = document.getText(); return [];
}
// Now we need to get the current path console.log("layerCompletionProvider");
const text = document.getText();
const jsonPath = getCursorPath(text, position); const jsonPath = getCursorPath(text, position);
const regex = /^layers\.\d+(.builtin)*$/; const regexes = [
if (regex.exec(jsonPath)) { /^layers\.\d+(.builtin)?$/,
/^(layers.\d+.)?presets.\d+.snapToLayer(.\d)*$/,
];
if (regexes.some((regex) => regex.exec(jsonPath))) {
// We need to get the available layers // We need to get the available layers
const layers = await getAvailableLayers(); const layers = await getAvailableLayers();
console.log(`Got ${layers.length} layers`); console.log(`Got ${layers.length} layers`);
@ -70,33 +81,38 @@ export const layerCompletionProvider =
* JSON paths: * JSON paths:
* - layers.{index} (If this is a string) * - layers.{index} (If this is a string)
* - layers.{index}.builtin * - layers.{index}.builtin
* - (layers.{index}.)presets.{index}.snapToLayer(.{index})
*/ */
export const layerDefinitionProvider = export const layerDefinitionProvider =
vscode.languages.registerDefinitionProvider( vscode.languages.registerDefinitionProvider(
{ {
language: "json", language: "json",
scheme: "file", scheme: "file",
pattern: "**/assets/themes/*/*.json", pattern: "**/assets/*/*/*.json",
}, },
{ {
provideDefinition( provideDefinition(
document: vscode.TextDocument, document: vscode.TextDocument,
position: vscode.Position position: vscode.Position
) { ) {
console.log("layerDefinitionProvider"); // Stop running if the file is called license_info.json
if (document.fileName.includes("license_info")) {
// We don't want to provide definitions for the license_info.json file return [];
if (document.fileName.endsWith("license_info.json")) {
return null;
} }
console.log("layerDefinitionProvider");
const text = document.getText(); const text = document.getText();
const jsonPath = getCursorPath(text, position); const jsonPath = getCursorPath(text, position);
const json = JSON.parse(text); const rawJsonPath = getRawCursorPath(text, position);
const regex = /^layers\.\d+(.builtin)*$/; const regexes = [
if (regex.exec(jsonPath)) { /^layers\.\d+(.builtin)?$/,
const layer = json.layers[parseInt(jsonPath.split(".")[1])]; /^(layers.\d+.)?presets.\d+.snapToLayer(.\d)*$/,
];
if (regexes.some((regex) => regex.exec(jsonPath))) {
console.log("Found reference to layer");
const layer = getValueFromPath(text, rawJsonPath);
// If an item is a string, it's a reference to a layer, also if it's an object and has a builtin property // If an item is a string, it's a reference to a layer, also if it's an object and has a builtin property
if (typeof layer === "string") { if (typeof layer === "string") {
// We have a reference to a layer // We have a reference to a layer

View file

@ -152,3 +152,38 @@ export function getValueFromPath(json: string, path: JSONPath): any {
} }
} }
} }
/**
* 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;
}