✨ Add some layer related providers
This commit is contained in:
parent
10822c6686
commit
61e61a4e2c
6 changed files with 262 additions and 62 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
156
src/layers.ts
Normal 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;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
54
src/theme.ts
54
src/theme.ts
|
@ -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
|
||||||
|
|
35
src/utils.ts
35
src/utils.ts
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue