From 1834293be9372a00802df07e2b7568b825997826 Mon Sep 17 00:00:00 2001 From: eyza Date: Fri, 25 Oct 2024 19:12:57 +0200 Subject: [PATCH] Improved GSC file references when two or more workspace folders are referenced for each other. When there are 2 workspaces where in each there is one file with the same "game" path (e.q. maps\mp\gametypes\script.gsc), only the last file is used (according to the workspace folder order in explorer). It applies for function and variable definitions in hover provider, auto-completion etc. --- src/GscCompletionItemProvider.ts | 9 +- src/GscDiagnosticsCollection.ts | 3 - src/GscFile.ts | 33 +-- src/GscFileCache.ts | 82 ++++-- src/GscFileParser.ts | 2 + src/GscFiles.ts | 104 +++++-- src/GscFunctions.ts | 85 +++++- src/GscSemanticTokensProvider.ts | 2 +- src/test/Tests.test.ts | 14 +- src/test/workspace/GscAll.test.ts | 55 ---- .../workspace/GscAll/FunctionReferences.gsc | 26 -- .../GscCompletionItemProvider.test.ts | 2 +- .../GscFileReferences.1/LowerUpperCase.gsc | 26 ++ .../FunctionReferencesFile.gsc | 0 .../GscFileReferences.1/scripts/file1.gsc | 16 ++ .../scripts/file_replaced_1.gsc | 1 + .../scripts/file_replaced_1_2.gsc | 1 + .../scripts/file_replaced_all.gsc | 1 + .../GscFileReferences.2/.vscode/settings.json | 5 + .../GscFileReferences.2/scripts/file2.gsc | 16 ++ .../scripts/file_replaced_1_2.gsc | 1 + .../scripts/file_replaced_all.gsc | 1 + .../GscFileReferences.3/.vscode/settings.json | 6 + .../GscFileReferences.3/scripts/file3.gsc | 16 ++ .../scripts/file_replaced_all.gsc | 1 + src/test/workspace/GscFileReferences.test.ts | 257 ++++++++++++++++++ .../vscode-cod-gsc-tests.code-workspace | 9 + 27 files changed, 613 insertions(+), 161 deletions(-) delete mode 100644 src/test/workspace/GscAll/FunctionReferences.gsc create mode 100644 src/test/workspace/GscFileReferences.1/LowerUpperCase.gsc rename src/test/workspace/{GscAll/FunctionReferencesFolder => GscFileReferences.1/LowerUpperCaseFolder}/FunctionReferencesFile.gsc (100%) create mode 100644 src/test/workspace/GscFileReferences.1/scripts/file1.gsc create mode 100644 src/test/workspace/GscFileReferences.1/scripts/file_replaced_1.gsc create mode 100644 src/test/workspace/GscFileReferences.1/scripts/file_replaced_1_2.gsc create mode 100644 src/test/workspace/GscFileReferences.1/scripts/file_replaced_all.gsc create mode 100644 src/test/workspace/GscFileReferences.2/.vscode/settings.json create mode 100644 src/test/workspace/GscFileReferences.2/scripts/file2.gsc create mode 100644 src/test/workspace/GscFileReferences.2/scripts/file_replaced_1_2.gsc create mode 100644 src/test/workspace/GscFileReferences.2/scripts/file_replaced_all.gsc create mode 100644 src/test/workspace/GscFileReferences.3/.vscode/settings.json create mode 100644 src/test/workspace/GscFileReferences.3/scripts/file3.gsc create mode 100644 src/test/workspace/GscFileReferences.3/scripts/file_replaced_all.gsc create mode 100644 src/test/workspace/GscFileReferences.test.ts diff --git a/src/GscCompletionItemProvider.ts b/src/GscCompletionItemProvider.ts index 3f223ac..bdcdeb9 100644 --- a/src/GscCompletionItemProvider.ts +++ b/src/GscCompletionItemProvider.ts @@ -35,7 +35,7 @@ export class GscCompletionItemProvider implements vscode.CompletionItemProvider const currentGame = gscFile.config.currentGame; - const items = await GscCompletionItemProvider.getCompletionItems(gscFile, position, currentGame, undefined, document.uri); + const items = await GscCompletionItemProvider.getCompletionItems(gscFile, position, currentGame, undefined); return items; } catch (error) { @@ -57,8 +57,7 @@ export class GscCompletionItemProvider implements vscode.CompletionItemProvider gscFile: GscFile, position: vscode.Position, currentGame: GscGame, - config?: CompletionConfig, - uri?: vscode.Uri + config?: CompletionConfig ): Promise { const completionItems: vscode.CompletionItem[] = []; @@ -115,7 +114,7 @@ export class GscCompletionItemProvider implements vscode.CompletionItemProvider // Add items for variables like level.aaa, game["bbb"] and local1.aaa[0][1] if (!config || config.variableItems) { - this.createVariableItems(completionItems, functionGroup.localVariableDefinitions, variableBeforeCursor, inWord, inStructureVariable, inArrayBrackets, uri); + this.createVariableItems(completionItems, functionGroup.localVariableDefinitions, variableBeforeCursor, inWord, inStructureVariable, inArrayBrackets, gscFile.uri); } // Add items for predefined keywords (like true, false, undefined, if, else, waittillframeend, ...) if (!config || config.keywordItems) { @@ -182,7 +181,7 @@ export class GscCompletionItemProvider implements vscode.CompletionItemProvider const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri); if (workspaceFolder !== undefined) { - const gscFiles = GscFiles.getReferenceableCachedFiles(workspaceFolder); + const gscFiles = GscFiles.getReferenceableCachedFiles(workspaceFolder.uri, false); // Level variables if (variableBeforeCursor.startsWith("level")) { diff --git a/src/GscDiagnosticsCollection.ts b/src/GscDiagnosticsCollection.ts index 99e2915..c8efb95 100644 --- a/src/GscDiagnosticsCollection.ts +++ b/src/GscDiagnosticsCollection.ts @@ -170,9 +170,6 @@ export class GscDiagnosticsCollection { } } - // TODO check where this file is referenced to update particular files - // It will be crusual for #include files - this.diagnosticCollection?.set(uri, gscFile.diagnostics); // ------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/GscFile.ts b/src/GscFile.ts index 1144604..6649deb 100644 --- a/src/GscFile.ts +++ b/src/GscFile.ts @@ -1,27 +1,13 @@ import * as vscode from 'vscode'; import { GscData } from "./GscFileParser"; -import { ConfigErrorDiagnostics, GscConfig, GscGame, GscGameConfig, GscGameRootFolder } from './GscConfig'; -import { GscWorkspaceFileData } from './GscFileCache'; +import { ConfigErrorDiagnostics, GscConfig, GscGame } from './GscConfig'; +import { GscFilesConfig, GscWorkspaceFileData } from './GscFileCache'; +import { GscFiles } from './GscFiles'; /** * This type holds workspace configuration for current GSC file to easily access configuration without actually reading it from config file. * When configuration is changed, it is updated in all GscFile instances. */ -export type GscFileConfig = { - /** All possible game root folders where GSC files can be found and referenced. */ - referenceableGameRootFolders: GscGameRootFolder[]; - /** Ignored function names */ - ignoredFunctionNames: string[]; - /** Ignored file paths */ - ignoredFilePaths: string[]; - /** Currently selected game */ - currentGame: GscGame; - /** Mode of diagnostics collection */ - errorDiagnostics: ConfigErrorDiagnostics; - /** Syntax configuration of the selected game */ - gameConfig: GscGameConfig; -}; - export class GscFile { @@ -32,8 +18,11 @@ export class GscFile { /** URI of the file */ uri: vscode.Uri; + /** Path of this file from game perspective. For example: maps\mp\gametypes\script.gsc */ + gamePath: string; + /** Configuration related to this file */ - config: GscFileConfig; + config: GscFilesConfig; /** Diagnostics generated for this file. @see GscFileDiagnostics.ts */ diagnostics: vscode.Diagnostic[] = []; @@ -58,10 +47,14 @@ export class GscFile { this.uri = uri; if (workspaceFolder !== undefined) { - this.config = GscWorkspaceFileData.getConfig(workspaceFolder); + this.config = GscWorkspaceFileData.loadConfig(workspaceFolder); } else { this.config = { referenceableGameRootFolders: [], + referenceableGameRootFoldersAll: [], + referenceableWorkspaceFolders: [], + referenceableWorkspaceFoldersAll: [], + rootFolder: undefined, currentGame: GscGame.UniversalGame, ignoredFunctionNames: [], ignoredFilePaths: [], @@ -69,6 +62,8 @@ export class GscFile { gameConfig: GscConfig.gamesConfigs.get(GscGame.UniversalGame)! }; } + + this.gamePath = GscFiles.getGamePathFromGscFile(this); } updateData(data: GscData, version: number) { diff --git a/src/GscFileCache.ts b/src/GscFileCache.ts index c9f5f5d..28b4c6b 100644 --- a/src/GscFileCache.ts +++ b/src/GscFileCache.ts @@ -1,24 +1,50 @@ import * as vscode from 'vscode'; -import { GscFile, GscFileConfig } from './GscFile'; +import { GscFile } from './GscFile'; import { GscFiles } from './GscFiles'; -import { GscConfig } from './GscConfig'; +import { ConfigErrorDiagnostics, GscConfig, GscGame, GscGameConfig, GscGameRootFolder } from './GscConfig'; import { Events } from './Events'; import { LoggerOutput } from './LoggerOutput'; +// Configuration of workspace that applies to all files within the workspace +export type GscFilesConfig = { + /** All possible game root folders where GSC files can be found and referenced. */ + referenceableGameRootFolders: GscGameRootFolder[]; + /** All possible game root folders where GSC files can be found and referenced, including reverse references */ + referenceableGameRootFoldersAll: GscGameRootFolder[]; + + /** All possible workspace folders where GSC files can be found and referenced */ + referenceableWorkspaceFolders: vscode.WorkspaceFolder[]; + /** All possible workspace folders where GSC files can be found and referenced, including reverse references */ + referenceableWorkspaceFoldersAll: vscode.WorkspaceFolder[]; + + /** Game root folder */ + rootFolder: GscGameRootFolder | undefined; + /** Ignored function names */ + ignoredFunctionNames: string[]; + /** Ignored file paths */ + ignoredFilePaths: string[]; + /** Currently selected game */ + currentGame: GscGame; + /** Mode of diagnostics collection */ + errorDiagnostics: ConfigErrorDiagnostics; + /** Syntax configuration of the selected game */ + gameConfig: GscGameConfig; +}; + export class GscCachedFilesPerWorkspace { private cachedFilesPerWorkspace: Map = new Map(); - createNewWorkspaceFileData(workspaceFolder: vscode.WorkspaceFolder): GscWorkspaceFileData { + createNewWorkspace(workspaceFolder: vscode.WorkspaceFolder): GscWorkspaceFileData { const data = new GscWorkspaceFileData(workspaceFolder); this.cachedFilesPerWorkspace.set(workspaceFolder.uri.toString(), data); return data; } - getWorkspaceFileData(workspaceUri: vscode.Uri): GscWorkspaceFileData | undefined { + getWorkspace(workspaceUri: vscode.Uri): GscWorkspaceFileData | undefined { return this.cachedFilesPerWorkspace.get(workspaceUri.toString()); } @@ -28,15 +54,15 @@ export class GscCachedFilesPerWorkspace { if (workspaceFolder === undefined) { return; } - let dataOfWorkspace = this.getWorkspaceFileData(workspaceFolder.uri); + let dataOfWorkspace = this.getWorkspace(workspaceFolder.uri); if (dataOfWorkspace === undefined) { return; } dataOfWorkspace.removeParsedFile(fileUri); } - removeWorkspaceFiles(workspaceUri: vscode.Uri) { - const workspaceData = this.getWorkspaceFileData(workspaceUri); + removeWorkspace(workspaceUri: vscode.Uri) { + const workspaceData = this.getWorkspace(workspaceUri); if (workspaceData === undefined) { return false; } @@ -60,9 +86,13 @@ export class GscCachedFilesPerWorkspace { export class GscWorkspaceFileData { private parsedFiles: Map = new Map(); + public config: GscFilesConfig; + constructor( public workspaceFolder: vscode.WorkspaceFolder - ) {} + ) { + this.config = GscWorkspaceFileData.loadConfig(this.workspaceFolder); + } addParsedFile(gscFile: GscFile) { if (!this.parsedFiles.has(gscFile.id)) { @@ -107,29 +137,35 @@ export class GscWorkspaceFileData { } updateConfiguration() { - const data = GscWorkspaceFileData.getConfig(this.workspaceFolder); + this.config = GscWorkspaceFileData.loadConfig(this.workspaceFolder); // Loop all GscFile and update their configuration for (const file of this.parsedFiles.values()) { - file.config.referenceableGameRootFolders = data.referenceableGameRootFolders; - file.config.currentGame = data.currentGame; - file.config.ignoredFunctionNames = data.ignoredFunctionNames; - file.config.ignoredFilePaths = data.ignoredFilePaths; - file.config.errorDiagnostics = data.errorDiagnostics; - file.config.gameConfig = data.gameConfig; + file.config = this.config; + + file.gamePath = GscFiles.getGamePathFromGscFile(file); } } - static getConfig(workspaceFolder: vscode.WorkspaceFolder): GscFileConfig { - // Get config for workspace folder - const referenceableGameRootFolders = GscFiles.getReferenceableGameRootFolders(workspaceFolder); + + /** + * Loads current settings for the workspace folder and returns them as GscFilesConfig object. + */ + public static loadConfig(workspaceFolder: vscode.WorkspaceFolder): GscFilesConfig { const currentGame = GscConfig.getSelectedGame(workspaceFolder.uri); - const ignoredFunctionNames = GscConfig.getIgnoredFunctionNames(workspaceFolder.uri); - const ignoredFilePaths = GscConfig.getIgnoredFilePaths(workspaceFolder.uri); - const errorDiagnostics = GscConfig.getErrorDiagnostics(workspaceFolder.uri); - const gameConfig = GscConfig.gamesConfigs.get(currentGame)!; - return {referenceableGameRootFolders, currentGame, ignoredFunctionNames, ignoredFilePaths, errorDiagnostics, gameConfig}; + return { + referenceableGameRootFolders: GscFiles.loadReferenceableGameRootFolders(workspaceFolder, false), + referenceableGameRootFoldersAll: GscFiles.loadReferenceableGameRootFolders(workspaceFolder, true), + referenceableWorkspaceFolders: GscFiles.loadReferenceableWorkspaceFolders(workspaceFolder, false), + referenceableWorkspaceFoldersAll: GscFiles.loadReferenceableWorkspaceFolders(workspaceFolder, true), + rootFolder: GscConfig.getGameRootFolder(workspaceFolder.uri), + currentGame: currentGame, + ignoredFunctionNames: GscConfig.getIgnoredFunctionNames(workspaceFolder.uri), + ignoredFilePaths: GscConfig.getIgnoredFilePaths(workspaceFolder.uri), + errorDiagnostics: GscConfig.getErrorDiagnostics(workspaceFolder.uri), + gameConfig: GscConfig.gamesConfigs.get(currentGame)! + }; } } diff --git a/src/GscFileParser.ts b/src/GscFileParser.ts index d14854a..2a097be 100644 --- a/src/GscFileParser.ts +++ b/src/GscFileParser.ts @@ -2258,6 +2258,8 @@ export class GscFileParser { } const funcName = innerGroup.items[0].items[0].getSingleToken()!.name; func = new GscFunction( + innerGroup, + innerGroup.items[0].items[0], funcName, funcName.toLowerCase(), paramTokens, diff --git a/src/GscFiles.ts b/src/GscFiles.ts index 9eff240..3e9472f 100644 --- a/src/GscFiles.ts +++ b/src/GscFiles.ts @@ -90,9 +90,9 @@ export class GscFiles { } // Get data of workspace that contains cached files - let dataOfWorkspace = this.cachedFiles.getWorkspaceFileData(workspaceFolder.uri); + let dataOfWorkspace = this.cachedFiles.getWorkspace(workspaceFolder.uri); if (dataOfWorkspace === undefined) { - dataOfWorkspace = this.cachedFiles.createNewWorkspaceFileData(workspaceFolder); + dataOfWorkspace = this.cachedFiles.createNewWorkspace(workspaceFolder); } // Try to get cached file @@ -221,7 +221,7 @@ export class GscFiles { } } - let dataOfWorkspace = this.cachedFiles.getWorkspaceFileData(workspaceUri); + let dataOfWorkspace = this.cachedFiles.getWorkspace(workspaceUri); if (dataOfWorkspace === undefined) { return undefined; // Given workspace was not saved in memory yet, meaning no file in this workspace was parsed @@ -249,7 +249,7 @@ export class GscFiles { } else { for (const workspaceUri of workspaceUris) { - const workspaceData = this.cachedFiles.getWorkspaceFileData(workspaceUri); + const workspaceData = this.cachedFiles.getWorkspace(workspaceUri); if (workspaceData !== undefined) { allParsedFiles = allParsedFiles.concat(workspaceData.getAllParsedFileData()); } @@ -315,13 +315,14 @@ export class GscFiles { /** - * Gets all possible workspace folders where the file can be found and referenced. + * Loads all possible workspace folders where the file can be found and referenced. * It includes current workspace and included workspace folders via settings. * The workspace folders are sorted by their indexes (first being the last workspace folder in editor). * @param workspaceFolder Workspace folder from which the reference is made. + * @param includeReversedReferences If true, also workspace folders that include the current workspace folder are included. * @returns Array of workspace folders where the file can be found. */ - public static getReferenceableWorkspaceFolders(workspaceFolder: vscode.WorkspaceFolder): vscode.WorkspaceFolder[] { + public static loadReferenceableWorkspaceFolders(workspaceFolder: vscode.WorkspaceFolder, includeReversedReferences: boolean): vscode.WorkspaceFolder[] { /* Define all possible locations where the file can be found @@ -329,6 +330,12 @@ export class GscFiles { - Included workspace folders via settings */ + /* + raw + mod - includes raw + mappack - includes mod and raw + */ + // Get included workspace folders from settings const includedWorkspaceFolderNames = GscConfig.getIncludedWorkspaceFolders(workspaceFolder.uri); // Convert them to WorkspaceFolder objects @@ -339,6 +346,17 @@ export class GscFiles { // Add also this document includedWorkspaceFolders.push(workspaceFolder); + // Find workspace folders that includes this workspace folder + if (includeReversedReferences) { + for (const workspaceFolder2 of vscode.workspace.workspaceFolders ?? []) { + const cfgIncludedWorkspaceFolderNames = GscConfig.getIncludedWorkspaceFolders(workspaceFolder2.uri); + // This workspace folder includes the referenced workspace folder + if (cfgIncludedWorkspaceFolderNames.includes(workspaceFolder.name)) { + includedWorkspaceFolders.push(workspaceFolder2); + } + } + } + // Sort the folders by workspace indexes // By this users can specify the order of mods loaded in game includedWorkspaceFolders.sort((a, b) => { @@ -348,7 +366,10 @@ export class GscFiles { // Reverse to loop the last folder as first includedWorkspaceFolders.reverse(); - return includedWorkspaceFolders; + // Return unique workspace folders + const uniqueWorkspaceFolders = Array.from(new Set(includedWorkspaceFolders)); + + return uniqueWorkspaceFolders; } @@ -356,17 +377,30 @@ export class GscFiles { * Gets all possible cached GSC files thats are referenceable from the specified workspace folder. * It includes files from current workspace and files from included workspace folders via settings. * @param workspaceFolder Workspace folder from which the files are referenced. + * @param includeReplacedFiles If true, all cached files are returned. If false, only files that would be referenced by the game are returned (e.g. there are 2 files with the same game path in 2 workspace folders, only the last workspace file will be used). * @returns Array of cached files that are available for the workspace folder. */ - public static getReferenceableCachedFiles(workspaceFolder: vscode.WorkspaceFolder): GscFile[] { + public static getReferenceableCachedFiles(workspaceFolderUri: vscode.Uri, includeReplacedFiles: boolean): GscFile[] { + + const workspaceCache = this.cachedFiles.getWorkspace(workspaceFolderUri); // Define all possible locations where the file can be found (globally included workspace folders) - const includedWorkspaceFolders = GscFiles.getReferenceableWorkspaceFolders(workspaceFolder); + const includedWorkspaceFolders = workspaceCache?.config.referenceableWorkspaceFolders ?? []; // Get all cached files from defined workspace folders const cachedFiles = this.getCachedFiles(includedWorkspaceFolders.map(f => f.uri)); - return cachedFiles; + if (includeReplacedFiles) { + return cachedFiles; + } else { + const newCachedFiles: Map = new Map(); + for (const gscFile of cachedFiles) { + if (!newCachedFiles.has(gscFile.gamePath)) { + newCachedFiles.set(gscFile.gamePath, gscFile); + } + } + return Array.from(newCachedFiles.values()); + } } @@ -374,12 +408,13 @@ export class GscFiles { * Get all possible game root folders where GSC files can be found and referenced. * It includes game root folder from current workspace and game root folders from included workspace folders via settings. * @param workspaceFolder Workspace folder from which the game root folders are referenced. + * @param includeReversedReferences If true, also workspace folders that include the current workspace folder are included. * @returns Array of game root folders where the file can be found. The first item is the game root folder from the workspace where the file is located. Other items are game root folders from included workspace folders sorted by their indexes. */ - public static getReferenceableGameRootFolders(workspaceFolder: vscode.WorkspaceFolder): GscGameRootFolder[] { + public static loadReferenceableGameRootFolders(workspaceFolder: vscode.WorkspaceFolder, includeReversedReferences: boolean): GscGameRootFolder[] { // Define all possible locations where the file can be found (globally included workspace folders) - const includedWorkspaceFolders = GscFiles.getReferenceableWorkspaceFolders(workspaceFolder); + const includedWorkspaceFolders = GscFiles.loadReferenceableWorkspaceFolders(workspaceFolder, includeReversedReferences); // Convert workspace folders to URIs taking game root folder into account const uris = includedWorkspaceFolders.map(f => GscConfig.getGameRootFolder(f.uri)!); @@ -394,9 +429,11 @@ export class GscFiles { * @param referencedFilePath Path in GSC file format (e.g. scripts\scriptName) * @returns The reference status and the parsed file data if found */ - public static getReferencedFileForFile(gscFile: GscFile, referencedFilePath: string): GscFileAndReferenceState { + public static getReferencedFileForFile(gscFile: GscFile, referencedFilePath: string, includeReversedReferences: boolean = false): GscFileAndReferenceState { - for (const referenceableGameRoot of gscFile.config.referenceableGameRootFolders) { + const gameRootFolders = includeReversedReferences ? gscFile.config.referenceableGameRootFoldersAll : gscFile.config.referenceableGameRootFolders; + + for (const referenceableGameRoot of gameRootFolders) { const gscFilePathUri = vscode.Uri.joinPath(referenceableGameRoot.uri, referencedFilePath.replace(/\\/g, '/') + ".gsc"); const gsc = GscFiles.getCachedFile(gscFilePathUri, referenceableGameRoot.workspaceFolder.uri); @@ -404,6 +441,22 @@ export class GscFiles { continue; // not found, try next workspace folder } + // File found, check if it is replaced by another file in another workspace folder + if (includeReversedReferences === false) { + const fileWithReverseReferences = this.getReferencedFileForFile(gscFile, referencedFilePath, true); + + if (gsc !== fileWithReverseReferences.gscFile && fileWithReverseReferences.referenceState !== GscFileReferenceState.NotFound) { + // File is replaced by another file in another workspace folder + if (fileWithReverseReferences.referenceState === GscFileReferenceState.IncludedWorkspaceFolder) { + fileWithReverseReferences.referenceState = GscFileReferenceState.IncludedWorkspaceFolderReversed; + } else if (fileWithReverseReferences.referenceState === GscFileReferenceState.LocalFile) { + // This should not happen + throw new Error("File is replaced by another file in another workspace folder, but it is not included workspace folder"); + } + return fileWithReverseReferences; + } + } + var state = GscFileReferenceState.LocalFile; if (referenceableGameRoot.workspaceFolder !== gscFile.workspaceFolder) { state = GscFileReferenceState.IncludedWorkspaceFolder; @@ -416,6 +469,21 @@ export class GscFiles { } + /** + * Converts the file URI into game path considering game root folder (eg. "c:/mod/src/maps/mp/script.gsc" -> "maps\mp\script.gsc") + */ + public static getGamePathFromGscFile(gscFile: GscFile): string { + if (gscFile.workspaceFolder && gscFile.config.rootFolder) { + const path = gscFile.uri.fsPath.replace(gscFile.config.rootFolder.uri.fsPath, "").replace(/\//g, '\\'); + return path; + } else { + return vscode.workspace.asRelativePath(gscFile.uri).replace(/\//g, '\\'); + } + } + + + + @@ -639,7 +707,7 @@ export class GscFiles { for (const removed of e.removed) { LoggerOutput.log("[GscFiles] Workspace folder removed", removed.uri.toString()); // Remove all cached files and the workspace itself - this.cachedFiles.removeWorkspaceFiles(removed.uri); + this.cachedFiles.removeWorkspace(removed.uri); } for (const added of e.added) { @@ -898,13 +966,11 @@ export class GscFiles { - - - export enum GscFileReferenceState { NotFound, LocalFile, - IncludedWorkspaceFolder + IncludedWorkspaceFolder, + IncludedWorkspaceFolderReversed } export type GscFileAndReferenceState = { diff --git a/src/GscFunctions.ts b/src/GscFunctions.ts index 700abf8..0a51076 100644 --- a/src/GscFunctions.ts +++ b/src/GscFunctions.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { GscGroup, GscToken, GscVariableDefinitionType } from './GscFileParser'; +import { GroupType, GscGroup, GscToken, GscVariableDefinitionType } from './GscFileParser'; import { GscFiles, GscFileReferenceState } from './GscFiles'; import { GscFile } from './GscFile'; import { CodFunctions } from './CodFunctions'; @@ -8,6 +8,10 @@ import { CodFunctions } from './CodFunctions'; export class GscFunction { constructor( + /** Function definition (type GroupType.FunctionDefinition) */ + public group: GscGroup, + /** Function name (type GroupType.FunctionDefinition) */ + public groupFunctionName: GscGroup, /** Function name (original as it is defined in file) */ public name: string, /** Lower-case function name, used to compare the same function names */ @@ -84,6 +88,12 @@ export type GscVariableDefinition = { }; +export type GscFunctionReference = { + func: GscGroup, + uri: vscode.Uri +}; + + @@ -212,12 +222,22 @@ export class GscFunctions { return undefined; } + let reason = ""; + switch (referenceData.referenceState) { + case GscFileReferenceState.IncludedWorkspaceFolder: + reason = "Included via workspace folder settings"; + break; + case GscFileReferenceState.IncludedWorkspaceFolderReversed: + reason = "Included via other workspace folder settings. \nFile in current workspace is being overwritten."; + break; + } + for (const f of referencedGscFile.data.functions) { if (!funcInfo || f.nameId === funcNameId) { const funcDefinition: GscFunctionDefinition = { func: f, uri: referencedGscFile.uri, - reason: referenceData.referenceState === GscFileReferenceState.IncludedWorkspaceFolder ? "Included via workspace folder settings" : "" + reason: reason }; funcDefinitions.push(funcDefinition); } @@ -270,4 +290,65 @@ export class GscFunctions { return funcDefinitions; } + + + /** + * Get all function references (function definitions, call or function pointer) in all GSC files for specified function name and file path. + */ + public static getFunctionReferences(gscFile: GscFile, funcInfo: {name: string, path: string | ""} | undefined) : GscFunctionReference[] | undefined { + + let funcReferences: GscFunctionReference[] = []; + const funcNameId = funcInfo ? funcInfo.name.toLowerCase() : undefined; + + // Get function definitions + const funcDefs = GscFunctions.getFunctionDefinitions(gscFile, funcInfo); + if (funcDefs === undefined || funcDefs.length === 0) { + return undefined; // Function not found + } + + // Add function definitions + funcReferences.push(...funcDefs.map(f => {return {func: f.func.groupFunctionName, uri: f.uri};})); + + // Find all function references in all files + const allFiles = gscFile.workspaceFolder ? GscFiles.getReferenceableCachedFiles(gscFile.workspaceFolder.uri, false) : [gscFile]; + + for (const gscFile2 of allFiles) { + gscFile2.data.root.walk((group) => { + if (group.type === GroupType.FunctionName && group.parent?.typeEqualsToOneOf(GroupType.FunctionCall, GroupType.FunctionPointer)) { + + const funcInfo2 = group.getFunctionReferenceInfo(); + if (funcInfo2 === undefined) { + return; // not a function + } + + if (funcInfo2.name.toLowerCase() !== funcNameId) { + return; // not the function we are looking for + } + + const funcDefs2 = GscFunctions.getFunctionDefinitions(gscFile2, funcInfo2); + if (funcDefs2 === undefined || funcDefs2.length === 0) { + return; // file not found + } + + //console.log("Found function call: " + funcInfo2.name + " in " + file.uri.fsPath + " at " + group.getRange().start.line + ":" + group.getRange().start.character); + + // Loop found definitions of this functuion reference + for (const funcDef2 of funcDefs2) { + // Check if function definitions match the found functions + for (const funcDef of funcDefs) { + + if (funcDef2.func.nameId === funcDef.func.nameId && funcDef2.uri.toString() === funcDef.uri.toString()) { + // Found + funcReferences.push({func: group, uri: gscFile2.uri}); + //console.log("Adding location: " + funcDef2.func.name + " in " + file.uri + " at " + group.getRange().start.line + ":" + group.getRange().start.character); + //return; + } + } + } + } + }); + } + + return funcReferences; + } } \ No newline at end of file diff --git a/src/GscSemanticTokensProvider.ts b/src/GscSemanticTokensProvider.ts index cf331f9..a67a197 100644 --- a/src/GscSemanticTokensProvider.ts +++ b/src/GscSemanticTokensProvider.ts @@ -68,7 +68,7 @@ export class GscSemanticTokensProvider implements vscode.DocumentSemanticTokensP const builder = new vscode.SemanticTokensBuilder(GscSemanticTokensProvider.legend); // Get the parsed file - var gscFile = await GscFiles.getCachedFile(document.uri); + var gscFile = GscFiles.getCachedFile(document.uri); // If the cached file is not found, or cached version is not the latest, wait till the file is parsed if (gscFile === undefined || (gscFile.version > -1 && gscFile.version !== document.version)) { diff --git a/src/test/Tests.test.ts b/src/test/Tests.test.ts index fd2ca24..9f8ec88 100644 --- a/src/test/Tests.test.ts +++ b/src/test/Tests.test.ts @@ -23,6 +23,9 @@ export async function activateExtension() { const extension = vscode.extensions.getExtension(EXTENSION_ID); assert.ok(extension, "Extension should be available"); + // Clear all editors + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + if (extension.isActive) { return; } @@ -46,9 +49,6 @@ export async function activateExtension() { await waitGsc; assert.ok(extension!.isActive, "Extension should be activated"); - - // Clear all editors - void vscode.commands.executeCommand('workbench.action.closeAllEditors'); } @@ -116,8 +116,8 @@ export function checkQuickFix(codeActions: vscode.CodeAction[], index: number, e export function checkHover(hover: vscode.Hover | undefined, expected: string) { - assert.ok(hover !== undefined); - assert.ok(hover.contents.length === 1); + assert.ok(hover !== undefined, `Hover is undefined - Actual: ${hover}, Expected: defined`); + assert.deepStrictEqual(hover.contents.length, 1); assert.ok(hover.contents[0] instanceof vscode.MarkdownString); assert.deepStrictEqual(hover.contents[0].value, expected, "Not equal:\n\nCurrent:\n'" + hover.contents[0].value + "'\n\nExpected:\n'" + expected + "'\n\n"); } @@ -135,8 +135,8 @@ export function getFunctionDescription(name: string, parameters: {name: string, export function checkDefinition(locations: vscode.Location[], expectedFileEnd: string) { assert.ok(locations !== undefined, "Locations are undefined"); - assert.ok(locations.length === 1, "Locations does not contain exactly one item"); - assert.ok(locations[0].uri.path.endsWith(expectedFileEnd), "Expected file end: " + expectedFileEnd + ". Actual: " + locations[0].uri.path); + assert.deepStrictEqual(locations.length, 1, "Locations does not contain exactly one item"); + assert.deepStrictEqual(locations[0].uri.path.slice(-expectedFileEnd.length), expectedFileEnd, "Expected file end: " + expectedFileEnd + ". Actual: " + locations[0].uri.path); } export async function checkDefinitionFunc(gscFile: GscFile, pos: vscode.Position, pathUri: string) { diff --git a/src/test/workspace/GscAll.test.ts b/src/test/workspace/GscAll.test.ts index d12356e..cab5bad 100644 --- a/src/test/workspace/GscAll.test.ts +++ b/src/test/workspace/GscAll.test.ts @@ -17,61 +17,6 @@ suite('GscAll', () => { await tests.activateExtension(); }); - - // Check case insensitivity of function calls (file paths, function names) - test('GscAll.FunctionReferences -> error + hover + definition', async () => { - try { - const gsc = await tests.loadGscFile(['GscAll', 'FunctionReferences.gsc']); - - // There should be no error - everything is case insensitive - assert.ok(gsc.diagnostics.length === 0); - - // Correct path - // FunctionReferencesFolder\FunctionReferencesFile::funcName(); - const hover1 = await GscHoverProvider.getHover(gsc, new vscode.Position(5, 58)); - tests.checkHover(hover1, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc").toString()).value); - //checkHover(hover1, "\n```\nfuncName()\n```\nFile: ```GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc```"); - const locations1 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(5, 58)); - tests.checkDefinition(locations1, "GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc"); - - // Lowercase path - // functionreferencesfolder\FunctionReferencesFile::funcName(); - const hover2 = await GscHoverProvider.getHover(gsc, new vscode.Position(8, 58)); - tests.checkHover(hover2, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc").toString()).value); - const locations2 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(8, 58)); - tests.checkDefinition(locations2, "GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc"); - - // Lowercase path + file - // functionreferencesfolder\functionreferencesfile::funcName(); - const hover3 = await GscHoverProvider.getHover(gsc, new vscode.Position(11, 58)); - tests.checkHover(hover3, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc").toString()).value); - const locations3 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(11, 58)); - tests.checkDefinition(locations3, "GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc"); - - // Lowercase path + file + func name - // functionreferencesfolder\functionreferencesfile::funcname(); - const hover4 = await GscHoverProvider.getHover(gsc, new vscode.Position(14, 58)); - tests.checkHover(hover4, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc").toString()).value); - const locations4 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(14, 58)); - tests.checkDefinition(locations4, "GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc"); - - // Hover over "includedFuncName();" - its case insensitive, external file should be found - const hover5 = await GscHoverProvider.getHover(gsc, new vscode.Position(17, 8)); - tests.checkHover(hover5, GscFunction.generateMarkdownDescription({name: "includedFuncName", parameters: []}, false, tests.filePathToUri("GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc").toString(), "Included via '#include'").value); - const locations5 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(17, 8)); - tests.checkDefinition(locations5, "GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc"); - - // Hover over "localFunc();" - its case insensitive, local func should be found - const hover6 = await GscHoverProvider.getHover(gsc, new vscode.Position(20, 8)); - tests.checkHover(hover6, GscFunction.generateMarkdownDescription({name: "LOCALFUNC", parameters: []}, true, undefined, undefined).value); - const locations6 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(20, 8)); - tests.checkDefinition(locations6, "GscAll/FunctionReferences.gsc"); - - } catch (error) { - tests.printDebugInfoForError(error); - } - }); - test('GscAll.SingleLine', async () => { const gsc = await tests.loadGscFile(['GscAll', 'SingleLine.gsc']); diff --git a/src/test/workspace/GscAll/FunctionReferences.gsc b/src/test/workspace/GscAll/FunctionReferences.gsc deleted file mode 100644 index 8fe53fe..0000000 --- a/src/test/workspace/GscAll/FunctionReferences.gsc +++ /dev/null @@ -1,26 +0,0 @@ -#include functionreferencesfolder\functionreferencesfile; - -main() { - - // Correct path - FunctionReferencesFolder\FunctionReferencesFile::funcName(); - - // Lowercase path - functionreferencesfolder\FunctionReferencesFile::funcName(); - - // Lowercase path + file - functionreferencesfolder\functionreferencesfile::funcName(); - - // Lowercase path + file + func name - functionreferencesfolder\functionreferencesfile::funcname(); - - // Hover sais: defined! - includedFuncName(); - - // Hover sais: defined! - localFunc(); -} - -LOCALFUNC() { - -} \ No newline at end of file diff --git a/src/test/workspace/GscCompletionItemProvider.test.ts b/src/test/workspace/GscCompletionItemProvider.test.ts index 3f3df97..b4e9783 100644 --- a/src/test/workspace/GscCompletionItemProvider.test.ts +++ b/src/test/workspace/GscCompletionItemProvider.test.ts @@ -102,7 +102,7 @@ suite('GscCompletionItemProvider', () => { tests.checkDiagnostic(gsc.diagnostics, 1, "Unexpected token", vscode.DiagnosticSeverity.Error); assert.ok(gsc.diagnostics.length === 2); - const completions = await GscCompletionItemProvider.getCompletionItems(gsc, new vscode.Position(3, 10), GscGame.UniversalGame, cfgVariablesOnly, gsc.uri); + const completions = await GscCompletionItemProvider.getCompletionItems(gsc, new vscode.Position(3, 10), GscGame.UniversalGame, cfgVariablesOnly); tests.checkCompletions(gsc, completions, 0, "aaa_included", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer]); tests.checkCompletions(gsc, completions, 1, "bbb_included", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer]); diff --git a/src/test/workspace/GscFileReferences.1/LowerUpperCase.gsc b/src/test/workspace/GscFileReferences.1/LowerUpperCase.gsc new file mode 100644 index 0000000..5034d98 --- /dev/null +++ b/src/test/workspace/GscFileReferences.1/LowerUpperCase.gsc @@ -0,0 +1,26 @@ +#include loweruppercasefolder\functionreferencesfile; + +main() { + + // Correct path + LowerUpperCaseFolder\FunctionReferencesFile::funcName(); + + // Lowercase path + loweruppercasefolder\FunctionReferencesFile::funcName(); + + // Lowercase path + file + loweruppercasefolder\functionreferencesfile::funcName(); + + // Lowercase path + file + func name + loweruppercasefolder\functionreferencesfile::funcname(); + + // Hover sais: defined! + includedFuncName(); + + // Hover sais: defined! + localFunc(); +} + +LOCALFUNC() { + +} \ No newline at end of file diff --git a/src/test/workspace/GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc b/src/test/workspace/GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc similarity index 100% rename from src/test/workspace/GscAll/FunctionReferencesFolder/FunctionReferencesFile.gsc rename to src/test/workspace/GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc diff --git a/src/test/workspace/GscFileReferences.1/scripts/file1.gsc b/src/test/workspace/GscFileReferences.1/scripts/file1.gsc new file mode 100644 index 0000000..7a97369 --- /dev/null +++ b/src/test/workspace/GscFileReferences.1/scripts/file1.gsc @@ -0,0 +1,16 @@ +main() { + scripts\file_replaced_all::main(); // File: GscFileReferences.3/scripts/file_replaced_all.gsc + + scripts\file_replaced_1_2::main(); // File: GscFileReferences.2/scripts/file_replaced_1_2.gsc + + scripts\file_replaced_1::main(); // File: GscFileReferences.1/scripts/file_replaced_1.gsc + + + scripts\file1::main(); + scripts\file2::main(); + scripts\file3::main(); + + + level.file1 = 1; + +} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.1/scripts/file_replaced_1.gsc b/src/test/workspace/GscFileReferences.1/scripts/file_replaced_1.gsc new file mode 100644 index 0000000..d3273e3 --- /dev/null +++ b/src/test/workspace/GscFileReferences.1/scripts/file_replaced_1.gsc @@ -0,0 +1 @@ +main() {} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.1/scripts/file_replaced_1_2.gsc b/src/test/workspace/GscFileReferences.1/scripts/file_replaced_1_2.gsc new file mode 100644 index 0000000..d3273e3 --- /dev/null +++ b/src/test/workspace/GscFileReferences.1/scripts/file_replaced_1_2.gsc @@ -0,0 +1 @@ +main() {} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.1/scripts/file_replaced_all.gsc b/src/test/workspace/GscFileReferences.1/scripts/file_replaced_all.gsc new file mode 100644 index 0000000..d3273e3 --- /dev/null +++ b/src/test/workspace/GscFileReferences.1/scripts/file_replaced_all.gsc @@ -0,0 +1 @@ +main() {} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.2/.vscode/settings.json b/src/test/workspace/GscFileReferences.2/.vscode/settings.json new file mode 100644 index 0000000..4832c87 --- /dev/null +++ b/src/test/workspace/GscFileReferences.2/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "gsc.includedWorkspaceFolders": [ + "GscFileReferences.1" + ] +} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.2/scripts/file2.gsc b/src/test/workspace/GscFileReferences.2/scripts/file2.gsc new file mode 100644 index 0000000..473db03 --- /dev/null +++ b/src/test/workspace/GscFileReferences.2/scripts/file2.gsc @@ -0,0 +1,16 @@ +main() { + scripts\file_replaced_all::main(); // File: GscFileReferences.3/scripts/file_replaced_all.gsc + + scripts\file_replaced_1_2::main(); // File: GscFileReferences.2/scripts/file_replaced_1_2.gsc + + scripts\file_replaced_1::main(); // File: GscFileReferences.1/scripts/file_replaced_1.gsc + + + scripts\file1::main(); + scripts\file2::main(); + scripts\file3::main(); + + + level.file2 = 1; + +} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.2/scripts/file_replaced_1_2.gsc b/src/test/workspace/GscFileReferences.2/scripts/file_replaced_1_2.gsc new file mode 100644 index 0000000..d3273e3 --- /dev/null +++ b/src/test/workspace/GscFileReferences.2/scripts/file_replaced_1_2.gsc @@ -0,0 +1 @@ +main() {} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.2/scripts/file_replaced_all.gsc b/src/test/workspace/GscFileReferences.2/scripts/file_replaced_all.gsc new file mode 100644 index 0000000..d3273e3 --- /dev/null +++ b/src/test/workspace/GscFileReferences.2/scripts/file_replaced_all.gsc @@ -0,0 +1 @@ +main() {} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.3/.vscode/settings.json b/src/test/workspace/GscFileReferences.3/.vscode/settings.json new file mode 100644 index 0000000..8e211b4 --- /dev/null +++ b/src/test/workspace/GscFileReferences.3/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "gsc.includedWorkspaceFolders": [ + "GscFileReferences.2", + "GscFileReferences.1" + ] +} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.3/scripts/file3.gsc b/src/test/workspace/GscFileReferences.3/scripts/file3.gsc new file mode 100644 index 0000000..9e2f10c --- /dev/null +++ b/src/test/workspace/GscFileReferences.3/scripts/file3.gsc @@ -0,0 +1,16 @@ +main() { + scripts\file_replaced_all::main(); // File: GscFileReferences.3/scripts/file_replaced_all.gsc + + scripts\file_replaced_1_2::main(); // File: GscFileReferences.2/scripts/file_replaced_1_2.gsc + + scripts\file_replaced_1::main(); // File: GscFileReferences.1/scripts/file_replaced_1.gsc + + + scripts\file1::main(); + scripts\file2::main(); + scripts\file3::main(); + + + level.file3 = 1; + +} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.3/scripts/file_replaced_all.gsc b/src/test/workspace/GscFileReferences.3/scripts/file_replaced_all.gsc new file mode 100644 index 0000000..d3273e3 --- /dev/null +++ b/src/test/workspace/GscFileReferences.3/scripts/file_replaced_all.gsc @@ -0,0 +1 @@ +main() {} \ No newline at end of file diff --git a/src/test/workspace/GscFileReferences.test.ts b/src/test/workspace/GscFileReferences.test.ts new file mode 100644 index 0000000..f1db3fd --- /dev/null +++ b/src/test/workspace/GscFileReferences.test.ts @@ -0,0 +1,257 @@ +import * as vscode from 'vscode'; +import * as tests from '../Tests.test'; +import assert from 'assert'; +import { GscHoverProvider } from '../../GscHoverProvider'; +import { GscFunction } from '../../GscFunctions'; +import { GscDefinitionProvider } from '../../GscDefinitionProvider'; +import { GscCompletionItemProvider } from '../../GscCompletionItemProvider'; +import { GscVariableDefinitionType } from '../../GscFileParser'; + + +suite('GscFileReferences', () => { + + setup(async () => { + await tests.activateExtension(); + }); + + + // Check case insensitivity of function calls (file paths, function names) + test('GscFileReferences - lower upper case', async () => { + try { + const gsc = await tests.loadGscFile(['GscFileReferences.1', 'LowerUpperCase.gsc']); + + // There should be no error - everything is case insensitive + assert.ok(gsc.diagnostics.length === 0); + + // Correct path + // FunctionReferencesFolder\FunctionReferencesFile::funcName(); + const hover1 = await GscHoverProvider.getHover(gsc, new vscode.Position(5, 57)); + tests.checkHover(hover1, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc").toString()).value); + //checkHover(hover1, "\n```\nfuncName()\n```\nFile: ```GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc```"); + const locations1 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(5, 57)); + tests.checkDefinition(locations1, "GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc"); + + // Lowercase path + // functionreferencesfolder\FunctionReferencesFile::funcName(); + const hover2 = await GscHoverProvider.getHover(gsc, new vscode.Position(8, 57)); + tests.checkHover(hover2, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc").toString()).value); + const locations2 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(8, 57)); + tests.checkDefinition(locations2, "GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc"); + + // Lowercase path + file + // functionreferencesfolder\functionreferencesfile::funcName(); + const hover3 = await GscHoverProvider.getHover(gsc, new vscode.Position(11, 57)); + tests.checkHover(hover3, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc").toString()).value); + const locations3 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(11, 57)); + tests.checkDefinition(locations3, "GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc"); + + // Lowercase path + file + func name + // functionreferencesfolder\functionreferencesfile::funcname(); + const hover4 = await GscHoverProvider.getHover(gsc, new vscode.Position(14, 57)); + tests.checkHover(hover4, GscFunction.generateMarkdownDescription({name: "funcName", parameters: []}, false, tests.filePathToUri("GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc").toString()).value); + const locations4 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(14, 57)); + tests.checkDefinition(locations4, "GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc"); + + // Hover over "includedFuncName();" - its case insensitive, external file should be found + const hover5 = await GscHoverProvider.getHover(gsc, new vscode.Position(17, 8)); + tests.checkHover(hover5, GscFunction.generateMarkdownDescription({name: "includedFuncName", parameters: []}, false, tests.filePathToUri("GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc").toString(), "Included via '#include'").value); + const locations5 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(17, 8)); + tests.checkDefinition(locations5, "GscFileReferences.1/LowerUpperCaseFolder/FunctionReferencesFile.gsc"); + + // Hover over "localFunc();" - its case insensitive, local func should be found + const hover6 = await GscHoverProvider.getHover(gsc, new vscode.Position(20, 8)); + tests.checkHover(hover6, GscFunction.generateMarkdownDescription({name: "LOCALFUNC", parameters: []}, true, undefined, undefined).value); + const locations6 = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, new vscode.Position(20, 8)); + tests.checkDefinition(locations6, "GscFileReferences.1/LowerUpperCase.gsc"); + + } catch (error) { + tests.printDebugInfoForError(error); + } + }); + + + // Check case insensitivity of function calls (file paths, function names) + test('GscFileReferences - included workspace folders 1', async () => { + try { + var gsc = await tests.loadGscFile(['GscFileReferences.1', 'scripts', 'file1.gsc']); + + + tests.checkDiagnostic(gsc.diagnostics, 0, "File 'scripts\\file2.gsc' was not found in workspace folder 'GscFileReferences.1'", vscode.DiagnosticSeverity.Error); + tests.checkDiagnostic(gsc.diagnostics, 1, "File 'scripts\\file3.gsc' was not found in workspace folder 'GscFileReferences.1'", vscode.DiagnosticSeverity.Error); + assert.ok(gsc.diagnostics.length === 2); + + + var position = new vscode.Position(1, 34); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.3/scripts/file_replaced_all.gsc", "Included via other workspace folder settings. \nFile in current workspace is being overwritten."); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.3/scripts/file_replaced_all.gsc"); + + var position = new vscode.Position(3, 34); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.2/scripts/file_replaced_1_2.gsc", "Included via other workspace folder settings. \nFile in current workspace is being overwritten."); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.2/scripts/file_replaced_1_2.gsc"); + + var position = new vscode.Position(5, 32); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.1/scripts/file_replaced_1.gsc", ""); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.1/scripts/file_replaced_1.gsc"); + + var position = new vscode.Position(8, 22); + var hover = await GscHoverProvider.getHover(gsc, position); + tests.checkHover(hover, GscFunction.generateMarkdownDescription({name: "main", parameters: []}, true, undefined, undefined).value); + var locations = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, position); + tests.checkDefinition(locations, "GscFileReferences.1/scripts/file1.gsc"); + + + // Write text into document + const document = await vscode.window.showTextDocument(gsc.uri); + await document.edit(editBuilder => { + editBuilder.insert(new vscode.Position(14, 4), "level."); + }); + await tests.waitForDiagnosticsChange(gsc.uri); + + var gsc = await tests.loadGscFile(['GscFileReferences.1', 'scripts', 'file1.gsc']); + + const completions = await GscCompletionItemProvider.getCompletionItems(gsc, new vscode.Position(14, 10), gsc.config.currentGame, undefined); + + tests.checkCompletions(gsc, completions, 0, "file1", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer], undefined); + assert.ok(completions.length === 1); + + // Close text editor + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + + + } catch (error) { + tests.printDebugInfoForError(error); + } + }); + + + + + + + // Check case insensitivity of function calls (file paths, function names) + test('GscFileReferences - included workspace folders 2', async () => { + try { + var gsc = await tests.loadGscFile(['GscFileReferences.2', 'scripts', 'file2.gsc']); + + + tests.checkDiagnostic(gsc.diagnostics, 0, "File 'scripts\\file3.gsc' was not found in workspace folder 'GscFileReferences.2', 'GscFileReferences.1'", vscode.DiagnosticSeverity.Error); + assert.ok(gsc.diagnostics.length === 1); + + + var position = new vscode.Position(1, 34); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.3/scripts/file_replaced_all.gsc", "Included via other workspace folder settings. \nFile in current workspace is being overwritten."); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.3/scripts/file_replaced_all.gsc"); + + var position = new vscode.Position(3, 34); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.2/scripts/file_replaced_1_2.gsc", ""); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.2/scripts/file_replaced_1_2.gsc"); + + var position = new vscode.Position(5, 32); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.1/scripts/file_replaced_1.gsc", "Included via workspace folder settings"); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.1/scripts/file_replaced_1.gsc"); + + + var position = new vscode.Position(8, 22); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.1/scripts/file1.gsc", "Included via workspace folder settings"); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.1/scripts/file1.gsc"); + + var position = new vscode.Position(9, 22); + var hover = await GscHoverProvider.getHover(gsc, position); + tests.checkHover(hover, GscFunction.generateMarkdownDescription({name: "main", parameters: []}, true, undefined, undefined).value); + var locations = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, position); + tests.checkDefinition(locations, "GscFileReferences.2/scripts/file2.gsc"); + + + // Write text into document + const document = await vscode.window.showTextDocument(gsc.uri); + await document.edit(editBuilder => { + editBuilder.insert(new vscode.Position(14, 4), "level."); + }); + await tests.waitForDiagnosticsChange(gsc.uri); + + var gsc = await tests.loadGscFile(['GscFileReferences.2', 'scripts', 'file2.gsc']); + + const completions = await GscCompletionItemProvider.getCompletionItems(gsc, new vscode.Position(14, 10), gsc.config.currentGame, undefined); + + tests.checkCompletions(gsc, completions, 0, "file2", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer], undefined); + tests.checkCompletions(gsc, completions, 1, "file1", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer], undefined); + assert.ok(completions.length === 2); + + // Close text editor + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + + + } catch (error) { + tests.printDebugInfoForError(error); + } + }); + + + + + + + // Check case insensitivity of function calls (file paths, function names) + test('GscFileReferences - included workspace folders 3', async () => { + try { + var gsc = await tests.loadGscFile(['GscFileReferences.3', 'scripts', 'file3.gsc']); + + + assert.ok(gsc.diagnostics.length === 0); + + + var position = new vscode.Position(1, 34); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.3/scripts/file_replaced_all.gsc", ""); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.3/scripts/file_replaced_all.gsc"); + + var position = new vscode.Position(3, 34); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.2/scripts/file_replaced_1_2.gsc", "Included via workspace folder settings"); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.2/scripts/file_replaced_1_2.gsc"); + + var position = new vscode.Position(5, 32); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.1/scripts/file_replaced_1.gsc", "Included via workspace folder settings"); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.1/scripts/file_replaced_1.gsc"); + + + var position = new vscode.Position(8, 22); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.1/scripts/file1.gsc", "Included via workspace folder settings"); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.1/scripts/file1.gsc"); + + var position = new vscode.Position(9, 22); + await tests.checkHoverExternalFunc(gsc, position, "main", [], "GscFileReferences.2/scripts/file2.gsc", "Included via workspace folder settings"); + await tests.checkDefinitionFunc(gsc, position, "GscFileReferences.2/scripts/file2.gsc"); + + var position = new vscode.Position(10, 22); + var hover = await GscHoverProvider.getHover(gsc, position); + tests.checkHover(hover, GscFunction.generateMarkdownDescription({name: "main", parameters: []}, true, undefined, undefined).value); + var locations = await GscDefinitionProvider.getFunctionDefinitionLocations(gsc, position); + tests.checkDefinition(locations, "GscFileReferences.3/scripts/file3.gsc"); + + + // Write text into document + const document = await vscode.window.showTextDocument(gsc.uri); + await document.edit(editBuilder => { + editBuilder.insert(new vscode.Position(14, 4), "level."); + }); + await tests.waitForDiagnosticsChange(gsc.uri); + + var gsc = await tests.loadGscFile(['GscFileReferences.3', 'scripts', 'file3.gsc']); + + const completions = await GscCompletionItemProvider.getCompletionItems(gsc, new vscode.Position(14, 10), gsc.config.currentGame, undefined); + + tests.checkCompletions(gsc, completions, 0, "file3", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer], undefined); + tests.checkCompletions(gsc, completions, 1, "file2", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer], undefined); + tests.checkCompletions(gsc, completions, 2, "file1", vscode.CompletionItemKind.Field, [GscVariableDefinitionType.Integer], undefined); + assert.ok(completions.length === 3); + + // Close text editor + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + + + } catch (error) { + tests.printDebugInfoForError(error); + } + }); + + +}); diff --git a/src/test/workspace/vscode-cod-gsc-tests.code-workspace b/src/test/workspace/vscode-cod-gsc-tests.code-workspace index 2b2ddce..d99ea7b 100644 --- a/src/test/workspace/vscode-cod-gsc-tests.code-workspace +++ b/src/test/workspace/vscode-cod-gsc-tests.code-workspace @@ -46,6 +46,15 @@ }, { "path": "GscFiles" + }, + { + "path": "GscFileReferences.1" + }, + { + "path": "GscFileReferences.2" + }, + { + "path": "GscFileReferences.3" } ] } \ No newline at end of file