diff --git a/src/layers.ts b/src/layers.ts index bde1e41..0e21040 100644 --- a/src/layers.ts +++ b/src/layers.ts @@ -205,105 +205,27 @@ export const tagRenderingImplementationProvider = if (references.length === 0) { return null; } else { - // TODO: This is way too much to be executing at this time, most of this should be cached - // TODO: Also, this seems to fail for the first time, but work for every subsequent time console.log(`Found ${references.length} references to ${to}`); const links: vscode.DefinitionLink[] = []; for (const reference of references) { - const originType = reference.reference?.from.split(".")[0]; - const originName = reference.reference?.from.split(".")[1]; - - // We need to open the file where the reference is located - const originFile = await vscode.workspace.findFiles( - `assets/${originType}/${originName}/${originName}.json` - ); - if (originFile.length === 0) { - continue; - } - - const originText = await vscode.workspace.fs.readFile( - originFile[0] - ); - const originTextString = new TextDecoder().decode(originText); - const origin = JSON.parse(originTextString); - - let tagRenderings: unknown[] = []; - let tagRenderingsPath: JSONPath = []; - - // Now we'll need to find the tagRenderings object, and its path - if (originType === "themes") { - const parts = reference.reference?.from.split("."); - if (!parts) { - continue; - } else { - console.log("Parts", parts); - // Now we need to find the correct inline layer - const layerIndex = origin.layers.findIndex( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (layer: any) => layer.id === parts[3] - ); - - const path: JSONPath = [ - parts[2], - layerIndex, - ...reference.jsonPath, - ]; - - console.log("Trying to get tagRenderings from theme", path); - - const tagRenderingsFromOrigin = getValueFromPath( - originTextString, - path - ); - if (!tagRenderingsFromOrigin) { - console.error( - "Could not find tagRenderings in theme", - originName - ); - continue; - } else { - // Yaay, we found the tagRenderings - console.log("Found tagRenderings in theme", originName); - tagRenderings = tagRenderingsFromOrigin as unknown[]; - tagRenderingsPath = path; - } - } - } else if (originType === "layers") { - tagRenderings = origin.tagRenderings; - tagRenderingsPath = ["tagRenderings"]; - } - - // The index is actually a really complicated, because a reference could be a string or an object with a builtin property, which can be a string or a list of strings - // Also if the reference is from an inline layer - const tagRenderingIndex = tagRenderings.findIndex( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (tr: any) => { - if (typeof tr === "string") { - return tr === reference.id; - } else if (typeof tr.builtin === "string") { - return tr.builtin === reference.id; - } - // } else if (tr.builtin) { - // return tr.builtin.includes(reference.id); - // } - } - ); - const path: JSONPath = [ - ...tagRenderingsPath, - tagRenderingIndex, - ]; - const startEnd = getStartEnd(originTextString, path); - console.log( - `Pushing link from ${document.fileName} to ${originFile[0].path} at ${startEnd.start.line}.${startEnd.start.character} to ${startEnd.end.line}.${startEnd.end.character}` + `Pushing link from ${document.fileName} to ${reference.reference?.to.uri?.fsPath} at ${reference.reference?.to.range?.start.line}.${reference.reference?.to.range?.start.character} to ${reference.reference?.to.range?.end.line}.${reference.reference?.to.range?.end.character}` ); - links.push({ - originSelectionRange: getStartEnd(text, rawJsonPath), - targetRange: startEnd, - targetUri: originFile[0], - }); + // Check if we have a targetRange and targetUri + if ( + reference.reference?.to.range && + reference.reference?.to.uri + ) { + links.push({ + originSelectionRange: reference.reference?.from.range, + targetRange: reference.reference?.to.range, + targetUri: reference.reference?.to.uri, + }); + } else { + console.error("Incomplete reference", reference); + } } console.log(`Found ${links.length} implementations`); return links; diff --git a/src/utils/cache.ts b/src/utils/cache.ts index bfaa5ab..4b5c0f9 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -4,6 +4,7 @@ import * as vscode from "vscode"; import { JSONPath } from "jsonc-parser"; +import { getStartEnd } from "./cursor"; /** * Worker class to handle the cache creation and updates @@ -164,15 +165,24 @@ export class CacheWorker { const text = new TextDecoder().decode(await content); const json = JSON.parse(text); + // Determine some locations + const from = `themes.${json.id}`; + const fromFile = uri; + // Look through the layers for (const layer of json.layers) { + const layerIndex = json.layers.indexOf(layer); + // Reference if it's a string if (typeof layer === "string") { // It is a reference console.log(`Reference found to ${layer} in ${filePath}`); - const from = `themes.${json.id}`; + const fromStartEnd = getStartEnd(text, ["layers", layerIndex]); const to = `layers.${layer}`; + const toFile = await vscode.workspace.findFiles( + `**/assets/layers/${layer}/${layer}.json` + ); this.cache.push({ id: layer, @@ -180,8 +190,15 @@ export class CacheWorker { jsonPath: ["layers"], type: "reference", reference: { - from, - to, + from: { + id: from, + uri: fromFile, + range: fromStartEnd, + }, + to: { + id: to, + uri: toFile[0], + }, type: "layer", }, }); @@ -192,8 +209,15 @@ export class CacheWorker { // Single layer console.log(`Reference found to ${layer.builtin} in ${filePath}`); - const from = `themes.${json.id}`; + const fromStartEnd = getStartEnd(text, [ + "layers", + layerIndex, + "builtin", + ]); const to = `layers.${layer.builtin}`; + const toFile = await vscode.workspace.findFiles( + `**/assets/layers/${layer.builtin}/${layer.builtin}.json` + ); this.cache.push({ id: layer.builtin, @@ -201,8 +225,15 @@ export class CacheWorker { jsonPath: ["layers"], type: "reference", reference: { - from, - to, + from: { + id: from, + uri: fromFile, + range: fromStartEnd, + }, + to: { + id: to, + uri: toFile[0], + }, type: "layer", }, }); @@ -211,8 +242,17 @@ export class CacheWorker { for (const builtinLayer of layer.builtin) { console.log(`Reference found to ${builtinLayer} in ${filePath}`); - const from = `themes.${json.id}`; + const builtinLayerIndex = layer.builtin.indexOf(builtinLayer); + const fromStartEnd = getStartEnd(text, [ + "layers", + layerIndex, + "builtin", + builtinLayerIndex, + ]); const to = `layers.${builtinLayer}`; + const toFile = await vscode.workspace.findFiles( + `**/assets/layers/${builtinLayer}/${builtinLayer}.json` + ); this.cache.push({ id: builtinLayer, @@ -220,8 +260,15 @@ export class CacheWorker { jsonPath: ["layers"], type: "reference", reference: { - from, - to, + from: { + id: from, + uri: fromFile, + range: fromStartEnd, + }, + to: { + id: to, + uri: toFile[0], + }, type: "layer", }, }); @@ -231,9 +278,9 @@ export class CacheWorker { // Inline layer else else { console.log(`Found inline layer ${layer.id} in ${filePath}`); - const text = JSON.stringify(layer); + const layerText = JSON.stringify(layer); const from = `themes.${json.id}.layers.${layer.id}`; - this.saveLayerTextToCache(text, uri, from, true); + await this.saveLayerTextToCache(layerText, uri, from, true, text); } } @@ -255,22 +302,24 @@ export class CacheWorker { const uriFileName = uriPathSplit[uriPathSplit.length - 1]; const from = `layers.${uriFileName.split(".")[0]}`; - this.saveLayerTextToCache(text, uri, from); + await this.saveLayerTextToCache(text, uri, from); } /** * Save a layer to the cache, given the text of the layer * - * @param text The text of the layer + * @param text Text representation of layer * @param uri The URI of the layer file * @param from The theme or layer where the layer is from, e.g. layers.bicycle_rental or themes.cyclofix.layers.0 * @param referencesOnly Whether to only save references, or also the tagRenderings and filters. This is useful for inline layers, because their filters and tagRenderings can't be reused + * @param fullFileText The full text of the original theme file, used for calculating position */ - private saveLayerTextToCache( + private async saveLayerTextToCache( text: string, uri: vscode.Uri, from: string, - referencesOnly = false + referencesOnly = false, + fullFileText?: string ) { const filePath = uri.fsPath; console.log("Saving layer to cache", filePath); @@ -287,93 +336,16 @@ export class CacheWorker { // Look through the tagRenderings, if the layer has any if (json.tagRenderings) { - for (const tagRendering of json.tagRenderings) { - // Check if it is a string and not an object - if (typeof tagRendering === "string") { - // It is a reference - console.log(`Reference found to ${tagRendering} in ${filePath}`); - - const to = tagRendering.includes(".") - ? `layers.${tagRendering.split(".")[0]}.tagRenderings.${ - tagRendering.split(".")[1] - }` - : `layers.questions.tagRenderings.${tagRendering}`; - - this.cache.push({ - id: tagRendering, - filePath: uri, - jsonPath: ["tagRenderings"], - type: "reference", - reference: { - from, - to, - type: "tagRendering", - }, - }); - } else if (typeof tagRendering === "object") { - // This is a tagRendering, or a reference to one - if (tagRendering.builtin) { - // This is a reference to a built-in tagRendering (or multiple ones) - if (typeof tagRendering.builtin === "string") { - // Single tagRendering - console.log( - `Reference found to ${tagRendering.builtin} in ${filePath}` - ); - - const to = tagRendering.builtin.includes(".") - ? `layers.${tagRendering.builtin.split(".")[0]}.tagRenderings.${ - tagRendering.builtin.split(".")[1] - }` - : `layers.questions.tagRenderings.${tagRendering.builtin}`; - - this.cache.push({ - id: tagRendering.builtin, - filePath: uri, - jsonPath: ["tagRenderings"], - type: "reference", - reference: { - from, - to, - type: "tagRendering", - }, - }); - } else { - // Multiple tagRenderings - for (const builtinTagRendering of tagRendering.builtin) { - console.log( - `Reference found to ${builtinTagRendering} in ${filePath}` - ); - - const to = builtinTagRendering.includes(".") - ? `layers.${ - builtinTagRendering.split(".")[0] - }.tagRenderings.${builtinTagRendering.split(".")[1]}` - : `layers.questions.tagRenderings.${builtinTagRendering}`; - - this.cache.push({ - id: builtinTagRendering, - filePath: uri, - jsonPath: ["tagRenderings"], - type: "reference", - reference: { - from, - to, - type: "tagRendering", - }, - }); - } - } - } else if (!referencesOnly) { - // This is a tagRendering, which can be reused - console.log(`TagRendering found in ${filePath}`); - this.cache.push({ - id: `${json.id}.${tagRendering.id}`, - filePath: uri, - jsonPath: ["tagRenderings"], - type: "tagRendering", - }); - } - } + try { + await this.saveTagRenderingsToCache( + text, + from, + uri, + referencesOnly, + fullFileText + ); + } catch (error) { + console.error(`Error saving tagRenderings for ${from}`, error); } } else { console.log("No tagRenderings found in", filePath); @@ -381,36 +353,16 @@ export class CacheWorker { if (json.filter) { // Look through the filters - for (const filter of json.filter) { - // Check if it is a string and not an object - if (typeof filter === "string") { - // It is a reference - console.log(`Reference found to ${filter} in ${filePath}`); - - const from = `layers.${json.id}`; - const to = `layers.${filter}`; - - this.cache.push({ - id: filter, - filePath: uri, - jsonPath: ["filters"], - type: "reference", - reference: { - from, - to, - type: "filter", - }, - }); - } else if (typeof filter === "object" && !referencesOnly) { - // This is a filter, which can be reused - console.log(`Filter found in ${filePath}`); - this.cache.push({ - id: `${json.id}.${filter.id}`, - filePath: uri, - jsonPath: ["filters"], - type: "filter", - }); - } + try { + await this.saveFiltersToCache( + text, + from, + uri, + referencesOnly, + fullFileText + ); + } catch (error) { + console.error(`Error saving filters for ${from}`, error); } } else { console.log("No filters found in", filePath); @@ -420,6 +372,305 @@ export class CacheWorker { this.printCache(); } + private async saveTagRenderingsToCache( + text: string, + from: string, + fromUri: vscode.Uri, + referencesOnly = false, + fullFileText?: string + ) { + const json = JSON.parse(text); + + for (const tagRendering of json.tagRenderings) { + // Check if it is a string and not an object + if (typeof tagRendering === "string") { + // It is a reference + console.log( + `Reference found to tagRendering ${tagRendering} in ${from}` + ); + + // The range is dependent on whether we're dealing with a full file or not + // const fromStartEnd = fullFileText + // ? getStartEnd(fullFileText, [ + // ...from.split("."), + // "", + // filterReferenceIndex, + // ]) + // : getStartEnd(text, ["filter", filterReferenceIndex]); + + // The range depends on whether we're dealing with a full file or not + // const fromStartEnd = fullFileText + // ? getStartEnd(fullFileText, [ + // ...from.split("."), + // "tagRenderings", + // json.tagRenderings.indexOf(tagRendering), + // ]) + // : getStartEnd(text, [ + // "tagRenderings", + // json.tagRenderings.indexOf(tagRendering), + // ]); + + // const to = tagRendering.includes(".") + // ? `layers.${tagRendering.split(".")[0]}.tagRenderings.${ + // tagRendering.split(".")[1] + // }` + // : `layers.questions.tagRenderings.${tagRendering}`; + // const toFile = await vscode.workspace.findFiles( + // `**/assets/layers/${to.split("."[1])}/${to.split(".")[1]}.json` + // ); + // // Read toFile and get the text + // const toContent = await vscode.workspace.fs.readFile(toFile[0]); + // const toText = new TextDecoder().decode(toContent); + // const toJson = JSON.parse(toText); + // const trIndex = toJson.tagRenderings.findIndex( + // // eslint-disable-next-line @typescript-eslint/no-explicit-any + // (tr: any) => tr.id === tagRendering.split(".")?.pop() + // ); + // const toRange = getStartEnd(toText, ["tagRenderings", trIndex]); + + // this.cache.push({ + // id: tagRendering, + // filePath: fromUri, + // jsonPath: ["tagRenderings"], + // type: "reference", + // reference: { + // from: { + // id: from, + // uri: fromUri, + // range: fromStartEnd, + // }, + // to: { + // id: to, + // uri: toFile[0], + // range: toRange, + // }, + // type: "tagRendering", + // }, + // }); + } + // } else if (typeof tagRendering === "object") { + // // This is a tagRendering, or a reference to one + // if (tagRendering.builtin) { + // // This is a reference to a built-in tagRendering (or multiple ones) + // if (typeof tagRendering.builtin === "string") { + // // Single tagRendering + // console.log( + // `Reference found to ${tagRendering.builtin} in ${from}` + // ); + + // // The range depends on whether we're dealing with a full file or not + // const fromStartEnd = fullFileText + // ? getStartEnd(fullFileText, [ + // ...from.split("."), + // "tagRenderings", + // json.tagRenderings.indexOf(tagRendering), + // ]) + // : getStartEnd(text, [ + // "tagRenderings", + // json.tagRenderings.indexOf(tagRendering), + // ]); + + // const to = tagRendering.builtin.includes(".") + // ? `layers.${tagRendering.builtin.split(".")[0]}.tagRenderings.${ + // tagRendering.builtin.split(".")[1] + // }` + // : `layers.questions.tagRenderings.${tagRendering.builtin}`; + // const toFile = await vscode.workspace.findFiles( + // `**/assets/layers/${to.split("."[1])}/${to.split(".")[1]}.json` + // ); + // // Read toFile and get the text + // const toContent = await vscode.workspace.fs.readFile(toFile[0]); + // const toText = new TextDecoder().decode(toContent); + // const toJson = JSON.parse(toText); + // const trIndex = toJson.tagRenderings.findIndex( + // // eslint-disable-next-line @typescript-eslint/no-explicit-any + // (tr: any) => tr.id === tagRendering.builtin.split(".")?.pop() + // ); + // const toRange = getStartEnd(toText, ["tagRenderings", trIndex]); + + // this.cache.push({ + // id: tagRendering.builtin, + // filePath: fromUri, + // jsonPath: ["tagRenderings"], + // type: "reference", + // reference: { + // from: { + // id: from, + // uri: fromUri, + // range: fromStartEnd, + // }, + // to: { + // id: to, + // uri: toFile[0], + // range: toRange, + // }, + // type: "tagRendering", + // }, + // }); + // } else { + // // Multiple tagRenderings + // for (const builtinTagRendering of tagRendering.builtin) { + // console.log( + // `Reference found to ${builtinTagRendering} in ${from}` + // ); + + // // The range depends on whether we're dealing with a full file or not + // const fromStartEnd = fullFileText + // ? getStartEnd(fullFileText, [ + // ...from.split("."), + // "tagRenderings", + // json.tagRenderings.indexOf(tagRendering), + // ]) + // : getStartEnd(text, [ + // "tagRenderings", + // json.tagRenderings.indexOf(tagRendering), + // ]); + + // const to = builtinTagRendering.includes(".") + // ? `layers.${builtinTagRendering.split(".")[0]}.tagRenderings.${ + // builtinTagRendering.split(".")[1] + // }` + // : `layers.questions.tagRenderings.${builtinTagRendering}`; + // const toFile = await vscode.workspace.findFiles( + // `**/assets/layers/${to.split("."[1])}/${to.split(".")[1]}.json` + // ); + // // Read toFile and get the text + // const toContent = await vscode.workspace.fs.readFile(toFile[0]); + // const toText = new TextDecoder().decode(toContent); + // const toJson = JSON.parse(toText); + // const trIndex = toJson.tagRenderings.findIndex( + // // eslint-disable-next-line @typescript-eslint/no-explicit-any + // (tr: any) => tr.id === builtinTagRendering.split(".")?.pop() + // ); + // const toRange = getStartEnd(toText, ["tagRenderings", trIndex]); + + // this.cache.push({ + // id: builtinTagRendering, + // filePath: fromUri, + // jsonPath: ["tagRenderings"], + // type: "reference", + // reference: { + // from: { + // id: from, + // uri: fromUri, + // range: fromStartEnd, + // }, + // to: { + // id: to, + // uri: toFile[0], + // range: toRange, + // }, + // type: "tagRendering", + // }, + // }); + // } + // } + // } else if (!referencesOnly) { + // // This is a tagRendering, which can be reused + // console.log(`TagRendering found in ${from}`); + // this.cache.push({ + // id: `${json.id}.${tagRendering.id}`, + // filePath: fromUri, + // jsonPath: ["tagRenderings"], + // type: "tagRendering", + // }); + // } + } + } + + /** + * Save filters to cache + * + * @param text Text representation of layer + * @param from The theme or layer where the layer is from, e.g. layers.bicycle_rental or themes.cyclofix.layers.0 + * @param fromUri URI of the layer file + * @param referencesOnly Whether to only save references, or also the tagRenderings and filters. This is useful for inline layers, because their filters and tagRenderings can't be reused + * @param fullFileText The full text of the original theme file, used for calculating position + */ + private async saveFiltersToCache( + text: string, + from: string, + fromUri: vscode.Uri, + referencesOnly = false, + fullFileText?: string + ) { + const json = JSON.parse(text); + + for (const filter of json.filter) { + const filterReferenceIndex = json.filter.indexOf(filter); + + // Check if it is a string and not an object + if (typeof filter === "string") { + // It is a reference + console.log(`Reference found to filter ${filter} in ${from}`); + + // The range is dependent on whether we're dealing with a full file or not + const fromStartEnd = fullFileText + ? getStartEnd(fullFileText, [ + ...from.split("."), + "filter", + filterReferenceIndex, + ]) + : getStartEnd(text, ["filter", filterReferenceIndex]); + + const filterId = filter.includes(".") ? filter.split(".")[1] : filter; + const to = filter.includes(".") + ? `layers.${filter.split(".")[0]}` + : `layers.filters`; + // Now we'll need to determine what file we need to look in + const toFileName = filter.includes(".") + ? `**/assets/layers/${filter.split(".")[0]}/${ + filter.split(".")[0] + }.json` + : `**/assets/layers/filters/filters.json`; + const toFile = await vscode.workspace.findFiles(toFileName); + + const toContent = await vscode.workspace.fs.readFile(toFile[0]); + const toText = new TextDecoder().decode(toContent); + const toJson = JSON.parse(toText); + const toFilterIndex = toJson.filter.findIndex( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (f: any) => f.id === filterId + ); + + if (toFilterIndex === -1) { + console.error(`Filter ${filter} not found`); + continue; + } + const toRange = getStartEnd(toText, ["filter", toFilterIndex]); + + this.cache.push({ + id: filter, + filePath: fromUri, + jsonPath: ["filters"], + type: "reference", + reference: { + from: { + id: from, + uri: fromUri, + range: fromStartEnd, + }, + to: { + id: to, + uri: toFile[0], + range: toRange, + }, + type: "filter", + }, + }); + } else if (typeof filter === "object" && !referencesOnly) { + // This is a filter, which can be reused + console.log(`Filter found in ${from}`); + this.cache.push({ + id: `${json.id}.${filter.id}`, + filePath: fromUri, + jsonPath: ["filters"], + type: "filter", + }); + } + } + } + /** * Print the current cache state * TODO: This probably needs to be removed at some point @@ -522,7 +773,7 @@ export class Cache { public getReferences(to: string): CacheItem[] { return this.cache.filter((item) => { if (item.type === "reference") { - return item.reference?.to === to; + return item.reference?.to.id === to; } return false; }); @@ -570,23 +821,41 @@ interface CacheItem { */ interface Reference { /** - * The theme or layer where the reference is from - * - * @example themes.cyclofix + * The place where the item is being used (eg. themes.cyclofix) */ - from: string; + from: ReferenceDetail; /** - * The path of the file where the reference points to - * This can also be more specific, like a tagRendering or filter - * - * @example layers.bicycle_rental - * @example layers.questions.tagRenderings.name + * The place where the item is defined (eg. layers.bicycle_rental) */ - to: string; + to: ReferenceDetail; /** * The type of item being referenced/reused */ type: "tagRendering" | "filter" | "layer"; } + +interface ReferenceDetail { + /** + * The path of the file for this side of the reference + * + * @example layers.bicycle_rental + * @example layers.questions.tagRenderings.name + * @example themes.cyclofix + */ + id: string; + + /** + * The URI of the file for this side of the reference + */ + uri?: vscode.Uri; + + /** + * The range of the reference in the file + * Useful for highlighting the reference in the file + * + * Only defined when referencing to a part of a file + */ + range?: vscode.Range; +}