From 89d125e12aa7765b52e598c3e679a7ec3693f49e Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Fri, 24 Jan 2025 18:49:02 -0600 Subject: [PATCH 01/73] Add Help command and show release notes on version update - Updated README.md to point to the project home page for full documentation. - Added "edk2code.help" command in package.json to provide a help option. - Registered the "edk2code.help" command in extension.ts to open the documentation URL. - Corrected the help URL for Cscope installation in cscope.ts. - Introduced showReleaseNotes function in newVersionMessage.ts to display release notes when the extension version updates. - Added utility functions in utils.ts to retrieve the current version and documentation URL from package.json. --- README.md | 2 +- package.json | 4 ++++ src/cscope.ts | 2 +- src/extension.ts | 9 +++++++-- src/newVersionMessage.ts | 30 ++++++++++++++++++++++++++++++ src/utils.ts | 11 +++++++++++ 6 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/newVersionMessage.ts diff --git a/README.md b/README.md index 5358679..82831e8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Ubuntu installation example: `sudo apt install cscope` ## Documentation -Check full documentation including setup on [project wiki page](https://github.com/intel/Edk2Code/wiki) +Check full documentation including setup on [project home page](https://intel.github.io/Edk2Code/) ## Contributing diff --git a/package.json b/package.json index 04ee72a..386281d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,10 @@ "main": "./out/extension.js", "contributes": { "commands": [ + { + "command": "edk2code.help", + "title": "EDK2: Help" + }, { "command": "edk2code.openConfigurationUi", "title": "EDK2: Workspace configuration (UI)" diff --git a/src/cscope.ts b/src/cscope.ts index bbd9653..2f2902c 100644 --- a/src/cscope.ts +++ b/src/cscope.ts @@ -120,7 +120,7 @@ export class Cscope { void vscode.window.showErrorMessage("Cscope not available in the system. Check help for install", "Help").then(async selection => { if (selection === "Help"){ await vscode.env.openExternal(vscode.Uri.parse( - 'https://github.com/intel/Edk2Code/wiki#cscope')); + 'https://intel.github.io/Edk2Code/requirements/#cscope')); } }); } diff --git a/src/extension.ts b/src/extension.ts index 0eaf912..3f5bd03 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,13 +15,14 @@ import { GuidProvider } from './Languages/guidProvider'; import { PathFind } from './pathfind'; import { EdkWorkspaces } from './index/edkWorkspace'; import { Edk2CallHierarchyProvider } from './callHiearchy'; -import { copyToClipboard, getCurrentDocument, gotoFile, showVirtualFile } from './utils'; +import { copyToClipboard, getCurrentDocument, getDocsUrl, gotoFile, showVirtualFile } from './utils'; import { ParserFactory } from './edkParser/parserFactory'; import { TreeDetailsDataProvider } from './TreeDataProvider'; import { DiagnosticManager } from './diagnostics'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; import { TreeItem } from './treeElements/TreeItem'; +import { showReleaseNotes } from './newVersionMessage'; // Global variables @@ -77,7 +78,11 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.openConfigurationUi', async ()=>{await cmds.openWpConfigGui();}), vscode.commands.registerCommand('edk2code.openConfigurationJson', async ()=>{await cmds.openWpConfigJson();}), vscode.commands.registerCommand('edk2code.rescanIndex', async ()=>{await cmds.rescanIndex();}), + vscode.commands.registerCommand('edk2code.help', async ()=>{ + const docUrl = getDocsUrl(); + await vscode.env.openExternal(vscode.Uri.parse(docUrl)); + }), vscode.commands.registerCommand('edk2code.openFile', async ()=>{await cmds.openFile();}), vscode.commands.registerCommand('edk2code.openLib', async()=>{await cmds.openLib();}), vscode.commands.registerCommand('edk2code.openModule', async ()=>{await cmds.openModule();}), @@ -200,7 +205,7 @@ export async function activate(context: vscode.ExtensionContext) { }); await vscode.commands.executeCommand('setContext', 'edk2code.isNodeFocusBackStack', false); - + void showReleaseNotes(context); } diff --git a/src/newVersionMessage.ts b/src/newVersionMessage.ts new file mode 100644 index 0000000..22332ef --- /dev/null +++ b/src/newVersionMessage.ts @@ -0,0 +1,30 @@ +import { getCurrentVersion } from "./utils"; +import * as vscode from 'vscode'; + + +export async function showReleaseNotes(context: vscode.ExtensionContext){ + const CURRENT_VERSION = getCurrentVersion(); + const previousVersion = context.globalState.get('extensionVersion'); + if (previousVersion !== CURRENT_VERSION) { + const panel = vscode.window.createWebviewPanel( + 'releaseNotes', + `EDK2Code Release Notes ${CURRENT_VERSION}`, + vscode.ViewColumn.One, + { + enableScripts: true, + } + ); + + panel.webview.html = ` +

Edk2Code ${CURRENT_VERSION}

+

Thanks for using the Edk2Code Vscode extension.

+

πŸŽ‰ If you find this extension useful πŸŽ‰:

+ +

View new features

+ `; + await context.globalState.update('extensionVersion', CURRENT_VERSION); + } +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index d2bda4b..48f4952 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -556,3 +556,14 @@ async function _walk(dir: string, baseDir: string, filelist: string[] = []) { return filelist; } +export function getCurrentVersion(): string { + const packageJsonPath = path.join(__dirname, '..', 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return packageJson.version; +} + +export function getDocsUrl():string{ + const packageJsonPath = path.join(__dirname, '..', 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return packageJson.homepage; +} \ No newline at end of file From fbc7bc5efac9e539047592b08776135fe9fff013 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Fri, 24 Jan 2025 20:03:56 -0600 Subject: [PATCH 02/73] goto statements outside of form/endform pairs in VFR/HFR files cause extension host to crash - In languageParser.ts, modified the condition in the addSymbol method to skip blocks where block.start and block.end are undefined. --- src/edkParser/languageParser.ts | 2 +- src/edkParser/vfrParser.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/edkParser/languageParser.ts b/src/edkParser/languageParser.ts index 29c0ab5..0945040 100644 --- a/src/edkParser/languageParser.ts +++ b/src/edkParser/languageParser.ts @@ -445,7 +445,7 @@ addSymbol(symbol: EdkSymbol) { this.decrementLineIndex(); let parserResult = block.parse(this); if (parserResult) { - if (block.isRoot) { continue; } + if (block.isRoot || (block.start === undefined && block.end === undefined)) { continue; } parseIndex = -1; // reset index if (this.lineIndex >= this.document.lineCount) { return; diff --git a/src/edkParser/vfrParser.ts b/src/edkParser/vfrParser.ts index 3754537..9fc6155 100644 --- a/src/edkParser/vfrParser.ts +++ b/src/edkParser/vfrParser.ts @@ -117,8 +117,8 @@ class BlockNumericSection extends BlockParser { class BlockGotoSection extends BlockParser { name = "Goto"; tag = /\bgoto\b/gi; - start = undefined; - end = undefined; + start = undefined; + end = undefined; type = Edk2SymbolType.vfrGoto; visible:boolean = true; context: BlockParser[] = [ @@ -141,7 +141,7 @@ export class VfrParser extends DocumentParser { new BlockStringSection(), new BlockPasswordSection(), new BlockNumericSection(), - new BlockGotoSection(), + new BlockGotoSection(true), ]; } From b4e1e9cb88bec2d9ec58c7e7eb87399f7a24d2ae Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Mon, 27 Jan 2025 11:30:19 -0600 Subject: [PATCH 03/73] to trace in multiple files to align with the new logging strategy. This affects logging statements in BuildFolder, CompletionProvider, DeclarationProvider, DefinitionProvider, ConfigAgent, GrayoutController, WorkspaceDefinitions, EdkWorkspace, PathFind, rgSearch, and EdkSymbol. - Updated the DebugLog class to use vscode.LogOutputChannel instead of vscode.OutputChannel for better logging capabilities. - Added a new logger instance initialization in extension.ts --- src/Languages/buildFolder.ts | 10 +++---- src/Languages/completionProvider.ts | 2 +- src/Languages/declarationProvider.ts | 2 +- src/Languages/definitionProvider.ts | 2 +- src/configuration.ts | 2 +- src/contextState/cmds.ts | 6 ++-- src/debugLog.ts | 41 ++++++---------------------- src/edkParser/languageParser.ts | 6 ++-- src/extension.ts | 1 + src/grayout.ts | 4 +-- src/index/definitions.ts | 2 +- src/index/edkWorkspace.ts | 18 ++++++------ src/pathfind.ts | 2 +- src/rg.ts | 12 ++++---- src/symbols/edkSymbols.ts | 2 +- src/treeElements/Library.ts | 2 +- 16 files changed, 46 insertions(+), 68 deletions(-) diff --git a/src/Languages/buildFolder.ts b/src/Languages/buildFolder.ts index 1216d5c..5cef1ca 100644 --- a/src/Languages/buildFolder.ts +++ b/src/Languages/buildFolder.ts @@ -30,7 +30,7 @@ export class BuildFolder { */ async getBuildOptions() { try { - gDebugLog.verbose("getBuildOptions()"); + gDebugLog.trace("getBuildOptions()"); // todo: change for multiple build folders let buildDefines: Map = new Map(); let dscFiles:Set = new Set(); @@ -43,7 +43,7 @@ export class BuildFolder { for (const buildOption of l.split(",")) { let [val, data] = split(buildOption, ":", 2); - gDebugLog.verbose(`Define: ${val}: ${data}`); + gDebugLog.trace(`Define: ${val}: ${data}`); buildDefines.set(val.trim(), data.trim()); } } @@ -60,7 +60,7 @@ export class BuildFolder { const newBuildActivePlatform = path.normalize(buildActivePlatform).replace(oldWorkspacePath, gWorkspacePath); if (fs.existsSync(newBuildActivePlatform)) { buildActivePlatform = newBuildActivePlatform; - gDebugLog.verbose(`Corrected Active platform: ${buildActivePlatform}`); + gDebugLog.trace(`Corrected Active platform: ${buildActivePlatform}`); if(this.replaceWorkspacePath !== undefined && this.replaceWorkspacePath !== oldWorkspacePath){ gDebugLog.error(`Multiple original workspace paths found: ${this.replaceWorkspacePath} and ${oldWorkspacePath}`); } @@ -77,7 +77,7 @@ export class BuildFolder { buildActivePlatform = getRealPathRelative(buildActivePlatform); - gDebugLog.verbose(`Active platform: ${buildActivePlatform}`); + gDebugLog.trace(`Active platform: ${buildActivePlatform}`); dscFiles.add(buildActivePlatform); } } @@ -100,7 +100,7 @@ export class BuildFolder { } const part = wrongPathParts.shift(); oldPathWorkspace.push(part); - gDebugLog.verbose(`Removed part: ${part}`); + gDebugLog.trace(`Removed part: ${part}`); } return undefined; } diff --git a/src/Languages/completionProvider.ts b/src/Languages/completionProvider.ts index 3d5f770..df59215 100644 --- a/src/Languages/completionProvider.ts +++ b/src/Languages/completionProvider.ts @@ -16,7 +16,7 @@ export class EdkCompletionProvider implements vscode.CompletionItemProvider { await parser.parseFile(); let selectedSymbol = parser.getSelectedSymbol(position); if (!selectedSymbol) { return []; } - gDebugLog.verbose(`Completion for: ${selectedSymbol.toString()}`); + gDebugLog.trace(`Completion for: ${selectedSymbol.toString()}`); if (selectedSymbol.onCompletion !== undefined) { let temp = await selectedSymbol.onCompletion(document, position, token, context); diff --git a/src/Languages/declarationProvider.ts b/src/Languages/declarationProvider.ts index ebec73b..aadc869 100644 --- a/src/Languages/declarationProvider.ts +++ b/src/Languages/declarationProvider.ts @@ -16,7 +16,7 @@ export class EdkDeclarationProvider implements vscode.DeclarationProvider { await parser.parseFile(); let selectedSymbol = parser.getSelectedSymbol(position); if (!selectedSymbol) { return []; } - gDebugLog.verbose(`Definition for: ${selectedSymbol.toString()}`); + gDebugLog.trace(`Definition for: ${selectedSymbol.toString()}`); if (selectedSymbol.onDeclaration !== undefined) { let temp = await selectedSymbol.onDeclaration(); return temp; diff --git a/src/Languages/definitionProvider.ts b/src/Languages/definitionProvider.ts index 5d91bf4..6778e86 100644 --- a/src/Languages/definitionProvider.ts +++ b/src/Languages/definitionProvider.ts @@ -63,7 +63,7 @@ export class EdkDefinitionProvider implements vscode.DefinitionProvider { await parser.parseFile(); let selectedSymbol = parser.getSelectedSymbol(position); if (!selectedSymbol) { return []; } - gDebugLog.verbose(`Definition for: ${selectedSymbol.toString()}`); + gDebugLog.trace(`Definition for: ${selectedSymbol.toString()}`); if (selectedSymbol.onDefinition !== undefined) { let temp = await selectedSymbol.onDefinition(parser); return temp; diff --git a/src/configuration.ts b/src/configuration.ts index c7c59d6..0a956eb 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -72,7 +72,7 @@ export class ConfigAgent { private readWpConfig(){ let settingsPath = getEdkCodeFolderFilePath(this.settingsFileName); - gDebugLog.verbose(`Loading configuration from ${settingsPath}`); + gDebugLog.trace(`Loading configuration from ${settingsPath}`); if(existsEdkCodeFolderFile(this.settingsFileName)){ try { diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 86c3f4f..c3f3dc0 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -25,7 +25,7 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; export async function rebuildIndexDatabase() { - gDebugLog.verbose("rebuildIndexDatabase()"); + gDebugLog.trace("rebuildIndexDatabase()"); // Pick build folder let buildPath = await vscode.window.showOpenDialog({ @@ -92,12 +92,12 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; } - gDebugLog.verbose("Loading from build"); + gDebugLog.trace("Loading from build"); let buildFolder = new BuildFolder(selectedFolders); let buildData = await buildFolder.getBuildOptions(); if (buildData) { - gDebugLog.verbose("Delete workspace files"); + gDebugLog.trace("Delete workspace files"); // await gEdkDatabase.clearWorkspace(); deleteEdkCodeFolder(); gConfigAgent.clearWpConfiguration(); diff --git a/src/debugLog.ts b/src/debugLog.ts index 846a366..6a25e64 100644 --- a/src/debugLog.ts +++ b/src/debugLog.ts @@ -11,11 +11,11 @@ export enum LogLevel { } export class DebugLog { - outConsole: vscode.OutputChannel; + outConsole: vscode.LogOutputChannel; public constructor() { - this.outConsole = vscode.window.createOutputChannel("EDK2Code"); + this.outConsole = vscode.window.createOutputChannel("EDK2Code", {log:true}); } public show(){ @@ -27,51 +27,28 @@ export class DebugLog { } public error(text:string){ - this.out(`[Edk2Code Error] ${text}`, LogLevel.error); + this.outConsole.error(text); let callStack = (new Error()).stack || ''; - this.out(`[Edk2Code Stack]\n${callStack}`, LogLevel.error); + this.outConsole.error(`[Stack]\n${callStack}`); } public info(text:string){ - this.out(`[Edk2Code Info] ${text}`, LogLevel.info); + this.outConsole.info(text); } - public verbose(text:string){ - this.out(`[Edk2Code Verb] ${text}`, LogLevel.verbose); + public trace(text:string){ + this.outConsole.trace(text); } public warning(text:string){ - this.out(`[Edk2Code Warn] ${text}`, LogLevel.warning); + this.outConsole.warn(text); } public debug(text:string){ - this.out(`[Edk2Code Debug] ${text}`, LogLevel.debug); + this.outConsole.debug(text); } - public out(text:string, level:LogLevel|undefined = undefined){ - if(level === undefined){ - this.outConsole.appendLine(text); - return; - } - - if(gConfigAgent === undefined){ - this.outConsole.appendLine(text); - console.log(text); - return; - } - - if (level <= gConfigAgent.getLogLevel()){ - this.outConsole.appendLine(text); - if(level === LogLevel.error){ - console.error(text); - }else if(level===LogLevel.warning){ - console.warn(text); - }else{ - console.info(text); - } - } - } } \ No newline at end of file diff --git a/src/edkParser/languageParser.ts b/src/edkParser/languageParser.ts index 0945040..6226d40 100644 --- a/src/edkParser/languageParser.ts +++ b/src/edkParser/languageParser.ts @@ -45,7 +45,7 @@ export abstract class BlockParser { } if (textLine.match(this.tag)) { - gDebugLog.verbose(`New block ${this.name} (${this.tag.toString()} -> ${textLine})`); + gDebugLog.trace(`New block ${this.name} (${this.tag.toString()} -> ${textLine})`); // Check if symbol is root definition if (this.isRoot && !docParser.isInRoot()) { @@ -111,12 +111,12 @@ export abstract class BlockParser { const contextLength = this.context.length; for (let i = 0; i < contextLength; i++) { const blockContext = this.context[i]; - gDebugLog.verbose(`Parse block: ${blockContext.name}`); + gDebugLog.trace(`Parse block: ${blockContext.name}`); // decrement line index as parser will get next line by default docParser.decrementLineIndex(); const isSymbolAdded = blockContext.parse(docParser); if (isSymbolAdded) { - gDebugLog.verbose("Added symbol"); + gDebugLog.trace("Added symbol"); if (blockContext.exclusive) { break; } diff --git a/src/extension.ts b/src/extension.ts index 3f5bd03..a9ca5fa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -55,6 +55,7 @@ export var gMapFileManager: MapFilesManager; // your extension is activated the very first time the command is executed export async function activate(context: vscode.ExtensionContext) { + const logger = vscode.window.createOutputChannel('grepc', { log: true }); diff --git a/src/grayout.ts b/src/grayout.ts index 8c24de1..031f339 100644 --- a/src/grayout.ts +++ b/src/grayout.ts @@ -26,13 +26,13 @@ export class GrayoutController { grayoutRange(unusdedRanges:vscode.Range[]) { - gDebugLog.verbose("grayoutRange()"); + gDebugLog.trace("grayoutRange()"); let activeEditor = vscode.window.activeTextEditor; if(!activeEditor){return;} if(activeEditor.document !== this.document){return;} - gDebugLog.verbose(`Unused Ranges: ${JSON.stringify(unusdedRanges)}`); + gDebugLog.trace(`Unused Ranges: ${JSON.stringify(unusdedRanges)}`); this.disposeDecoration(); diff --git a/src/index/definitions.ts b/src/index/definitions.ts index c6a4b3f..50e79c6 100644 --- a/src/index/definitions.ts +++ b/src/index/definitions.ts @@ -58,7 +58,7 @@ export class WorkspaceDefinitions { } setDefinition(key:string, value:string, location:vscode.Location|undefined){ - gDebugLog.verbose(`setDefinition: ${key} = ${value}`); + gDebugLog.trace(`setDefinition: ${key} = ${value}`); this.defines.set(key, {name: key, value:value, location:location}); } diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index e6d26f2..b9e9b29 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -274,11 +274,11 @@ async getWorkspace(uri: vscode.Uri): Promise { async loadConfig() { this.workspaces = []; - gDebugLog.verbose("Loading Configuration"); + gDebugLog.trace("Loading Configuration"); // TODO: enable to get more commands available //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', false); let dscPaths = gConfigAgent.getBuildDscPaths(); - gDebugLog.verbose(`dscPaths = ${dscPaths}`); + gDebugLog.trace(`dscPaths = ${dscPaths}`); for (const dscPath of dscPaths) { let dscDocument = await openTextDocument(vscode.Uri.file(path.join(gWorkspacePath, dscPath))); let edkWorkspace = new EdkWorkspace(dscDocument); @@ -393,7 +393,7 @@ export class EdkWorkspace { } // reset conditional stack this.workInProgress = true; - gDebugLog.verbose(`Start finding defines in ${this.mainDsc.fsPath}`); + gDebugLog.trace(`Start finding defines in ${this.mainDsc.fsPath}`); this.conditionalStack = []; this.defines.resetDefines(); @@ -425,7 +425,7 @@ export class EdkWorkspace { DiagnosticManager.error(conditionOpen.uri,conditionOpen.lineNo,EdkDiagnosticCodes.conditionalMissform, "Condition block not closed"); } this.workInProgress = false; - gDebugLog.verbose("Finding done."); + gDebugLog.trace("Finding done."); // Populate workspace definitions this.platformName = this.defines.getDefinition("PLATFORM_NAME") || undefined; @@ -537,7 +537,7 @@ export class EdkWorkspace { private async _processDocument(document: vscode.TextDocument, type: 'DSC' | 'FDF') { DiagnosticManager.clearProblems(document.uri); - gDebugLog.verbose(`_process${type}: ${document.fileName}`); + gDebugLog.trace(`_process${type}: ${document.fileName}`); if (this.isDocumentInIndex(document)) { gDebugLog.warning(`_process${type}: ${document.fileName} already in inactiveLines`); @@ -557,10 +557,10 @@ export class EdkWorkspace { let isRangeActive = false; let unuseRangeStart = 0; - gDebugLog.verbose(`# Parsing ${type} Document: ${document.uri.fsPath}`); + gDebugLog.trace(`# Parsing ${type} Document: ${document.uri.fsPath}`); for (let line of text) { lineIndex++; - gDebugLog.verbose(`\t\t${lineIndex}: ${line}`); + gDebugLog.trace(`\t\t${lineIndex}: ${line}`); line = this.stripComment(line); if (line.length === 0){continue;} @@ -874,7 +874,7 @@ export class EdkWorkspace { } // reset conditional stack this.workInProgress = true; - gDebugLog.verbose(`Start finding defines fdf`); + gDebugLog.trace(`Start finding defines fdf`); this.conditionalStack = []; if (this.flashDefinitionDocument) { @@ -884,7 +884,7 @@ export class EdkWorkspace { } } this.workInProgress = false; - gDebugLog.verbose("Finding fdf done."); + gDebugLog.trace("Finding fdf done."); return true; } diff --git a/src/pathfind.ts b/src/pathfind.ts index eecb74e..3637acc 100644 --- a/src/pathfind.ts +++ b/src/pathfind.ts @@ -42,7 +42,7 @@ export class PathFind{ async findPath(pathArg: string, relativePath: string|undefined = "") { pathArg = pathArg.replaceAll(/(\\+|\/+)/gi, path.sep); let ws = gWorkspacePath; - gDebugLog.verbose(`Looking for: ${pathArg}`); + gDebugLog.trace(`Looking for: ${pathArg}`); // Restrict path characters if (pathArg.match(/[\[\]\#\%\&\{\}\<\>\*\?\!\'\@\|\β€˜\`\β€œ,'"'\^]/gi)) { diff --git a/src/rg.ts b/src/rg.ts index 1f5f92b..4fad83d 100644 --- a/src/rg.ts +++ b/src/rg.ts @@ -32,18 +32,18 @@ export async function rgSearch(text:string, includeExt:string[]=[], filesPath:st command += " -- ./"; } - gDebugLog.verbose(command); + gDebugLog.trace(command); let textResult=""; try { edkStatusBar.setWorking(); textResult = await exec(command, gWorkspacePath); edkStatusBar.clearWorking(); } catch (error) { - gDebugLog.verbose(`rgSearch: ${error}`); + gDebugLog.trace(`rgSearch: ${error}`); resolve([]); } - gDebugLog.verbose(textResult); + gDebugLog.trace(textResult); let locations:Location[] = []; for (const jsonData of textResult.split('\n')) { if(jsonData===""){continue;} @@ -82,18 +82,18 @@ export async function rgSearchText(text:string, includeExt:string[]=[], filesPat command += " -- ./"; } - gDebugLog.verbose(command); + gDebugLog.trace(command); let textResult=""; try { edkStatusBar.setWorking(); textResult = await exec(command, gWorkspacePath); edkStatusBar.clearWorking(); } catch (error) { - gDebugLog.verbose(`rgSearch: ${error}`); + gDebugLog.trace(`rgSearch: ${error}`); resolve([]); } - gDebugLog.verbose(textResult); + gDebugLog.trace(textResult); let textMatches:string[] = []; for (const jsonData of textResult.split('\n')) { if(jsonData===""){continue;} diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 8fac23c..7037fa7 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -63,7 +63,7 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { if(parent){ this.sectionProperties = parent.sectionProperties; } - gDebugLog.verbose(`Symbol Created: ${location.range.start.line}: ${this.toString()}`); + gDebugLog.trace(`Symbol Created: ${location.range.start.line}: ${this.toString()}`); } toString() { diff --git a/src/treeElements/Library.ts b/src/treeElements/Library.ts index 0a600c1..60d557a 100644 --- a/src/treeElements/Library.ts +++ b/src/treeElements/Library.ts @@ -132,7 +132,7 @@ export class EdkInfNodeLibrary extends EdkInfNode{ this.setDuplicatedLibrary(); }else{ librarySet.add(uri.fsPath); - gDebugLog.verbose(`${uri.fsPath}`); + gDebugLog.trace(`${uri.fsPath}`); } } } From 7047be883c4fa9c6091b566fc30e2f4a974c5161 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Mon, 27 Jan 2025 12:04:44 -0600 Subject: [PATCH 04/73] Move new version page code to separate folder --- src/extension.ts | 2 +- src/{ => newVersionPage}/newVersionMessage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{ => newVersionPage}/newVersionMessage.ts (94%) diff --git a/src/extension.ts b/src/extension.ts index a9ca5fa..7a7f795 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,7 +22,7 @@ import { DiagnosticManager } from './diagnostics'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; import { TreeItem } from './treeElements/TreeItem'; -import { showReleaseNotes } from './newVersionMessage'; +import { showReleaseNotes } from './newVersionPage/newVersionMessage'; // Global variables diff --git a/src/newVersionMessage.ts b/src/newVersionPage/newVersionMessage.ts similarity index 94% rename from src/newVersionMessage.ts rename to src/newVersionPage/newVersionMessage.ts index 22332ef..9c09852 100644 --- a/src/newVersionMessage.ts +++ b/src/newVersionPage/newVersionMessage.ts @@ -1,4 +1,4 @@ -import { getCurrentVersion } from "./utils"; +import { getCurrentVersion } from "../utils"; import * as vscode from 'vscode'; From 08cb81355945caa68256a6e1cc896e42b78d93c6 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Fri, 7 Feb 2025 16:16:55 -0600 Subject: [PATCH 05/73] Update help URLs in various files to point to the new documentation structure --- package.json | 2 +- src/cscope.ts | 2 +- src/libraryTree.ts | 2 +- src/ui/messages.ts | 2 +- src/usedFileTracker.ts | 16 +++++----------- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 386281d..b2d892c 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ }, { "command": "edk2code.openModule", - "title": "EDK2: Open Module " + "title": "EDK2: Open Module" }, { "command": "edk2code.debugCommand", diff --git a/src/cscope.ts b/src/cscope.ts index 2f2902c..a0e1b04 100644 --- a/src/cscope.ts +++ b/src/cscope.ts @@ -120,7 +120,7 @@ export class Cscope { void vscode.window.showErrorMessage("Cscope not available in the system. Check help for install", "Help").then(async selection => { if (selection === "Help"){ await vscode.env.openExternal(vscode.Uri.parse( - 'https://intel.github.io/Edk2Code/requirements/#cscope')); + 'https://intel.github.io/Edk2Code/getting_started/#cscope-installation-windowslinux')); } }); } diff --git a/src/libraryTree.ts b/src/libraryTree.ts index 210e7e3..bbfe918 100644 --- a/src/libraryTree.ts +++ b/src/libraryTree.ts @@ -54,7 +54,7 @@ export async function showLibraryTree(){ void vscode.window.showErrorMessage("Module information was not generated during compilation.", "Help").then(async selection => { if (selection === "Help"){ await vscode.env.openExternal(vscode.Uri.parse( - 'https://github.com/intel/Edk2Code/wiki/Index-source-code#enable-compile-information')); + 'https://intel.github.io/Edk2Code/advance_features/#enabling-compile-information')); } }); return; diff --git a/src/ui/messages.ts b/src/ui/messages.ts index ed45ad8..88b69ad 100644 --- a/src/ui/messages.ts +++ b/src/ui/messages.ts @@ -17,7 +17,7 @@ export function askReloadFiles(){ export function infoMissingCompileInfo(){ void vscode.window.showInformationMessage("EDK2 Compile Information folder is missing.", "How to enable?").then(async selection => { if (selection === "How to enable?"){ - void vscode.env.openExternal(vscode.Uri.parse("https://github.com/intel/Edk2Code/wiki/Index-source-code#enable-compile-information")); + void vscode.env.openExternal(vscode.Uri.parse("https://intel.github.io/Edk2Code/advance_features/#how-to-enable-compile-information")); } }); } diff --git a/src/usedFileTracker.ts b/src/usedFileTracker.ts index c542a1f..e3b6f3f 100644 --- a/src/usedFileTracker.ts +++ b/src/usedFileTracker.ts @@ -28,7 +28,7 @@ export class FileUseWarning { if(langId===undefined){return;} if(!["c","cpp","edk2_dsc","edk2_inf","edk2_dec","asl","edk2_vfr","edk2_fdf", "edk2_uni"].includes(langId)){ edkStatusBar.setColor('statusBarItem.activeBackground'); - edkStatusBar.setHelpUrl("https://github.com/intel/Edk2Code/wiki"); + edkStatusBar.setHelpUrl("https://intel.github.io/Edk2Code/"); edkStatusBar.setText(`No EDK file`); return; } @@ -42,11 +42,6 @@ export class FileUseWarning { // // Notify user if file is in use // - - // edkStatusBar.setHelpUrl("https://github.com/intel/Edk2Code/wiki"); - // edkStatusBar.setText(""); - // edkStatusBar.setText(`$(check) ${path.basename(document.fileName)}`); - if(gEdkWorkspaces.isConfigured()){ let wps = await gEdkWorkspaces.getWorkspace(editor.document.uri); let wpFound = []; @@ -58,7 +53,7 @@ export class FileUseWarning { } if(wpFound.length){ - edkStatusBar.setHelpUrl("https://github.com/intel/Edk2Code/wiki"); + edkStatusBar.setHelpUrl("https://intel.github.io/Edk2Code/"); edkStatusBar.setColor('statusBarItem.activeBackground'); edkStatusBar.setText(`${wpFound.join("|")}`); edkStatusBar.setToolTip(""); @@ -67,20 +62,19 @@ export class FileUseWarning { // If file is an H file, then check in cscope.files if(gCscope.includesFile(editor.document.uri)){ - edkStatusBar.setHelpUrl("https://github.com/intel/Edk2Code/wiki"); + edkStatusBar.setHelpUrl("https://intel.github.io/Edk2Code/"); edkStatusBar.setColor('statusBarItem.activeBackground'); edkStatusBar.setText(`(Included Path) ${path.basename(document.fileName)}`); edkStatusBar.setToolTip(""); return; } - edkStatusBar.setColor('statusBarItem.warningBackground'); - edkStatusBar.setHelpUrl("https://github.com/intel/Edk2Code/wiki/Functionality#status-bar"); + edkStatusBar.setHelpUrl("https://intel.github.io/Edk2Code/getting_started/#status-bar"); edkStatusBar.setText(`$(warning) ${path.basename(document.fileName)}`); edkStatusBar.setToolTip("This file is not used by any loaded workspace"); return; }else{ - edkStatusBar.setHelpUrl("https://github.com/intel/Edk2Code/wiki/Index-source-code"); + edkStatusBar.setHelpUrl("https://intel.github.io/Edk2Code/advance_features/#indexing-source-code"); edkStatusBar.setColor('statusBarItem.activeBackground'); edkStatusBar.setText(`Workspace not found`); edkStatusBar.setToolTip("No workspace is configured"); From 68958e477e54c33dfde749f4e3d8fb06d7975124 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Fri, 7 Feb 2025 16:20:50 -0600 Subject: [PATCH 06/73] Update warning message for library context in EDK2Code --- src/contextState/cmds.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index c3f3dc0..9bd7304 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -389,7 +389,7 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; }else{ // list all modules and ask for context - void vscode.window.showWarningMessage("This INF is a library. This command only works with EDK Modules for now"); + void vscode.window.showWarningMessage("EDK2Code couldn't find a module that uses this library"); return; } } From a5b5168ca05e0030fda7836ed917c5dbd6dd1b13 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Fri, 7 Feb 2025 16:48:43 -0600 Subject: [PATCH 07/73] Bump version to 1.1.0 and add release notes for new features --- package.json | 2 +- src/newVersionPage/1.1.0.md | 31 +++++++++++++++++++++++++ src/newVersionPage/newVersionMessage.ts | 28 +++++++--------------- 3 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 src/newVersionPage/1.1.0.md diff --git a/package.json b/package.json index b2d892c..262373e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code", "displayName": "Edk2code", "description": "EDK2 code support", - "version": "1.0.8", + "version": "1.1.0", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", diff --git a/src/newVersionPage/1.1.0.md b/src/newVersionPage/1.1.0.md new file mode 100644 index 0000000..5ff6d3d --- /dev/null +++ b/src/newVersionPage/1.1.0.md @@ -0,0 +1,31 @@ +# EDK2Code new version + +Thank you for installing the new version of the EDK2Code extension! πŸŽ‰ + +For detailed documentation, please visit: [EDK2Code Documentation](https://intel.github.io/Edk2Code/). + +You can find our GitHub repository here: [EDK2Code GitHub Repository](https://github.com/intel/Edk2Code). + +If you find this extension useful, please consider giving us a star on GitHub or leaving a review in the VSCode Marketplace. Your support is greatly appreciated! ⭐ + +# New features + +## [Module map](https://intel.github.io/Edk2Code/advance_features/#module-map) + +You can right click on a compiled INF file and selectΒ `EDK2: Show Module Map` + +![module-map-context-menu](https://intel.github.io/Edk2Code/images/module-map-context-menu.png) + +This will open the EDK2 submenu showing the libraries and source files that were used to compile that INF. + +![module-map](https://intel.github.io/Edk2Code/images/module-map.png) + +This feature is helpful in visualizing how a module includes various libraries. It also provides insights into how C files within the module include header files. By understanding these relationships, developers can better manage dependencies. + +## [Error Detection](https://intel.github.io/Edk2Code/advance_features/#error-detection) + +The DSC analysis can identify potential issues within the DSC files, such incorrect paths, duplicated libraries, etc. These issues are highlighted and shown in the Visual Studio Code "Problems" window. + +![55e73504-3e8b-4b58-a9fa-5fc64a89614f](https://intel.github.io/Edk2Code/images/55e73504-3e8b-4b58-a9fa-5fc64a89614f.png) + +You can disable this using the `edk2code.enableDiagnostics` setting \ No newline at end of file diff --git a/src/newVersionPage/newVersionMessage.ts b/src/newVersionPage/newVersionMessage.ts index 9c09852..af7d1bb 100644 --- a/src/newVersionPage/newVersionMessage.ts +++ b/src/newVersionPage/newVersionMessage.ts @@ -1,30 +1,18 @@ import { getCurrentVersion } from "../utils"; import * as vscode from 'vscode'; - +import * as path from 'path'; +import * as fs from 'fs'; export async function showReleaseNotes(context: vscode.ExtensionContext){ const CURRENT_VERSION = getCurrentVersion(); const previousVersion = context.globalState.get('extensionVersion'); if (previousVersion !== CURRENT_VERSION) { - const panel = vscode.window.createWebviewPanel( - 'releaseNotes', - `EDK2Code Release Notes ${CURRENT_VERSION}`, - vscode.ViewColumn.One, - { - enableScripts: true, - } - ); - - panel.webview.html = ` -

Edk2Code ${CURRENT_VERSION}

-

Thanks for using the Edk2Code Vscode extension.

-

πŸŽ‰ If you find this extension useful πŸŽ‰:

- -

View new features

- `; await context.globalState.update('extensionVersion', CURRENT_VERSION); + + const releaseNotesPath = path.join(context.extensionPath, 'src', 'newVersionPage', `${CURRENT_VERSION}.md`); + if (fs.existsSync(releaseNotesPath)) { + const releaseNotesUri = vscode.Uri.file(releaseNotesPath); + await vscode.commands.executeCommand('markdown.showPreview', releaseNotesUri); + } } } \ No newline at end of file From af015eddd3f797acd8030db4e5a5c6f699998c82 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Fri, 7 Feb 2025 16:55:44 -0600 Subject: [PATCH 08/73] Add script to create draft releases using GitHub CLI --- create_draft_release.js | 24 ++++++++++++++++++++++++ package.json | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 create_draft_release.js diff --git a/create_draft_release.js b/create_draft_release.js new file mode 100644 index 0000000..f58ac75 --- /dev/null +++ b/create_draft_release.js @@ -0,0 +1,24 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); + +// Check if gh CLI is installed +try { + execSync('gh --version', { stdio: 'ignore' }); +} catch (error) { + console.error('gh CLI is not installed. Please install it first.'); + process.exit(1); +} + +// Parse the version from package.json +const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); +const version = packageJson.version; + +// Get the latest commit hash +const latestCommit = execSync('git rev-parse HEAD').toString().trim(); + +// Create a new draft release with the version name +execSync(`gh release create v${version} -t "v${version}" -n "Draft release for commit ${latestCommit}" --draft`, { stdio: 'inherit' }); + +// Print the URL to open the release page in the browser +const repoUrl = execSync('gh repo view --json url --jq ".url"').toString().trim(); +console.log(`Release created. You can view it at: ${repoUrl}/releases/tag/v${version}`); diff --git a/package.json b/package.json index 262373e..29d8cd9 100644 --- a/package.json +++ b/package.json @@ -490,7 +490,8 @@ "watch": "tsc -watch -p ./", "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", - "test": "node ./out/test/runTest.js" + "test": "node ./out/test/runTest.js", + "draft-release": "node create_draft_release.js" }, "devDependencies": { "@types/mocha": "^10.0.1", From fedb543c2b4743dcc7702abf3d797b2cd3668fee Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Thu, 27 Feb 2025 12:44:47 -0600 Subject: [PATCH 09/73] Refactor workspace path handling and add utility functions - Removed the `gWorkspacePath` variable and its associated imports and references in `TreeDataProvider.ts`, `compileCommands.ts`, `cppUtils.ts`, `moduleReport.ts`, and `pathfind.ts`. - Added a new utility function `findClosestCommonDirectory` to determine the closest common directory among workspace folders in `utils.ts`. - Added a new utility function `getCommonPath` to find the common path between two directories in `utils.ts`. - Updated `activate` function in `extension.ts` to use the new `findClosestCommonDirectory` utility function. - Simplified and cleaned up comments, and removed unnecessary code in `extension.ts` and `utils.ts`. --- src/TreeDataProvider.ts | 2 +- src/compileCommands.ts | 1 - src/cppProviders/cppUtils.ts | 2 +- src/extension.ts | 19 +++++---------- src/moduleReport.ts | 2 +- src/pathfind.ts | 1 - src/utils.ts | 47 +++++++++++++++++++++++++++++++----- 7 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/TreeDataProvider.ts b/src/TreeDataProvider.ts index 077c59a..d6b5bc7 100644 --- a/src/TreeDataProvider.ts +++ b/src/TreeDataProvider.ts @@ -1,7 +1,7 @@ import path = require('path'); import * as vscode from 'vscode'; import { TreeItemLabel } from 'vscode'; -import { edkLensTreeDetailProvider, gCompileCommands, gEdkWorkspaces, gMapFileManager, gPathFind, gWorkspacePath } from './extension'; +import { edkLensTreeDetailProvider, gCompileCommands, gEdkWorkspaces, gMapFileManager, gPathFind } from './extension'; import { InfParser } from './edkParser/infParser'; import { getParser } from './edkParser/parserFactory'; import { Edk2SymbolType } from './symbols/symbolsType'; diff --git a/src/compileCommands.ts b/src/compileCommands.ts index f57e6d0..d9cd4bf 100644 --- a/src/compileCommands.ts +++ b/src/compileCommands.ts @@ -1,7 +1,6 @@ import path = require("path"); -import { gWorkspacePath } from "./extension"; import * as fs from 'fs'; import { readEdkCodeFolderFile, writeEdkCodeFolderFile } from "./edk2CodeFolder"; import { normalizePath } from "./utils"; diff --git a/src/cppProviders/cppUtils.ts b/src/cppProviders/cppUtils.ts index cb2ebe9..80673cd 100644 --- a/src/cppProviders/cppUtils.ts +++ b/src/cppProviders/cppUtils.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; -import { gDebugLog, gWorkspacePath } from '../extension'; +import { gDebugLog } from '../extension'; import * as fs from 'fs'; import { infoMissingCppExtension } from '../ui/messages'; import { closeFileIfOpened, delay } from '../utils'; diff --git a/src/extension.ts b/src/extension.ts index 7a7f795..367a9c6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,7 +15,7 @@ import { GuidProvider } from './Languages/guidProvider'; import { PathFind } from './pathfind'; import { EdkWorkspaces } from './index/edkWorkspace'; import { Edk2CallHierarchyProvider } from './callHiearchy'; -import { copyToClipboard, getCurrentDocument, getDocsUrl, gotoFile, showVirtualFile } from './utils'; +import { copyToClipboard, findClosestCommonDirectory, getCurrentDocument, getDocsUrl, gotoFile, showVirtualFile } from './utils'; import { ParserFactory } from './edkParser/parserFactory'; import { TreeDetailsDataProvider } from './TreeDataProvider'; import { DiagnosticManager } from './diagnostics'; @@ -55,12 +55,10 @@ export var gMapFileManager: MapFilesManager; // your extension is activated the very first time the command is executed export async function activate(context: vscode.ExtensionContext) { - const logger = vscode.window.createOutputChannel('grepc', { log: true }); - - - if (vscode.workspace.workspaceFolders !== undefined) { - gWorkspacePath = vscode.workspace.workspaceFolders[0].uri.fsPath; + let workspacePaths = vscode.workspace.workspaceFolders.map(folder => folder.uri.fsPath); + gWorkspacePath = findClosestCommonDirectory(workspacePaths); + console.log(gWorkspacePath); }else{ return; } @@ -90,10 +88,6 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.gotoDefinition', async (fileUri)=>{ await cmds.gotoDefinitionCscope(fileUri);}), vscode.commands.registerCommand('edk2code.gotoDefinitionInput', ()=>{cmds.gotoDefinitionInput();}), - // wrong - // vscode.commands.registerCommand('edk2code.searchCcode', ()=>{throw new Error("ContextState not initialized");}), - - vscode.commands.registerCommand('edk2code.gotoInf',async (fileUri)=>{await cmds.gotoInf(fileUri);}), vscode.commands.registerCommand('edk2code.dscUsage', async (fileUri)=>{await cmds.gotoDscDeclaration(fileUri);}), vscode.commands.registerCommand('edk2code.dscInclusion', async (fileUri)=>{await cmds.gotoDscInclusion(fileUri);}), @@ -186,8 +180,7 @@ export async function activate(context: vscode.ExtensionContext) { gCscopeAgent = new CscopeAgent(); if(gCscope.existCscopeFile()){ - // eslint-disable-next-line @typescript-eslint/no-floating-promises - gCscope.reload().then(()=>{ + void gCscope.reload().then(()=>{ if(gConfigAgent.getUseEdkCallHiearchy()){ gEdk2CallHierarchyProvider = new Edk2CallHierarchyProvider(); } @@ -206,8 +199,8 @@ export async function activate(context: vscode.ExtensionContext) { }); await vscode.commands.executeCommand('setContext', 'edk2code.isNodeFocusBackStack', false); + void showReleaseNotes(context); - } diff --git a/src/moduleReport.ts b/src/moduleReport.ts index a24b400..21825ca 100644 --- a/src/moduleReport.ts +++ b/src/moduleReport.ts @@ -1,5 +1,5 @@ import path = require("path"); -import { gModuleReport, gWorkspacePath } from "./extension"; +import { gModuleReport } from "./extension"; import * as fs from 'fs'; import { assert } from "console"; import { normalizePath, pathCompare } from "./utils"; diff --git a/src/pathfind.ts b/src/pathfind.ts index 3637acc..851bdd0 100644 --- a/src/pathfind.ts +++ b/src/pathfind.ts @@ -41,7 +41,6 @@ export class PathFind{ async findPath(pathArg: string, relativePath: string|undefined = "") { pathArg = pathArg.replaceAll(/(\\+|\/+)/gi, path.sep); - let ws = gWorkspacePath; gDebugLog.trace(`Looking for: ${pathArg}`); // Restrict path characters diff --git a/src/utils.ts b/src/utils.ts index 48f4952..29f203f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -190,9 +190,6 @@ export function getRealPath(inputPath: string) { return ""; } let fullRealPath = fs.realpathSync.native(fullPath); - - // In case user is using subst. Replace workspace path - // fullRealPath = path.join(gWorkspacePath, fullRealPath.slice(fullRealPath.length - path.relative(gWorkspacePath, fullPath).length)); return fullRealPath; } @@ -309,9 +306,15 @@ export async function copyToClipboard(data:string, message:string="Data copied t export function isWorkspacePath(p: string) { - let x = gWorkspacePath; - const relativePath = path.relative(gWorkspacePath, p); - return !relativePath.includes("..") && relativePath !== p; + for (const folder of vscode.workspace.workspaceFolders!) { + const relativePath = path.relative(folder.uri.fsPath, p); + + if(!relativePath.includes("..") && relativePath !== p){ + return true; + } + } + return false; + } export function trimSpaces(text: string) { @@ -566,4 +569,36 @@ export function getDocsUrl():string{ const packageJsonPath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return packageJson.homepage; +} + + +export function findClosestCommonDirectory(paths: string[]): string { + if (paths.length === 0){ + return ""; + } + + let commonPath = paths[0]; + for (let i = 1; i < paths.length; i++) { + commonPath = getCommonPath(commonPath, paths[i]); + if (commonPath === ''){ + break; + } + } + return commonPath; +} + +export function getCommonPath(path1: string, path2: string): string { + const path1Parts = path1.split(path.sep); + const path2Parts = path2.split(path.sep); + const length = Math.min(path1Parts.length, path2Parts.length); + + let commonParts = []; + for (let i = 0; i < length; i++) { + if (path1Parts[i] === path2Parts[i]) { + commonParts.push(path1Parts[i]); + } else { + break; + } + } + return commonParts.join(path.sep); } \ No newline at end of file From 90462b86761dbbcd48dce56fd4b6e1d81379551b Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Thu, 27 Mar 2025 10:02:37 -0500 Subject: [PATCH 10/73] Update scripts structure and enhance configuration checks - Updated .vscodeignore to ignore the scripts directory. - Changed package.json version from 1.1.0 to 1.1.1-preview. - Moved create_draft_release.js to the scripts directory. - Added a check in msCpp.ts to ensure the workspace is a folder before fixing CPP properties and made minor formatting adjustments. - Updated tsconfig.json to exclude scripts, out, and node_modules directories. --- .vscodeignore | 1 + package.json | 2 +- .../create_draft_release.js | 0 src/cppProviders/msCpp.ts | 9 +++++++-- tsconfig.json | 8 ++++++-- 5 files changed, 15 insertions(+), 5 deletions(-) rename create_draft_release.js => scripts/create_draft_release.js (100%) diff --git a/.vscodeignore b/.vscodeignore index 6d17873..16e0171 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -7,3 +7,4 @@ vsc-extension-quickstart.md **/.eslintrc.json **/*.map **/*.ts +scripts/** diff --git a/package.json b/package.json index 29d8cd9..591a456 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code", "displayName": "Edk2code", "description": "EDK2 code support", - "version": "1.1.0", + "version": "1.1.1-preview", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", diff --git a/create_draft_release.js b/scripts/create_draft_release.js similarity index 100% rename from create_draft_release.js rename to scripts/create_draft_release.js diff --git a/src/cppProviders/msCpp.ts b/src/cppProviders/msCpp.ts index 97fcda4..978379e 100644 --- a/src/cppProviders/msCpp.ts +++ b/src/cppProviders/msCpp.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { CppProvider } from './cppProvider'; import * as fs from 'fs'; -import { closeFileIfOpened, delay } from '../utils'; +import {closeFileIfOpened, delay } from '../utils'; import { gDebugLog, gWorkspacePath } from '../extension'; import path = require("path"); import { updateCompilesCommandCpp } from '../ui/messages'; @@ -24,8 +24,13 @@ export class MsCppProvider extends CppProvider { async validateConfiguration(): Promise { + if (vscode.workspace.workspaceFile !== undefined){ + // Don't try to fix CPP if the workspace is not a folder + return true; + } + if(!this.isCppPropertiesFile()){ - if(! await this.isFixFile()){ + if(!await this.isFixFile()){ return true; } await this.touchCppPropertiesFile(); // Asks CPP extension to create the file diff --git a/tsconfig.json b/tsconfig.json index 892bd18..9c9c7a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,12 +12,16 @@ "allowJs": true, "rootDir": "src", "strict": true /* enable all strict type-checking options */, - /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ - } + }, + "exclude": [ + "scripts/**/*", + "out/**/*", + "node_modules/**/*" + ] } From 6a4aabff34bbb67553ee491ccc03dfefaa9a9c58 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Thu, 27 Mar 2025 16:43:15 -0500 Subject: [PATCH 11/73] Enhance logging and conditional evaluation - In src/debugLog.ts, changed the `debug` method to use `trace` for more detailed logging. - In src/diagnostics.ts, added a new diagnostic code `errorMessage` and its corresponding description. - In src/index/definitions.ts, added the method `isDefined` to check if a definition exists. Enhanced `replaceDefines` method to handle replacements for boolean and numeric values properly. - In src/index/edkWorkspace.ts, introduced operators for conditional evaluation and improved the handling of `!error` directives. Created a method `evaluateExpression` for better evaluation of conditions and added extensive debug logging for range and conditional evaluations. --- src/debugLog.ts | 2 +- src/diagnostics.ts | 2 + src/index/definitions.ts | 18 ++++- src/index/edkWorkspace.ts | 147 ++++++++++++++++++++++++++++++++------ 4 files changed, 144 insertions(+), 25 deletions(-) diff --git a/src/debugLog.ts b/src/debugLog.ts index 6a25e64..41fa06e 100644 --- a/src/debugLog.ts +++ b/src/debugLog.ts @@ -45,7 +45,7 @@ export class DebugLog { } public debug(text:string){ - this.outConsole.debug(text); + this.outConsole.trace(text); } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 45856cb..a5e9e7d 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -29,6 +29,7 @@ emptyFile, circularDependency, inactiveCode, edk2CodeUnsuported, +errorMessage } export const edkErrorDescriptions: Map = new Map([ @@ -58,6 +59,7 @@ export const edkErrorDescriptions: Map = new Map([ [EdkDiagnosticCodes.circularDependency, "Circular dependency"], [EdkDiagnosticCodes.inactiveCode, "Inactive code"], [EdkDiagnosticCodes.edk2CodeUnsuported, "Edk2Code unsupported"], + [EdkDiagnosticCodes.errorMessage, "Error message"], ]); export class DiagnosticManager { diff --git a/src/index/definitions.ts b/src/index/definitions.ts index 50e79c6..d78a6f1 100644 --- a/src/index/definitions.ts +++ b/src/index/definitions.ts @@ -66,6 +66,10 @@ export class WorkspaceDefinitions { this.defines = new Map(); } + isDefined(text:string){ + return this.defines.has(text); + } + replaceDefines(text: string) { let replaced = false; let maxIterations = 10; @@ -82,7 +86,19 @@ export class WorkspaceDefinitions { for (const [key,value] of this.defines.entries()) { if(text.includes(`$(${key})`)){ replaced = true; - text = text.replaceAll(`$(${key})`, value.value); + let replacement; + if(value.value.toLowerCase() === "true"){ + replacement = "TRUE"; + }else if(value.value.toLowerCase() === "false"){ + replacement = "FALSE"; + }else if(isNaN(parseInt(value.value))){ + replacement = `"${value.value}"`; + }else{ + replacement = value.value; + } + + + text = text.replaceAll(`$(${key})`, replacement); } } diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index b9e9b29..aa0a3c8 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -13,6 +13,40 @@ import { EdkSymbolInfLibrary } from '../symbols/infSymbols'; import { DiagnosticManager, EdkDiagnosticCodes } from '../diagnostics'; import { PathFind } from '../pathfind'; +const OPERATORS = { + 'or': { precedence: 1, fn: (x: boolean, y: boolean) => x || y }, + 'OR': { precedence: 1, fn: (x: boolean, y: boolean) => x || y }, + '||': { precedence: 1, fn: (x: boolean, y: boolean) => x || y }, + 'and': { precedence: 2, fn: (x: boolean, y: boolean) => x && y }, + 'AND': { precedence: 2, fn: (x: boolean, y: boolean) => x && y }, + '&&': { precedence: 2, fn: (x: boolean, y: boolean) => x && y }, + '|': { precedence: 3, fn: (x: number, y: number) => x | y }, + '^': { precedence: 4, fn: (x: number, y: number) => x ^ y }, + '&': { precedence: 5, fn: (x: number, y: number) => x & y }, + '==': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x === y }, + '!=': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x !== y }, + 'EQ': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x === y }, + 'NE': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x !== y }, + '<=': { precedence: 7, fn: (x: number, y: number) => x <= y }, + '>=': { precedence: 7, fn: (x: number, y: number) => x >= y }, + '<': { precedence: 7, fn: (x: number, y: number) => x < y }, + '>': { precedence: 7, fn: (x: number, y: number) => x > y }, + '+': { precedence: 8, fn: (x: number, y: number) => x + y }, + '-': { precedence: 8, fn: (x: number, y: number) => x - y }, + '*': { precedence: 9, fn: (x: number, y: number) => x * y }, + '/': { precedence: 9, fn: (x: number, y: number) => x / y }, + '%': { precedence: 9, fn: (x: number, y: number) => x % y }, + '!': { precedence: 10, fn: (y:any, x: boolean) => !x }, + 'not': { precedence: 10, fn: (y:any, x: boolean) => !x }, + 'NOT': { precedence: 10, fn: (y:any, x: boolean) => !x }, + '~': { precedence: 10, fn: (y:any, x: number) => ~x }, + '<<': { precedence: 11, fn: (x: number, y: number) => x << y }, + '>>': { precedence: 11, fn: (x: number, y: number) => x >> y }, + 'in': { precedence: 12, fn: (x: string, y: string) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x.includes(y) }, + 'IN': { precedence: 12, fn: (x: string, y: string) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x.includes(y) }, +}; + + const dscSectionTypes = ['defines','packages','buildoptions','skuids','libraryclasses','components','userextensions','defaultstores','pcdsfeatureflag','pcdsfixedatbuild','pcdspatchableinmodule','pcdsdynamicdefault','pcdsdynamichii','pcdsdynamicvpd','pcdsdynamicexdefault','pcdsdynamicexhii','pcdsdynamicexvpd']; @@ -598,9 +632,14 @@ export class EdkWorkspace { continue; } + if(line.startsWith("!error ")){ + DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.errorMessage, line.replace("!error ","")); + } + if (isRangeActive) { isRangeActive = false; let lineIndexEnd = lineIndex - 1; + gDebugLog.debug(`New grayout ${document.fileName} -> unuseRangeStart: ${unuseRangeStart} lineIndexEnd: ${lineIndexEnd}`); doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); } @@ -705,6 +744,13 @@ export class EdkWorkspace { } } + if (isRangeActive) { + isRangeActive = false; + let lineIndexEnd = lineIndex - 1; + gDebugLog.debug(`New grayout ${document.fileName} -> unuseRangeStart: ${unuseRangeStart} lineIndexEnd: ${lineIndexEnd}`); + doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); + } + this.updateGrayoutRange(document, doucumentGrayoutRange); } @@ -939,13 +985,13 @@ export class EdkWorkspace { this.conditionOpen.push({uri:documentUri, lineNo:lineIndex}); switch(tokens[0].toLowerCase()){ case "!if": - conditionValue = this.evaluateConditional(conditionStr.replaceAll(UNDEFINED_VARIABLE,'FALSE'), documentUri, lineIndex); + conditionValue = this.evaluateConditional(conditionStr, documentUri, lineIndex); break; case "!ifdef": - conditionValue = this.pushConditional((tokens[1] !== UNDEFINED_VARIABLE)); + conditionValue = this.pushConditional(this.defines.isDefined(tokens[1])); break; case "!ifndef": - conditionValue = this.pushConditional((tokens[1] === UNDEFINED_VARIABLE)); + conditionValue = this.pushConditional(!this.defines.isDefined(tokens[1])); break; } @@ -964,7 +1010,7 @@ export class EdkWorkspace { DiagnosticManager.error(documentUri, lineIndex, EdkDiagnosticCodes.conditionalMissform, "!elseif without !if"); return true; } - conditionValue = this.evaluateConditional(conditionStr.replaceAll(UNDEFINED_VARIABLE,'FALSE'), documentUri, lineIndex); + conditionValue = this.evaluateConditional(conditionStr, documentUri, lineIndex); parentActive = this.conditionStack.length <= 1 || this.conditionStack[this.conditionStack.length - 2].active; // Update the top of the stack @@ -1039,36 +1085,91 @@ export class EdkWorkspace { this.conditionalStack.push(v); return v; } + + evaluateExpression(expression: string): any { + const originalExpression = expression; + if(expression.includes(UNDEFINED_VARIABLE)){ + gDebugLog.debug(`Evaluate expression: ${expression} -> ${false}`); + return false; + } + // add space to parenteses + expression = expression.replaceAll(/\(/g, " ( "); + expression = expression.replaceAll(/\)/g, " ) "); + expression = expression.replaceAll(/\s+/g, " "); + + + let tokens = expression.match(/(?:[^\s"]+|"[^"]*")+/g) || []; + let outputQueue = []; + let operatorStack: string[] = []; + let parentesisBalance = 0; + for (const token of tokens) { + if (!isNaN(Number(token))) { + outputQueue.push(Number(token)); + } else if (token.toLowerCase() === 'true' || token.toLowerCase() === 'false') { + outputQueue.push(token.toLowerCase() === 'true'); + } else if(token.trim().startsWith('"') && token.trim().endsWith('"')){ + outputQueue.push(token.trim().slice(1,-1)); + } else if (token in OPERATORS) { + while (operatorStack.length && operatorStack[operatorStack.length - 1] in OPERATORS && + OPERATORS[token as keyof typeof OPERATORS].precedence <= OPERATORS[operatorStack[operatorStack.length - 1] as keyof typeof OPERATORS].precedence) { + outputQueue.push(operatorStack.pop()); + } + operatorStack.push(token); + } else if (token === '(') { + parentesisBalance++; + operatorStack.push(token); + } else if (token === ')') { + parentesisBalance--; + while (operatorStack.length && operatorStack[operatorStack.length - 1] !== '(') { + outputQueue.push(operatorStack.pop()); + } + operatorStack.pop(); + } else{ + throw new Error(`Error evaluating expression: ${originalExpression} - bad Operand ${token}`); + } + } - private evaluateConditional(text: string, documentUri:vscode.Uri, lineNo:number): any { + if(parentesisBalance !== 0){ + throw new Error(`Error evaluating expression: ${originalExpression} - Parenthesis balance error`); + } + + while (operatorStack.length) { + outputQueue.push(operatorStack.pop()); + } + + let stack: any[] = []; + for (const token of outputQueue) { + if (token! as keyof typeof OPERATORS in OPERATORS) { + let y = stack.pop() as never; + let x = stack.pop() as never; + stack.push(OPERATORS[token! as keyof typeof OPERATORS].fn(x, y)); + }else{ + stack.push(token); + } + } - let data = text.replaceAll(/"true"/gi, "true"); // replace booleans - data = data.replaceAll(/"false"/gi, "false"); - data = data.replaceAll(/ and /gi, " && "); - data = data.replaceAll(/ or /gi, " || "); - data = data.replace(/ not /gi, " ! "); - data = data.replaceAll(/(? 1){ + throw new Error(`Error evaluating expression: ${originalExpression}`); + } + const retValue = (stack[0]===UNDEFINED_VARIABLE)? false : stack[0]; + gDebugLog.debug(`Evaluate expression: ${originalExpression} -> ${retValue}`); + return retValue; + } - while (data.match(/"in"/gi)) { - let inIndex = data.search(/"in"/i); - let last = data.slice(inIndex + '"in"'.length).replace(/("\w+")/i, '$1)').trim(); - let beging = data.slice(0, inIndex).trim(); - data = beging + ".includes(" + last; - } + private evaluateConditional(text: string, documentUri:vscode.Uri, lineNo:number): any { + // let data = text.replaceAll(/(? Date: Thu, 27 Mar 2025 20:09:18 -0500 Subject: [PATCH 12/73] Bump version to 1.1.1 and add boolean evaluation utility functions Update status bar text and remove redundant value checks in workspace definitions; add test DSC operations Refactor operator functions for improved validation and readability --- package.json | 2 +- src/index/definitions.ts | 10 +- src/index/edkWorkspace.ts | 503 +++++++++++++++++++------------------ src/index/operations.ts | 267 ++++++++++++++++++++ src/statusBar.ts | 2 +- test/testDscOperations.dsc | 138 ++++++++++ 6 files changed, 667 insertions(+), 255 deletions(-) create mode 100644 src/index/operations.ts create mode 100644 test/testDscOperations.dsc diff --git a/package.json b/package.json index 591a456..963c196 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code", "displayName": "Edk2code", "description": "EDK2 code support", - "version": "1.1.1-preview", + "version": "1.1.1", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", diff --git a/src/index/definitions.ts b/src/index/definitions.ts index d78a6f1..643ffb3 100644 --- a/src/index/definitions.ts +++ b/src/index/definitions.ts @@ -57,7 +57,15 @@ export class WorkspaceDefinitions { return undefined; } + isBoolean(value:string){ + value = value.toLowerCase(); + return value === "true" || value === "false"; + } + setDefinition(key:string, value:string, location:vscode.Location|undefined){ + + + gDebugLog.trace(`setDefinition: ${key} = ${value}`); this.defines.set(key, {name: key, value:value, location:location}); } @@ -91,8 +99,6 @@ export class WorkspaceDefinitions { replacement = "TRUE"; }else if(value.value.toLowerCase() === "false"){ replacement = "FALSE"; - }else if(isNaN(parseInt(value.value))){ - replacement = `"${value.value}"`; }else{ replacement = value.value; } diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index aa0a3c8..04af188 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -12,40 +12,8 @@ import { Edk2SymbolType } from '../symbols/symbolsType'; import { EdkSymbolInfLibrary } from '../symbols/infSymbols'; import { DiagnosticManager, EdkDiagnosticCodes } from '../diagnostics'; import { PathFind } from '../pathfind'; - -const OPERATORS = { - 'or': { precedence: 1, fn: (x: boolean, y: boolean) => x || y }, - 'OR': { precedence: 1, fn: (x: boolean, y: boolean) => x || y }, - '||': { precedence: 1, fn: (x: boolean, y: boolean) => x || y }, - 'and': { precedence: 2, fn: (x: boolean, y: boolean) => x && y }, - 'AND': { precedence: 2, fn: (x: boolean, y: boolean) => x && y }, - '&&': { precedence: 2, fn: (x: boolean, y: boolean) => x && y }, - '|': { precedence: 3, fn: (x: number, y: number) => x | y }, - '^': { precedence: 4, fn: (x: number, y: number) => x ^ y }, - '&': { precedence: 5, fn: (x: number, y: number) => x & y }, - '==': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x === y }, - '!=': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x !== y }, - 'EQ': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x === y }, - 'NE': { precedence: 6, fn: (x: any, y: any) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x !== y }, - '<=': { precedence: 7, fn: (x: number, y: number) => x <= y }, - '>=': { precedence: 7, fn: (x: number, y: number) => x >= y }, - '<': { precedence: 7, fn: (x: number, y: number) => x < y }, - '>': { precedence: 7, fn: (x: number, y: number) => x > y }, - '+': { precedence: 8, fn: (x: number, y: number) => x + y }, - '-': { precedence: 8, fn: (x: number, y: number) => x - y }, - '*': { precedence: 9, fn: (x: number, y: number) => x * y }, - '/': { precedence: 9, fn: (x: number, y: number) => x / y }, - '%': { precedence: 9, fn: (x: number, y: number) => x % y }, - '!': { precedence: 10, fn: (y:any, x: boolean) => !x }, - 'not': { precedence: 10, fn: (y:any, x: boolean) => !x }, - 'NOT': { precedence: 10, fn: (y:any, x: boolean) => !x }, - '~': { precedence: 10, fn: (y:any, x: number) => ~x }, - '<<': { precedence: 11, fn: (x: number, y: number) => x << y }, - '>>': { precedence: 11, fn: (x: number, y: number) => x >> y }, - 'in': { precedence: 12, fn: (x: string, y: string) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x.includes(y) }, - 'IN': { precedence: 12, fn: (x: string, y: string) => (x===UNDEFINED_VARIABLE||y===UNDEFINED_VARIABLE)?false:x.includes(y) }, -}; - +import { OPERATORS } from './operations'; +import * as edkStatusBar from '../statusBar'; const dscSectionTypes = ['defines','packages','buildoptions','skuids','libraryclasses','components','userextensions','defaultstores','pcdsfeatureflag','pcdsfixedatbuild','pcdspatchableinmodule','pcdsdynamicdefault','pcdsdynamichii','pcdsdynamicvpd','pcdsdynamicexdefault','pcdsdynamicexhii','pcdsdynamicexvpd']; @@ -422,59 +390,72 @@ export class EdkWorkspace { async proccessWorkspace() { - if (this.workInProgress) { - return false; - } - // reset conditional stack - this.workInProgress = true; - gDebugLog.trace(`Start finding defines in ${this.mainDsc.fsPath}`); - this.conditionalStack = []; - - this.defines.resetDefines(); - this.definesFdf.resetDefines(); - - this.filesLibraries = []; - this.filesModules = []; - this.filesDsc = []; - for (const ctrl of this._grayoutControllers) { - ctrl.dispose(); - } - this._grayoutControllers = []; - this.libraryTypeTrack = new Map(); - - - this.conditionStack = []; - this.result = []; - this.conditionOpen = []; - - - - - + try{ + if(this.platformName !== undefined){ + edkStatusBar.pushText(`Parsing ${this.platformName}`); + }else{ + edkStatusBar.pushText(`Loading ${this.mainDsc.fsPath}`); + } - this.parsedDocuments = new Map(); - let mainDscDocument = await vscode.workspace.openTextDocument(this.mainDsc); - await this._processDocument(mainDscDocument, "DSC"); - for (const conditionOpen of this.conditionOpen) { - DiagnosticManager.error(conditionOpen.uri,conditionOpen.lineNo,EdkDiagnosticCodes.conditionalMissform, "Condition block not closed"); - } - this.workInProgress = false; - gDebugLog.trace("Finding done."); - - // Populate workspace definitions - this.platformName = this.defines.getDefinition("PLATFORM_NAME") || undefined; - let flashDefinitionString = this.defines.getDefinition("FLASH_DEFINITION") || undefined; - if (flashDefinitionString) { - let flashDefinitionPath = await gPathFind.findPath(flashDefinitionString); - if(flashDefinitionPath.length>0){ - this.flashDefinitionDocument = await openTextDocument(flashDefinitionPath[0].uri); + edkStatusBar.setWorking(); + if (this.workInProgress) { + return false; + } + // reset conditional stack + this.workInProgress = true; + gDebugLog.trace(`Start finding defines in ${this.mainDsc.fsPath}`); + this.conditionalStack = []; + + this.defines.resetDefines(); + this.definesFdf.resetDefines(); + + this.filesLibraries = []; + this.filesModules = []; + this.filesDsc = []; + for (const ctrl of this._grayoutControllers) { + ctrl.dispose(); + } + this._grayoutControllers = []; + this.libraryTypeTrack = new Map(); + + + this.conditionStack = []; + this.result = []; + this.conditionOpen = []; + + + + + + + this.parsedDocuments = new Map(); + let mainDscDocument = await vscode.workspace.openTextDocument(this.mainDsc); + await this._processDocument(mainDscDocument, "DSC"); + for (const conditionOpen of this.conditionOpen) { + DiagnosticManager.error(conditionOpen.uri,conditionOpen.lineNo,EdkDiagnosticCodes.conditionalMissform, "Condition block not closed"); } + this.workInProgress = false; + gDebugLog.trace("Finding done."); + + // Populate workspace definitions + this.platformName = this.defines.getDefinition("PLATFORM_NAME") || undefined; + let flashDefinitionString = this.defines.getDefinition("FLASH_DEFINITION") || undefined; + if (flashDefinitionString) { + let flashDefinitionPath = await gPathFind.findPath(flashDefinitionString); + if(flashDefinitionPath.length>0){ + this.flashDefinitionDocument = await openTextDocument(flashDefinitionPath[0].uri); + } + } + + await this.findDefinesFdf(); + + this.processComplete = true; + return true; + }finally{ + edkStatusBar.popText(); + edkStatusBar.clearWorking(); } - - await this.findDefinesFdf(); - - this.processComplete = true; - return true; + } async getInfReference(uri: vscode.Uri) { @@ -570,188 +551,194 @@ export class EdkWorkspace { private async _processDocument(document: vscode.TextDocument, type: 'DSC' | 'FDF') { - DiagnosticManager.clearProblems(document.uri); - gDebugLog.trace(`_process${type}: ${document.fileName}`); - - if (this.isDocumentInIndex(document)) { - gDebugLog.warning(`_process${type}: ${document.fileName} already in inactiveLines`); - return; - } - - let doucumentGrayoutRange = []; - this.parsedDocuments.set(document.uri.fsPath, []); - if (type === 'DSC') { - this.filesDsc.push(document); - } else { - this.filesFdf.push(document); - } - - let text = document.getText().split(/\r?\n/); - let lineIndex = -1; - let isRangeActive = false; - let unuseRangeStart = 0; - - gDebugLog.trace(`# Parsing ${type} Document: ${document.uri.fsPath}`); - for (let line of text) { - lineIndex++; - gDebugLog.trace(`\t\t${lineIndex}: ${line}`); - line = this.stripComment(line); - - if (line.length === 0){continue;} - - // PCDs - if (line.match(REGEX_PCD_LINE)) { - let [fullPcd, pcdValue] = split(line, "|", 2); - pcdValue = pcdValue.split("|")[0].trim(); - if (pcdValue.startsWith('L"')) { - pcdValue = pcdValue.slice(1); - } - let [pcdNamespace, pcdName] = split(fullPcd, ".", 2); - if (!this.pcdDefinitions.has(pcdNamespace)) { - this.pcdDefinitions.set(pcdNamespace, new Map()); - } - this.pcdDefinitions.get(pcdNamespace)?.set( - pcdName, - { - name: pcdName, - value: pcdValue, - position: new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)) - } - ); - } - line = this.defines.replaceDefines(line); - line = this.replacePcds(line); - let isInActiveCode = this.processConditional(line, lineIndex, document.uri); - - if (!isInActiveCode) { - if (!isRangeActive) { - isRangeActive = true; - unuseRangeStart = lineIndex; - } - continue; - } - - if(line.startsWith("!error ")){ - DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.errorMessage, line.replace("!error ","")); - } - - if (isRangeActive) { - isRangeActive = false; - let lineIndexEnd = lineIndex - 1; - gDebugLog.debug(`New grayout ${document.fileName} -> unuseRangeStart: ${unuseRangeStart} lineIndexEnd: ${lineIndexEnd}`); - doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); + DiagnosticManager.clearProblems(document.uri); + gDebugLog.trace(`_process${type}: ${document.fileName}`); + + if (this.isDocumentInIndex(document)) { + gDebugLog.warning(`_process${type}: ${document.fileName} already in inactiveLines`); + return; } - + + let doucumentGrayoutRange = []; + this.parsedDocuments.set(document.uri.fsPath, []); if (type === 'DSC') { - // Sections - let match = line.match(REGEX_DSC_SECTION); - if (match) { - const sectionType = match[0].split(".")[0]; - if (!dscSectionTypes.includes(sectionType.toLowerCase())) { - DiagnosticManager.warning(document.uri, lineIndex, EdkDiagnosticCodes.unknownSectionType, sectionType); + this.filesDsc.push(document); + } else { + this.filesFdf.push(document); + } + + let text = document.getText().split(/\r?\n/); + let lineIndex = -1; + let isRangeActive = false; + let unuseRangeStart = 0; + + gDebugLog.trace(`# Parsing ${type} Document: ${document.uri.fsPath}`); + for (let line of text) { + lineIndex++; + gDebugLog.trace(`\t\t${lineIndex}: ${line}`); + line = this.stripComment(line); + + if (line.length === 0){continue;} + + // PCDs + if (line.match(REGEX_PCD_LINE)) { + let [fullPcd, pcdValue] = split(line, "|", 2); + pcdValue = pcdValue.split("|")[0].trim(); + if (pcdValue.startsWith('L"')) { + pcdValue = pcdValue.slice(1); + } + let [pcdNamespace, pcdName] = split(fullPcd, ".", 2); + if (!this.pcdDefinitions.has(pcdNamespace)) { + this.pcdDefinitions.set(pcdNamespace, new Map()); + } + this.pcdDefinitions.get(pcdNamespace)?.set( + pcdName, + { + name: pcdName, + value: pcdValue, + position: new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)) + } + ); + } + + line = this.defines.replaceDefines(line); + line = this.replacePcds(line); + let isInActiveCode = this.processConditional(line, lineIndex, document.uri); + + if (!isInActiveCode) { + if (!isRangeActive) { + isRangeActive = true; + unuseRangeStart = lineIndex; } - this.sectionsStack = [match[0]]; continue; } - } - - // Defines - if (line.match(REGEX_DEFINE)) { - let key = line.replace(/define/gi, "").trim(); - key = split(key, "=", 2)[0].trim(); - let value = split(line, "=", 2)[1].trim(); - if (value.includes(`$(${key})`)) { - gDebugLog.info(`Circular define: ${key}: ${value}`); - } else { - if (type === 'DSC') { - this.defines.setDefinition(key, value, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0))); - } else { - this.definesFdf.setDefinition(key, value, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0))); + + if(line.startsWith("!error ")){ + DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.errorMessage, line.replace("!error ","")); + } + + if (isRangeActive) { + isRangeActive = false; + let lineIndexEnd = lineIndex - 1; + gDebugLog.debug(`New grayout ${document.fileName} -> unuseRangeStart: ${unuseRangeStart} lineIndexEnd: ${lineIndexEnd}`); + doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); + } + + if (type === 'DSC') { + // Sections + let match = line.match(REGEX_DSC_SECTION); + if (match) { + const sectionType = match[0].split(".")[0]; + if (!dscSectionTypes.includes(sectionType.toLowerCase())) { + DiagnosticManager.warning(document.uri, lineIndex, EdkDiagnosticCodes.unknownSectionType, sectionType); + } + this.sectionsStack = [match[0]]; + continue; } } - continue; - } - - // Includes - if (line.match(REGEX_INCLUDE)) { - let value = line.replace(/!include/gi, "").trim(); - let location = await gPathFind.findPath(value, document.uri.fsPath); - if (location.length > 0) { - let includedDocument = await openTextDocument(location[0].uri); - if (type === 'DSC') { - await this._processDocument(includedDocument, 'DSC'); + + // Defines + if (line.match(REGEX_DEFINE)) { + let key = line.replace(/define/gi, "").trim(); + key = split(key, "=", 2)[0].trim(); + let value = split(line, "=", 2)[1].trim(); + if (value.includes(`$(${key})`)) { + gDebugLog.info(`Circular define: ${key}: ${value}`); } else { - this.filesFdf.push(includedDocument); - await this._processDocument(includedDocument, 'FDF'); + if (type === 'DSC') { + this.defines.setDefinition(key, value, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0))); + } else { + this.definesFdf.setDefinition(key, value, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0))); + } } + continue; } - continue; - } - - if (type === 'DSC') { - - // Libraries - let match = line.match(REGEX_LIBRARY_PATH); - if (match) { - let filePath = match[0].trim(); - let results = await gPathFind.findPath(filePath, document.uri.fsPath); - if (results.length === 0) { - DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.missingPath, filePath); - } - let newLibDefinition = new InfDsc(filePath, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)), this.sectionsStack[this.sectionsStack.length - 1], line); - const libName = newLibDefinition.text.split("|")[0].trim(); - const libNameTag = libName + " - " + newLibDefinition.getModuleTypeStr(); - if (this.libraryTypeTrack.has(libNameTag) && (libName.toLocaleLowerCase() !== "null") && newLibDefinition.parent === undefined) { - if (this.sectionsStack[this.sectionsStack.length - 1].toLowerCase().endsWith(".inf")) { - continue; - } - let previousLibDefinition = this.libraryTypeTrack.get(libNameTag)!; - DiagnosticManager.warning(previousLibDefinition.location.uri, previousLibDefinition.location.range.start.line, - EdkDiagnosticCodes.duplicateStatement, - `Library overwritten: ${libName}`, - [vscode.DiagnosticTag.Unnecessary], - [new vscode.DiagnosticRelatedInformation(newLibDefinition.location, "New definition")]); - const index = this.filesLibraries.indexOf(previousLibDefinition); - if (index > -1) { - this.filesLibraries[index] = newLibDefinition; + + // Includes + if (line.match(REGEX_INCLUDE)) { + let value = line.replace(/!include/gi, "").trim(); + let location = await gPathFind.findPath(value, document.uri.fsPath); + if (location.length > 0) { + gDebugLog.trace(`START Including: ${location[0].uri.fsPath}`); + let includedDocument = await openTextDocument(location[0].uri); + if (type === 'DSC') { + await this._processDocument(includedDocument, 'DSC'); + } else { + this.filesFdf.push(includedDocument); + await this._processDocument(includedDocument, 'FDF'); } - } else { - this.filesLibraries.push(newLibDefinition); + gDebugLog.trace(`END Including: ${location[0].uri.fsPath}`); } - this.libraryTypeTrack.set(libNameTag, newLibDefinition); continue; } - - // Modules - match = line.match(REGEX_MODULE_PATH); - if (match) { - let filePath = match[0].trim(); - let results = await gPathFind.findPath(filePath, document.uri.fsPath); - if (results.length === 0) { - DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.missingPath, filePath); + + if (type === 'DSC') { + + // Libraries + let match = line.match(REGEX_LIBRARY_PATH); + if (match) { + let filePath = match[0].trim(); + let results = await gPathFind.findPath(filePath, document.uri.fsPath); + if (results.length === 0) { + DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.missingPath, filePath); + } + let newLibDefinition = new InfDsc(filePath, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)), this.sectionsStack[this.sectionsStack.length - 1], line); + const libName = newLibDefinition.text.split("|")[0].trim(); + const libNameTag = libName + " - " + newLibDefinition.getModuleTypeStr(); + if (this.libraryTypeTrack.has(libNameTag) && (libName.toLocaleLowerCase() !== "null") && newLibDefinition.parent === undefined) { + if (this.sectionsStack[this.sectionsStack.length - 1].toLowerCase().endsWith(".inf")) { + continue; + } + let previousLibDefinition = this.libraryTypeTrack.get(libNameTag)!; + DiagnosticManager.warning(previousLibDefinition.location.uri, previousLibDefinition.location.range.start.line, + EdkDiagnosticCodes.duplicateStatement, + `Library overwritten: ${libName}`, + [vscode.DiagnosticTag.Unnecessary], + [new vscode.DiagnosticRelatedInformation(newLibDefinition.location, "New definition")]); + const index = this.filesLibraries.indexOf(previousLibDefinition); + if (index > -1) { + this.filesLibraries[index] = newLibDefinition; + } + } else { + this.filesLibraries.push(newLibDefinition); + } + this.libraryTypeTrack.set(libNameTag, newLibDefinition); + continue; } - let inf = new InfDsc(filePath, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)), this.sectionsStack[0], line); - if (filePath.toLowerCase().endsWith(".inf")) { - if (this.sectionsStack[this.sectionsStack.length - 1].toLowerCase().endsWith(".inf")) { - this.sectionsStack.pop(); + + // Modules + match = line.match(REGEX_MODULE_PATH); + if (match) { + let filePath = match[0].trim(); + let results = await gPathFind.findPath(filePath, document.uri.fsPath); + if (results.length === 0) { + DiagnosticManager.error(document.uri, lineIndex, EdkDiagnosticCodes.missingPath, filePath); } + let inf = new InfDsc(filePath, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)), this.sectionsStack[0], line); + if (filePath.toLowerCase().endsWith(".inf")) { + if (this.sectionsStack[this.sectionsStack.length - 1].toLowerCase().endsWith(".inf")) { + this.sectionsStack.pop(); + } + } + this.sectionsStack.push(filePath); + this.filesModules.push(inf); + continue; } - this.sectionsStack.push(filePath); - this.filesModules.push(inf); - continue; } } - } - - if (isRangeActive) { - isRangeActive = false; - let lineIndexEnd = lineIndex - 1; - gDebugLog.debug(`New grayout ${document.fileName} -> unuseRangeStart: ${unuseRangeStart} lineIndexEnd: ${lineIndexEnd}`); - doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); - } + + if (isRangeActive) { + isRangeActive = false; + let lineIndexEnd = lineIndex - 1; + gDebugLog.debug(`New grayout ${document.fileName} -> unuseRangeStart: ${unuseRangeStart} lineIndexEnd: ${lineIndexEnd}`); + doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); + } + + this.updateGrayoutRange(document, doucumentGrayoutRange); + - this.updateGrayoutRange(document, doucumentGrayoutRange); + } private getLangId(uri:vscode.Uri){ @@ -1088,10 +1075,16 @@ export class EdkWorkspace { evaluateExpression(expression: string): any { const originalExpression = expression; - if(expression.includes(UNDEFINED_VARIABLE)){ - gDebugLog.debug(`Evaluate expression: ${expression} -> ${false}`); - return false; - } + + // EDK sets undefined values as false + expression = expression.replaceAll(`"???"`, "FALSE"); + + // + // if(expression.includes(UNDEFINED_VARIABLE)){ + // gDebugLog.debug(`Evaluate expression: ${expression} -> ${false}`); + // return false; + // } + // add space to parenteses expression = expression.replaceAll(/\(/g, " ( "); expression = expression.replaceAll(/\)/g, " ) "); @@ -1104,28 +1097,36 @@ export class EdkWorkspace { let parentesisBalance = 0; for (const token of tokens) { if (!isNaN(Number(token))) { + // Numbers outputQueue.push(Number(token)); } else if (token.toLowerCase() === 'true' || token.toLowerCase() === 'false') { + // Boolean values outputQueue.push(token.toLowerCase() === 'true'); } else if(token.trim().startsWith('"') && token.trim().endsWith('"')){ - outputQueue.push(token.trim().slice(1,-1)); + // String values + outputQueue.push(token); } else if (token in OPERATORS) { + // Operators while (operatorStack.length && operatorStack[operatorStack.length - 1] in OPERATORS && OPERATORS[token as keyof typeof OPERATORS].precedence <= OPERATORS[operatorStack[operatorStack.length - 1] as keyof typeof OPERATORS].precedence) { outputQueue.push(operatorStack.pop()); } operatorStack.push(token); } else if (token === '(') { + // Parentesis parentesisBalance++; operatorStack.push(token); } else if (token === ')') { + // Parentesis parentesisBalance--; while (operatorStack.length && operatorStack[operatorStack.length - 1] !== '(') { outputQueue.push(operatorStack.pop()); } operatorStack.pop(); } else{ - throw new Error(`Error evaluating expression: ${originalExpression} - bad Operand ${token}`); + // Strings without quotes + outputQueue.push(`"${token}"`); + // throw new Error(`Error evaluating expression: ${originalExpression} - bad Operand ${token}`); } } diff --git a/src/index/operations.ts b/src/index/operations.ts new file mode 100644 index 0000000..9ca6b97 --- /dev/null +++ b/src/index/operations.ts @@ -0,0 +1,267 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { UNDEFINED_VARIABLE } from "./definitions"; + +export const OPERATORS = { + 'or': { precedence: 1, fn: _or }, + 'OR': { precedence: 1, fn: _or }, + '||': { precedence: 1, fn: _or }, + 'and': { precedence: 2, fn: _and}, + 'AND': { precedence: 2, fn: _and}, + '&&': { precedence: 2, fn: _and}, + '|': { precedence: 3, fn: _bitwise_or }, + '^': { precedence: 4, fn: _xor }, + '&': { precedence: 5, fn: _bitsiwe_and }, + '!=': { precedence: 6, fn: _not_equals}, + 'NE': { precedence: 6, fn: _not_equals}, + '==': { precedence: 6, fn: _equals }, + 'EQ': { precedence: 6, fn: _equals }, + '<=': { precedence: 7, fn: _lessOrEquals }, + '>=': { precedence: 7, fn: _greaterOrEquals }, + '<': { precedence: 7, fn: _lessThan }, + '>': { precedence: 7, fn: _greaterThan }, + '+': { precedence: 8, fn: _plus }, + '-': { precedence: 8, fn: _subtract}, + '*': { precedence: 9, fn: _times }, + '/': { precedence: 9, fn: _division }, + '%': { precedence: 9, fn: _module }, + '!': { precedence: 10, fn: _not }, + 'not': { precedence: 10, fn: _not }, + 'NOT': { precedence: 10, fn: _not }, + '~': { precedence: 10, fn: _bitwiseNot }, + '<<': { precedence: 11, fn: _leftShift }, + '>>': { precedence: 11, fn: _rightShift }, + 'in': { precedence: 12, fn: _in}, + 'IN': { precedence: 12, fn: _in}, +}; + + + +function boolToNumber(value: boolean): number { + if (typeof value !== 'boolean') { + return value; + } + return value ? 1 : 0; +} + +function _in(x:any, y: any) { + if (x === UNDEFINED_VARIABLE || y === UNDEFINED_VARIABLE) { + return false; + } + if(typeof x === 'string'){ + x = x.replaceAll('"', ''); + } + if(typeof y === 'string'){ + y = y.replaceAll('"', ''); + y = y.split(" "); + } + + if(y.includes){ + return y.includes(x); + } + return false; + +} + +function _bitwiseNot(y:any, x: number) { + if(validateSingle(x, 'number', '~')) { + return ~x; + } + return false; + +} + +function _lessThan(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '>')) { + return x > y; + } + return false; +} + +function _lessOrEquals(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '<=')) { + return x <= y; + } + return false; +} + +function _greaterThan(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '>')) { + return x > y; + } + return false; +} + +function _greaterOrEquals(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '>=')) { + return x >= y; + } + return false; +} + +function _leftShift(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '<<')) { + return x << y; + } + false; +} + +function _rightShift(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '>>')) { + return x >> y; + } + false; +} + +function _times(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '*')) { + return x * y; + } + return false; +} + +function _division(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '/')) { + if (y === 0) { + throw new Error(`Invalid expression: Division by zero.`); + } + return x / y; + } + + return false; +} + +function _module(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + + + + if(validate(x, y, 'number', '%')) { + if (y === 0) { + throw new Error(`Invalid expression: Module by zero.`); + } + return x % y; + } + + return false; +} + +function _plus(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + + if(validate(x, y, 'number', '+')) { + return x + y; + } + return false; +} + +function _subtract(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '-')) { + return x - y; + } + return false; +} + +function _xor(x:any, y: any) { + y = boolToNumber(y); + x = boolToNumber(x); + if(validate(x, y, 'number', '^')) { + return x ^ y; + } + return false; + +} + +function _bitwise_or(x:number, y: number) { + + if(validate(x, y, 'number', '|')) { + return x | y; + } + return false; + +} + +function _bitsiwe_and(x:number, y: number) { + if(validate(x, y, 'number', '&')) { + return x & y; + } + return false; + +} + +function _equals(x:any, y: any) { + if (x === UNDEFINED_VARIABLE || y === UNDEFINED_VARIABLE) { + return false; + } + return x === y; +} + +function _not_equals(x:any, y: any) { + if (x === UNDEFINED_VARIABLE || y === UNDEFINED_VARIABLE) { + return false; + } + return x !== y; +} + +function _not(y:any, x: boolean) { + if(validateSingle(x, 'boolean', 'not')) { + return !x; + } + return false; +} + +function _and(x:any, y: boolean) { + if (validate(x, y, 'boolean', 'and')) { + return x && y; + } + return false; +} + +function _or(x:any, y: boolean) { + + if(validate(x, y, 'boolean', 'or')) { + return x || y; + } + return false; + + +} + +function validateSingle(x:any, expected:string, operator:string) { + if (x === UNDEFINED_VARIABLE) { + return false; + } + if (typeof x !== expected) { + throw new Error(`Invalid expression: This operator cannot be used in ${typeof x} expression: [${operator}].`); + } + return true; +} + +function validate(x:any, y:any, expected:string, operator:string) { + if (x === UNDEFINED_VARIABLE || y === UNDEFINED_VARIABLE) { + return false; + } + if (typeof x !== expected || typeof y !== expected) { + throw new Error(`Invalid expression: This operator cannot be used in ${typeof x} ${operator} ${typeof y} expression: [${operator}].`); + } + return true; +} diff --git a/src/statusBar.ts b/src/statusBar.ts index 6ffce08..bd7c84c 100644 --- a/src/statusBar.ts +++ b/src/statusBar.ts @@ -43,7 +43,7 @@ export function pushText(text:string){ export function popText(){ let text = textStack.pop(); - myStatusBarItem.text = text? text:"EDK2: Started*"; + myStatusBarItem.text = text? text:"EDK2Code"; myStatusBarItem.show(); myStatusBarItem.tooltip = ""; } diff --git a/test/testDscOperations.dsc b/test/testDscOperations.dsc new file mode 100644 index 0000000..e2a602a --- /dev/null +++ b/test/testDscOperations.dsc @@ -0,0 +1,138 @@ +[Defines] + PLATFORM_NAME = TestOperations + PLATFORM_GUID = 12345678-1234-1234-1234-123456789abc + PLATFORM_VERSION = 1.0 + DSC_SPECIFICATION = 0x00010017 + + DEFINE VAR1 = TRUE + DEFINE VAR2 = TRUE + DEFINE VAR3 = FALSE + DEFINE VAR4 = TRUE + DEFINE VAR5 = 5 + DEFINE VAR6 = 10 + DEFINE VAR7 = 15 + DEFINE VAR8 = 10 + DEFINE VAR9 = 5 + DEFINE VAR10 = 5 + DEFINE VAR11 = 10 + DEFINE VAR12 = 10 + DEFINE VAR13 = 3 + DEFINE VAR14 = 2 + DEFINE VAR15 = 5 + DEFINE VAR16 = 0 + DEFINE VAR17 = 6 + DEFINE VAR18 = 1 + DEFINE VAR19 = 6 + DEFINE VAR20 = 1 + DEFINE VAR21 = 10 + DEFINE VAR22 = 2 + DEFINE VAR23 = 0xFF + DEFINE VAR24 = 0x00 + DEFINE VAR25 = 2 + DEFINE VAR26 = 4 + DEFINE VAR27 = 1 + DEFINE VAR29 = TRUE + DEFINE VAR30 = FALSE + DEFINE VAR31 = TRUE + DEFINE VAR32 = "TEST" + DEFINE VAR33 = TEST + + +[Components] +!if $(VAR33) == TEST + # !error "VAR33 is equal to TEST" +!else + !error "VAR33 is not equal to TEST" +!endif + +!if $(VAR33) == "TEST" + # !error "VAR33 is equal to string TEST" +!else + !error "VAR33 is not equal to string TEST" +!endif + +!if $(VAR32) == TEST + # !error "VAR32 is equal to TEST" +!else + !error "VAR32 is not equal to TEST" +!endif + +!if $(VAR32) == "TEST" + # !error "VAR32 is equal to string TEST" +!else + !error "VAR32 is not equal to string TEST" +!endif + +!if TEST == "TEST" + # !error "Literal TEST is equal to string TEST" +!else + !error "Literal TEST is not equal to string TEST" +!endif + +!if "2" in "2 3 4 5" + # !error "String '2' is found in '2 3 4 5'" +!else + !error "String '2' is not found in '2 3 4 5'" +!endif + +!if FALSE ^ 5 + # !error "Bitwise XOR between FALSE and 5 is non-zero" +!else + !error "Bitwise XOR between FALSE and 5 is zero" +!endif + +!if $(VAR13) + $(VAR14) == $(VAR15) - $(VAR16) + # !error "Addition and subtraction result in equality" +!else + !error "Addition and subtraction do not result in equality" +!endif + +!if $(UNDEFINED_VAR) == FALSE + # !error "UNDEFINED_VAR is equal to FALSE" +!else + !error "UNDEFINED_VAR is not equal to FALSE" +!endif + +!if $(VAR1) == $(VAR2) && $(VAR3) != $(VAR4) + # !error "VAR1 equals VAR2 and VAR3 does not equal VAR4" +!else + !error "VAR1 does not equal VAR2 or VAR3 equals VAR4" +!endif + +!if $(VAR5) < $(VAR6) || $(VAR7) > $(VAR8) + # !error "VAR5 is less than VAR6 or VAR7 is greater than VAR8" +!else + !error "VAR5 is not less than VAR6 and VAR7 is not greater than VAR8" +!endif + +!if $(VAR9) <= $(VAR10) && $(VAR11) >= $(VAR12) + # !error "VAR9 is less than or equal to VAR10 and VAR11 is greater than or equal to VAR12" +!else + !error "VAR9 is not less than or equal to VAR10 or VAR11 is not greater than or equal to VAR12" +!endif + +!if $(VAR17) * $(VAR18) == $(VAR19) / $(VAR20) + # !error "Multiplication and division result in equality" +!else + !error "Multiplication and division do not result in equality" +!endif + +!if $(VAR21) % $(VAR22) == 0 + # !error "VAR21 is divisible by VAR22" +!else + !error "VAR21 is not divisible by VAR22" +!endif + +!if ~$(VAR23) == $(VAR24) + !error "Bitwise NOT of VAR23 equals VAR24" +!else + # !error "Bitwise NOT of VAR23 does not equal VAR24" +!endif + +!if $(VAR25) << 1 == $(VAR26) >> 1 + !error "Left shift of VAR25 equals right shift of VAR26" +!else + # !error "Left shift of VAR25 does not equal right shift of VAR26" +!endif + +!error "ALL Test Passed" \ No newline at end of file From 78fafe5fdae4e239304a7f34f7ce9d045ac2e9f0 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Mon, 8 Sep 2025 17:56:16 -0500 Subject: [PATCH 13/73] Update LICENSE.txt Update --- LICENSE.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index bd0820c..a9039f7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Intel Corporation +Copyright (c) 2025 Intel Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,5 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + +SOFTWARE. From 4345de12eea89422086513793b218d09f2bf0940 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Tue, 22 Apr 2025 18:10:54 -0500 Subject: [PATCH 14/73] Refactor diagnostic handling in parser classes and update draft release script path --- package.json | 2 +- src/edkParser/infParser.ts | 27 ++++++++++++++++++- src/edkParser/languageParser.ts | 8 ++++++ src/edkParser/parserFactory.ts | 2 ++ src/symbols/edkSymbols.ts | 47 +++++++++++++++++++++++++++++++++ src/symbols/infSymbols.ts | 41 ++++++++-------------------- 6 files changed, 95 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 963c196..152a039 100644 --- a/package.json +++ b/package.json @@ -491,7 +491,7 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "draft-release": "node create_draft_release.js" + "draft-release": "node scripts/create_draft_release.js" }, "devDependencies": { "@types/mocha": "^10.0.1", diff --git a/src/edkParser/infParser.ts b/src/edkParser/infParser.ts index b5bd3eb..dd8a81a 100644 --- a/src/edkParser/infParser.ts +++ b/src/edkParser/infParser.ts @@ -1,8 +1,13 @@ -import { Edk2SymbolType } from "../symbols/symbolsType"; +import { debuglog } from "util"; +import { Edk2SymbolType, typeToStr } from "../symbols/symbolsType"; import { createRange, split } from "../utils"; import { REGEX_ANY_BUT_SECTION, REGEX_DEFINE } from "./commonParser"; import { BlockParser, DocumentParser } from "./languageParser"; +import { EdkSymbol } from "../symbols/edkSymbols"; +import { ParserFactory } from "./parserFactory"; +import * as vscode from 'vscode'; +import { DiagnosticManager, EdkDiagnosticCodes } from "../diagnostics"; class BlockModuleType extends BlockParser { name= "ModuleType"; @@ -163,6 +168,11 @@ class BlockGuid extends BlockParser { end = undefined; type = Edk2SymbolType.infGuid; visible:boolean = true; + diagnostic = async (docParser:DocumentParser, symbol:EdkSymbol)=>{ + if(!await symbol.isInDec(Edk2SymbolType.decGuid)){ + DiagnosticManager.error(docParser.document.uri, symbol.range, EdkDiagnosticCodes.statementNoKey, `No ${typeToStr.get(this.type)} found for ${symbol.name} in .dec files`); + } + }; } class BlockDepex extends BlockParser { @@ -196,6 +206,11 @@ class BlockProtocol extends BlockParser { end = undefined; type = Edk2SymbolType.infProtocol; visible:boolean = true; + diagnostic = async (docParser:DocumentParser, symbol:EdkSymbol)=>{ + if(!await symbol.isInDec(Edk2SymbolType.decProtocol)){ + DiagnosticManager.error(docParser.document.uri, symbol.range, EdkDiagnosticCodes.statementNoKey, `No ${typeToStr.get(this.type)} found for ${symbol.name} in .dec files`); + } + }; } class BlockProtocolsSection extends BlockParser { @@ -218,6 +233,11 @@ class BlockPpi extends BlockParser { end = undefined; type = Edk2SymbolType.infPpi; visible:boolean = true; + diagnostic = async (docParser:DocumentParser, symbol:EdkSymbol)=>{ + if(!await symbol.isInDec(Edk2SymbolType.decPpi)){ + DiagnosticManager.error(docParser.document.uri, symbol.range, EdkDiagnosticCodes.statementNoKey, `No ${typeToStr.get(this.type)} found for ${symbol.name} in .dec files`); + } + }; } class BlockPpisSection extends BlockParser { @@ -239,6 +259,11 @@ class BlockPcd extends BlockParser { end = undefined; type = Edk2SymbolType.infPcd; visible:boolean = true; + diagnostic = async (docParser:DocumentParser, symbol:EdkSymbol)=>{ + if(!await symbol.isInDec(Edk2SymbolType.decPcd)){ + DiagnosticManager.error(docParser.document.uri, symbol.range, EdkDiagnosticCodes.statementNoKey, `No ${typeToStr.get(this.type)} found for ${symbol.name} in .dec files`); + } + }; } class BlockPcdSection extends BlockParser { diff --git a/src/edkParser/languageParser.ts b/src/edkParser/languageParser.ts index 6226d40..e78b1d1 100644 --- a/src/edkParser/languageParser.ts +++ b/src/edkParser/languageParser.ts @@ -20,6 +20,7 @@ export abstract class BlockParser { abstract visible: boolean; // Indicates if the block is visible or hidden exclusive: boolean = true; // Indicates if other blocks should parse this line isRoot: boolean = false; // Indicates if this block is in the root block of the document + diagnostic: undefined | ((docParser:DocumentParser, symbol:EdkSymbol) => Promise) = undefined; constructor(isRoot: boolean = false) { this.isRoot = isRoot; @@ -44,6 +45,8 @@ export abstract class BlockParser { return false; } + + if (textLine.match(this.tag)) { gDebugLog.trace(`New block ${this.name} (${this.tag.toString()} -> ${textLine})`); @@ -70,6 +73,11 @@ export abstract class BlockParser { docParser.addSymbol(symbol); docParser.pushSymbolStack(symbol); + if(this.diagnostic){ + void this.diagnostic(docParser, symbol); + } + + // look for block start if (this.start) { diff --git a/src/edkParser/parserFactory.ts b/src/edkParser/parserFactory.ts index 89d1596..2185404 100644 --- a/src/edkParser/parserFactory.ts +++ b/src/edkParser/parserFactory.ts @@ -7,6 +7,7 @@ import { DecParser } from './decParser'; import { VfrParser } from './vfrParser'; import { AslParser } from './aslParser'; import { openTextDocument } from '../utils'; +import { DiagnosticManager } from '../diagnostics'; export async function getParser(uri:vscode.Uri){ let infDocument = await openTextDocument(uri); @@ -22,6 +23,7 @@ export class ParserFactory { getParser(document: vscode.TextDocument) { let languageId = document.languageId; + DiagnosticManager.clearProblems(document.uri); switch (languageId) { case "asl": return new AslParser(document); diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 7037fa7..587e266 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -3,6 +3,8 @@ import { gDebugLog } from '../extension'; import { Edk2SymbolType, typeToStr } from './symbolsType'; import { DocumentParser } from '../edkParser/languageParser'; import { SectionProperties } from '../index/edkWorkspace'; +import { ParserFactory } from '../edkParser/parserFactory'; +import path = require('path'); @@ -28,6 +30,8 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { parser:DocumentParser; + + protected _textLine: string; public get textLine(): string { return this.parser.defines.replaceDefines(this._textLine); @@ -66,6 +70,49 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { gDebugLog.trace(`Symbol Created: ${location.range.start.line}: ${this.toString()}`); } + async decCompletion(type:Edk2SymbolType, completionKind:vscode.CompletionItemKind=vscode.CompletionItemKind.File){ + let retData = []; + let factory = new ParserFactory(); + let decs = this.parser.getSymbolsType(Edk2SymbolType.infPackage); + for (const dec of decs) { + let decTextPath = await dec.getValue(); + let document = await vscode.workspace.openTextDocument(vscode.Uri.file(decTextPath)); + let decParser = factory.getParser(document); + if(decParser){ + await decParser.parseFile(); + let decPpis = decParser.getSymbolsType(type); + for (const decPpi of decPpis) { + let decPpiValue = await decPpi.getKey(); + retData.push(new vscode.CompletionItem({label:decPpiValue, detail:" " + path.basename(decPpi.location.uri.fsPath), description:""}, completionKind)); + } + } + } + return retData; + } + + async isInDec(type:Edk2SymbolType){ + let factory = new ParserFactory(); + let decs = this.parser.getSymbolsType(Edk2SymbolType.infPackage); + const testKey = await this.getKey(); + for (const dec of decs) { + let decTextPath = await dec.getValue(); + let document = await vscode.workspace.openTextDocument(vscode.Uri.file(decTextPath)); + let decParser = factory.getParser(document); + if(decParser){ + await decParser.parseFile(); + let decSymbols = decParser.getSymbolsType(type); + for (const decSymbol of decSymbols) { + const symbolKey = await decSymbol.getKey(); + if(symbolKey === testKey){ + return true; + } + } + } + } + return false; + } + + toString() { return `(${this.range.start.line + 1},${this.range.start.character}),(${this.range.end.line + 1},${this.range.end.character})(${this.typeToString()}): ${this.name}`; } diff --git a/src/symbols/infSymbols.ts b/src/symbols/infSymbols.ts index d942edc..3dc1150 100644 --- a/src/symbols/infSymbols.ts +++ b/src/symbols/infSymbols.ts @@ -82,7 +82,7 @@ export class EdkSymbolInfSectionProtocols extends InfSection { kind = vscode.SymbolKind.Class; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decProtocol, vscode.CompletionItemKind.Interface); + return this.decCompletion(Edk2SymbolType.decProtocol, vscode.CompletionItemKind.Interface); }; onDefinition: undefined; onHover: undefined; @@ -96,7 +96,7 @@ export class EdkSymbolInfPpi extends EdkSymbol { kind = vscode.SymbolKind.Event; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decPpi, vscode.CompletionItemKind.Interface); + return this.decCompletion(Edk2SymbolType.decPpi, vscode.CompletionItemKind.Interface); }; onDefinition = async (parser:DocumentParser)=>{ @@ -112,7 +112,7 @@ export class EdkSymbolInfSectionPpis extends InfSection { kind = vscode.SymbolKind.Class; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decPpi, vscode.CompletionItemKind.Interface); + return this.decCompletion(Edk2SymbolType.decPpi, vscode.CompletionItemKind.Interface); }; onDefinition: undefined; onHover: undefined; @@ -124,7 +124,7 @@ export class EdkSymbolInfSectionGuids extends InfSection { kind = vscode.SymbolKind.Class; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decGuid, vscode.CompletionItemKind.Constant); + return this.decCompletion(Edk2SymbolType.decGuid, vscode.CompletionItemKind.Constant); }; onDefinition: undefined; onHover: undefined; @@ -137,7 +137,7 @@ export class EdkSymbolinfSectionDepex extends InfSection { kind = vscode.SymbolKind.Class; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decGuid, vscode.CompletionItemKind.Constant); + return this.decCompletion(Edk2SymbolType.decGuid, vscode.CompletionItemKind.Constant); }; onDefinition: undefined; onHover: undefined; @@ -150,7 +150,7 @@ export class EdkSymbolInfSectionPcds extends InfSection { kind = vscode.SymbolKind.Class; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decPcd, vscode.CompletionItemKind.Constant); + return this.decCompletion(Edk2SymbolType.decPcd, vscode.CompletionItemKind.Constant); }; onDefinition: undefined; onHover: undefined; @@ -424,7 +424,7 @@ export class EdkSymbolInfProtocol extends EdkSymbol { kind = vscode.SymbolKind.Event; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decProtocol, vscode.CompletionItemKind.Interface); + return this.decCompletion(Edk2SymbolType.decProtocol, vscode.CompletionItemKind.Interface); }; onDefinition = async (parser:DocumentParser)=>{ return decDefinition(this, Edk2SymbolType.decProtocol); @@ -438,7 +438,7 @@ export class EdkSymbolInfPcd extends EdkSymbol { kind = vscode.SymbolKind.String; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decPcd, vscode.CompletionItemKind.Constant); + return this.decCompletion(Edk2SymbolType.decPcd, vscode.CompletionItemKind.Constant); }; onDefinition = async (parser:DocumentParser)=>{ return decDefinition(this, Edk2SymbolType.decPcd); @@ -452,7 +452,7 @@ export class EdkSymbolInfGuid extends EdkSymbol { kind = vscode.SymbolKind.Number; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decGuid, vscode.CompletionItemKind.Constant); + return this.decCompletion(Edk2SymbolType.decGuid, vscode.CompletionItemKind.Constant); }; onDefinition = async (parser:DocumentParser)=>{ return decDefinition(this,Edk2SymbolType.decGuid); @@ -466,7 +466,7 @@ export class EdkSymbolInfDepex extends EdkSymbol { kind = vscode.SymbolKind.Property; onCompletion = async (parser:DocumentParser)=>{ - return decCompletion(this, Edk2SymbolType.decPpi, vscode.CompletionItemKind.Interface); + return this.decCompletion(Edk2SymbolType.decPpi, vscode.CompletionItemKind.Interface); }; onDefinition = async (parser:DocumentParser)=>{ @@ -497,26 +497,7 @@ export class EdkSymbolInfFunction extends EdkSymbol { } -async function decCompletion(thisSymbol:EdkSymbol, type:Edk2SymbolType, completionKind:vscode.CompletionItemKind=vscode.CompletionItemKind.File){ - let retData = []; - let factory = new ParserFactory(); - let thisPpi = await thisSymbol.getKey(); - let decs = thisSymbol.parser.getSymbolsType(Edk2SymbolType.infPackage); - for (const dec of decs) { - let decTextPath = await dec.getValue(); - let document = await vscode.workspace.openTextDocument(vscode.Uri.file(decTextPath)); - let decParser = factory.getParser(document); - if(decParser){ - await decParser.parseFile(); - let decPpis = decParser.getSymbolsType(type); - for (const decPpi of decPpis) { - let decPpiValue = await decPpi.getKey(); - retData.push(new vscode.CompletionItem({label:decPpiValue, detail:" " + path.basename(decPpi.location.uri.fsPath), description:""}, completionKind)); - } - } - } - return retData; -} + async function decDefinition(thisSymbol:EdkSymbol, type:Edk2SymbolType){ let factory = new ParserFactory(); From e0faa7d4afdc1b9a5864d1eb39cf359f4dbd4cbc Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Wed, 29 Oct 2025 10:04:41 -0500 Subject: [PATCH 15/73] Add VSCode link support for references and enhance diagnostic handling --- package.json | 6 ++++ src/Languages/symbolProvider.ts | 3 ++ src/TreeDataProvider.ts | 57 +++++++++++++++++++++++++++++---- src/configuration.ts | 4 +++ src/diagnostics.ts | 14 ++++++++ src/edkParser/languageParser.ts | 10 ++++-- src/edkParser/parserFactory.ts | 3 +- src/index/edkWorkspace.ts | 39 ++++++++++++---------- src/symbols/edkSymbols.ts | 18 +++++++++-- 9 files changed, 123 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 152a039..8e55d8e 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,12 @@ "items": { "type": "string" } + }, + "edk2code.addVscodeLinksToReferences": { + "order": 2, + "type": "boolean", + "markdownDescription": "Add VSCode links to copied references. When enabled, the tree output will include clickable markdown links that open files at specific line numbers when pasted in markdown documents.", + "default": false } } } diff --git a/src/Languages/symbolProvider.ts b/src/Languages/symbolProvider.ts index 3b4cd13..29ea666 100644 --- a/src/Languages/symbolProvider.ts +++ b/src/Languages/symbolProvider.ts @@ -6,6 +6,7 @@ import { CompletionItemKind } from 'vscode'; import { ParserFactory } from '../edkParser/parserFactory'; import { gConfigAgent, gEdkWorkspaces, gGrayOutController } from '../extension'; import { Debouncer } from '../debouncer'; +import { DiagnosticManager } from '../diagnostics'; export class EdkSymbolProvider implements vscode.DocumentSymbolProvider { @@ -37,6 +38,8 @@ export class EdkSymbolProvider implements vscode.DocumentSymbolProvider { public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken) { // Create a parser for the document + DiagnosticManager.clearProblems(document.uri); + let factory = new ParserFactory(); let parser = factory.getParser(document); if (parser) { diff --git a/src/TreeDataProvider.ts b/src/TreeDataProvider.ts index d6b5bc7..4904b4a 100644 --- a/src/TreeDataProvider.ts +++ b/src/TreeDataProvider.ts @@ -1,7 +1,7 @@ import path = require('path'); import * as vscode from 'vscode'; import { TreeItemLabel } from 'vscode'; -import { edkLensTreeDetailProvider, gCompileCommands, gEdkWorkspaces, gMapFileManager, gPathFind } from './extension'; +import { edkLensTreeDetailProvider, gCompileCommands, gConfigAgent, gEdkWorkspaces, gMapFileManager, gPathFind } from './extension'; import { InfParser } from './edkParser/infParser'; import { getParser } from './edkParser/parserFactory'; import { Edk2SymbolType } from './symbols/symbolsType'; @@ -61,18 +61,61 @@ export class TreeDetailsDataProvider implements vscode.TreeDataProvider= 2) { + const position = item.command.arguments[1] as vscode.Position; + if (position && typeof position.line === 'number') { + const line = position.line + 1; + const char = position.character + 1; + return `vscode://file${uri.path}:${line}:${char}`; + } + } + + // Fallback - just link to the file without specific line + return `vscode://file${uri.path}`; + } + + return null; + } + toString(){ let result = ''; - for (const item of this.data) { - result += this.getHierarchy(item); + for (let i = 0; i < this.data.length; i++) { + const isLast = i === this.data.length - 1; + result += this.getHierarchy(this.data[i], 0, isLast, ''); } return result; } diff --git a/src/configuration.ts b/src/configuration.ts index 0a956eb..374fa39 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -62,6 +62,10 @@ export class ConfigAgent { return this.get("enableDiagnostics"); } + isAddVscodeLinksToReferences(){ + return this.get("addVscodeLinksToReferences"); + } + reloadConfigFile(){ this.workspaceConfig = this.readWpConfig(); } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index a5e9e7d..54b58ff 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -142,6 +142,8 @@ export class DiagnosticManager { relatedInformation?: vscode.DiagnosticRelatedInformation[]|undefined) { + + if(gConfigAgent.isDiagnostics() === false){ this.clearAllProblems(); return undefined; @@ -154,6 +156,18 @@ export class DiagnosticManager { }else{ range = new vscode.Range(line as number, 0, line as number, Number.MAX_VALUE); } + + // Check if the diagnostic already exists + const existingDiagnostics = DiagnosticManager.getDiagnostic(documentUri); + const isDuplicate = existingDiagnostics.some(diagnostic => + diagnostic.range.isEqual(range) && + diagnostic.message === message && + diagnostic.severity === severity + ); + + if (isDuplicate) { + return undefined; // Skip adding duplicate diagnostic + } const diagnostic = new vscode.Diagnostic(range, message, severity); diff --git a/src/edkParser/languageParser.ts b/src/edkParser/languageParser.ts index e78b1d1..4e58d04 100644 --- a/src/edkParser/languageParser.ts +++ b/src/edkParser/languageParser.ts @@ -73,9 +73,13 @@ export abstract class BlockParser { docParser.addSymbol(symbol); docParser.pushSymbolStack(symbol); - if(this.diagnostic){ - void this.diagnostic(docParser, symbol); - } + + + if (this.diagnostic) { + setTimeout(async () => { + await this.diagnostic!(docParser, symbol); + }, 1); // Adjust the delay (in milliseconds) as needed + } diff --git a/src/edkParser/parserFactory.ts b/src/edkParser/parserFactory.ts index 2185404..b2ef4ee 100644 --- a/src/edkParser/parserFactory.ts +++ b/src/edkParser/parserFactory.ts @@ -8,6 +8,7 @@ import { VfrParser } from './vfrParser'; import { AslParser } from './aslParser'; import { openTextDocument } from '../utils'; import { DiagnosticManager } from '../diagnostics'; +import { Debouncer } from '../debouncer'; export async function getParser(uri:vscode.Uri){ let infDocument = await openTextDocument(uri); @@ -23,7 +24,7 @@ export class ParserFactory { getParser(document: vscode.TextDocument) { let languageId = document.languageId; - DiagnosticManager.clearProblems(document.uri); + switch (languageId) { case "asl": return new AslParser(document); diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 04af188..12f80f5 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -14,6 +14,7 @@ import { DiagnosticManager, EdkDiagnosticCodes } from '../diagnostics'; import { PathFind } from '../pathfind'; import { OPERATORS } from './operations'; import * as edkStatusBar from '../statusBar'; +import { debuglog } from 'util'; const dscSectionTypes = ['defines','packages','buildoptions','skuids','libraryclasses','components','userextensions','defaultstores','pcdsfeatureflag','pcdsfixedatbuild','pcdspatchableinmodule','pcdsdynamicdefault','pcdsdynamichii','pcdsdynamicvpd','pcdsdynamicexdefault','pcdsdynamicexhii','pcdsdynamicexvpd']; @@ -333,19 +334,19 @@ export class EdkWorkspace { public set filesModules(value: InfDsc[]) { this._filesModules = value; } - private _filesDsc: vscode.TextDocument[] = []; - private _filesFdf: vscode.TextDocument[] = []; + private _filesDsc: Set = new Set(); + private _filesFdf: Set = new Set(); - public get filesDsc(): vscode.TextDocument[] { + public get filesDsc(): Set { return this._filesDsc; } - public set filesDsc(value: vscode.TextDocument[]) { + public set filesDsc(value: Set) { this._filesDsc = value; } - public get filesFdf(): vscode.TextDocument[] { + public get filesFdf(): Set { return this._filesFdf; } - public set filesFdf(value: vscode.TextDocument[]) { + public set filesFdf(value: Set) { this._filesFdf = value; } @@ -367,11 +368,11 @@ export class EdkWorkspace { } public dscList(){ - return this.filesDsc.map(x=>x.uri.fsPath); + return Array.from(this.filesDsc).map(x=>x.uri.fsPath); } public fdfList(){ - return this.filesFdf.map(x=>x.uri.fsPath); + return Array.from(this.filesFdf).map(x=>x.uri.fsPath); } public getFilesList(){ @@ -411,7 +412,7 @@ export class EdkWorkspace { this.filesLibraries = []; this.filesModules = []; - this.filesDsc = []; + this.filesDsc = new Set(); for (const ctrl of this._grayoutControllers) { ctrl.dispose(); } @@ -556,16 +557,16 @@ export class EdkWorkspace { gDebugLog.trace(`_process${type}: ${document.fileName}`); if (this.isDocumentInIndex(document)) { - gDebugLog.warning(`_process${type}: ${document.fileName} already in inactiveLines`); + gDebugLog.trace(`_process${type}: ${document.fileName} already in inactiveLines`); return; } let doucumentGrayoutRange = []; this.parsedDocuments.set(document.uri.fsPath, []); if (type === 'DSC') { - this.filesDsc.push(document); + this.filesDsc.add(document); } else { - this.filesFdf.push(document); + this.filesFdf.add(document); } let text = document.getText().split(/\r?\n/); @@ -665,7 +666,7 @@ export class EdkWorkspace { if (type === 'DSC') { await this._processDocument(includedDocument, 'DSC'); } else { - this.filesFdf.push(includedDocument); + this.filesFdf.add(includedDocument); await this._processDocument(includedDocument, 'FDF'); } gDebugLog.trace(`END Including: ${location[0].uri.fsPath}`); @@ -788,21 +789,23 @@ export class EdkWorkspace { switch (this.getLangId(uri)) { case "edk2_dsc": + case "edk2_fdf": + // fdf files can be included in dsc files for (const dsc of this.filesDsc) { if(uri.fsPath === dsc.fileName) { return true; } } - return false; - case "edk2_fdf": + + for (const fdf of this.filesFdf) { if(uri.fsPath === fdf.fileName){ return true; } } - break; + return false; case "edk2_dec": break; case "edk2_inf": @@ -876,7 +879,9 @@ export class EdkWorkspace { let value = line.replace(/!include/gi, "").trim(); let location = await gPathFind.findPath(value); if (!location.length){continue;} + gDebugLog.trace(`Looking include: ${location[0].uri.fsPath}`); if(location[0].uri.fsPath === uri.fsPath){ + gDebugLog.trace(`Include found: ${location[0].uri.fsPath} in ${document.uri.fsPath} line ${lineIndex}`); retLocations.push(new vscode.Location(document.uri, createRange(lineIndex,lineIndex,0))); } } @@ -912,7 +917,7 @@ export class EdkWorkspace { if (this.flashDefinitionDocument) { if (!this.isDocumentInIndex(this.flashDefinitionDocument)) { - this.filesFdf = []; + this.filesFdf = new Set(); await this._processDocument(this.flashDefinitionDocument,"FDF"); } } diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 587e266..7ddd7e7 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -5,8 +5,10 @@ import { DocumentParser } from '../edkParser/languageParser'; import { SectionProperties } from '../index/edkWorkspace'; import { ParserFactory } from '../edkParser/parserFactory'; import path = require('path'); +import { debuglog } from 'util'; +import { log } from 'console'; - +let decSymbolCache = new Map(); export abstract class EdkSymbol extends vscode.DocumentSymbol { @@ -94,15 +96,25 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { let factory = new ParserFactory(); let decs = this.parser.getSymbolsType(Edk2SymbolType.infPackage); const testKey = await this.getKey(); + + for (const dec of decs) { let decTextPath = await dec.getValue(); + if(decSymbolCache.has(testKey)){ + let cachedPath = decSymbolCache.get(testKey); + if(cachedPath === decTextPath){ + return true; + } + } let document = await vscode.workspace.openTextDocument(vscode.Uri.file(decTextPath)); let decParser = factory.getParser(document); if(decParser){ await decParser.parseFile(); let decSymbols = decParser.getSymbolsType(type); for (const decSymbol of decSymbols) { + const symbolKey = await decSymbol.getKey(); + decSymbolCache.set(symbolKey, decTextPath); if(symbolKey === testKey){ return true; } @@ -123,12 +135,12 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { } async getValue(){ - gDebugLog.error(`getValue not implemented for ${this.type}`); + // gDebugLog.warning(`getValue not implemented for ${this.type}`); return this.textLine; } async getKey(){ - gDebugLog.error(`getKey not implemented for ${this.type}`); + // gDebugLog.warning(`getKey not implemented for ${this.type}`); return this.textLine; } From 9dd39e50b979ec2fe97db0d96b504c50e5b9d84b Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Wed, 29 Oct 2025 10:12:14 -0500 Subject: [PATCH 16/73] Bump version to 1.1.2 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e55d8e..97ba351 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code", "displayName": "Edk2code", "description": "EDK2 code support", - "version": "1.1.1", + "version": "1.1.2", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", From 545ecc88f52eb57781eaf096eb949b6e1a629c1f Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Wed, 29 Oct 2025 10:41:50 -0500 Subject: [PATCH 17/73] Enhance path resolution in EdkWorkspace and FdfSymbols classes --- src/index/edkWorkspace.ts | 14 +++++++------- src/symbols/fdfSymbols.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 12f80f5..9323039 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -442,7 +442,7 @@ export class EdkWorkspace { this.platformName = this.defines.getDefinition("PLATFORM_NAME") || undefined; let flashDefinitionString = this.defines.getDefinition("FLASH_DEFINITION") || undefined; if (flashDefinitionString) { - let flashDefinitionPath = await gPathFind.findPath(flashDefinitionString); + let flashDefinitionPath = await gPathFind.findPath(flashDefinitionString, path.dirname(this.mainDsc.fsPath)); if(flashDefinitionPath.length>0){ this.flashDefinitionDocument = await openTextDocument(flashDefinitionPath[0].uri); } @@ -837,16 +837,16 @@ export class EdkWorkspace { } } for (const inf of relativeInf) { - let location = await gPathFind.findPath(inf); + let infLocation = await gPathFind.findPath(inf); - if (location.length === 0){continue;} + if (infLocation.length === 0){continue;} - let parser = await getParser(location[0].uri); + let parser = await getParser(infLocation[0].uri); if (parser) { let sources = parser.getSymbolsType(Edk2SymbolType.infSource); for (const source of sources) { - let location = await gPathFind.findPath(await source.getValue()); - if(location[0].uri.fsPath === uri.fsPath){ + let sourceLocation = await gPathFind.findPath(await source.getValue(), path.dirname(infLocation[0].uri.fsPath)); + if(sourceLocation[0].uri.fsPath === uri.fsPath){ return true; } } @@ -877,7 +877,7 @@ export class EdkWorkspace { if(line.match(/\!include\s/gi)){ line = this.defines.replaceDefines(line); let value = line.replace(/!include/gi, "").trim(); - let location = await gPathFind.findPath(value); + let location = await gPathFind.findPath(value, path.dirname(document.uri.fsPath)); if (!location.length){continue;} gDebugLog.trace(`Looking include: ${location[0].uri.fsPath}`); if(location[0].uri.fsPath === uri.fsPath){ diff --git a/src/symbols/fdfSymbols.ts b/src/symbols/fdfSymbols.ts index 605ae7c..1c295ec 100644 --- a/src/symbols/fdfSymbols.ts +++ b/src/symbols/fdfSymbols.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import { Edk2SymbolType } from "./symbolsType"; import { WorkspaceDefinitions } from "../index/definitions"; import { DocumentParser } from "../edkParser/languageParser"; +import * as path from 'path'; export class EdkSymbolDscSection extends EdkSymbol { type = Edk2SymbolType.dscSection; @@ -33,8 +34,9 @@ export class EdkSymbolFdfInf extends EdkSymbol { onCompletion: undefined; onDefinition = async (parser:DocumentParser)=>{ - let path = await this.getValue(); - return await gPathFind.findPath(path); + let pathValue = await this.getValue(); + let relPath = path.dirname(parser.document.uri.fsPath); + return await gPathFind.findPath(pathValue, relPath); }; async getValue(){ @@ -75,8 +77,9 @@ export class EdkSymbolFdfInclude extends EdkSymbol { onCompletion: undefined; onDefinition = async (parser:DocumentParser)=>{ - let path = await this.getValue(); - return await gPathFind.findPath(path); + let pathValue = await this.getValue(); + let relPath = path.dirname(parser.document.uri.fsPath); + return await gPathFind.findPath(pathValue, relPath); }; async getValue(){ From 6efca7e8805a1018628f32f718955d672abdbd80 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Wed, 29 Oct 2025 14:20:29 -0500 Subject: [PATCH 18/73] Refactor TreeItem to use markdown formatting for labels and descriptions in toString method --- src/TreeDataProvider.ts | 9 +++++---- src/treeElements/TreeItem.ts | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/TreeDataProvider.ts b/src/TreeDataProvider.ts index 4904b4a..40e4680 100644 --- a/src/TreeDataProvider.ts +++ b/src/TreeDataProvider.ts @@ -62,17 +62,18 @@ export class TreeDetailsDataProvider implements vscode.TreeDataProvider Date: Mon, 9 Feb 2026 13:01:12 -0600 Subject: [PATCH 19/73] Expanded definitions in DSC files. This improves on hover for DSC DEFINES $(VARIABLE) expanding definitinos recursively --- src/index/definitions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index/definitions.ts b/src/index/definitions.ts index 643ffb3..5f39a90 100644 --- a/src/index/definitions.ts +++ b/src/index/definitions.ts @@ -64,7 +64,8 @@ export class WorkspaceDefinitions { setDefinition(key:string, value:string, location:vscode.Location|undefined){ - + // Expand any existing definitions referenced in the value + value = this.replaceDefines(value); gDebugLog.trace(`setDefinition: ${key} = ${value}`); this.defines.set(key, {name: key, value:value, location:location}); From 31f1edc861c5b7b4004a7951eb5c8066a118395e Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Mon, 9 Feb 2026 14:14:26 -0600 Subject: [PATCH 20/73] Adding EDK2: Show workspace Defines This allows user to see the DEFINES and PCDs used in the workspace --- package.json | 15 +++++++ src/TreeDataProvider.ts | 2 + src/contextState/cmds.ts | 21 ++++++++- src/extension.ts | 22 ++++++--- src/index/edkWorkspace.ts | 6 ++- src/treeElements/DefinesTreeItem.ts | 70 +++++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 src/treeElements/DefinesTreeItem.ts diff --git a/package.json b/package.json index 97ba351..922f679 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,11 @@ "command": "edk2code.NodeFocusBack", "title": "Node Focus Back", "icon": "$(arrow-left)" + }, + { + "command": "edk2code.showWorkspaceDefines", + "title": "EDK2: Show workspace Defines", + "icon": "$(refresh)" } ], "configuration": [ @@ -209,6 +214,7 @@ "views": { "explorer": [ { + "icon": "assets/icon.png", "id": "detailsView", "name": "Edk2", "type": "tree" @@ -299,6 +305,10 @@ { "command": "edk2code.NodeFocusBack", "when": "false" + }, + { + "command": "edk2code.showWorkspaceDefines", + "when": "true" } ], "editor/title": [], @@ -312,6 +322,11 @@ "command": "edk2code.copyTreeData", "when": "view == detailsView", "group": "navigation" + }, + { + "command": "edk2code.showWorkspaceDefines", + "when": "view == detailsView", + "group": "navigation" } ], "editor/context": [ diff --git a/src/TreeDataProvider.ts b/src/TreeDataProvider.ts index 40e4680..ab99583 100644 --- a/src/TreeDataProvider.ts +++ b/src/TreeDataProvider.ts @@ -12,6 +12,7 @@ import { EdkSymbol } from './symbols/edkSymbols'; import { DiagnosticManager, EdkDiagnosticCodes } from './diagnostics'; import { CompileCommandsEntry } from './compileCommands'; import { TreeItem } from './treeElements/TreeItem'; +import { DefinesRootItem } from './treeElements/DefinesTreeItem'; export class TreeDetailsDataProvider implements vscode.TreeDataProvider { @@ -21,6 +22,7 @@ export class TreeDetailsDataProvider implements vscode.TreeDataProvider; +// export var edkDefinesTreeProvider: DefinesTreeDataProvider; + export var gMapFileManager: MapFilesManager; @@ -156,11 +159,12 @@ export async function activate(context: vscode.ExtensionContext) { void copyToClipboard(path, "Path copied to clipboard"); }), - - + vscode.commands.registerCommand('edk2code.showWorkspaceDefines', async ()=>{await cmds.showDefines();}) ]; - context.subscriptions.concat(commands); + // We need to concat custom commands, because they are not in the list of commands + // because they are added after the extension is activated + context.subscriptions.push(...commands); gExtensionContext = context; edkStatusBar.init(context); @@ -170,7 +174,15 @@ export async function activate(context: vscode.ExtensionContext) { gMapFileManager = new MapFilesManager(); gCompileCommands = new CompileCommands(); + edkLensTreeDetailProvider = new TreeDetailsDataProvider(); + edkLensTreeDetailView = vscode.window.createTreeView('detailsView', { treeDataProvider: edkLensTreeDetailProvider, showCollapseAll:true }); + + // edkDefinesTreeProvider = new DefinesTreeDataProvider(); + // vscode.window.createTreeView('definesView', { treeDataProvider: edkDefinesTreeProvider, showCollapseAll: true }); + + await gEdkWorkspaces.loadConfig(); + // edkDefinesTreeProvider.refresh(); gFileUseWarning = new FileUseWarning(); @@ -187,11 +199,7 @@ export async function activate(context: vscode.ExtensionContext) { }); } - - edkLensTreeDetailProvider = new TreeDetailsDataProvider(); - edkLensTreeDetailView = vscode.window.createTreeView('detailsView', { treeDataProvider: edkLensTreeDetailProvider, showCollapseAll:true }); - edkLensTreeDetailProvider.refresh(); edkLensTreeDetailView.onDidExpandElement(async event => { let node = event.element as TreeItem; await node.expand(); diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 9323039..2136f06 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -29,7 +29,7 @@ type ConditonOpenBlock = { lineNo:number; }; -interface Pcd { +export interface Pcd { name:string; value:string position:vscode.Location @@ -516,6 +516,10 @@ export class EdkWorkspace { return this.pcdDefinitions.get(namespace); } + getAllPcds() { + return this.pcdDefinitions; + } + private stripComment(line: string): string { if (!line.includes("#")) { diff --git a/src/treeElements/DefinesTreeItem.ts b/src/treeElements/DefinesTreeItem.ts new file mode 100644 index 0000000..73283d5 --- /dev/null +++ b/src/treeElements/DefinesTreeItem.ts @@ -0,0 +1,70 @@ +import * as vscode from 'vscode'; +import { gEdkWorkspaces } from '../extension'; +import { TreeItem } from './TreeItem'; + +export class DefinesRootItem extends TreeItem { + constructor( label: string) { + super(label, vscode.TreeItemCollapsibleState.Collapsed); + } + + // Override to populate children dynamically + async expand() { + this.children = []; // Clear current children + + if (this.label === "DEFINES") { + this.populateDefines(); + } else if (this.label === "PCDs") { + this.populatePcds(); + } + } + + private populateDefines() { + if (!gEdkWorkspaces || !gEdkWorkspaces.workspaces) { return; } + + for (const wp of gEdkWorkspaces.workspaces) { + let defs = wp.getDefinitions(); + for (const [key, value] of defs.entries()) { + const label = `${key}`; + const item = new TreeItem(label, vscode.TreeItemCollapsibleState.None); + item.description = value.value; + if (value.location) { + item.command = { + command: 'vscode.open', + title: 'Open', + arguments: [value.location.uri, { selection: value.location.range }] + }; + item.tooltip = label; + } + this.addChildren(item); + } + } + } + + private populatePcds() { + if (!gEdkWorkspaces || !gEdkWorkspaces.workspaces) { return; } + + for (const wp of gEdkWorkspaces.workspaces) { + let pcds = wp.getAllPcds(); + for (const [namespace, pcdMap] of pcds) { + for (const [name, pcd] of pcdMap) { + const label = `${namespace}.${name} = ${pcd.value}`; + const item = new TreeItem(label, vscode.TreeItemCollapsibleState.None); + item.command = { + command: 'vscode.open', + title: 'Open', + arguments: [pcd.position.uri, { selection: pcd.position.range }] + }; + item.tooltip = label; + this.addChildren(item); + } + } + } + } + + // Needed because TreeItem implementation uses it + addChildren(node: TreeItem) { + node.parent = this; + // this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; // Don't auto expand when adding children internally + this.children.push(node); + } +} From 2fd2747595d7328bd4a79bae33488f765e1de511 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Mon, 9 Feb 2026 15:02:04 -0600 Subject: [PATCH 21/73] Add diagnostic for undefined DEFINES This allows user to identify missing definitions for DEFINES in DSC files --- src/Languages/symbolProvider.ts | 3 --- src/diagnostics.ts | 4 +++- src/index/edkWorkspace.ts | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Languages/symbolProvider.ts b/src/Languages/symbolProvider.ts index 29ea666..a463fa5 100644 --- a/src/Languages/symbolProvider.ts +++ b/src/Languages/symbolProvider.ts @@ -38,12 +38,9 @@ export class EdkSymbolProvider implements vscode.DocumentSymbolProvider { public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken) { // Create a parser for the document - DiagnosticManager.clearProblems(document.uri); - let factory = new ParserFactory(); let parser = factory.getParser(document); if (parser) { - await parser.parseFile(); return parser.symbolsTree; } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 54b58ff..0937814 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -29,7 +29,8 @@ emptyFile, circularDependency, inactiveCode, edk2CodeUnsuported, -errorMessage +errorMessage, +undefinedVariable } export const edkErrorDescriptions: Map = new Map([ @@ -60,6 +61,7 @@ export const edkErrorDescriptions: Map = new Map([ [EdkDiagnosticCodes.inactiveCode, "Inactive code"], [EdkDiagnosticCodes.edk2CodeUnsuported, "Edk2Code unsupported"], [EdkDiagnosticCodes.errorMessage, "Error message"], + [EdkDiagnosticCodes.undefinedVariable, "Undefined variable"], ]); export class DiagnosticManager { diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 2136f06..a60fa04 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -19,6 +19,13 @@ import { debuglog } from 'util'; const dscSectionTypes = ['defines','packages','buildoptions','skuids','libraryclasses','components','userextensions','defaultstores','pcdsfeatureflag','pcdsfixedatbuild','pcdspatchableinmodule','pcdsdynamicdefault','pcdsdynamichii','pcdsdynamicvpd','pcdsdynamicexdefault','pcdsdynamicexhii','pcdsdynamicexvpd']; +// EDK2 built-in macros that should not trigger undefined variable warnings +const EDK2_BUILTIN_MACROS = new Set([ + "WORKSPACE", "EDK_SOURCE", "EFI_SOURCE", "TARGET", "TOOL_CHAIN_TAG", + "ARCH", "MODULE_NAME", "OUTPUT_DIRECTORY", "BUILD_NUMBER", "INF_VERSION", + "NAMED_GUID", "INF_OUTPUT" +]); + type ConditionBlock = { active: boolean; // Whether the block is active or not satisfied: boolean; // Whether the condition has been satisfied @@ -581,6 +588,7 @@ export class EdkWorkspace { gDebugLog.trace(`# Parsing ${type} Document: ${document.uri.fsPath}`); for (let line of text) { lineIndex++; + let originalLine = line; gDebugLog.trace(`\t\t${lineIndex}: ${line}`); line = this.stripComment(line); @@ -656,6 +664,19 @@ export class EdkWorkspace { } else { this.definesFdf.setDefinition(key, value, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0))); } + + // Warn about undefined variables directly referenced in the raw value + let originalValue = split(originalLine, "=", 2)[1].trim(); + let unresolvedVars = originalValue.match(REGEX_VAR_USAGE); + if (unresolvedVars) { + let defines = type === 'DSC' ? this.defines : this.definesFdf; + for (const unresolved of new Set(unresolvedVars)) { + let varName = unresolved.replace(/\$\(\s*/, '').replace(/\s*\)/, ''); + if (!defines.isDefined(varName) && !EDK2_BUILTIN_MACROS.has(varName.toUpperCase())) { + DiagnosticManager.warning(document.uri, lineIndex, EdkDiagnosticCodes.undefinedVariable, `${unresolved}`); + } + } + } } continue; } From e534386d7f47eb4987659a4727dd067cd737af58 Mon Sep 17 00:00:00 2001 From: Guillermo Antonio Palomino Sosa Date: Mon, 9 Feb 2026 17:05:26 -0600 Subject: [PATCH 22/73] Added diagnostic for overwrite DEFINES DEFINES that are replaced without referencing it self will show a warning --- languages/syntaxes/edk2_fdf.tmLanguage.json | 6 ++++- src/diagnostics.ts | 4 ++- src/edkParser/commonParser.ts | 2 +- src/index/edkWorkspace.ts | 29 +++++++++++++++++++-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/languages/syntaxes/edk2_fdf.tmLanguage.json b/languages/syntaxes/edk2_fdf.tmLanguage.json index 3a090b0..520c0ae 100644 --- a/languages/syntaxes/edk2_fdf.tmLanguage.json +++ b/languages/syntaxes/edk2_fdf.tmLanguage.json @@ -12,6 +12,10 @@ { "//": "TO-DO: Too much specific keywords in FDF, should revisit for finding better way of color syntax." }, + { + "name": "variable.language.edk2_dsc", + "match": "\\$\\(.*?\\)" + }, { "name": "variable.language.edk2_fdf", "match": "((? = new Map([ @@ -62,6 +63,7 @@ export const edkErrorDescriptions: Map = new Map([ [EdkDiagnosticCodes.edk2CodeUnsuported, "Edk2Code unsupported"], [EdkDiagnosticCodes.errorMessage, "Error message"], [EdkDiagnosticCodes.undefinedVariable, "Undefined variable"], + [EdkDiagnosticCodes.duplicateDefine, "Duplicate DEFINE overrides previous value"], ]); export class DiagnosticManager { diff --git a/src/edkParser/commonParser.ts b/src/edkParser/commonParser.ts index 2d1158d..5ecccd3 100644 --- a/src/edkParser/commonParser.ts +++ b/src/edkParser/commonParser.ts @@ -1,4 +1,4 @@ -export const REGEX_DEFINE = /^(?:(?![=\!]).)*=.*/gi; +export const REGEX_DEFINE = /^\s*(?:DEFINE\s+\w+|(?:DSC_SPECIFICATION|PLATFORM_GUID|PLATFORM_VERSION|PLATFORM_NAME|SKUID_IDENTIFIER|SUPPORTED_ARCHITECTURES|BUILD_TARGETS|OUTPUT_DIRECTORY|FLASH_DEFINITION|BUILD_NUMBER|FIX_LOAD_TOP_MEMORY_ADDRESS|TIME_STAMP_FILE|RFC_LANGUAGES|ISO_LANGUAGES|VPD_TOOL_GUID|PCD_INFO_GENERATION|PCD_VAR_CHECK_GENERATION|PREBUILD|POSTBUILD))\s*=.*/gi; export const REGEX_INCLUDE = /\s*\!include/gi; export const REGEX_PATH_FILE = /(?<=^.*?\|\s*)[a-zA-Z\/\\0-9\._]+\.([a-z\d])/gi; diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index a60fa04..63ebc5b 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -656,9 +656,34 @@ export class EdkWorkspace { let key = line.replace(/define/gi, "").trim(); key = split(key, "=", 2)[0].trim(); let value = split(line, "=", 2)[1].trim(); - if (value.includes(`$(${key})`)) { + let originalValue = split(originalLine, "=", 2)[1].trim(); + + if (value.includes(`$(${key})`) || originalValue.includes(`$(${key})`)) { gDebugLog.info(`Circular define: ${key}: ${value}`); } else { + // Warn if DEFINE is being redefined without referencing itself + let defines = type === 'DSC' ? this.defines : this.definesFdf; + if (defines.isDefined(key)) { + let previousLocation = defines.getDefinitionLocation(key); + let relatedInfo: vscode.DiagnosticRelatedInformation[] | undefined; + if (previousLocation) { + relatedInfo = [ + new vscode.DiagnosticRelatedInformation( + previousLocation, + `Previous definition of '${key}'` + ) + ]; + } + DiagnosticManager.warning( + document.uri, + lineIndex, + EdkDiagnosticCodes.duplicateDefine, + `'${key}' is redefined without referencing its previous value. Use $(${key}) to extend it.`, + [], + relatedInfo + ); + } + if (type === 'DSC') { this.defines.setDefinition(key, value, new vscode.Location(document.uri, new vscode.Position(lineIndex, 0))); } else { @@ -666,7 +691,7 @@ export class EdkWorkspace { } // Warn about undefined variables directly referenced in the raw value - let originalValue = split(originalLine, "=", 2)[1].trim(); + let unresolvedVars = originalValue.match(REGEX_VAR_USAGE); if (unresolvedVars) { let defines = type === 'DSC' ? this.defines : this.definesFdf; From 97f792e66c5b538723886802b9e487d9a7c5e5c6 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 5 Mar 2026 18:18:17 -0600 Subject: [PATCH 23/73] Create action bar --- package.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 922f679..f891aab 100644 --- a/package.json +++ b/package.json @@ -211,10 +211,23 @@ } } ], + "viewsContainers": { + "activitybar": [ + { + "id": "edk2code-sidebar", + "title": "Edk2Code", + "icon": "assets/icon.png" + } + ] + }, "views": { - "explorer": [ + "edk2code-sidebar": [ + { + "id": "workspaceView", + "name": "Workspace", + "type": "tree" + }, { - "icon": "assets/icon.png", "id": "detailsView", "name": "Edk2", "type": "tree" From f4f76e68903b2d9f1e9b4eab98f01803a1429593 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 5 Mar 2026 18:40:36 -0600 Subject: [PATCH 24/73] Add includes in workspace view --- instructions/outline.md | 91 +++++++++++++++++ package.json | 14 +++ src/contextState/cmds.ts | 4 +- src/extension.ts | 30 +++++- src/index/edkWorkspace.ts | 36 ++++++- src/workspaceTree/WorkspaceTreeProvider.ts | 112 +++++++++++++++++++++ 6 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 instructions/outline.md create mode 100644 src/workspaceTree/WorkspaceTreeProvider.ts diff --git a/instructions/outline.md b/instructions/outline.md new file mode 100644 index 0000000..68acd3d --- /dev/null +++ b/instructions/outline.md @@ -0,0 +1,91 @@ +Read [](file:///d%3A/github/Edk2Code/src/Languages/symbolProvider.ts#1-1), lines 1 to 100 + +Read [](file:///d%3A/github/Edk2Code/src/symbols/dscSymbols.ts#1-1), lines 1 to 100 + +Read [](file:///d%3A/github/Edk2Code/src/Languages/languages.ts) + +Read [](file:///d%3A/github/Edk2Code/src/edkParser/dscParser.ts#1-1), lines 1 to 120 + +Read [](file:///d%3A/github/Edk2Code/src/edkParser/languageParser.ts#1-1), lines 1 to 100 + +Read [](file:///d%3A/github/Edk2Code/src/symbols/symbolFactory.ts#1-1), lines 1 to 80 + +Read [](file:///d%3A/github/Edk2Code/src/edkParser/languageParser.ts#100-100), lines 100 to 100 + +Here's the flow, end to end: + +--- + +### 1. Registration (languages.ts) + +`initLanguages()` calls: +```ts +vscode.languages.registerDocumentSymbolProvider( + { scheme: 'file', language: 'edk2_dsc' }, + new EdkSymbolProvider() +); +``` +This tells VS Code to call `EdkSymbolProvider` whenever the Outline/breadcrumb panel needs symbols for a `edk2_dsc` file. + +--- + +### 2. `EdkSymbolProvider` (symbolProvider.ts) + +Implements `vscode.DocumentSymbolProvider`. VS Code calls `provideDocumentSymbols()`, which: +1. Creates a parser via `ParserFactory.getParser(document)` β€” returns a `DscParser` for DSC files. +2. Calls `parser.parseFile()`. +3. Returns `parser.symbolsTree` β€” a `DocumentSymbol[]` tree. + +--- + +### 3. DSC Parser β€” `BlockParser` tree (dscParser.ts) + +The parser is defined as a hierarchy of `BlockParser` subclasses. Each one has: +- `tag`: regex that identifies lines starting this block +- `start`/`end`: optional regexes for block boundaries +- `type`: an `Edk2SymbolType` enum value +- `context`: nested child `BlockParser[]` + +For DSC, the structure looks like: + +``` +BlockDefines β†’ [Defines] / [BuildOptions] + └─ BlockDefinition (DEFINE foo = bar) +BlockComponents β†’ [Components.*] + └─ BlockComponentInf (*.inf entries) + β”œβ”€ BlocklibraryDef + └─ BlockPcd +BlockLibraryClasses β†’ [LibraryClasses.*] + └─ BlocklibraryDef +BlockIncludes β†’ !include lines +``` + +--- + +### 4. `BlockParser.parse()` (languageParser.ts) + +For each line, it: +1. Matches `tag` against the current line. +2. Calls `SymbolFactory.produceSymbol(type, ...)` to instantiate the right symbol class. +3. Calls `docParser.addSymbol(symbol)` and `pushSymbolStack(symbol)` β€” this nests the symbol under whatever is currently on the stack. +4. Recurses through `context` child parsers for inner lines, until `end` is matched. + +--- + +### 5. Symbol classes (dscSymbols.ts) + +Each DSC concept maps to a typed class with a `vscode.SymbolKind`: + +| DSC element | Class | `SymbolKind` | +|---|---|---| +| `[Defines]`, `[Components]` | `EdkSymbolDscSection` | `Class` | +| `DEFINE key = val` | `EdkSymbolDscDefine` | `Constant` | +| `LibClass\|path.inf` | `EdkSymbolDscLibraryDefinition` | `Module` | +| PCD entries | `EdkSymbolDscPcdDefinition` | `String` | +| `!include` | `EdkSymbolDscInclude` | `File` | + +--- + +### Summary + +VS Code calls `provideDocumentSymbols` β†’ `DscParser` scans the file line-by-line using a tree of regex-based `BlockParser` rules β†’ matched lines are turned into `EdkSymbol` objects (via `SymbolFactory`) and pushed onto a hierarchy stack β†’ the resulting `DocumentSymbol[]` tree (`symbolsTree`) is returned to VS Code, which renders it as the Outline view with proper nesting and icons. \ No newline at end of file diff --git a/package.json b/package.json index f891aab..a8c30ab 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,11 @@ "command": "edk2code.showWorkspaceDefines", "title": "EDK2: Show workspace Defines", "icon": "$(refresh)" + }, + { + "command": "edk2code.selectWorkspaceView", + "title": "EDK2: Select Workspace", + "icon": "$(list-selection)" } ], "configuration": [ @@ -322,6 +327,10 @@ { "command": "edk2code.showWorkspaceDefines", "when": "true" + }, + { + "command": "edk2code.selectWorkspaceView", + "when": "true" } ], "editor/title": [], @@ -331,6 +340,11 @@ "group": "navigation", "when": "view == detailsView && edk2code.isNodeFocusBackStack" }, + { + "command": "edk2code.selectWorkspaceView", + "group": "navigation", + "when": "view == workspaceView" + }, { "command": "edk2code.copyTreeData", "when": "view == detailsView", diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 7809646..315de00 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -4,7 +4,7 @@ import { rgSearch } from "../rg"; import { delay, getCurrentWord, gotoFile, isWorkspacePath, listFilesRecursive, openTextDocument, pathCompare, profileEnd, profileStart, readLines, toPosix } from "../utils"; import path = require("path"); import * as fs from 'fs'; -import { edkLensTreeDetailProvider, edkLensTreeDetailView, gConfigAgent, gCscope, gDebugLog, gEdkWorkspaces, gExtensionContext, gMapFileManager, gPathFind, gWorkspacePath } from "../extension"; +import { edkLensTreeDetailProvider, edkLensTreeDetailView, edkWorkspaceTreeProvider, gConfigAgent, gCscope, gDebugLog, gEdkWorkspaces, gExtensionContext, gMapFileManager, gPathFind, gWorkspacePath } from "../extension"; import { glob } from "fast-glob"; import { BuildFolder } from "../Languages/buildFolder"; import { EdkWorkspace, InfDsc } from "../index/edkWorkspace"; @@ -140,6 +140,7 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; void vscode.window.showInformationMessage("Build data loaded"); await gEdkWorkspaces.loadConfig(); + edkWorkspaceTreeProvider.refresh(); await showDefines(); } @@ -552,6 +553,7 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; cancellable: true }, async (progress, reject) => { await gEdkWorkspaces.loadConfig(); + edkWorkspaceTreeProvider.refresh(); let filesList:string[] = []; let decList:string[] = []; let hFiles:string[] = []; diff --git a/src/extension.ts b/src/extension.ts index e96fbde..8db4f87 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,7 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; +import * as path from 'path'; import { ConfigAgent } from './configuration'; import { Cscope, CscopeAgent } from './cscope'; @@ -20,6 +21,7 @@ import { ParserFactory } from './edkParser/parserFactory'; import { TreeDetailsDataProvider } from './TreeDataProvider'; // import { DefinesTreeDataProvider } from './definesPanel'; import { DiagnosticManager } from './diagnostics'; +import { WorkspaceTreeProvider } from './workspaceTree/WorkspaceTreeProvider'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; import { TreeItem } from './treeElements/TreeItem'; @@ -49,6 +51,9 @@ export var gDiagnosticManager:DiagnosticManager; export var edkLensTreeDetailProvider: TreeDetailsDataProvider; export var edkLensTreeDetailView: vscode.TreeView; +export var edkWorkspaceTreeProvider: WorkspaceTreeProvider; +export var edkWorkspaceTreeView: vscode.TreeView; + // export var edkDefinesTreeProvider: DefinesTreeDataProvider; export var gMapFileManager: MapFilesManager; @@ -159,7 +164,27 @@ export async function activate(context: vscode.ExtensionContext) { void copyToClipboard(path, "Path copied to clipboard"); }), - vscode.commands.registerCommand('edk2code.showWorkspaceDefines', async ()=>{await cmds.showDefines();}) + vscode.commands.registerCommand('edk2code.showWorkspaceDefines', async ()=>{await cmds.showDefines();}), + + vscode.commands.registerCommand('edk2code.selectWorkspaceView', async () => { + const workspaces = gEdkWorkspaces.workspaces; + if (workspaces.length === 0) { + void vscode.window.showInformationMessage('No EDK2 workspaces loaded yet.'); + return; + } + const items = workspaces.map((ws, i) => ({ + label: ws.platformName ?? path.basename(ws.mainDsc.fsPath), + description: vscode.workspace.asRelativePath(ws.mainDsc, false), + index: i + })); + const picked = await vscode.window.showQuickPick(items, { + placeHolder: 'Select workspace to display', + title: 'EDK2: Select Workspace' + }); + if (picked !== undefined) { + edkWorkspaceTreeProvider.selectWorkspace(picked.index); + } + }) ]; // We need to concat custom commands, because they are not in the list of commands @@ -180,8 +205,11 @@ export async function activate(context: vscode.ExtensionContext) { // edkDefinesTreeProvider = new DefinesTreeDataProvider(); // vscode.window.createTreeView('definesView', { treeDataProvider: edkDefinesTreeProvider, showCollapseAll: true }); + edkWorkspaceTreeProvider = new WorkspaceTreeProvider(); + edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true }); await gEdkWorkspaces.loadConfig(); + edkWorkspaceTreeProvider.refresh(); // edkDefinesTreeProvider.refresh(); gFileUseWarning = new FileUseWarning(); diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 63ebc5b..a8408e5 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -42,6 +42,15 @@ export interface Pcd { position:vscode.Location } +export interface IncludeNode { + /** Location of the !include directive in the parent file */ + location: vscode.Location; + /** Resolved URI of the included file */ + uri: vscode.Uri; + /** Nested includes found inside the included file */ + children: IncludeNode[]; +} + export class SectionProperties{ properties: SectionProperty[] = []; constructor(){ @@ -321,9 +330,12 @@ export class EdkWorkspace { private sectionsStack: string[] = []; private parsedDocuments: Map = new Map(); + + // Elements private defines: WorkspaceDefinitions = new WorkspaceDefinitions(); - definesFdf: WorkspaceDefinitions = new WorkspaceDefinitions(); private pcdDefinitions: Map> = new Map(); + + definesFdf: WorkspaceDefinitions = new WorkspaceDefinitions(); private libraryTypeTrack = new Map(); @@ -359,6 +371,11 @@ export class EdkWorkspace { private _grayoutControllers:GrayoutController[] = []; + private _includeTree: IncludeNode[] = []; + public get includeTree(): IncludeNode[] { + return this._includeTree; + } + public updateGrayoutRange(document: vscode.TextDocument, range: vscode.Range[]){ for (const grayoutController of this._grayoutControllers) { @@ -430,6 +447,7 @@ export class EdkWorkspace { this.conditionStack = []; this.result = []; this.conditionOpen = []; + this._includeTree = []; @@ -562,7 +580,7 @@ export class EdkWorkspace { } - private async _processDocument(document: vscode.TextDocument, type: 'DSC' | 'FDF') { + private async _processDocument(document: vscode.TextDocument, type: 'DSC' | 'FDF', parentIncludeNode?: IncludeNode) { DiagnosticManager.clearProblems(document.uri); gDebugLog.trace(`_process${type}: ${document.fileName}`); @@ -712,12 +730,22 @@ export class EdkWorkspace { let location = await gPathFind.findPath(value, document.uri.fsPath); if (location.length > 0) { gDebugLog.trace(`START Including: ${location[0].uri.fsPath}`); + const includeNode: IncludeNode = { + location: new vscode.Location(document.uri, new vscode.Position(lineIndex, 0)), + uri: location[0].uri, + children: [] + }; + if (parentIncludeNode) { + parentIncludeNode.children.push(includeNode); + } else { + this._includeTree.push(includeNode); + } let includedDocument = await openTextDocument(location[0].uri); if (type === 'DSC') { - await this._processDocument(includedDocument, 'DSC'); + await this._processDocument(includedDocument, 'DSC', includeNode); } else { this.filesFdf.add(includedDocument); - await this._processDocument(includedDocument, 'FDF'); + await this._processDocument(includedDocument, 'FDF', includeNode); } gDebugLog.trace(`END Including: ${location[0].uri.fsPath}`); } diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts new file mode 100644 index 0000000..911d183 --- /dev/null +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -0,0 +1,112 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import { EdkWorkspace, IncludeNode } from '../index/edkWorkspace'; +import { gEdkWorkspaces } from '../extension'; + +// ─── Tree Item: Workspace root (the main DSC) ──────────────────────────────── + +export class WorkspaceRootItem extends vscode.TreeItem { + constructor(public readonly workspace: EdkWorkspace) { + const label = path.basename(workspace.mainDsc.fsPath); + super(label, vscode.TreeItemCollapsibleState.Expanded); + this.description = workspace.platformName ?? ''; + this.tooltip = new vscode.MarkdownString( + `**Workspace root**\n\n\`${workspace.mainDsc.fsPath}\`` + ); + this.iconPath = new vscode.ThemeIcon('file-code'); + this.contextValue = 'workspaceRoot'; + this.command = { + command: 'vscode.open', + title: 'Open DSC', + arguments: [workspace.mainDsc] + }; + } +} + +// ─── Tree Item: An !include node inside the tree ────────────────────────────── + +export class IncludeTreeItem extends vscode.TreeItem { + constructor(public readonly node: IncludeNode) { + const label = path.basename(node.uri.fsPath); + super( + label, + node.children.length > 0 + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + ); + this.description = vscode.workspace.asRelativePath(node.uri, false); + this.tooltip = new vscode.MarkdownString( + `**Included file**\n\n\`${node.uri.fsPath}\`\n\n` + + `Directive at: \`${node.location.uri.fsPath}:${node.location.range.start.line + 1}\`` + ); + this.iconPath = new vscode.ThemeIcon('file'); + this.contextValue = 'includeNode'; + // Clicking jumps to the !include directive in the parent file + this.command = { + command: 'edk2code.gotoFile', + title: 'Go to !include directive', + arguments: [node.location.uri, node.location.range] + }; + } +} + +type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem; + +// ─── Tree data provider ─────────────────────────────────────────────────────── + +export class WorkspaceTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = + new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + private _activeIndex: number = 0; + + get activeIndex(): number { + return this._activeIndex; + } + + refresh(): void { + // Clamp index in case workspaces were removed + const max = Math.max(0, gEdkWorkspaces.workspaces.length - 1); + this._activeIndex = Math.min(this._activeIndex, max); + this._onDidChangeTreeData.fire(); + } + + /** + * Switch the displayed workspace and refresh the view. + */ + selectWorkspace(index: number): void { + this._activeIndex = index; + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: WorkspaceTreeNode): vscode.TreeItem { + return element; + } + + getChildren(element?: WorkspaceTreeNode): WorkspaceTreeNode[] { + const workspaces = gEdkWorkspaces.workspaces; + if (workspaces.length === 0) { + return []; + } + + // Root level: emit the single WorkspaceRootItem for the active workspace + if (!element) { + const ws = workspaces[this._activeIndex]; + if (!ws) { return []; } + return [new WorkspaceRootItem(ws)]; + } + + // Under the workspace root: top-level include nodes + if (element instanceof WorkspaceRootItem) { + return element.workspace.includeTree.map(n => new IncludeTreeItem(n)); + } + + // Under an include node: its nested children + if (element instanceof IncludeTreeItem) { + return element.node.children.map(n => new IncludeTreeItem(n)); + } + + return []; + } +} From fcbe3d28ad55d3386ce64dc0c1be1b622eb5adfb Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 5 Mar 2026 19:26:19 -0600 Subject: [PATCH 25/73] Add sub trees into workspace view --- package.json | 51 +++++ src/configuration.ts | 10 + src/extension.ts | 21 +- src/workspaceTree/WorkspaceTreeProvider.ts | 254 +++++++++++++++++++-- 4 files changed, 320 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index a8c30ab..e152459 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,20 @@ "command": "edk2code.selectWorkspaceView", "title": "EDK2: Select Workspace", "icon": "$(list-selection)" + }, + { + "command": "edk2code.copyWorkspaceTree", + "title": "EDK2: Copy workspace tree", + "icon": "$(copy)" + }, + { + "command": "edk2code.filterWorkspaceSymbols", + "title": "EDK2: Filter workspace symbols", + "icon": "$(filter)" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "title": "Copy path" } ], "configuration": [ @@ -331,6 +345,18 @@ { "command": "edk2code.selectWorkspaceView", "when": "true" + }, + { + "command": "edk2code.copyWorkspaceTree", + "when": "true" + }, + { + "command": "edk2code.filterWorkspaceSymbols", + "when": "true" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "when": "false" } ], "editor/title": [], @@ -345,6 +371,16 @@ "group": "navigation", "when": "view == workspaceView" }, + { + "command": "edk2code.copyWorkspaceTree", + "group": "navigation", + "when": "view == workspaceView" + }, + { + "command": "edk2code.filterWorkspaceSymbols", + "group": "navigation", + "when": "view == workspaceView" + }, { "command": "edk2code.copyTreeData", "when": "view == detailsView", @@ -408,6 +444,21 @@ "command": "edk2code.focusOnNode", "group": "navigation", "when": "view == detailsView" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "group": "navigation", + "when": "view == workspaceView && viewItem == workspaceRoot" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "group": "navigation", + "when": "view == workspaceView && viewItem == includeNode" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "group": "navigation", + "when": "view == workspaceView && viewItem == symbolNode" } ] }, diff --git a/src/configuration.ts b/src/configuration.ts index 374fa39..caabc6e 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -13,6 +13,7 @@ export interface WorkspaceConfig { packagePaths:string[]; dscPaths:string[]; buildDefines:string[]; + workspaceTreeFilters?: number[]; } export interface WorkspaceConfigErrors{ @@ -261,6 +262,15 @@ export class ConfigAgent { return this.workspaceConfig.dscPaths; } + getWorkspaceTreeFilters(): number[] | undefined { + return this.workspaceConfig.workspaceTreeFilters; + } + + setWorkspaceTreeFilters(filters: number[]): void { + this.workspaceConfig.workspaceTreeFilters = filters; + this.writeWorkspaceConfig(this.workspaceConfig); + } + getIsGenIgnoreFile() { return this.get("generateIgnoreFile"); } diff --git a/src/extension.ts b/src/extension.ts index 8db4f87..6e4e195 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,7 @@ import { ParserFactory } from './edkParser/parserFactory'; import { TreeDetailsDataProvider } from './TreeDataProvider'; // import { DefinesTreeDataProvider } from './definesPanel'; import { DiagnosticManager } from './diagnostics'; -import { WorkspaceTreeProvider } from './workspaceTree/WorkspaceTreeProvider'; +import { WorkspaceTreeProvider, WorkspaceRootItem, IncludeTreeItem, DocumentSymbolItem } from './workspaceTree/WorkspaceTreeProvider'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; import { TreeItem } from './treeElements/TreeItem'; @@ -166,6 +166,25 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.showWorkspaceDefines', async ()=>{await cmds.showDefines();}), + vscode.commands.registerCommand('edk2code.copyWorkspaceTree', async () => { + const text = await edkWorkspaceTreeProvider.serializeTree(); + await vscode.env.clipboard.writeText(text); + void vscode.window.showInformationMessage('Workspace tree copied to clipboard.'); + }), + + vscode.commands.registerCommand('edk2code.copyWorkspaceNodePath', async (node: WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem) => { + if (!('treePath' in node)) { + return; + } + const treePath = node.treePath.join(' > '); + await vscode.env.clipboard.writeText(treePath); + void vscode.window.showInformationMessage(`Workspace path copied: ${treePath}`); + }), + + vscode.commands.registerCommand('edk2code.filterWorkspaceSymbols', async () => { + await edkWorkspaceTreeProvider.showFilterPicker(); + }), + vscode.commands.registerCommand('edk2code.selectWorkspaceView', async () => { const workspaces = gEdkWorkspaces.workspaces; if (workspaces.length === 0) { diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 911d183..fe37c07 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -1,14 +1,76 @@ import * as vscode from 'vscode'; import * as path from 'path'; import { EdkWorkspace, IncludeNode } from '../index/edkWorkspace'; -import { gEdkWorkspaces } from '../extension'; +import { getParser } from '../edkParser/parserFactory'; +import { EdkSymbol } from '../symbols/edkSymbols'; +import { Edk2SymbolType } from '../symbols/symbolsType'; +import { gConfigAgent, gEdkWorkspaces } from '../extension'; + +// ─── DSC symbol types available for filtering ───────────────────────────────── + +export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; description: string }[] = [ + { type: Edk2SymbolType.dscDefine, label: 'Defines', description: 'dscDefine' }, + { type: Edk2SymbolType.dscLibraryDefinition, label: 'Library classes', description: 'dscLibraryDefinition' }, + { type: Edk2SymbolType.dscModuleDefinition, label: 'Components', description: 'dscModuleDefinition' }, + { type: Edk2SymbolType.dscSection, label: 'Sections', description: 'dscSection' }, + { type: Edk2SymbolType.dscPcdDefinition, label: 'PCDs', description: 'dscPcdDefinition' }, + { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' }, +]; + +// ─── Symbol kind β†’ codicon mapping ─────────────────────────────────────────── + +function symbolKindIcon(kind: vscode.SymbolKind): vscode.ThemeIcon { + const map: Partial> = { + [vscode.SymbolKind.File]: 'symbol-file', + [vscode.SymbolKind.Module]: 'symbol-module', + [vscode.SymbolKind.Namespace]: 'symbol-namespace', + [vscode.SymbolKind.Package]: 'symbol-package', + [vscode.SymbolKind.Class]: 'symbol-class', + [vscode.SymbolKind.Method]: 'symbol-method', + [vscode.SymbolKind.Property]: 'symbol-property', + [vscode.SymbolKind.Field]: 'symbol-field', + [vscode.SymbolKind.Constructor]: 'symbol-constructor', + [vscode.SymbolKind.Enum]: 'symbol-enum', + [vscode.SymbolKind.Interface]: 'symbol-interface', + [vscode.SymbolKind.Function]: 'symbol-function', + [vscode.SymbolKind.Variable]: 'symbol-variable', + [vscode.SymbolKind.Constant]: 'symbol-constant', + [vscode.SymbolKind.String]: 'symbol-string', + [vscode.SymbolKind.Number]: 'symbol-number', + [vscode.SymbolKind.Boolean]: 'symbol-boolean', + [vscode.SymbolKind.Array]: 'symbol-array', + [vscode.SymbolKind.Object]: 'symbol-object', + [vscode.SymbolKind.Key]: 'symbol-key', + [vscode.SymbolKind.Null]: 'symbol-null', + [vscode.SymbolKind.EnumMember]: 'symbol-enum-member', + [vscode.SymbolKind.Struct]: 'symbol-struct', + [vscode.SymbolKind.Event]: 'symbol-event', + [vscode.SymbolKind.Operator]: 'symbol-operator', + [vscode.SymbolKind.TypeParameter]: 'symbol-type-parameter', + }; + return new vscode.ThemeIcon(map[kind] ?? 'circle-small'); +} + +// ─── Helper: load symbols for a URI via the parser ─────────────────────────── + +async function loadSymbols(uri: vscode.Uri): Promise { + try { + const parser = await getParser(uri); + return (parser?.symbolsTree ?? []) as EdkSymbol[]; + } catch { + return []; + } +} // ─── Tree Item: Workspace root (the main DSC) ──────────────────────────────── export class WorkspaceRootItem extends vscode.TreeItem { + public readonly treePath: string[]; + constructor(public readonly workspace: EdkWorkspace) { const label = path.basename(workspace.mainDsc.fsPath); super(label, vscode.TreeItemCollapsibleState.Expanded); + this.treePath = [label]; this.description = workspace.platformName ?? ''; this.tooltip = new vscode.MarkdownString( `**Workspace root**\n\n\`${workspace.mainDsc.fsPath}\`` @@ -26,14 +88,12 @@ export class WorkspaceRootItem extends vscode.TreeItem { // ─── Tree Item: An !include node inside the tree ────────────────────────────── export class IncludeTreeItem extends vscode.TreeItem { - constructor(public readonly node: IncludeNode) { + public readonly treePath: string[]; + + constructor(public readonly node: IncludeNode, parentPath: string[]) { const label = path.basename(node.uri.fsPath); - super( - label, - node.children.length > 0 - ? vscode.TreeItemCollapsibleState.Collapsed - : vscode.TreeItemCollapsibleState.None - ); + super(label, vscode.TreeItemCollapsibleState.Collapsed); + this.treePath = [...parentPath, vscode.workspace.asRelativePath(node.uri, false)]; this.description = vscode.workspace.asRelativePath(node.uri, false); this.tooltip = new vscode.MarkdownString( `**Included file**\n\n\`${node.uri.fsPath}\`\n\n` + @@ -50,7 +110,87 @@ export class IncludeTreeItem extends vscode.TreeItem { } } -type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem; +// ─── Tree Item: A document symbol (outline entry) inside a file ────────────── + +export class DocumentSymbolItem extends vscode.TreeItem { + public readonly symbolType: Edk2SymbolType; + public readonly treePath: string[]; + + constructor( + public readonly symbol: EdkSymbol, + public readonly fileUri: vscode.Uri, + activeFilters: Set, + parentPath: string[] + ) { + const visibleChildren = symbol.children.filter( + c => activeFilters.has((c as EdkSymbol).type) + ); + const isDscInclude = symbol.type === Edk2SymbolType.dscInclude; + super( + symbol.name, + visibleChildren.length > 0 || isDscInclude + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + ); + this.treePath = [...parentPath, symbol.name]; + this.symbolType = symbol.type; + this.description = symbol.detail || undefined; + this.tooltip = symbol.name; + this.iconPath = symbolKindIcon(symbol.kind); + this.contextValue = 'symbolNode'; + // Clicking navigates to the symbol's location in its file + this.command = { + command: 'edk2code.gotoFile', + title: 'Go to symbol', + arguments: [fileUri, symbol.selectionRange] + }; + } +} + +type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem; + +// ─── Helper: find an IncludeNode by the location of its !include directive ─── + +function findIncludeNode(nodes: IncludeNode[], location: vscode.Location): IncludeNode | undefined { + for (const node of nodes) { + if (node.location.uri.fsPath === location.uri.fsPath && + node.location.range.start.line === location.range.start.line) { + return node; + } + const found = findIncludeNode(node.children, location); + if (found) { return found; } + } + return undefined; +} + +// ─── Recursive text serializer ─────────────────────────────────────────────── + +async function serializeSymbol(symbol: EdkSymbol, fileUri: vscode.Uri, indent: string, filter: Set): Promise { + const line = `${indent}${symbol.name}${symbol.detail ? ' - ' + symbol.detail : ''}\n`; + let out = line; + for (const child of symbol.children) { + const edkChild = child as EdkSymbol; + if (filter.has(edkChild.type)) { + out += await serializeSymbol(edkChild, fileUri, indent + ' ', filter); + } + } + return out; +} + +async function serializeIncludeNode(node: IncludeNode, indent: string, filter: Set): Promise { + const rel = vscode.workspace.asRelativePath(node.uri, false); + let out = `${indent}!include ${rel}\n`; + const symbols = await loadSymbols(node.uri); + for (const sym of symbols) { + if (filter.has(sym.type)) { + out += await serializeSymbol(sym, node.uri, indent + ' ', filter); + } + } + for (const child of node.children) { + out += await serializeIncludeNode(child, indent + ' ', filter); + } + return out; +} // ─── Tree data provider ─────────────────────────────────────────────────────── @@ -61,6 +201,15 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider = (() => { + const saved = gConfigAgent.getWorkspaceTreeFilters(); + if (saved && saved.length > 0) { + return new Set(saved as Edk2SymbolType[]); + } + return new Set(DSC_FILTER_TYPES.map(f => f.type)); + })(); + get activeIndex(): number { return this._activeIndex; } @@ -80,31 +229,106 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider { + type FilterPickItem = vscode.QuickPickItem & { type: Edk2SymbolType }; + const items: FilterPickItem[] = DSC_FILTER_TYPES.map(f => ({ + label: f.label, + description: f.description, + picked: this._activeFilters.has(f.type), + type: f.type + })); + + const picked = await vscode.window.showQuickPick(items, { + canPickMany: true, + placeHolder: 'Select symbol types to show', + title: 'EDK2: Filter workspace symbols' + }); + + // Cancelled β†’ leave filter unchanged + if (picked === undefined) { return; } + + this._activeFilters = new Set(picked.map(p => p.type)); + gConfigAgent.setWorkspaceTreeFilters([...this._activeFilters]); + this._onDidChangeTreeData.fire(); + } + + /** + * Serialize the whole displayed workspace tree to an indented string. + */ + async serializeTree(): Promise { + const ws = gEdkWorkspaces.workspaces[this._activeIndex]; + if (!ws) { return ''; } + + const rootLabel = path.basename(ws.mainDsc.fsPath); + const rel = vscode.workspace.asRelativePath(ws.mainDsc, false); + let out = `${rootLabel} (${rel})\n`; + + const rootSymbols = await loadSymbols(ws.mainDsc); + for (const sym of rootSymbols) { + if (this._activeFilters.has(sym.type)) { + out += await serializeSymbol(sym, ws.mainDsc, ' ', this._activeFilters); + } + } + for (const node of ws.includeTree) { + out += await serializeIncludeNode(node, ' ', this._activeFilters); + } + return out; + } + getTreeItem(element: WorkspaceTreeNode): vscode.TreeItem { return element; } - getChildren(element?: WorkspaceTreeNode): WorkspaceTreeNode[] { + async getChildren(element?: WorkspaceTreeNode): Promise { const workspaces = gEdkWorkspaces.workspaces; if (workspaces.length === 0) { return []; } - // Root level: emit the single WorkspaceRootItem for the active workspace + // Root level: the single WorkspaceRootItem for the active workspace if (!element) { const ws = workspaces[this._activeIndex]; if (!ws) { return []; } return [new WorkspaceRootItem(ws)]; } - // Under the workspace root: top-level include nodes + // Under the workspace root: symbols of the main DSC if (element instanceof WorkspaceRootItem) { - return element.workspace.includeTree.map(n => new IncludeTreeItem(n)); + const symbols = await loadSymbols(element.workspace.mainDsc); + return symbols + .filter(s => this._activeFilters.has(s.type)) + .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath)); } - // Under an include node: its nested children + // Under an include node: symbols of that file only if (element instanceof IncludeTreeItem) { - return element.node.children.map(n => new IncludeTreeItem(n)); + const symbols = await loadSymbols(element.node.uri); + return symbols + .filter(s => this._activeFilters.has(s.type)) + .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath)); + } + + // Under a symbol: for !include directives expand into the included file; + // for all other symbols expand their parsed children. + if (element instanceof DocumentSymbolItem) { + if (element.symbolType === Edk2SymbolType.dscInclude) { + const ws = gEdkWorkspaces.workspaces[this._activeIndex]; + if (ws) { + const node = findIncludeNode(ws.includeTree, element.symbol.location); + if (node) { + const symbols = await loadSymbols(node.uri); + return symbols + .filter(s => this._activeFilters.has(s.type)) + .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath)); + } + } + } + return (element.symbol.children as EdkSymbol[]) + .filter(c => this._activeFilters.has(c.type)) + .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath)); } return []; From a3d47bb110069b3c316106868e1a4c5c66fe764d Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 08:54:38 -0600 Subject: [PATCH 26/73] Remove duplicated logic to get icon from edk2 symbol --- src/TreeDataProvider.ts | 61 +--------------------- src/symbols/edkSymbols.ts | 32 ++++++++++++ src/treeElements/Symbol.ts | 61 +--------------------- src/workspaceTree/WorkspaceTreeProvider.ts | 36 +------------ 4 files changed, 35 insertions(+), 155 deletions(-) diff --git a/src/TreeDataProvider.ts b/src/TreeDataProvider.ts index ab99583..e26e0cd 100644 --- a/src/TreeDataProvider.ts +++ b/src/TreeDataProvider.ts @@ -224,65 +224,6 @@ class Edk2TreeItem extends TreeItem { -function getIconForSymbolKind(kind: vscode.SymbolKind): string { - switch(kind) { - case vscode.SymbolKind.File: - return "symbol-file"; - case vscode.SymbolKind.Module: - return "symbol-module"; - case vscode.SymbolKind.Namespace: - return "symbol-namespace"; - case vscode.SymbolKind.Package: - return "symbol-package"; - case vscode.SymbolKind.Class: - return "symbol-class"; - case vscode.SymbolKind.Method: - return "symbol-method"; - case vscode.SymbolKind.Property: - return "symbol-property"; - case vscode.SymbolKind.Field: - return "symbol-field"; - case vscode.SymbolKind.Constructor: - return "symbol-constructor"; - case vscode.SymbolKind.Enum: - return "symbol-enum"; - case vscode.SymbolKind.Interface: - return "symbol-interface"; - case vscode.SymbolKind.Function: - return "symbol-function"; - case vscode.SymbolKind.Variable: - return "symbol-variable"; - case vscode.SymbolKind.Constant: - return "symbol-constant"; - case vscode.SymbolKind.String: - return "symbol-string"; - case vscode.SymbolKind.Number: - return "symbol-number"; - case vscode.SymbolKind.Boolean: - return "symbol-boolean"; - case vscode.SymbolKind.Array: - return "symbol-array"; - case vscode.SymbolKind.Object: - return "symbol-object"; - case vscode.SymbolKind.Key: - return "symbol-key"; - case vscode.SymbolKind.Null: - return "symbol-null"; - case vscode.SymbolKind.EnumMember: - return "symbol-enum-member"; - case vscode.SymbolKind.Struct: - return "symbol-struct"; - case vscode.SymbolKind.Event: - return "symbol-event"; - case vscode.SymbolKind.Operator: - return "symbol-operator"; - case vscode.SymbolKind.TypeParameter: - return "symbol-type-parameter"; - default: - return "symbol-misc"; - } -} - export class SourceSymbolTreeItem extends TreeItem{ uri:vscode.Uri; range:vscode.Range; @@ -290,7 +231,7 @@ export class SourceSymbolTreeItem extends TreeItem{ super(uri, vscode.TreeItemCollapsibleState.Collapsed); this.label = symbol.name; this.description = symbol.detail.length?symbol.detail:vscode.SymbolKind[symbol.kind]; - this.iconPath = new vscode.ThemeIcon(getIconForSymbolKind(symbol.kind)); + this.iconPath = EdkSymbol.iconForKind(symbol.kind); this.collapsibleState = vscode.TreeItemCollapsibleState.None; this.range = symbol.range; this.uri = uri; diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 7ddd7e7..99f4200 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -144,5 +144,37 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { return this.textLine; } + static iconForKind(kind: vscode.SymbolKind): vscode.ThemeIcon { + const map: Partial> = { + [vscode.SymbolKind.File]: 'symbol-file', + [vscode.SymbolKind.Module]: 'symbol-module', + [vscode.SymbolKind.Namespace]: 'symbol-namespace', + [vscode.SymbolKind.Package]: 'symbol-package', + [vscode.SymbolKind.Class]: 'symbol-class', + [vscode.SymbolKind.Method]: 'symbol-method', + [vscode.SymbolKind.Property]: 'symbol-property', + [vscode.SymbolKind.Field]: 'symbol-field', + [vscode.SymbolKind.Constructor]: 'symbol-constructor', + [vscode.SymbolKind.Enum]: 'symbol-enum', + [vscode.SymbolKind.Interface]: 'symbol-interface', + [vscode.SymbolKind.Function]: 'symbol-function', + [vscode.SymbolKind.Variable]: 'symbol-variable', + [vscode.SymbolKind.Constant]: 'symbol-constant', + [vscode.SymbolKind.String]: 'symbol-string', + [vscode.SymbolKind.Number]: 'symbol-number', + [vscode.SymbolKind.Boolean]: 'symbol-boolean', + [vscode.SymbolKind.Array]: 'symbol-array', + [vscode.SymbolKind.Object]: 'symbol-object', + [vscode.SymbolKind.Key]: 'symbol-key', + [vscode.SymbolKind.Null]: 'symbol-null', + [vscode.SymbolKind.EnumMember]: 'symbol-enum-member', + [vscode.SymbolKind.Struct]: 'symbol-struct', + [vscode.SymbolKind.Event]: 'symbol-event', + [vscode.SymbolKind.Operator]: 'symbol-operator', + [vscode.SymbolKind.TypeParameter]: 'symbol-type-parameter', + }; + return new vscode.ThemeIcon(map[kind] ?? 'symbol-misc'); + } + } diff --git a/src/treeElements/Symbol.ts b/src/treeElements/Symbol.ts index 4aae153..012c307 100644 --- a/src/treeElements/Symbol.ts +++ b/src/treeElements/Symbol.ts @@ -29,65 +29,6 @@ var baseTypeSet = new Set([ ]); -function getIconForSymbolKind(kind: vscode.SymbolKind): string { - switch(kind) { - case vscode.SymbolKind.File: - return "symbol-file"; - case vscode.SymbolKind.Module: - return "symbol-module"; - case vscode.SymbolKind.Namespace: - return "symbol-namespace"; - case vscode.SymbolKind.Package: - return "symbol-package"; - case vscode.SymbolKind.Class: - return "symbol-class"; - case vscode.SymbolKind.Method: - return "symbol-method"; - case vscode.SymbolKind.Property: - return "symbol-property"; - case vscode.SymbolKind.Field: - return "symbol-field"; - case vscode.SymbolKind.Constructor: - return "symbol-constructor"; - case vscode.SymbolKind.Enum: - return "symbol-enum"; - case vscode.SymbolKind.Interface: - return "symbol-interface"; - case vscode.SymbolKind.Function: - return "symbol-function"; - case vscode.SymbolKind.Variable: - return "symbol-variable"; - case vscode.SymbolKind.Constant: - return "symbol-constant"; - case vscode.SymbolKind.String: - return "symbol-string"; - case vscode.SymbolKind.Number: - return "symbol-number"; - case vscode.SymbolKind.Boolean: - return "symbol-boolean"; - case vscode.SymbolKind.Array: - return "symbol-array"; - case vscode.SymbolKind.Object: - return "symbol-object"; - case vscode.SymbolKind.Key: - return "symbol-key"; - case vscode.SymbolKind.Null: - return "symbol-null"; - case vscode.SymbolKind.EnumMember: - return "symbol-enum-member"; - case vscode.SymbolKind.Struct: - return "symbol-struct"; - case vscode.SymbolKind.Event: - return "symbol-event"; - case vscode.SymbolKind.Operator: - return "symbol-operator"; - case vscode.SymbolKind.TypeParameter: - return "symbol-type-parameter"; - default: - return "symbol-misc"; - } - } - export class EdkSymbolNode extends EdkNode{ uri:vscode.Uri; range:vscode.Range; @@ -99,7 +40,7 @@ export class EdkSymbolNode extends EdkNode{ this.label = symbol.name; this.name = symbol.name; this.description = symbol.detail.length?symbol.detail:vscode.SymbolKind[symbol.kind]; - this.iconPath = new vscode.ThemeIcon(getIconForSymbolKind(symbol.kind)); + this.iconPath = EdkSymbol.iconForKind(symbol.kind); this.collapsibleState = vscode.TreeItemCollapsibleState.None; this.range = symbol.range; this.uri = uri; diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index fe37c07..43fcacf 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -17,40 +17,6 @@ export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; descriptio { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' }, ]; -// ─── Symbol kind β†’ codicon mapping ─────────────────────────────────────────── - -function symbolKindIcon(kind: vscode.SymbolKind): vscode.ThemeIcon { - const map: Partial> = { - [vscode.SymbolKind.File]: 'symbol-file', - [vscode.SymbolKind.Module]: 'symbol-module', - [vscode.SymbolKind.Namespace]: 'symbol-namespace', - [vscode.SymbolKind.Package]: 'symbol-package', - [vscode.SymbolKind.Class]: 'symbol-class', - [vscode.SymbolKind.Method]: 'symbol-method', - [vscode.SymbolKind.Property]: 'symbol-property', - [vscode.SymbolKind.Field]: 'symbol-field', - [vscode.SymbolKind.Constructor]: 'symbol-constructor', - [vscode.SymbolKind.Enum]: 'symbol-enum', - [vscode.SymbolKind.Interface]: 'symbol-interface', - [vscode.SymbolKind.Function]: 'symbol-function', - [vscode.SymbolKind.Variable]: 'symbol-variable', - [vscode.SymbolKind.Constant]: 'symbol-constant', - [vscode.SymbolKind.String]: 'symbol-string', - [vscode.SymbolKind.Number]: 'symbol-number', - [vscode.SymbolKind.Boolean]: 'symbol-boolean', - [vscode.SymbolKind.Array]: 'symbol-array', - [vscode.SymbolKind.Object]: 'symbol-object', - [vscode.SymbolKind.Key]: 'symbol-key', - [vscode.SymbolKind.Null]: 'symbol-null', - [vscode.SymbolKind.EnumMember]: 'symbol-enum-member', - [vscode.SymbolKind.Struct]: 'symbol-struct', - [vscode.SymbolKind.Event]: 'symbol-event', - [vscode.SymbolKind.Operator]: 'symbol-operator', - [vscode.SymbolKind.TypeParameter]: 'symbol-type-parameter', - }; - return new vscode.ThemeIcon(map[kind] ?? 'circle-small'); -} - // ─── Helper: load symbols for a URI via the parser ─────────────────────────── async function loadSymbols(uri: vscode.Uri): Promise { @@ -136,7 +102,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { this.symbolType = symbol.type; this.description = symbol.detail || undefined; this.tooltip = symbol.name; - this.iconPath = symbolKindIcon(symbol.kind); + this.iconPath = EdkSymbol.iconForKind(symbol.kind); this.contextValue = 'symbolNode'; // Clicking navigates to the symbol's location in its file this.command = { From cb6c165cc6866b274564c24e8515c09e56d358da Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 09:31:06 -0600 Subject: [PATCH 27/73] Adding subsection symbols to modules --- src/edkParser/dscParser.ts | 55 +++++++++++- src/symbols/README.md | 160 +++++++++++++++++++++++++++++++++++ src/symbols/dscSymbols.ts | 22 +++++ src/symbols/edkSymbols.ts | 11 ++- src/symbols/symbolFactory.ts | 6 +- src/symbols/symbolsType.ts | 4 + 6 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 src/symbols/README.md diff --git a/src/edkParser/dscParser.ts b/src/edkParser/dscParser.ts index f758fe4..f38c89d 100644 --- a/src/edkParser/dscParser.ts +++ b/src/edkParser/dscParser.ts @@ -19,6 +19,54 @@ class BlockIncludes extends BlockParser { +class BlockBuildOption extends BlockParser { + name = "BuildOption"; + tag = /^[\w\*\:\|]+\s*=\s*.*/gi; + start = undefined; + end = undefined; + type = Edk2SymbolType.dscBuildOption; + visible: boolean = true; +} + +class BlockComponentSubLibraryClasses extends BlockParser { + name = "ComponentLibraryClasses"; + tag = /^<\s*LibraryClasses\s*>/gi; + start = undefined; + end = /(^<)|(^\})/gi; + type = Edk2SymbolType.dscComponentSubSection; + visible: boolean = true; + context: BlockParser[] = [ + new BlocklibraryDef(), + new BlockIncludes(), + ]; +} + +class BlockComponentSubPcds extends BlockParser { + name = "ComponentPcds"; + tag = /^<\s*Pcd[^>]*>/gi; + start = undefined; + end = /(^<)|(^\})/gi; + type = Edk2SymbolType.dscComponentSubSection; + visible: boolean = true; + context: BlockParser[] = [ + new BlockPcd(), + new BlockIncludes(), + ]; +} + +class BlockComponentSubBuildOptions extends BlockParser { + name = "ComponentBuildOptions"; + tag = /^<\s*BuildOptions\s*>/gi; + start = undefined; + end = /(^<)|(^\})/gi; + type = Edk2SymbolType.dscComponentSubSection; + visible: boolean = true; + context: BlockParser[] = [ + new BlockBuildOption(), + new BlockIncludes(), + ]; +} + class BlockComponentInf extends BlockParser { name= "ComponentInf"; tag= /^[\s\.\w\$\(\)_\-\\\/]*\.inf/gi; @@ -28,8 +76,11 @@ class BlockComponentInf extends BlockParser { visible:boolean = true; context: BlockParser[] = [ - new BlocklibraryDef(), - new BlockPcd(), + new BlockComponentSubLibraryClasses(), + new BlockComponentSubPcds(), + new BlockComponentSubBuildOptions(), + // new BlocklibraryDef(), + // new BlockPcd(), ]; } diff --git a/src/symbols/README.md b/src/symbols/README.md new file mode 100644 index 0000000..274de7f --- /dev/null +++ b/src/symbols/README.md @@ -0,0 +1,160 @@ +# Symbols + +This folder contains the symbol type system used by the EDK II parser. Each language (DSC, INF, DEC, FDF, ASL, VFR) has its own symbol file, plus shared infrastructure. + +## File Overview + +| File | Purpose | +|---|---| +| `symbolsType.ts` | Enum of all symbol types + string mapping | +| `edkSymbols.ts` | Abstract base class `EdkSymbol` | +| `symbolFactory.ts` | Factory that instantiates the correct symbol class from a type | +| `dscSymbols.ts` | DSC-specific symbol classes | +| `infSymbols.ts` | INF-specific symbol classes | +| `decSymbols.ts` | DEC-specific symbol classes | +| `fdfSymbols.ts` | FDF-specific symbol classes | +| `aslSymbols.ts` | ASL-specific symbol classes | +| `vfrSymbols.ts` | VFR-specific symbol classes | +| `commonSymbols.ts` | Shared symbol classes (conditions, unknown) | + +--- + +## How to Add a New Symbol Type + +Adding a new symbol requires changes in **four places**. The example below follows the addition of `dscComponentSubSection` and `dscBuildOption` for DSC component scoped sub-elements (``, ``, ``). + +### 1. Add the type to `symbolsType.ts` + +Add the new enum value to `Edk2SymbolType` and a corresponding entry in `typeToStr`: + +```typescript +// symbolsType.ts +export enum Edk2SymbolType { + // ... + dscSection, + dscComponentSubSection, // <-- new + dscPcdDefinition, + dscBuildOption, // <-- new + // ... +} + +export var typeToStr: Map = new Map([ + // ... + [Edk2SymbolType.dscComponentSubSection, "dscComponentSubSection"], + [Edk2SymbolType.dscBuildOption, "dscBuildOption"], + // ... +]); +``` + +### 2. Create the symbol class in the appropriate `*Symbols.ts` file + +Extend `EdkSymbol` and set at minimum `type` and `kind`. Implement `onDefinition`, `onCompletion`, etc. if the symbol needs navigation or hover support. + +```typescript +// dscSymbols.ts +export class EdkSymbolDscComponentSubSection extends EdkSymbol { + type = Edk2SymbolType.dscComponentSubSection; + kind = vscode.SymbolKind.Namespace; + + onCompletion: undefined; + onDefinition: undefined; + onHover: undefined; + onDeclaration: undefined; +} + +export class EdkSymbolDscBuildOption extends EdkSymbol { + type = Edk2SymbolType.dscBuildOption; + kind = vscode.SymbolKind.Property; + + onCompletion: undefined; + onDefinition: undefined; + onHover: undefined; + onDeclaration: undefined; +} +``` + +Optionally override `nameRegex` (defined on the base `EdkSymbol`) to extract a cleaner display name from the raw text line: + +```typescript +// Example from EdkSymbolDscModuleDefinition +protected get nameRegex(): RegExp { return /^\s*([\w/.\\-]+\.inf)/i; } +``` + +When `nameRegex` is set, the constructor uses the first capture group (or the full match) as `this.name` instead of the raw text line. + +### 3. Register the type in `symbolFactory.ts` + +Import the new class and add a `case` to `produceSymbol()`: + +```typescript +// symbolFactory.ts +import { /* existing */, EdkSymbolDscComponentSubSection, EdkSymbolDscBuildOption } from "./dscSymbols"; + +produceSymbol(type: Edk2SymbolType, textLine: string, location: vscode.Location, parser: DocumentParser) { + switch (type) { + // ... + case Edk2SymbolType.dscComponentSubSection: + return new EdkSymbolDscComponentSubSection(textLine, location, true, true, parser); + case Edk2SymbolType.dscBuildOption: + return new EdkSymbolDscBuildOption(textLine, location, true, true, parser); + // ... + } +} +``` + +### 4. Add a `BlockParser` in the relevant parser file + +In `src/edkParser/`, create a `BlockParser` subclass that sets `type` to the new enum value and define `tag` / `end` / `context` as needed. Then add it to the `context` array of the parent block. + +```typescript +// dscParser.ts +class BlockBuildOption extends BlockParser { + name = "BuildOption"; + tag = /^[\w\*\:\|]+\s*=\s*.*/gi; + start = undefined; + end = undefined; + type = Edk2SymbolType.dscBuildOption; + visible: boolean = true; +} + +class BlockComponentSubBuildOptions extends BlockParser { + name = "ComponentBuildOptions"; + tag = /^<\s*BuildOptions\s*>/gi; + start = undefined; + end = /(^<)|(^\})/gi; + type = Edk2SymbolType.dscComponentSubSection; + visible: boolean = true; + context: BlockParser[] = [ + new BlockBuildOption(), + new BlockIncludes(), + ]; +} + +// Wire into the parent block's context: +class BlockComponentInf extends BlockParser { + // ... + context: BlockParser[] = [ + new BlockComponentSubBuildOptions(), + // ... + ]; +} +``` + +--- + +## `EdkSymbol` base class + +All symbol classes extend `EdkSymbol` (defined in `edkSymbols.ts`), which itself extends `vscode.DocumentSymbol`. Key members: + +| Member | Description | +|---|---| +| `type` | The `Edk2SymbolType` enum value | +| `kind` | The `vscode.SymbolKind` used for the outline/breadcrumb icon | +| `textLine` | The raw source line (with defines resolved via `parser.defines`) | +| `location` | `vscode.Location` (URI + range) | +| `sectionProperties` | Inherited from the parent symbol's section context | +| `nameRegex` | Optional getter; when set, extracts the symbol's display name from `textLine` | +| `onDefinition` | Async function returning locations for go-to-definition | +| `onCompletion` | Async function returning completion items | +| `onHover` | Async function returning hover content | +| `onDeclaration` | Async function returning declaration locations | diff --git a/src/symbols/dscSymbols.ts b/src/symbols/dscSymbols.ts index 1abf6c0..6cda7da 100644 --- a/src/symbols/dscSymbols.ts +++ b/src/symbols/dscSymbols.ts @@ -99,10 +99,32 @@ export class EdkSymbolDscLine extends EdkSymbol{ onDeclaration: undefined; } +export class EdkSymbolDscComponentSubSection extends EdkSymbol { + type = Edk2SymbolType.dscComponentSubSection; + kind = vscode.SymbolKind.Namespace; + + onCompletion: undefined; + onDefinition: undefined; + onHover: undefined; + onDeclaration: undefined; +} + +export class EdkSymbolDscBuildOption extends EdkSymbol { + type = Edk2SymbolType.dscBuildOption; + kind = vscode.SymbolKind.Property; + + onCompletion: undefined; + onDefinition: undefined; + onHover: undefined; + onDeclaration: undefined; +} + export class EdkSymbolDscModuleDefinition extends EdkSymbol{ type = Edk2SymbolType.dscModuleDefinition; kind = vscode.SymbolKind.Event; + protected get nameRegex(): RegExp { return /^\s*([\w/.\\-]+\.inf)/i; } + onCompletion: undefined; onDefinition = async ()=>{ let path = this.textLine.replace(/\s*\{.*/, ""); diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 99f4200..5170c0f 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -30,6 +30,9 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { guid:string = ""; parser:DocumentParser; + + /** Override in subclasses to extract a specific portion of the text line as the symbol name. */ + protected get nameRegex(): RegExp | undefined { return undefined; } @@ -63,7 +66,13 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { this.location = location; this._textLine = textLine; this.parser = parser; - this.name = textLine.replaceAll(/\s+/gi," "); + const regex = this.nameRegex; + if (regex) { + const match = textLine.match(regex); + this.name = match ? (match[1] ?? match[0]).trim() : textLine.trim(); + } else { + this.name = textLine.replaceAll(/\s+/gi, " "); + } let parent = parser.symbolStack[parser.symbolStack.length - 1]; this.sectionProperties = new SectionProperties(); if(parent){ diff --git a/src/symbols/symbolFactory.ts b/src/symbols/symbolFactory.ts index abd76d3..b2969d4 100644 --- a/src/symbols/symbolFactory.ts +++ b/src/symbols/symbolFactory.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { gDebugLog } from "../extension"; -import { EdkSymbolDscDefine, EdkSymbolDscInclude, EdkSymbolDscLibraryDefinition, EdkSymbolDscLine, EdkSymbolDscModuleDefinition, EdkSymbolDscPcdDefinition, EdkSymbolDscSection } from "./dscSymbols"; +import { EdkSymbolDscDefine, EdkSymbolDscInclude, EdkSymbolDscLibraryDefinition, EdkSymbolDscLine, EdkSymbolDscModuleDefinition, EdkSymbolDscPcdDefinition, EdkSymbolDscSection, EdkSymbolDscComponentSubSection, EdkSymbolDscBuildOption } from "./dscSymbols"; import { EdkSymbolFdfSection, EdkSymbolFdfInf, EdkSymbolFdfDefinition, EdkSymbolFdfFile, EdkSymbolFdfInclude } from './fdfSymbols'; import { EdkSymbolInfSectionLibraries, EdkSymbolInfSectionProtocols, EdkSymbolInfSectionPpis, EdkSymbolInfSectionGuids, EdkSymbolInfSectionPcds, EdkSymbolInfSection, EdkSymbolInfDefine, EdkSymbolInfSource, EdkSymbolInfLibrary, EdkSymbolInfPackage, EdkSymbolInfPpi, EdkSymbolInfProtocol, EdkSymbolInfPcd, EdkSymbolInfGuid, EdkSymbolInfDepex, EdkSymbolInfBinary, EdkSymbolInfFunction, EdkSymbolInfSectionSource, EdkSymbolSectionPackages, EdkSymbolinfSectionDepex } from './infSymbols'; import { EdkSymbolDecSection, EdkSymbolDecDefine, EdkSymbolDecLibrary, EdkSymbolDecPackage, EdkSymbolDecPpi, EdkSymbolDecProtocol, EdkSymbolDecPcd, EdkSymbolDecGuid, EdkSymbolDecIncludes } from './decSymbols'; @@ -32,6 +32,10 @@ export class SymbolFactory { return new EdkSymbolDscInclude(textLine, location, true, true, parser); case Edk2SymbolType.dscLine: return new EdkSymbolDscLine(textLine, location, true, true, parser); + case Edk2SymbolType.dscComponentSubSection: + return new EdkSymbolDscComponentSubSection(textLine, location, true, true, parser); + case Edk2SymbolType.dscBuildOption: + return new EdkSymbolDscBuildOption(textLine, location, true, true, parser); case Edk2SymbolType.infSectionSource: return new EdkSymbolInfSectionSource(textLine, location, true, true, parser); case Edk2SymbolType.infSectionPackages: diff --git a/src/symbols/symbolsType.ts b/src/symbols/symbolsType.ts index 89b3ed9..517a472 100644 --- a/src/symbols/symbolsType.ts +++ b/src/symbols/symbolsType.ts @@ -4,7 +4,9 @@ export enum Edk2SymbolType { dscLibraryDefinition, dscModuleDefinition, dscSection, + dscComponentSubSection, dscPcdDefinition, + dscBuildOption, dscInclude, dscLine, infDefine, @@ -69,7 +71,9 @@ export var typeToStr: Map = new Map( [Edk2SymbolType.dscLibraryDefinition, "dscLibraryDefinition"], [Edk2SymbolType.dscModuleDefinition, "dscModuleDefinition"], [Edk2SymbolType.dscSection, "dscSection"], + [Edk2SymbolType.dscComponentSubSection, "dscComponentSubSection"], [Edk2SymbolType.dscPcdDefinition, "dscPcdDefinition"], + [Edk2SymbolType.dscBuildOption, "dscBuildOption"], [Edk2SymbolType.dscInclude, "dscInclude"], [Edk2SymbolType.dscLine, "dscLine"], [Edk2SymbolType.infDefine, "infDefine"], From ed99a1bccb4d21481c5dffc8b2fe9b25a518e808 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 11:02:48 -0600 Subject: [PATCH 28/73] Improve dsc parser to handle module subcontext section --- src/edkParser/README.md | 260 ++++++++++++++++++++++++++++++++ src/edkParser/dscParser.ts | 2 + src/edkParser/languageParser.ts | 34 ++++- 3 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 src/edkParser/README.md diff --git a/src/edkParser/README.md b/src/edkParser/README.md new file mode 100644 index 0000000..49e003d --- /dev/null +++ b/src/edkParser/README.md @@ -0,0 +1,260 @@ +# EDK II Language Parser + +This folder implements a recursive-descent, line-oriented parser that transforms EDK II source files (`.dsc`, `.inf`, `.dec`, `.fdf`, `.vfr`, `.asl`) into a tree of `EdkSymbol` objects used for navigation, outline views, and diagnostics throughout the extension. + +## Architecture Overview + +``` + ParserFactory + | + selects by languageId + | + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + DscParser InfParser DecParser FdfParser ... + β”‚ + (extends DocumentParser) + β”‚ + blockParsers: BlockParser[] ← top-level parsers + β”‚ + └─ each BlockParser has + tag / start / end ← regex rules + context: BlockParser[] ← nested parsers +``` + +### Key classes + +| Class | File | Role | +|---|---|---| +| `BlockParser` | `languageParser.ts` | Abstract block parser β€” matches, opens, and closes blocks using regex rules | +| `DocumentParser` | `languageParser.ts` | Base class for all language parsers β€” drives the line-by-line loop and manages the symbol tree/stack | +| `ParserFactory` | `parserFactory.ts` | Instantiates the correct `DocumentParser` subclass by VS Code `languageId` | +| `DscParser`, `InfParser`, etc. | `dscParser.ts`, `infParser.ts`, etc. | Concrete parsers that define `blockParsers`, comment syntax, and language-specific state | + +--- + +## Parsing Lifecycle + +### 1. Entry point β€” `parseFile()` + +``` +DocumentParser.parseFile() + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ for each line in the document: β”‚ + β”‚ skip comment lines β”‚ + β”‚ call parseLine() β”‚ + β”‚ increment lineIndex β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +`parseFile()` iterates from line 1 to the end of the document. For every non-comment line it calls `parseLine()`. + +### 2. Top-level dispatch β€” `parseLine()` + +```typescript +async parseLine() { + for (parseIndex = 0 .. blockParsers.length) { + decrementLineIndex(); // rewind so block.parse() can re-read + result = blockParsers[parseIndex].parse(this); + if (result) { + // If the block consumed multiple lines (has start/end), reset + // parseIndex to -1 so parsing continues from the first parser + // on the new current line. + // If the block is root-only or single-line, just continue + // trying the next parser on the same line. + } + } +} +``` + +Each `BlockParser` in `blockParsers` is tried in order. When a parser matches and it is a multi-line block (has `start`/`end`), `parseIndex` resets to `-1` so the next line is evaluated again from the first parser. Single-line or root-only parsers let the loop continue without resetting. + +--- + +## BlockParser β€” Start, Content, and End + +The core parsing logic lives in `BlockParser.parse()`. A block parser is configured by four regex properties and a list of child parsers: + +| Property | Required | Purpose | +|---|---|---| +| `tag` | **yes** | Regex the current line must match for this block to activate | +| `excludeTag` | no | If the line matches this regex, the block is skipped even if `tag` matches | +| `start` | no | Regex marking the beginning of the block's body (e.g. `{`) | +| `end` | no | Regex marking the end of the block (e.g. `}` or `[`) | +| `context` | no | Child `BlockParser[]` that will be tried on every line inside the block body | + +### Three block shapes + +The combination of `start` and `end` determines how a block behaves: + +#### Single-line block (`start = undefined`, `end = undefined`) + +``` + tag matches β†’ create symbol β†’ pop immediately β†’ return true +``` + +The block matches exactly one line. The symbol is created and immediately finalized. Examples: `BlockDefinition`, `BlocklibraryDef`, `BlockPcd`, `BlockBuildOption`. + +#### Section block (`start = undefined`, `end = /regex/`) + +``` + tag matches β†’ create symbol β†’ push onto stack + for each subsequent line: + try context[] child parsers + if line matches end β†’ pop symbol β†’ return true + EOF β†’ pop symbol β†’ return true +``` + +The block begins at the `tag` line and continues until a line matches `end`. Every line inside the block is tested against `context[]` child parsers. Examples: `BlockComponentsSection` (ends at `^\[`), `BlockComponentSubLibraryClasses` (ends at `^<` or `^\}`). + +#### Delimited block (`start = /regex/`, `end = /regex/`) + +``` + tag matches β†’ create symbol β†’ push onto stack + scan forward until a line matches start (e.g. "{") + (if end is hit first, pop and return early) + then for each subsequent line: + try context[] child parsers + if line matches end β†’ pop symbol β†’ return true + EOF β†’ pop symbol β†’ return true +``` + +After the tag line matches, the parser scans forward looking for the `start` delimiter before entering the content loop. This handles constructs where the opening brace may appear on the same line as the tag or on a subsequent line. Example: `BlockComponentInf` (tag matches `*.inf`, start matches `{`, end matches `}` or `[`). + +### Flowchart + +``` +parse(docParser) + β”‚ + β”œβ”€ read line, check tag/excludeTag + β”‚ └─ no match β†’ return false + β”‚ + β”œβ”€ check isRoot constraint + β”‚ + β”œβ”€ create EdkSymbol via SymbolFactory + β”‚ └─ addSymbol() + pushSymbolStack() + β”‚ + β”œβ”€ if start is defined: + β”‚ β”‚ scan lines until start matches + β”‚ β”‚ (if end matches first β†’ popSymbolStack, return true) + β”‚ β”‚ + β”‚ └─ fall through to content loop ↓ + β”‚ + β”œβ”€ else if end is undefined: + β”‚ └─ single-line: popSymbolStack β†’ return true + β”‚ + β”œβ”€ else: fall through to content loop ↓ + β”‚ + β”œβ”€ CONTENT LOOP: while hasPendingLines() + β”‚ β”‚ + β”‚ β”œβ”€ read next line (skip empty) + β”‚ β”‚ + β”‚ β”œβ”€ for each child in context[]: + β”‚ β”‚ β”‚ decrement lineIndex (rewind) + β”‚ β”‚ β”‚ child.parse(docParser) ← recursive + β”‚ β”‚ β”‚ if matched and exclusive β†’ break + β”‚ β”‚ β”‚ + β”‚ β”‚ + β”‚ └─ if line matches end β†’ popSymbolStack β†’ return true + β”‚ + └─ EOF reached β†’ popSymbolStack β†’ return true +``` + +--- + +## How Symbols Are Added + +### Symbol creation + +When a `BlockParser`'s `tag` matches, a symbol is created through this sequence: + +1. **Range** β€” An initial range is created from the matched line's start to the end of the document (a placeholder that gets narrowed later). +2. **SymbolFactory** β€” `symbolFactory.produceSymbol(type, textLine, location, parser)` instantiates the specific `EdkSymbol` subclass based on the `Edk2SymbolType` enum. +3. **addSymbol()** β€” The symbol is placed into the tree: + - If `symbolStack` is empty β†’ pushed to `symbolsTree` (root level). + - Otherwise β†’ added as a `child` of the current top-of-stack symbol, and `parent` is set. + - In all cases, also appended to the flat `symbolsList`. +4. **pushSymbolStack()** β€” The symbol is pushed onto the stack to become the current parent for any nested symbols. + +### Symbol finalization + +When the block ends (either by matching `end`, reaching EOF, or being a single-line block), `popSymbolStack()` is called: + +- The symbol is popped from the stack. +- Its **range is narrowed**: the end position is adjusted from the initial "rest of document" placeholder to the actual last line the block covered. + - If the symbol has children β†’ end is set to `lineIndex - 2` (the line before the end delimiter). + - If no children β†’ end is set to `lineIndex - 1` (the tag line itself for single-line blocks). + +### Tree structure + +After parsing, `DocumentParser` holds two views of the symbols: + +| Collection | Type | Description | +|---|---|---| +| `symbolsTree` | `EdkSymbol[]` | Hierarchical tree β€” only root-level symbols; children are nested via `symbol.children` | +| `symbolsList` | `EdkSymbol[]` | Flat list β€” every symbol in document order, for fast iteration and filtering | + +The tree structure corresponds directly to block nesting. For example, parsing a DSC `[Components]` section produces: + +``` +EdkSymbolDscSection "Components.X64" + β”œβ”€ EdkSymbolDscModuleDefinition "Module.inf" + β”‚ β”œβ”€ EdkSymbolDscComponentSubSection "" + β”‚ β”‚ └─ EdkSymbolDscLibraryDefinition "Lib|Path.inf" + β”‚ β”œβ”€ EdkSymbolDscComponentSubSection "" + β”‚ β”‚ └─ EdkSymbolDscPcdDefinition "gPkg.PcdName|value" + β”‚ └─ EdkSymbolDscComponentSubSection "" + β”‚ └─ EdkSymbolDscBuildOption "MSFT:*_*_*_CC_FLAGS = /D FLAG" + └─ EdkSymbolDscModuleDefinition "Other.inf" +``` + +--- + +## Root-only parsers (`isRoot = true`) + +Some parsers are registered twice in `blockParsers`: + +```typescript +blockParsers: BlockParser[] = [ + new BlockComponentsSection(), // normal: matches inside any context + // ... + new BlockComponentInf(true), // isRoot: only matches at the document root +]; +``` + +When `isRoot` is `true`, the parser only activates when `symbolStack` is empty (i.e. no parent block is open). This allows a block to act as a "catch-all" for lines that appear outside any section, ensuring they still get a symbol without interfering with the normal section hierarchy. + +--- + +## `exclusive` flag + +When a child parser in `context[]` matches a line and `exclusive` is `true` (the default), no further child parsers are tried for that line. Setting `exclusive = false` would allow multiple child parsers to process the same line. + +--- + +## Comment handling + +Each `DocumentParser` subclass defines its comment syntax: + +```typescript +commentStart: string = "/*"; // block comment open +commentEnd: string = "*/"; // block comment close +commentLine: string[] = ["#"]; // line comment prefixes +``` + +`removeComment()` strips line comments and tracks block comment state via the `inComment` flag. Lines that are entirely comments are skipped by `parseFile()` before calling `parseLine()`. + +--- + +## File Overview + +| File | Purpose | +|---|---| +| `languageParser.ts` | `BlockParser` + `DocumentParser` base classes | +| `parserFactory.ts` | Factory that instantiates the correct parser by `languageId` | +| `commonParser.ts` | Shared regex constants used across language parsers | +| `dscParser.ts` | DSC file parser (sections, components, library classes, PCDs, build options) | +| `infParser.ts` | INF module file parser | +| `decParser.ts` | DEC package declaration parser | +| `fdfParser.ts` | FDF flash description parser | +| `vfrParser.ts` | VFR visual forms parser | +| `aslParser.ts` | ASL/ACPI source parser | diff --git a/src/edkParser/dscParser.ts b/src/edkParser/dscParser.ts index f38c89d..ce6413e 100644 --- a/src/edkParser/dscParser.ts +++ b/src/edkParser/dscParser.ts @@ -73,6 +73,8 @@ class BlockComponentInf extends BlockParser { start= /.*?{/; end= /(^\})|(^\[)|(^[\s\.\w\$\(\)_\-\\\/]*\.inf)|(^\!include)/gi; type= Edk2SymbolType.dscModuleDefinition; + startContext= /\{/; + endContext= /^\s*\}/gi; visible:boolean = true; context: BlockParser[] = [ diff --git a/src/edkParser/languageParser.ts b/src/edkParser/languageParser.ts index 4e58d04..3a70c84 100644 --- a/src/edkParser/languageParser.ts +++ b/src/edkParser/languageParser.ts @@ -21,6 +21,8 @@ export abstract class BlockParser { exclusive: boolean = true; // Indicates if other blocks should parse this line isRoot: boolean = false; // Indicates if this block is in the root block of the document diagnostic: undefined | ((docParser:DocumentParser, symbol:EdkSymbol) => Promise) = undefined; + startContext: RegExp | undefined; // When matched during start phase, endContext replaces end for block termination + endContext: RegExp | undefined; // End pattern used when startContext was matched constructor(isRoot: boolean = false) { this.isRoot = isRoot; @@ -81,7 +83,11 @@ export abstract class BlockParser { }, 1); // Adjust the delay (in milliseconds) as needed } - + // Check if context mode is active (startContext matched on the tag line) + let inContext = false; + if (this.startContext && textLine.match(this.startContext)) { + inContext = true; + } // look for block start if (this.start) { @@ -98,6 +104,10 @@ export abstract class BlockParser { docParser.popSymbolStack(); return true; } + // Check startContext on lines scanned for block start + if (!inContext && this.startContext && textLine.match(this.startContext)) { + inContext = true; + } } } else { if (this.end === undefined) { @@ -106,6 +116,9 @@ export abstract class BlockParser { } } + // Use endContext instead of end when context mode is active + const activeEnd = inContext && this.endContext ? this.endContext : this.end; + // Parse block content while (docParser.hasPendingLines()) { // create a symbol @@ -120,6 +133,7 @@ export abstract class BlockParser { } // parse block + let contextMatched = false; const contextLength = this.context.length; for (let i = 0; i < contextLength; i++) { const blockContext = this.context[i]; @@ -129,14 +143,30 @@ export abstract class BlockParser { const isSymbolAdded = blockContext.parse(docParser); if (isSymbolAdded) { gDebugLog.trace("Added symbol"); + contextMatched = true; if (blockContext.exclusive) { break; } } } + // If a context parser consumed lines, peek at the last consumed line + // to check if it also matches our endContext (child may have consumed the delimiter) + if (contextMatched && inContext && this.endContext) { + const peekIdx = docParser.lineIndex - 1; + if (peekIdx >= 0 && peekIdx < docParser.document.lineCount) { + const peekText = docParser.removeComment( + docParser.document.lineAt(peekIdx).text + ); + if (peekText.match(this.endContext)) { + docParser.popSymbolStack(); + return true; + } + } + } + // Check the end tag - if (this.end && textLine.match(this.end)) { + if (activeEnd && textLine.match(activeEnd)) { docParser.popSymbolStack(); return true; } From 2c158ac22c6054fc0cc66e95f64c3e24c433333c Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 11:23:16 -0600 Subject: [PATCH 29/73] Modified symbols for lib and module --- package.json | 14 ++++++++++++++ src/extension.ts | 5 +++++ src/symbols/dscSymbols.ts | 4 ++-- src/symbols/fdfSymbols.ts | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e152459..1334bb5 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,11 @@ { "command": "edk2code.copyWorkspaceNodePath", "title": "Copy path" + }, + { + "command": "edk2code.refreshWorkspaceConfig", + "title": "EDK2: Refresh workspace config", + "icon": "$(refresh)" } ], "configuration": [ @@ -357,6 +362,10 @@ { "command": "edk2code.copyWorkspaceNodePath", "when": "false" + }, + { + "command": "edk2code.refreshWorkspaceConfig", + "when": "true" } ], "editor/title": [], @@ -381,6 +390,11 @@ "group": "navigation", "when": "view == workspaceView" }, + { + "command": "edk2code.refreshWorkspaceConfig", + "group": "navigation", + "when": "view == workspaceView" + }, { "command": "edk2code.copyTreeData", "when": "view == detailsView", diff --git a/src/extension.ts b/src/extension.ts index 6e4e195..211519f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -185,6 +185,11 @@ export async function activate(context: vscode.ExtensionContext) { await edkWorkspaceTreeProvider.showFilterPicker(); }), + vscode.commands.registerCommand('edk2code.refreshWorkspaceConfig', async () => { + await gEdkWorkspaces.loadConfig(); + edkWorkspaceTreeProvider.refresh(); + }), + vscode.commands.registerCommand('edk2code.selectWorkspaceView', async () => { const workspaces = gEdkWorkspaces.workspaces; if (workspaces.length === 0) { diff --git a/src/symbols/dscSymbols.ts b/src/symbols/dscSymbols.ts index 6cda7da..227518e 100644 --- a/src/symbols/dscSymbols.ts +++ b/src/symbols/dscSymbols.ts @@ -47,7 +47,7 @@ export class EdkSymbolDscDefine extends EdkSymbol{ export class EdkSymbolDscLibraryDefinition extends EdkSymbol{ type = Edk2SymbolType.dscLibraryDefinition; - kind = vscode.SymbolKind.Module; + kind = vscode.SymbolKind.Field; onCompletion: undefined; onDefinition = async (parser:DocumentParser)=>{ @@ -121,7 +121,7 @@ export class EdkSymbolDscBuildOption extends EdkSymbol { export class EdkSymbolDscModuleDefinition extends EdkSymbol{ type = Edk2SymbolType.dscModuleDefinition; - kind = vscode.SymbolKind.Event; + kind = vscode.SymbolKind.Method; protected get nameRegex(): RegExp { return /^\s*([\w/.\\-]+\.inf)/i; } diff --git a/src/symbols/fdfSymbols.ts b/src/symbols/fdfSymbols.ts index 1c295ec..66e5647 100644 --- a/src/symbols/fdfSymbols.ts +++ b/src/symbols/fdfSymbols.ts @@ -29,7 +29,7 @@ export class EdkSymbolFdfSection extends EdkSymbol { export class EdkSymbolFdfInf extends EdkSymbol { type = Edk2SymbolType.fdfInf; - kind = vscode.SymbolKind.Event; + kind = vscode.SymbolKind.Method; onCompletion: undefined; From de6b4c793913bb086871e8c42e671e5b123375d9 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 13:54:30 -0600 Subject: [PATCH 30/73] added Build option symbol --- src/edkParser/README.md | 37 ++++++++++++++++++++++ src/edkParser/dscParser.ts | 17 +++++++++- src/extension.ts | 4 +-- src/symbols/README.md | 31 ++++++++++++++++++ src/symbols/dscSymbols.ts | 10 ++++++ src/symbols/symbolFactory.ts | 4 ++- src/symbols/symbolsType.ts | 2 ++ src/workspaceTree/WorkspaceTreeProvider.ts | 14 ++++---- 8 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/edkParser/README.md b/src/edkParser/README.md index 49e003d..86e33a8 100644 --- a/src/edkParser/README.md +++ b/src/edkParser/README.md @@ -225,6 +225,43 @@ When `isRoot` is `true`, the parser only activates when `symbolStack` is empty ( --- +## `startContext` / `endContext` β€” alternate end delimiter + +Some EDK II constructs use a delimiter that appears *on the same line as the tag* to enter a special sub-block, and that sub-block requires a different `end` pattern than the surrounding block. `startContext` and `endContext` handle this case without needing a separate `BlockParser`. + +| Property | Type | Purpose | +|---|---|---| +| `startContext` | `RegExp \| undefined` | If this pattern matches the tag line (or any line scanned while looking for `start`), the block switches to `endContext` as its terminator | +| `endContext` | `RegExp \| undefined` | Alternative `end` regex used only when `startContext` was matched | + +### How it works + +1. After the `tag` matches and the symbol is created, the parser checks whether the tag line also matches `startContext`. If so, `inContext = true`. +2. While scanning forward looking for `start`, each scanned line is also tested against `startContext`. +3. Once the content loop begins, `activeEnd` is resolved: + - `inContext && endContext` β†’ `activeEnd = endContext` + - otherwise β†’ `activeEnd = end` +4. Additionally, after a child context parser consumes a line, the **last consumed line** is peeked and tested against `endContext`. This handles the case where the child parser itself consumed the closing delimiter. + +### Example β€” `BlockComponentInf` + +`BlockComponentInf` matches `*.inf` lines. A component entry can optionally have an opening brace `{` on the same (or next) line, introducing a sub-block with scoped overrides. Without `startContext`/`endContext`, both `{...}` and bare entries would need separate parsers. + +```typescript +class BlockComponentInf extends BlockParser { + tag = /^[\s\.\w\$\(\)_\-\\\/]*\.inf/gi; + start = /.*?{/; // look for the opening brace + end = /(^\})|(^\[)|(\.inf)|(^\!include)/gi; // bare-entry terminators + startContext = /\{/; // brace on the tag/start line β†’ enter context mode + endContext = /^\s*\}/gi; // context mode ends only on closing brace + // ... +} +``` + +When `{` is found, `inContext` becomes `true` and `endContext` (`^\s*\}`) takes over from `end`, so only a closing brace terminates the sub-block. When `{` is absent, `end` applies as usual and the entry is treated as a single-line construct. + +--- + ## `exclusive` flag When a child parser in `context[]` matches a line and `exclusive` is `true` (the default), no further child parsers are tried for that line. Setting `exclusive = false` would allow multiple child parsers to process the same line. diff --git a/src/edkParser/dscParser.ts b/src/edkParser/dscParser.ts index ce6413e..395ad71 100644 --- a/src/edkParser/dscParser.ts +++ b/src/edkParser/dscParser.ts @@ -161,9 +161,22 @@ class BlockSimpleLine extends BlockParser { // Main sections // +class BlockBuildOptionsSection extends BlockParser { + name = "BuildOptions"; + tag = /\[\s*buildoptions.*?\]/gi; + start = undefined; + end = /^\[/gi; + type = Edk2SymbolType.dscBuildOptionsSection; + visible: boolean = true; + context: BlockParser[] = [ + new BlockBuildOption(), + new BlockIncludes(), + ]; +} + class BlockDefines extends BlockParser { name= "Defines"; - tag= /\[\s*(defines|buildoptions)\s*\]/gi; + tag= /\[\s*defines\s*\]/gi; start= undefined; end= /^\[/gi; type= Edk2SymbolType.dscSection; @@ -264,6 +277,7 @@ export class DscParser extends DocumentParser { blockParsers: BlockParser[] = [ new BlockDefines(), + new BlockBuildOptionsSection(), new BlockComponentsSection(), new BlockLibraryClasses(), new BlockSkuIds(), @@ -275,6 +289,7 @@ export class DscParser extends DocumentParser { new BlockComponentInf(true), new BlocklibraryDef(true), new BlockPcd(true), + new BlockBuildOption(true), ]; diff --git a/src/extension.ts b/src/extension.ts index 211519f..6e91d27 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -176,9 +176,9 @@ export async function activate(context: vscode.ExtensionContext) { if (!('treePath' in node)) { return; } - const treePath = node.treePath.join(' > '); + const treePath = node.treePath.map((p, i) => ' '.repeat(i) + p).join('\n'); await vscode.env.clipboard.writeText(treePath); - void vscode.window.showInformationMessage(`Workspace path copied: ${treePath}`); + void vscode.window.showInformationMessage(`Workspace path copied to clipboard`); }), vscode.commands.registerCommand('edk2code.filterWorkspaceSymbols', async () => { diff --git a/src/symbols/README.md b/src/symbols/README.md index 274de7f..3cc1290 100644 --- a/src/symbols/README.md +++ b/src/symbols/README.md @@ -140,6 +140,37 @@ class BlockComponentInf extends BlockParser { } ``` +If the new type should also appear as a root-level fallback (so lines are captured even when they appear outside any section), register an additional `isRoot = true` instance at the end of `DscParser.blockParsers`: + +```typescript +blockParsers: BlockParser[] = [ + new BlockBuildOptionsSection(), // normal section block + // ... + new BlockBuildOption(true), // isRoot fallback +]; +``` + +### 5. Register the type in `WorkspaceTreeProvider.ts` + +The workspace tree view (`src/workspaceTree/WorkspaceTreeProvider.ts`) uses `DSC_FILTER_TYPES` to control which symbol types are rendered. Any new type that should appear in the tree **must** be added here β€” both for top-level rendering and for collapsible-state detection on parent nodes. + +```typescript +// WorkspaceTreeProvider.ts +export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; description: string }[] = [ + // ... + { type: Edk2SymbolType.dscBuildOptionsSection, label: 'Build options', description: 'dscBuildOptionsSection' }, + { type: Edk2SymbolType.dscBuildOption, label: 'Build option entries', description: 'dscBuildOption' }, + // ... +]; +``` + +This array serves three roles: +1. **Root filter** β€” only symbols whose type is in this set are shown at the workspace root level. +2. **Child filter** β€” `getChildren()` uses it to filter children of every tree node. +3. **Collapsible state** β€” `DocumentSymbolItem` counts `visibleChildren` against this set to decide whether a node should be expandable. + +Omitting a type from `DSC_FILTER_TYPES` silently hides the symbol and all its children in the tree, even if parsing logs show "Added symbol" correctly. + --- ## `EdkSymbol` base class diff --git a/src/symbols/dscSymbols.ts b/src/symbols/dscSymbols.ts index 227518e..040ac06 100644 --- a/src/symbols/dscSymbols.ts +++ b/src/symbols/dscSymbols.ts @@ -99,6 +99,16 @@ export class EdkSymbolDscLine extends EdkSymbol{ onDeclaration: undefined; } +export class EdkSymbolDscBuildOptionsSection extends EdkSymbol { + type = Edk2SymbolType.dscBuildOptionsSection; + kind = vscode.SymbolKind.Class; + + onCompletion: undefined; + onDefinition: undefined; + onHover: undefined; + onDeclaration: undefined; +} + export class EdkSymbolDscComponentSubSection extends EdkSymbol { type = Edk2SymbolType.dscComponentSubSection; kind = vscode.SymbolKind.Namespace; diff --git a/src/symbols/symbolFactory.ts b/src/symbols/symbolFactory.ts index b2969d4..8bef072 100644 --- a/src/symbols/symbolFactory.ts +++ b/src/symbols/symbolFactory.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { gDebugLog } from "../extension"; -import { EdkSymbolDscDefine, EdkSymbolDscInclude, EdkSymbolDscLibraryDefinition, EdkSymbolDscLine, EdkSymbolDscModuleDefinition, EdkSymbolDscPcdDefinition, EdkSymbolDscSection, EdkSymbolDscComponentSubSection, EdkSymbolDscBuildOption } from "./dscSymbols"; +import { EdkSymbolDscDefine, EdkSymbolDscInclude, EdkSymbolDscLibraryDefinition, EdkSymbolDscLine, EdkSymbolDscModuleDefinition, EdkSymbolDscPcdDefinition, EdkSymbolDscSection, EdkSymbolDscBuildOptionsSection, EdkSymbolDscComponentSubSection, EdkSymbolDscBuildOption } from "./dscSymbols"; import { EdkSymbolFdfSection, EdkSymbolFdfInf, EdkSymbolFdfDefinition, EdkSymbolFdfFile, EdkSymbolFdfInclude } from './fdfSymbols'; import { EdkSymbolInfSectionLibraries, EdkSymbolInfSectionProtocols, EdkSymbolInfSectionPpis, EdkSymbolInfSectionGuids, EdkSymbolInfSectionPcds, EdkSymbolInfSection, EdkSymbolInfDefine, EdkSymbolInfSource, EdkSymbolInfLibrary, EdkSymbolInfPackage, EdkSymbolInfPpi, EdkSymbolInfProtocol, EdkSymbolInfPcd, EdkSymbolInfGuid, EdkSymbolInfDepex, EdkSymbolInfBinary, EdkSymbolInfFunction, EdkSymbolInfSectionSource, EdkSymbolSectionPackages, EdkSymbolinfSectionDepex } from './infSymbols'; import { EdkSymbolDecSection, EdkSymbolDecDefine, EdkSymbolDecLibrary, EdkSymbolDecPackage, EdkSymbolDecPpi, EdkSymbolDecProtocol, EdkSymbolDecPcd, EdkSymbolDecGuid, EdkSymbolDecIncludes } from './decSymbols'; @@ -20,6 +20,8 @@ export class SymbolFactory { switch (type) { case Edk2SymbolType.dscSection: return new EdkSymbolDscSection(textLine, location, true, true, parser); + case Edk2SymbolType.dscBuildOptionsSection: + return new EdkSymbolDscBuildOptionsSection(textLine, location, true, true, parser); case Edk2SymbolType.dscDefine: return new EdkSymbolDscDefine(textLine, location, true, true, parser); case Edk2SymbolType.dscLibraryDefinition: diff --git a/src/symbols/symbolsType.ts b/src/symbols/symbolsType.ts index 517a472..13c99a0 100644 --- a/src/symbols/symbolsType.ts +++ b/src/symbols/symbolsType.ts @@ -4,6 +4,7 @@ export enum Edk2SymbolType { dscLibraryDefinition, dscModuleDefinition, dscSection, + dscBuildOptionsSection, dscComponentSubSection, dscPcdDefinition, dscBuildOption, @@ -71,6 +72,7 @@ export var typeToStr: Map = new Map( [Edk2SymbolType.dscLibraryDefinition, "dscLibraryDefinition"], [Edk2SymbolType.dscModuleDefinition, "dscModuleDefinition"], [Edk2SymbolType.dscSection, "dscSection"], + [Edk2SymbolType.dscBuildOptionsSection, "dscBuildOptionsSection"], [Edk2SymbolType.dscComponentSubSection, "dscComponentSubSection"], [Edk2SymbolType.dscPcdDefinition, "dscPcdDefinition"], [Edk2SymbolType.dscBuildOption, "dscBuildOption"], diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 43fcacf..ec72902 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -9,12 +9,14 @@ import { gConfigAgent, gEdkWorkspaces } from '../extension'; // ─── DSC symbol types available for filtering ───────────────────────────────── export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; description: string }[] = [ - { type: Edk2SymbolType.dscDefine, label: 'Defines', description: 'dscDefine' }, - { type: Edk2SymbolType.dscLibraryDefinition, label: 'Library classes', description: 'dscLibraryDefinition' }, - { type: Edk2SymbolType.dscModuleDefinition, label: 'Components', description: 'dscModuleDefinition' }, - { type: Edk2SymbolType.dscSection, label: 'Sections', description: 'dscSection' }, - { type: Edk2SymbolType.dscPcdDefinition, label: 'PCDs', description: 'dscPcdDefinition' }, - { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' }, + { type: Edk2SymbolType.dscDefine, label: 'Defines', description: 'dscDefine' }, + { type: Edk2SymbolType.dscLibraryDefinition, label: 'Library classes', description: 'dscLibraryDefinition' }, + { type: Edk2SymbolType.dscModuleDefinition, label: 'Components', description: 'dscModuleDefinition' }, + { type: Edk2SymbolType.dscSection, label: 'Sections', description: 'dscSection' }, + { type: Edk2SymbolType.dscBuildOptionsSection, label: 'Build options', description: 'dscBuildOptionsSection' }, + { type: Edk2SymbolType.dscBuildOption, label: 'Build option entries', description: 'dscBuildOption' }, + { type: Edk2SymbolType.dscPcdDefinition, label: 'PCDs', description: 'dscPcdDefinition' }, + { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' }, ]; // ─── Helper: load symbols for a URI via the parser ─────────────────────────── From 00584693e01a9b80541f37dfcc769c94f6963552 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 14:34:22 -0600 Subject: [PATCH 31/73] Added focus con workspace view --- package.json | 32 ++++ src/extension.ts | 86 ++++++++++- src/workspaceTree/WorkspaceTreeProvider.ts | 165 ++++++++++++++++++++- 3 files changed, 272 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 1334bb5..441cb7b 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,15 @@ "command": "edk2code.refreshWorkspaceConfig", "title": "EDK2: Refresh workspace config", "icon": "$(refresh)" + }, + { + "command": "edk2code.revealEditorInWorkspaceTree", + "title": "EDK2: Reveal active editor in workspace tree", + "icon": "$(target)" + }, + { + "command": "edk2code.focusEditorInWorkspaceView", + "title": "EDK2: Focus on workspace view" } ], "configuration": [ @@ -366,6 +375,14 @@ { "command": "edk2code.refreshWorkspaceConfig", "when": "true" + }, + { + "command": "edk2code.revealEditorInWorkspaceTree", + "when": "true" + }, + { + "command": "edk2code.focusEditorInWorkspaceView", + "when": "false" } ], "editor/title": [], @@ -395,6 +412,11 @@ "group": "navigation", "when": "view == workspaceView" }, + { + "command": "edk2code.revealEditorInWorkspaceTree", + "group": "navigation", + "when": "view == workspaceView" + }, { "command": "edk2code.copyTreeData", "when": "view == detailsView", @@ -442,6 +464,16 @@ "group": "navigation", "when": "editorLangId == edk2_dsc || editorLangId == edk2_fdf" }, + { + "command": "edk2code.focusEditorInWorkspaceView", + "group": "navigation", + "when": "editorLangId == edk2_dsc && edk2code.editorFileInWorkspaceTree" + }, + { + "command": "edk2code.focusEditorInWorkspaceView", + "group": "navigation", + "when": "editorLangId == edk2_inf && edk2code.infFileInWorkspaceTree" + }, { "command": "edk2code.showReferences", "group": "navigation", diff --git a/src/extension.ts b/src/extension.ts index 6e91d27..e4bde33 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,8 @@ import { ParserFactory } from './edkParser/parserFactory'; import { TreeDetailsDataProvider } from './TreeDataProvider'; // import { DefinesTreeDataProvider } from './definesPanel'; import { DiagnosticManager } from './diagnostics'; -import { WorkspaceTreeProvider, WorkspaceRootItem, IncludeTreeItem, DocumentSymbolItem } from './workspaceTree/WorkspaceTreeProvider'; +import { WorkspaceTreeProvider, WorkspaceRootItem, IncludeTreeItem, DocumentSymbolItem, WorkspaceTreeNode, isFileInWorkspaceTree, isInfInWorkspaces } from './workspaceTree/WorkspaceTreeProvider'; +import { InfDsc } from './index/edkWorkspace'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; import { TreeItem } from './treeElements/TreeItem'; @@ -52,7 +53,7 @@ export var edkLensTreeDetailProvider: TreeDetailsDataProvider; export var edkLensTreeDetailView: vscode.TreeView; export var edkWorkspaceTreeProvider: WorkspaceTreeProvider; -export var edkWorkspaceTreeView: vscode.TreeView; +export var edkWorkspaceTreeView: vscode.TreeView; // export var edkDefinesTreeProvider: DefinesTreeDataProvider; @@ -208,6 +209,63 @@ export async function activate(context: vscode.ExtensionContext) { if (picked !== undefined) { edkWorkspaceTreeProvider.selectWorkspace(picked.index); } + }), + + vscode.commands.registerCommand('edk2code.revealEditorInWorkspaceTree', async () => { + await edkWorkspaceTreeProvider.revealActiveEditor(edkWorkspaceTreeView); + }), + + vscode.commands.registerCommand('edk2code.focusEditorInWorkspaceView', async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { return; } + + const langId = editor.document.languageId; + + // DSC / DSC-include: reveal directly by cursor position + if (langId === 'edk2_dsc') { + await edkWorkspaceTreeProvider.revealActiveEditor(edkWorkspaceTreeView); + return; + } + + // INF: first find DSC declaration(s), then reveal that location + if (langId === 'edk2_inf') { + const fileUri = editor.document.uri; + const wps = await gEdkWorkspaces.getWorkspace(fileUri); + + let declarations: InfDsc[] = []; + for (const wp of wps) { + declarations = declarations.concat(await wp.getDscDeclaration(fileUri)); + } + + if (declarations.length === 0) { + void vscode.window.showInformationMessage('This INF file has no DSC declaration in the loaded workspaces.'); + return; + } + + let chosen: InfDsc; + if (declarations.length === 1) { + chosen = declarations[0]; + } else { + const items = declarations.map(d => ({ + label: vscode.workspace.asRelativePath(d.location.uri, false), + description: `line ${d.location.range.start.line + 1}`, + detail: d.text.trim(), + decl: d + })); + const picked = await vscode.window.showQuickPick(items, { + placeHolder: 'Multiple DSC declarations found – select one to reveal', + title: 'EDK2: Focus on workspace view' + }); + if (!picked) { return; } + chosen = picked.decl; + } + + await edkWorkspaceTreeProvider.revealLocation( + chosen.location.uri, + chosen.location.range.start, + edkWorkspaceTreeView + ); + } }) ]; @@ -259,7 +317,29 @@ export async function activate(context: vscode.ExtensionContext) { }); await vscode.commands.executeCommand('setContext', 'edk2code.isNodeFocusBackStack', false); - + + // ─── Track whether the active editor belongs to the workspace tree ───────── + async function updateEditorInWorkspaceContext(editor: vscode.TextEditor | undefined): Promise { + const uri = editor?.document.uri; + const langId = editor?.document.languageId; + + const inDscTree = + uri !== undefined && + isFileInWorkspaceTree(uri, gEdkWorkspaces.workspaces); + + const inInfWorkspace = + uri !== undefined && + langId === 'edk2_inf' && + isInfInWorkspaces(uri, gEdkWorkspaces.workspaces); + + void vscode.commands.executeCommand('setContext', 'edk2code.editorFileInWorkspaceTree', inDscTree); + void vscode.commands.executeCommand('setContext', 'edk2code.infFileInWorkspaceTree', inInfWorkspace); + } + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(editor => { void updateEditorInWorkspaceContext(editor); }) + ); + void updateEditorInWorkspaceContext(vscode.window.activeTextEditor); + void showReleaseNotes(context); } diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index ec72902..e8ce641 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -30,14 +30,55 @@ async function loadSymbols(uri: vscode.Uri): Promise { } } +// ─── Helper: collect all file URIs reachable in an include tree ─────────────── + +function collectIncludeUris(nodes: IncludeNode[], out: Set): void { + for (const node of nodes) { + out.add(node.uri.fsPath); + collectIncludeUris(node.children, out); + } +} + +/** + * Returns true when the given URI is the main DSC or any file included + * (directly or transitively) in at least one workspace of the provided list. + */ +export function isFileInWorkspaceTree(uri: vscode.Uri, workspaces: EdkWorkspace[]): boolean { + for (const ws of workspaces) { + if (ws.mainDsc.fsPath === uri.fsPath) { return true; } + const uris = new Set(); + collectIncludeUris(ws.includeTree, uris); + if (uris.has(uri.fsPath)) { return true; } + } + return false; +} + +/** + * Returns true when the given INF URI is referenced as a module or library + * in at least one loaded workspace. + * Uses the same path-suffix check that EdkWorkspace.isFileInUse() performs. + */ +export function isInfInWorkspaces(uri: vscode.Uri, workspaces: EdkWorkspace[]): boolean { + for (const ws of workspaces) { + for (const mod of ws.filesModules) { + if (uri.fsPath.includes(mod.path)) { return true; } + } + for (const lib of ws.filesLibraries) { + if (uri.fsPath.includes(lib.path)) { return true; } + } + } + return false; +} + // ─── Tree Item: Workspace root (the main DSC) ──────────────────────────────── export class WorkspaceRootItem extends vscode.TreeItem { public readonly treePath: string[]; - constructor(public readonly workspace: EdkWorkspace) { + constructor(public readonly workspace: EdkWorkspace, public readonly wsIndex: number) { const label = path.basename(workspace.mainDsc.fsPath); super(label, vscode.TreeItemCollapsibleState.Expanded); + this.id = `wsr:${wsIndex}`; this.treePath = [label]; this.description = workspace.platformName ?? ''; this.tooltip = new vscode.MarkdownString( @@ -88,7 +129,8 @@ export class DocumentSymbolItem extends vscode.TreeItem { public readonly symbol: EdkSymbol, public readonly fileUri: vscode.Uri, activeFilters: Set, - parentPath: string[] + parentPath: string[], + public readonly parent: WorkspaceRootItem | DocumentSymbolItem | undefined ) { const visibleChildren = symbol.children.filter( c => activeFilters.has((c as EdkSymbol).type) @@ -100,6 +142,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None ); + this.id = `dsi:${fileUri.fsPath}:${symbol.selectionRange.start.line}:${symbol.selectionRange.start.character}`; this.treePath = [...parentPath, symbol.name]; this.symbolType = symbol.type; this.description = symbol.detail || undefined; @@ -115,7 +158,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { } } -type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem; +export type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem; // ─── Helper: find an IncludeNode by the location of its !include directive ─── @@ -160,6 +203,38 @@ async function serializeIncludeNode(node: IncludeNode, indent: string, filter: S return out; } +// ─── Helper: find the deepest filtered symbol that contains a position ──────── + +function findDeepestSymbolAt( + symbols: EdkSymbol[], + position: vscode.Position, + filter: Set +): EdkSymbol | undefined { + let best: EdkSymbol | undefined; + for (const sym of symbols) { + if (!filter.has(sym.type)) { continue; } + const inRange = sym.range.contains(position) || sym.selectionRange.contains(position); + if (inRange) { + best = sym; + const deeper = findDeepestSymbolAt(sym.children as EdkSymbol[], position, filter); + if (deeper) { best = deeper; } + } + } + // Fallback: if no symbol contains the position, return the one closest by line + if (!best) { + let closestDist = Infinity; + for (const sym of symbols) { + if (!filter.has(sym.type)) { continue; } + const dist = Math.abs(sym.selectionRange.start.line - position.line); + if (dist < closestDist) { + closestDist = dist; + best = sym; + } + } + } + return best; +} + // ─── Tree data provider ─────────────────────────────────────────────────────── export class WorkspaceTreeProvider implements vscode.TreeDataProvider { @@ -260,7 +335,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider this._activeFilters.has(s.type)) - .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath)); + .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element)); } // Under an include node: symbols of that file only @@ -276,7 +351,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider this._activeFilters.has(s.type)) - .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath)); + .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined)); } // Under a symbol: for !include directives expand into the included file; @@ -290,15 +365,89 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider this._activeFilters.has(s.type)) - .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath)); + .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element)); } } } return (element.symbol.children as EdkSymbol[]) .filter(c => this._activeFilters.has(c.type)) - .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath)); + .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element)); } return []; } + + getParent(element: WorkspaceTreeNode): vscode.ProviderResult { + if (element instanceof WorkspaceRootItem) { return undefined; } + if (element instanceof DocumentSymbolItem) { return element.parent; } + return undefined; + } + + /** + * Reveal the tree node that best matches the symbol at the cursor in the active editor. + */ + async revealActiveEditor(treeView: vscode.TreeView): Promise { + const editor = vscode.window.activeTextEditor; + if (!editor) { + void vscode.window.showInformationMessage('No active editor.'); + return; + } + await this.revealLocation(editor.document.uri, editor.selection.active, treeView); + } + + /** + * Reveal the tree node that best matches the symbol at a specific location. + */ + async revealLocation( + uri: vscode.Uri, + position: vscode.Position, + treeView: vscode.TreeView + ): Promise { + // Load symbols for the file and find the deepest one at the position + const symbols = await loadSymbols(uri); + if (symbols.length === 0) { + void vscode.window.showInformationMessage('No EDK2 symbols found in the active file.'); + return; + } + + const target = findDeepestSymbolAt(symbols, position, this._activeFilters); + if (!target) { + void vscode.window.showInformationMessage('No EDK2 symbol found at the cursor position.'); + return; + } + + // Traverse the tree to find the matching DocumentSymbolItem + const rootItems = await this.getChildren(undefined); + for (const root of rootItems) { + const found = await this._findItemForSymbol(root, uri, target); + if (found) { + await treeView.reveal(found, { select: true, focus: false, expand: true }); + return; + } + } + + void vscode.window.showInformationMessage('Symbol not found in the workspace tree.'); + } + + /** Recursively walk the tree to find a DocumentSymbolItem matching (fileUri, targetSymbol). */ + private async _findItemForSymbol( + parent: WorkspaceTreeNode, + fileUri: vscode.Uri, + targetSymbol: EdkSymbol + ): Promise { + const children = await this.getChildren(parent); + for (const child of children) { + if ( + child instanceof DocumentSymbolItem && + child.fileUri.fsPath === fileUri.fsPath && + child.symbol.selectionRange.start.line === targetSymbol.selectionRange.start.line && + child.symbol.selectionRange.start.character === targetSymbol.selectionRange.start.character + ) { + return child; + } + const found = await this._findItemForSymbol(child, fileUri, targetSymbol); + if (found) { return found; } + } + return undefined; + } } From 748f8bab791660ff9e21942a787229bfbd333aca Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 14:58:26 -0600 Subject: [PATCH 32/73] Added drag and drop --- src/extension.ts | 2 +- src/workspaceTree/WorkspaceTreeProvider.ts | 91 +++++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index e4bde33..1a48d96 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -288,7 +288,7 @@ export async function activate(context: vscode.ExtensionContext) { // vscode.window.createTreeView('definesView', { treeDataProvider: edkDefinesTreeProvider, showCollapseAll: true }); edkWorkspaceTreeProvider = new WorkspaceTreeProvider(); - edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true }); + edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider }); await gEdkWorkspaces.loadConfig(); edkWorkspaceTreeProvider.refresh(); diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index e8ce641..4dc7724 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -4,7 +4,7 @@ import { EdkWorkspace, IncludeNode } from '../index/edkWorkspace'; import { getParser } from '../edkParser/parserFactory'; import { EdkSymbol } from '../symbols/edkSymbols'; import { Edk2SymbolType } from '../symbols/symbolsType'; -import { gConfigAgent, gEdkWorkspaces } from '../extension'; +import { edkWorkspaceTreeView, gConfigAgent, gEdkWorkspaces } from '../extension'; // ─── DSC symbol types available for filtering ───────────────────────────────── @@ -237,11 +237,98 @@ function findDeepestSymbolAt( // ─── Tree data provider ─────────────────────────────────────────────────────── -export class WorkspaceTreeProvider implements vscode.TreeDataProvider { +export class WorkspaceTreeProvider implements vscode.TreeDataProvider, vscode.TreeDragAndDropController { private _onDidChangeTreeData = new vscode.EventEmitter(); readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + // ─── Drag & Drop ────────────────────────────────────────────────────────── + readonly dropMimeTypes: string[] = ['application/vnd.code.tree.workspaceview']; + readonly dragMimeTypes: string[] = ['application/vnd.code.tree.workspaceview']; + + /** Stashed source info from the last drag operation. */ + private _draggedSource: { fileUri: vscode.Uri; range: vscode.Range } | undefined; + + handleDrag( + source: readonly WorkspaceTreeNode[], + _dataTransfer: vscode.DataTransfer, + _token: vscode.CancellationToken + ): void { + const item = source.find( + (s): s is DocumentSymbolItem => s instanceof DocumentSymbolItem + ); + if (!item) { + this._draggedSource = undefined; + return; + } + this._draggedSource = { fileUri: item.fileUri, range: item.symbol.range }; + } + + async handleDrop( + target: WorkspaceTreeNode | undefined, + _dataTransfer: vscode.DataTransfer, + _token: vscode.CancellationToken + ): Promise { + const source = this._draggedSource; + this._draggedSource = undefined; + if (!source || !target) { return; } + if (!(target instanceof DocumentSymbolItem)) { return; } + + const targetRange = target.symbol.range; + + // Skip dropping onto itself + if ( + source.fileUri.fsPath === target.fileUri.fsPath && + source.range.isEqual(targetRange) + ) { + return; + } + + const sourceDoc = await vscode.workspace.openTextDocument(source.fileUri); + + // Read full lines of the source symbol + const srcStart = source.range.start.line; + const srcEnd = source.range.end.line; + let textToMove = ''; + for (let i = srcStart; i <= srcEnd; i++) { + textToMove += sourceDoc.lineAt(i).text + '\n'; + } + + // Delete range: whole lines + const deleteRange = srcEnd + 1 < sourceDoc.lineCount + ? new vscode.Range(srcStart, 0, srcEnd + 1, 0) + : new vscode.Range( + srcStart === 0 ? 0 : srcStart - 1, + srcStart === 0 ? 0 : sourceDoc.lineAt(srcStart - 1).text.length, + srcEnd, + sourceDoc.lineAt(srcEnd).text.length + ); + + // Insert right after the target symbol's last line + const targetDoc = await vscode.workspace.openTextDocument(target.fileUri); + const tgtEnd = targetRange.end.line; + let insertPos: vscode.Position; + let insertText: string; + + if (tgtEnd + 1 < targetDoc.lineCount) { + insertPos = new vscode.Position(tgtEnd + 1, 0); + insertText = textToMove; + } else { + insertPos = new vscode.Position(tgtEnd, targetDoc.lineAt(tgtEnd).text.length); + insertText = '\n' + textToMove.replace(/\n$/, ''); + } + + const edit = new vscode.WorkspaceEdit(); + edit.delete(source.fileUri, deleteRange); + edit.insert(target.fileUri, insertPos, insertText); + await vscode.workspace.applyEdit(edit); + + // Refresh tree and reveal the target symbol + this.refresh(); + const tgtPosition = target.symbol.selectionRange.start; + await this.revealLocation(target.fileUri, tgtPosition, edkWorkspaceTreeView); + } + private _activeIndex: number = 0; /** Which DSC symbol types are currently visible. Loaded from config; defaults to all. */ From d98e15009caf69933f0185eddc8a5823266c711d Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 6 Mar 2026 18:18:04 -0600 Subject: [PATCH 33/73] Fixed subsection after drag and drop implemented --- package.json | 4 +-- src/contextState/cmds.ts | 1 - src/extension.ts | 2 -- src/index/edkWorkspace.ts | 3 +- src/workspaceTree/WorkspaceTreeProvider.ts | 32 +++++++++++++++------- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 441cb7b..847aa30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "edk2code", - "displayName": "Edk2code", + "name": "edk2code-dev", + "displayName": "Edk2code-dev", "description": "EDK2 code support", "version": "1.1.2", "icon": "assets/icon.png", diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 315de00..80070a3 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -140,7 +140,6 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; void vscode.window.showInformationMessage("Build data loaded"); await gEdkWorkspaces.loadConfig(); - edkWorkspaceTreeProvider.refresh(); await showDefines(); } diff --git a/src/extension.ts b/src/extension.ts index 1a48d96..88a3af5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -188,7 +188,6 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.refreshWorkspaceConfig', async () => { await gEdkWorkspaces.loadConfig(); - edkWorkspaceTreeProvider.refresh(); }), vscode.commands.registerCommand('edk2code.selectWorkspaceView', async () => { @@ -291,7 +290,6 @@ export async function activate(context: vscode.ExtensionContext) { edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider }); await gEdkWorkspaces.loadConfig(); - edkWorkspaceTreeProvider.refresh(); // edkDefinesTreeProvider.refresh(); gFileUseWarning = new FileUseWarning(); diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index a8408e5..9e1efe3 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; -import { gCompileCommands, gConfigAgent, gDebugLog, gMapFileManager, gPathFind, gWorkspacePath } from '../extension'; +import { edkWorkspaceTreeProvider, gCompileCommands, gConfigAgent, gDebugLog, gMapFileManager, gPathFind, gWorkspacePath } from '../extension'; import { GrayoutController } from '../grayout'; import { createRange, openTextDocument, pathCompare, split } from '../utils'; import { REGEX_DEFINE as REGEX_DEFINE, REGEX_DSC_SECTION, REGEX_INCLUDE as REGEX_INCLUDE, REGEX_LIBRARY_PATH, REGEX_MODULE_PATH, REGEX_PCD_LINE, REGEX_VAR_USAGE } from "../edkParser/commonParser"; @@ -476,6 +476,7 @@ export class EdkWorkspace { await this.findDefinesFdf(); this.processComplete = true; + edkWorkspaceTreeProvider.refresh(); return true; }finally{ edkStatusBar.popText(); diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 4dc7724..83e9574 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -19,6 +19,18 @@ export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; descriptio { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' }, ]; +// Structural / container types that are always visible in the tree regardless of +// the user's filter selection. These types exist only to group children and +// filtering them out would hide all their descendants. +const STRUCTURAL_TYPES = new Set([ + Edk2SymbolType.dscComponentSubSection, +]); + +/** Returns true when a symbol type should be shown in the tree. */ +function isTypeVisible(type: Edk2SymbolType, activeFilters: Set): boolean { + return activeFilters.has(type) || STRUCTURAL_TYPES.has(type); +} + // ─── Helper: load symbols for a URI via the parser ─────────────────────────── async function loadSymbols(uri: vscode.Uri): Promise { @@ -133,7 +145,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { public readonly parent: WorkspaceRootItem | DocumentSymbolItem | undefined ) { const visibleChildren = symbol.children.filter( - c => activeFilters.has((c as EdkSymbol).type) + c => isTypeVisible((c as EdkSymbol).type, activeFilters) ); const isDscInclude = symbol.type === Edk2SymbolType.dscInclude; super( @@ -181,7 +193,7 @@ async function serializeSymbol(symbol: EdkSymbol, fileUri: vscode.Uri, indent: s let out = line; for (const child of symbol.children) { const edkChild = child as EdkSymbol; - if (filter.has(edkChild.type)) { + if (isTypeVisible(edkChild.type, filter)) { out += await serializeSymbol(edkChild, fileUri, indent + ' ', filter); } } @@ -193,7 +205,7 @@ async function serializeIncludeNode(node: IncludeNode, indent: string, filter: S let out = `${indent}!include ${rel}\n`; const symbols = await loadSymbols(node.uri); for (const sym of symbols) { - if (filter.has(sym.type)) { + if (isTypeVisible(sym.type, filter)) { out += await serializeSymbol(sym, node.uri, indent + ' ', filter); } } @@ -212,7 +224,7 @@ function findDeepestSymbolAt( ): EdkSymbol | undefined { let best: EdkSymbol | undefined; for (const sym of symbols) { - if (!filter.has(sym.type)) { continue; } + if (!isTypeVisible(sym.type, filter)) { continue; } const inRange = sym.range.contains(position) || sym.selectionRange.contains(position); if (inRange) { best = sym; @@ -224,7 +236,7 @@ function findDeepestSymbolAt( if (!best) { let closestDist = Infinity; for (const sym of symbols) { - if (!filter.has(sym.type)) { continue; } + if (!isTypeVisible(sym.type, filter)) { continue; } const dist = Math.abs(sym.selectionRange.start.line - position.line); if (dist < closestDist) { closestDist = dist; @@ -398,7 +410,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider this._activeFilters.has(s.type)) + .filter(s => isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element)); } @@ -437,7 +449,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider this._activeFilters.has(s.type)) + .filter(s => isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined)); } @@ -451,13 +463,13 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider this._activeFilters.has(s.type)) + .filter(s => isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element)); } } } return (element.symbol.children as EdkSymbol[]) - .filter(c => this._activeFilters.has(c.type)) + .filter(c => isTypeVisible(c.type, this._activeFilters)) .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element)); } From b3dac188968db5c12c602413bec60f32828aa583 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Mon, 9 Mar 2026 12:07:15 -0500 Subject: [PATCH 34/73] Refactor grayout controller --- src/Languages/symbolProvider.ts | 2 +- src/extension.ts | 4 +- src/grayout.ts | 121 ++++++++++++++++++++++++++++++-- src/index/edkWorkspace.ts | 35 ++------- 4 files changed, 125 insertions(+), 37 deletions(-) diff --git a/src/Languages/symbolProvider.ts b/src/Languages/symbolProvider.ts index a463fa5..e150fa7 100644 --- a/src/Languages/symbolProvider.ts +++ b/src/Languages/symbolProvider.ts @@ -4,7 +4,7 @@ import { getStaticPath, itsPcdSelected } from '../utils'; import path = require('path'); import { CompletionItemKind } from 'vscode'; import { ParserFactory } from '../edkParser/parserFactory'; -import { gConfigAgent, gEdkWorkspaces, gGrayOutController } from '../extension'; +import { gConfigAgent, gEdkWorkspaces } from '../extension'; import { Debouncer } from '../debouncer'; import { DiagnosticManager } from '../diagnostics'; diff --git a/src/extension.ts b/src/extension.ts index 88a3af5..022573d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,7 +9,7 @@ import { DebugLog } from './debugLog'; import * as edkStatusBar from './statusBar'; import { FileUseWarning } from './usedFileTracker'; import * as cmds from "./contextState/cmds"; -import { GrayoutController } from './grayout'; + import { initLanguages } from './Languages/languages'; import { ModuleReport } from './moduleReport'; import { GuidProvider } from './Languages/guidProvider'; @@ -44,7 +44,7 @@ var gEdk2CallHierarchyProvider: Edk2CallHierarchyProvider; export var gDebugLog: DebugLog; export var gFileUseWarning: FileUseWarning; -export var gGrayOutController: GrayoutController; + export var gModuleReport: ModuleReport; export var gGuidProvider:GuidProvider; export var gDiagnosticManager:DiagnosticManager; diff --git a/src/grayout.ts b/src/grayout.ts index 031f339..de08244 100644 --- a/src/grayout.ts +++ b/src/grayout.ts @@ -1,8 +1,121 @@ import * as vscode from 'vscode'; -import { getCurrentDocument } from './utils'; import { gDebugLog } from './extension'; +/** + * Manages grayout decorations for inactive code regions across all documents. + * + * Uses a single shared TextEditorDecorationType and a single set of event + * listeners instead of per-file controllers. This avoids: + * - Creating/disposing decoration types on every tab switch (main perf issue) + * - N event listeners for N files + * - Reference equality checks on TextDocument that silently fail + */ +export class GrayoutManager { + /** Single shared decoration type β€” created once, reused for all files */ + private decoration: vscode.TextEditorDecorationType; + + /** Map from fsPath -> grayout ranges for that file */ + private rangeMap: Map = new Map(); + + private disposables: vscode.Disposable[] = []; + + constructor() { + this.decoration = vscode.window.createTextEditorDecorationType({ + isWholeLine: true, + light: { opacity: "0.3" }, + dark: { opacity: "0.3" }, + }); + + // Single listener: re-apply decorations when the active editor changes + this.disposables.push( + vscode.window.onDidChangeActiveTextEditor((editor) => { + if (editor) { + this.applyToEditor(editor); + } + }) + ); + + // Handle split editors and editor reuse + this.disposables.push( + vscode.window.onDidChangeVisibleTextEditors((editors) => { + for (const editor of editors) { + this.applyToEditor(editor); + } + }) + ); + } + + /** + * Store grayout ranges for a document and immediately apply decorations + * to all visible editors showing that document. + */ + setRanges(uri: vscode.Uri, ranges: vscode.Range[]) { + gDebugLog.trace(`GrayoutManager.setRanges(): ${uri.fsPath} (${ranges.length} ranges)`); + this.rangeMap.set(uri.fsPath, ranges); + this.applyToUri(uri.fsPath); + } + + /** + * Remove grayout ranges for a document and clear its decorations. + */ + clearRanges(uri: vscode.Uri) { + gDebugLog.trace(`GrayoutManager.clearRanges(): ${uri.fsPath}`); + this.rangeMap.delete(uri.fsPath); + this.applyToUri(uri.fsPath); + } + + /** + * Remove all grayout ranges and clear decorations from all visible editors. + */ + clearAll() { + gDebugLog.trace(`GrayoutManager.clearAll()`); + this.rangeMap.clear(); + // Clear decorations on all visible editors + for (const editor of vscode.window.visibleTextEditors) { + editor.setDecorations(this.decoration, []); + } + } + + /** + * Apply stored decorations to a specific editor. + */ + private applyToEditor(editor: vscode.TextEditor) { + const fsPath = editor.document.uri.fsPath; + const ranges = this.rangeMap.get(fsPath); + if (ranges && ranges.length > 0) { + gDebugLog.trace(`GrayoutManager: Applying ${ranges.length} ranges to ${fsPath}`); + editor.setDecorations(this.decoration, ranges); + } else { + // Clear any stale decorations for files not in the map + editor.setDecorations(this.decoration, []); + } + } + + /** + * Apply decorations to all visible editors showing the given file. + */ + private applyToUri(fsPath: string) { + for (const editor of vscode.window.visibleTextEditors) { + if (editor.document.uri.fsPath === fsPath) { + this.applyToEditor(editor); + } + } + } + + dispose() { + this.decoration.dispose(); + for (const d of this.disposables) { + d.dispose(); + } + this.disposables = []; + this.rangeMap.clear(); + } +} + +/** + * @deprecated Use GrayoutManager instead. Kept temporarily for backward compatibility. + */ export class GrayoutController { decoration:vscode.TextEditorDecorationType|undefined; @@ -15,7 +128,6 @@ export class GrayoutController { this.document = document; this.range = range; - // let subscriptions: vscode.Disposable[] = []; this.changeEvent = vscode.window.onDidChangeActiveTextEditor(()=>{ if(vscode.window.activeTextEditor?.document.uri.fsPath === this.document.uri.fsPath){ this.doGrayOut(); @@ -30,7 +142,7 @@ export class GrayoutController { let activeEditor = vscode.window.activeTextEditor; if(!activeEditor){return;} - if(activeEditor.document !== this.document){return;} + if(activeEditor.document.uri.fsPath !== this.document.uri.fsPath){return;} gDebugLog.trace(`Unused Ranges: ${JSON.stringify(unusdedRanges)}`); @@ -48,9 +160,6 @@ export class GrayoutController { this.decoration = decoration; - - - // Block content const blockDecorationOptions: vscode.DecorationOptions[] = []; for (const targetRange of unusdedRanges) { const decoration = { range: targetRange}; diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 9e1efe3..49772af 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { edkWorkspaceTreeProvider, gCompileCommands, gConfigAgent, gDebugLog, gMapFileManager, gPathFind, gWorkspacePath } from '../extension'; -import { GrayoutController } from '../grayout'; +import { GrayoutManager } from '../grayout'; import { createRange, openTextDocument, pathCompare, split } from '../utils'; import { REGEX_DEFINE as REGEX_DEFINE, REGEX_DSC_SECTION, REGEX_INCLUDE as REGEX_INCLUDE, REGEX_LIBRARY_PATH, REGEX_MODULE_PATH, REGEX_PCD_LINE, REGEX_VAR_USAGE } from "../edkParser/commonParser"; import { UNDEFINED_VARIABLE, WorkspaceDefinitions } from "./definitions"; @@ -369,7 +369,7 @@ export class EdkWorkspace { this._filesFdf = value; } - private _grayoutControllers:GrayoutController[] = []; + private _grayoutManager: GrayoutManager = new GrayoutManager(); private _includeTree: IncludeNode[] = []; public get includeTree(): IncludeNode[] { @@ -377,18 +377,7 @@ export class EdkWorkspace { } public updateGrayoutRange(document: vscode.TextDocument, range: vscode.Range[]){ - for (const grayoutController of this._grayoutControllers) { - - if(grayoutController.document.uri.fsPath === document.uri.fsPath){ - grayoutController.range = range; - grayoutController.doGrayOut(); - return; - } - } - const dscGrayoutController = new GrayoutController(document, range); - dscGrayoutController.doGrayOut(); - this._grayoutControllers.push(dscGrayoutController); - + this._grayoutManager.setRanges(document.uri, range); } public dscList(){ @@ -437,10 +426,7 @@ export class EdkWorkspace { this.filesLibraries = []; this.filesModules = []; this.filesDsc = new Set(); - for (const ctrl of this._grayoutControllers) { - ctrl.dispose(); - } - this._grayoutControllers = []; + this._grayoutManager.clearAll(); this.libraryTypeTrack = new Map(); @@ -815,9 +801,8 @@ export class EdkWorkspace { doucumentGrayoutRange.push(new vscode.Range(new vscode.Position(unuseRangeStart, 0), new vscode.Position(lineIndexEnd, 0))); } + this.parsedDocuments.set(document.uri.fsPath, doucumentGrayoutRange); this.updateGrayoutRange(document, doucumentGrayoutRange); - - } @@ -971,12 +956,7 @@ export class EdkWorkspace { } isDocumentInIndex(document: vscode.TextDocument): boolean { - for (const doc of this.parsedDocuments.keys()) { - if(doc === document.fileName) { - return true; - } - } - return false; + return this.parsedDocuments.has(document.uri.fsPath); } getGrayoutRange(document: vscode.TextDocument): vscode.Range[] { @@ -1016,8 +996,7 @@ export class EdkWorkspace { // check if document is in index documents if (this.isDocumentInIndex(document)) { let grayoutRange = this.getGrayoutRange(document); - let grayoutController = new GrayoutController(document, grayoutRange); - grayoutController.doGrayOut(); + this._grayoutManager.setRanges(document.uri, grayoutRange); return; } } From e4cd9194d0459d87d7735dcdff06143243a07fdc Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Mon, 9 Mar 2026 14:16:52 -0500 Subject: [PATCH 35/73] Use just name for findfiles --- src/edkParser/commonParser.ts | 4 ++-- src/index/edkWorkspace.ts | 9 ++++++--- src/pathfind.ts | 27 ++++++++++++++++++++------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/edkParser/commonParser.ts b/src/edkParser/commonParser.ts index 5ecccd3..b4cbeb7 100644 --- a/src/edkParser/commonParser.ts +++ b/src/edkParser/commonParser.ts @@ -1,6 +1,6 @@ -export const REGEX_DEFINE = /^\s*(?:DEFINE\s+\w+|(?:DSC_SPECIFICATION|PLATFORM_GUID|PLATFORM_VERSION|PLATFORM_NAME|SKUID_IDENTIFIER|SUPPORTED_ARCHITECTURES|BUILD_TARGETS|OUTPUT_DIRECTORY|FLASH_DEFINITION|BUILD_NUMBER|FIX_LOAD_TOP_MEMORY_ADDRESS|TIME_STAMP_FILE|RFC_LANGUAGES|ISO_LANGUAGES|VPD_TOOL_GUID|PCD_INFO_GENERATION|PCD_VAR_CHECK_GENERATION|PREBUILD|POSTBUILD))\s*=.*/gi; +export const REGEX_DEFINE = /^\s*(?:DEFINE\s+\w+|)\s*=.*/gi; export const REGEX_INCLUDE = /\s*\!include/gi; - +export const REGEX_EQUAL = /\s*\w+\s*=\s*?.*/gi; export const REGEX_PATH_FILE = /(?<=^.*?\|\s*)[a-zA-Z\/\\0-9\._]+\.([a-z\d])/gi; export const REGEX_PATH_FOLDER = /^\s*[a-zA-Z\/\\0-9\._]+/gi; diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 49772af..8274ec2 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -3,7 +3,7 @@ import * as vscode from 'vscode'; import { edkWorkspaceTreeProvider, gCompileCommands, gConfigAgent, gDebugLog, gMapFileManager, gPathFind, gWorkspacePath } from '../extension'; import { GrayoutManager } from '../grayout'; import { createRange, openTextDocument, pathCompare, split } from '../utils'; -import { REGEX_DEFINE as REGEX_DEFINE, REGEX_DSC_SECTION, REGEX_INCLUDE as REGEX_INCLUDE, REGEX_LIBRARY_PATH, REGEX_MODULE_PATH, REGEX_PCD_LINE, REGEX_VAR_USAGE } from "../edkParser/commonParser"; +import { REGEX_DEFINE, REGEX_DSC_SECTION, REGEX_EQUAL, REGEX_INCLUDE as REGEX_INCLUDE, REGEX_LIBRARY_PATH, REGEX_MODULE_PATH, REGEX_PCD_LINE, REGEX_VAR_USAGE } from "../edkParser/commonParser"; import { UNDEFINED_VARIABLE, WorkspaceDefinitions } from "./definitions"; import * as fs from 'fs'; import path = require('path'); @@ -657,14 +657,17 @@ export class EdkWorkspace { } // Defines - if (line.match(REGEX_DEFINE)) { + if (line.match(REGEX_DEFINE) || + (this.sectionsStack.length > 0 && + this.sectionsStack[this.sectionsStack.length - 1].toLowerCase() === "defines" && + line.match(REGEX_EQUAL))) { let key = line.replace(/define/gi, "").trim(); key = split(key, "=", 2)[0].trim(); let value = split(line, "=", 2)[1].trim(); let originalValue = split(originalLine, "=", 2)[1].trim(); if (value.includes(`$(${key})`) || originalValue.includes(`$(${key})`)) { - gDebugLog.info(`Circular define: ${key}: ${value}`); + gDebugLog.trace(`Circular define: ${key}: ${value}`); } else { // Warn if DEFINE is being redefined without referencing itself let defines = type === 'DSC' ? this.defines : this.definesFdf; diff --git a/src/pathfind.ts b/src/pathfind.ts index 851bdd0..529c3b6 100644 --- a/src/pathfind.ts +++ b/src/pathfind.ts @@ -2,7 +2,6 @@ import path = require("path"); import * as fs from 'fs'; import * as vscode from 'vscode'; import glob = require("fast-glob"); -import { getRealPathRelative } from "./utils"; import { gConfigAgent, gDebugLog, gWorkspacePath } from "./extension"; import { REGEX_VAR_USAGE } from "./edkParser/commonParser"; @@ -95,15 +94,29 @@ export class PathFind{ gDebugLog.warning(`Global find: ${pathArg}`); + let normalizedArg = pathArg.replaceAll('\\', '/').replaceAll(REGEX_VAR_USAGE, '**'); + // Search by filename only (like Ctrl+P), then filter by path suffix + let fileName = path.basename(pathArg); + let paths = await vscode.workspace.findFiles(`**/${fileName}`, null); + + // Filter results that match the full path pattern + let filteredPaths = paths.filter(p => { + let normalizedFsPath = p.fsPath.replaceAll('\\', '/'); + // Build a regex from the normalized path arg, replacing ** with .* + let patternStr = normalizedArg.replaceAll('**', '.*').replace(/[.*+?^${}()|[\]\\]/g, (m) => m === '.*' ? '.*' : '\\' + m); + // Re-apply .* for the glob wildcards after escaping + patternStr = normalizedArg.split('**').map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*'); + return new RegExp(patternStr + '$', 'i').test(normalizedFsPath); + }); + + // If no filtered results but we had unfiltered results, use filename matches as fallback + if(filteredPaths.length === 0 && paths.length > 0){ + filteredPaths = paths; + } - let globPath = pathArg.replaceAll('/', '\\').replaceAll(REGEX_VAR_USAGE, '**'); - let paths = await vscode.workspace.findFiles(`**\\${globPath}`); let retPath = []; - for (const p of paths) { + for (const p of filteredPaths) { retPath.push(new vscode.Location(vscode.Uri.file(p.fsPath), new vscode.Position(0, 0))); - // Add dinamyc include paths - let newPath = p.fsPath.slice(0,p.fsPath.length - pathArg.length - 1); - gConfigAgent.pushBuildPackagePaths(getRealPathRelative(newPath)); } if(retPath.length === 0){ From 5193be861f60468213fc1fa2fdc5257edc790774 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Mon, 9 Mar 2026 14:35:09 -0500 Subject: [PATCH 36/73] Adding folders to missing paths instead of file names --- src/pathfind.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/pathfind.ts b/src/pathfind.ts index 529c3b6..2ce6103 100644 --- a/src/pathfind.ts +++ b/src/pathfind.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import glob = require("fast-glob"); import { gConfigAgent, gDebugLog, gWorkspacePath } from "./extension"; import { REGEX_VAR_USAGE } from "./edkParser/commonParser"; +import { toPosix } from "./utils"; export class PathFind{ @@ -36,7 +37,7 @@ export class PathFind{ ]; - private missingFiles: string[] = []; + private missingPaths: string[] = []; async findPath(pathArg: string, relativePath: string|undefined = "") { pathArg = pathArg.replaceAll(/(\\+|\/+)/gi, path.sep); @@ -56,8 +57,7 @@ export class PathFind{ return []; } - if(this.missingFiles.includes(pathArg)){ - //Todo: Add job to look for the file + if(this.isKnownMissing(pathArg)){ return []; } @@ -121,7 +121,7 @@ export class PathFind{ if(retPath.length === 0){ gDebugLog.warning(`Missing file: ${pathArg}`); - this.missingFiles.push(path.join(relativePath, pathArg)); + this.addMissingPath(pathArg); } return retPath; @@ -140,6 +140,62 @@ export class PathFind{ } } + /** + * Check if pathArg (or any of its parent segments) is already known to be missing. + */ + private isKnownMissing(pathArg: string): boolean { + const normalized = toPosix(pathArg); + return this.missingPaths.some(missing => { + return normalized === missing || normalized.startsWith(missing + '/'); + }); + } + + /** + * Walk up parent directories of pathArg and find the highest-level + * directory that doesn't exist in the workspace or package paths. + * Cache that root so all future lookups under it are skipped. + */ + private addMissingPath(pathArg: string): void { + // Don't add if already covered by an existing missing path + if (this.isKnownMissing(pathArg)) { + return; + } + + const posixPath = toPosix(pathArg); + const parts = posixPath.split('/'); + let missingRoot = posixPath; + + // Walk from the top-level segment downward; find the first parent that + // does NOT exist anywhere, and cache that instead of the full path. + for (let i = 1; i < parts.length; i++) { + const parentPath = parts.slice(0, i).join(path.sep); + if (!this.parentExistsInWorkspace(parentPath)) { + missingRoot = parentPath; + break; + } + } + + gDebugLog.trace(`Caching missing path: ${missingRoot}`); + this.missingPaths.push(toPosix(missingRoot)); + } + + /** + * Check whether a relative directory exists under the workspace root + * or any of the configured package paths. + */ + private parentExistsInWorkspace(parentPath: string): boolean { + if (fs.existsSync(path.join(gWorkspacePath, parentPath))) { + return true; + } + const packagePaths = gConfigAgent.getBuildPackagePaths(); + for (const relPath of packagePaths) { + if (fs.existsSync(path.join(relPath, parentPath))) { + return true; + } + } + return false; + } + produceLocation(path:string){ return [new vscode.Location(vscode.Uri.file(path), new vscode.Position(0, 0))]; } From fcf874b09cc76db9b372e83c38384320075faeea Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Mon, 9 Mar 2026 14:41:59 -0500 Subject: [PATCH 37/73] Adding loading indication for tree --- src/index/edkWorkspace.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 8274ec2..d3cfd87 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; -import { edkWorkspaceTreeProvider, gCompileCommands, gConfigAgent, gDebugLog, gMapFileManager, gPathFind, gWorkspacePath } from '../extension'; +import { edkWorkspaceTreeProvider, edkWorkspaceTreeView, gCompileCommands, gConfigAgent, gDebugLog, gMapFileManager, gPathFind, gWorkspacePath } from '../extension'; import { GrayoutManager } from '../grayout'; import { createRange, openTextDocument, pathCompare, split } from '../utils'; import { REGEX_DEFINE, REGEX_DSC_SECTION, REGEX_EQUAL, REGEX_INCLUDE as REGEX_INCLUDE, REGEX_LIBRARY_PATH, REGEX_MODULE_PATH, REGEX_PCD_LINE, REGEX_VAR_USAGE } from "../edkParser/commonParser"; @@ -404,6 +404,15 @@ export class EdkWorkspace { async proccessWorkspace() { + return vscode.window.withProgress( + { location: { viewId: 'workspaceView' } }, + async () => { + return this._doProccessWorkspace(); + } + ); + } + + private async _doProccessWorkspace() { try{ if(this.platformName !== undefined){ edkStatusBar.pushText(`Parsing ${this.platformName}`); From 5aeeeffb77632330f9d2bff3119a1698c2faa7a0 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Mon, 9 Mar 2026 15:01:32 -0500 Subject: [PATCH 38/73] Setting to disable cscope --- package.json | 6 ++ src/configuration.ts | 64 ++++++++++++++++ src/cscope.ts | 11 +++ src/extension.ts | 2 +- src/index/edkWorkspace.ts | 9 +++ src/pathfind.ts | 19 ++++- src/ternarySearchTree.ts | 155 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/ternarySearchTree.ts diff --git a/package.json b/package.json index 847aa30..9111a99 100644 --- a/package.json +++ b/package.json @@ -215,6 +215,12 @@ "markdownDescription": "Expand circular include or duplicated libraries in the Edk2 Map view", "default": false }, + "edk2code.useCscope": { + "order": 2, + "type": "boolean", + "markdownDescription": "Enable or disable cscope integration. When disabled, cscope database will not be built or queried, and features like call hierarchy and cscope-based go-to-definition will be unavailable. `(reload of vscode after setup)`", + "default": true + }, "edk2code.cscopeOverwritePath": { "order": 2, "type": "string", diff --git a/src/configuration.ts b/src/configuration.ts index caabc6e..2d87a25 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -7,6 +7,7 @@ import { askReloadFiles } from './ui/messages'; import { readFile } from './utils'; import { SettingsPanel } from './settings/settingsPanel'; import { getEdkCodeFolderFilePath, existsEdkCodeFolderFile, writeEdkCodeFolderFile } from './edk2CodeFolder'; +import { TernarySearchTree } from './ternarySearchTree'; export interface WorkspaceConfig { @@ -40,6 +41,21 @@ export class ConfigAgent { private workspaceConfig:WorkspaceConfig; private settingsFileName: string = "edk2_workspace_properties.json"; + /** + * Temporary T-tree file index built when the workspace is being processed. + * Used by PathFind as a fast lookup while packagePaths are not yet populated. + * Set to `null` when the processing is finished. + */ + private _fileIndex: TernarySearchTree | null = null; + + /** + * Tracks whether the last workspace processing completed successfully. + * When `false`, the next `loadConfig` / `proccessWorkspace` will build + * a temporary T-tree so PathFind can resolve files without relying on + * the (still-empty) packagePaths. + */ + private _workspaceProcessComplete: boolean = false; + public constructor() { this.vscodeSettings = vscode.workspace.getConfiguration('edk2code'); this.workspaceConfig = this.readWpConfig(); @@ -105,6 +121,50 @@ export class ConfigAgent { clearWpConfiguration(){ this.workspaceConfig = this.getCleanWpConfig(); + // Mark workspace as not-yet-processed so the next loadConfig + // will build a temporary T-tree for fast file lookups. + this._workspaceProcessComplete = false; + } + + /** + * Returns `true` if the previous workspace processing completed + * successfully (i.e. packagePaths are fully populated). + */ + isWorkspaceProcessComplete(): boolean { + return this._workspaceProcessComplete; + } + + /** + * Mark the workspace processing as complete and dispose of the + * temporary file index so that normal `findFiles` is used again. + */ + setWorkspaceProcessComplete(): void { + this._workspaceProcessComplete = true; + if (this._fileIndex) { + this._fileIndex.dispose(); + this._fileIndex = null; + } + } + + /** + * Build the temporary T-tree file index if the workspace has not + * yet been fully processed (packagePaths not populated). + * This should be called at the beginning of workspace processing. + */ + async buildFileIndexIfNeeded(): Promise { + if (!this._workspaceProcessComplete) { + this._fileIndex = new TernarySearchTree(); + await this._fileIndex.buildFromWorkspace(); + } + } + + /** + * Returns the temporary file index if one is active, or `null` + * when the workspace is fully processed and findFiles should be + * used instead. + */ + getFileIndex(): TernarySearchTree | null { + return this._fileIndex; } initConfigWatcher(){ @@ -291,6 +351,10 @@ export class ConfigAgent { return this.get("extraIgnorePatterns"); } + getUseCscope() { + return this.get("useCscope"); + } + getCscopeOverwritePath() { return (this.get("cscopeOverwritePath")).trim(); } diff --git a/src/cscope.ts b/src/cscope.ts index a0e1b04..a42f6ef 100644 --- a/src/cscope.ts +++ b/src/cscope.ts @@ -132,6 +132,7 @@ export class Cscope { } writeCscopeFile(fileList:string[]){ + if(!gConfigAgent.getUseCscope()){ return; } if(fileList.length === 0){ return; } @@ -164,6 +165,10 @@ export class Cscope { async reload(progressWindow=false){ gDebugLog.info("CSCOPE reload database"); + if(!gConfigAgent.getUseCscope()){ + gDebugLog.info("CSCOPE is disabled by setting"); + return; + } if(!this.cscopeInstalled){ this.showCscopeErrorMessage(); return; @@ -182,17 +187,20 @@ export class Cscope { } async getCaller(text:string){ + if(!gConfigAgent.getUseCscope()){ return []; } let result = await this.cscopeCommandWindow(text, CscopeCmd.findCallers, "Looking callers"); let temp = this.parseResult(result, text); return temp; } async getCallee(text:string){ + if(!gConfigAgent.getUseCscope()){ return []; } let result = await this.cscopeCommandWindow(text, CscopeCmd.findCallee, "Looking callees"); return this.parseResult(result, text); } async search(text:string){ + if(!gConfigAgent.getUseCscope()){ return []; } let result = await this.cscopeCommandWindow(text, CscopeCmd.findEgrep, "Searching"); let searchResult = this.parseResult(result, text); @@ -209,6 +217,7 @@ export class Cscope { } async getDefinitionPositions(text:string, showWindows:boolean=true){ + if(!gConfigAgent.getUseCscope()){ return []; } let windDescription = "Looking for definition"; if(!showWindows){ @@ -295,6 +304,7 @@ export class CscopeAgent { } async writeCscopeFile(fileList:string[]){ + if(!gConfigAgent.getUseCscope()){ return; } if(fileList.length === 0){ return; } @@ -328,6 +338,7 @@ export class CscopeAgent { /** * Updates Cscope database based on cscope.files elements */ + if(!gConfigAgent.getUseCscope()){ return; } const debouncer = Debouncer.getInstance(); debouncer.debounce("updateCscopeDb", async () => { let cscopeFilesPath = getEdkCodeFolderFilePath("cscope.files"); diff --git a/src/extension.ts b/src/extension.ts index 022573d..3694c65 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -299,7 +299,7 @@ export async function activate(context: vscode.ExtensionContext) { gCscope = new Cscope(); gCscopeAgent = new CscopeAgent(); - if(gCscope.existCscopeFile()){ + if(gConfigAgent.getUseCscope() && gCscope.existCscopeFile()){ void gCscope.reload().then(()=>{ if(gConfigAgent.getUseEdkCallHiearchy()){ gEdk2CallHierarchyProvider = new Edk2CallHierarchyProvider(); diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index d3cfd87..efe45e3 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -296,6 +296,12 @@ async getWorkspace(uri: vscode.Uri): Promise { gDebugLog.trace("Loading Configuration"); // TODO: enable to get more commands available //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', false); + + // If the previous workspace processing did not complete (e.g. + // after a clear), build a temporary T-tree so PathFind can + // resolve files without relying on the empty packagePaths. + await gConfigAgent.buildFileIndexIfNeeded(); + let dscPaths = gConfigAgent.getBuildDscPaths(); gDebugLog.trace(`dscPaths = ${dscPaths}`); for (const dscPath of dscPaths) { @@ -307,6 +313,9 @@ async getWorkspace(uri: vscode.Uri): Promise { } gMapFileManager.load(); gCompileCommands.load(); + // Workspace processing is complete – mark flag and dispose + // of the temporary T-tree so PathFind reverts to findFiles. + gConfigAgent.setWorkspaceProcessComplete(); //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', true); } } diff --git a/src/pathfind.ts b/src/pathfind.ts index 2ce6103..7add752 100644 --- a/src/pathfind.ts +++ b/src/pathfind.ts @@ -2,9 +2,10 @@ import path = require("path"); import * as fs from 'fs'; import * as vscode from 'vscode'; import glob = require("fast-glob"); + import { gConfigAgent, gDebugLog, gWorkspacePath } from "./extension"; import { REGEX_VAR_USAGE } from "./edkParser/commonParser"; -import { toPosix } from "./utils"; +import { toPosix, getRealPathRelative } from "./utils"; export class PathFind{ @@ -97,7 +98,17 @@ export class PathFind{ let normalizedArg = pathArg.replaceAll('\\', '/').replaceAll(REGEX_VAR_USAGE, '**'); // Search by filename only (like Ctrl+P), then filter by path suffix let fileName = path.basename(pathArg); - let paths = await vscode.workspace.findFiles(`**/${fileName}`, null); + + // If a temporary T-tree index exists (workspace is being rebuilt + // after a clear) use it for an instant lookup; otherwise fall + // back to the normal vscode.workspace.findFiles API. + const fileIndex = gConfigAgent.getFileIndex(); + let paths: vscode.Uri[]; + if (fileIndex) { + paths = fileIndex.findFilesByName(fileName); + } else { + paths = await vscode.workspace.findFiles(`**/${fileName}`, null); + } // Filter results that match the full path pattern let filteredPaths = paths.filter(p => { @@ -117,6 +128,10 @@ export class PathFind{ let retPath = []; for (const p of filteredPaths) { retPath.push(new vscode.Location(vscode.Uri.file(p.fsPath), new vscode.Position(0, 0))); + + // Add dinamyc include paths + let newPath = p.fsPath.slice(0,p.fsPath.length - pathArg.length - 1); + gConfigAgent.pushBuildPackagePaths(getRealPathRelative(newPath)); } if(retPath.length === 0){ diff --git a/src/ternarySearchTree.ts b/src/ternarySearchTree.ts new file mode 100644 index 0000000..def3846 --- /dev/null +++ b/src/ternarySearchTree.ts @@ -0,0 +1,155 @@ +import * as vscode from 'vscode'; +import { gDebugLog, gWorkspacePath } from './extension'; + +/** + * A node in the Ternary Search Tree. + * Each node stores a single character and branches into three children: + * left – characters less than this node's character + * mid – next character in the same key + * right – characters greater than this node's character + * + * When `isEnd` is true the path from the root through mid-pointers + * spells out a complete key, and `values` holds the associated URIs. + */ +interface TSTNode { + char: string; + left: TSTNode | null; + mid: TSTNode | null; + right: TSTNode | null; + isEnd: boolean; + values: vscode.Uri[]; +} + +/** + * Ternary Search Tree used as a temporary in-memory file index. + * + * Built when the workspace is cleared and being rebuilt so that + * `PathFind.findPath` can resolve file names without waiting for + * `vscode.workspace.findFiles` (which may be slow / stale during + * a rebuild). + * + * Keys are **lower-cased file basenames** so look-ups are + * case-insensitive. Each key maps to one or more `vscode.Uri`s. + */ +export class TernarySearchTree { + private root: TSTNode | null = null; + private _size: number = 0; + + /** Number of unique keys stored in the tree. */ + get size(): number { + return this._size; + } + + // ── Insertion ──────────────────────────────────────────────── + + /** + * Insert a key β†’ value pair into the tree. + * If the key already exists the value is appended to its list. + */ + insert(key: string, value: vscode.Uri): void { + if (key.length === 0) { return; } + this.root = this._insert(this.root, key, value, 0); + } + + private _insert(node: TSTNode | null, key: string, value: vscode.Uri, idx: number): TSTNode { + const ch = key[idx]; + + if (node === null) { + node = { char: ch, left: null, mid: null, right: null, isEnd: false, values: [] }; + } + + if (ch < node.char) { + node.left = this._insert(node.left, key, value, idx); + } else if (ch > node.char) { + node.right = this._insert(node.right, key, value, idx); + } else if (idx < key.length - 1) { + node.mid = this._insert(node.mid, key, value, idx + 1); + } else { + if (!node.isEnd) { + node.isEnd = true; + this._size++; + } + node.values.push(value); + } + + return node; + } + + // ── Exact-match search ─────────────────────────────────────── + + /** + * Return all URIs associated with `key`, or an empty array if not found. + */ + search(key: string): vscode.Uri[] { + if (key.length === 0) { return []; } + const node = this._search(this.root, key, 0); + if (node && node.isEnd) { + return node.values; + } + return []; + } + + private _search(node: TSTNode | null, key: string, idx: number): TSTNode | null { + if (node === null) { return null; } + + const ch = key[idx]; + + if (ch < node.char) { + return this._search(node.left, key, idx); + } else if (ch > node.char) { + return this._search(node.right, key, idx); + } else if (idx < key.length - 1) { + return this._search(node.mid, key, idx + 1); + } else { + return node; + } + } + + // ── Bulk build ─────────────────────────────────────────────── + + /** + * Scan *all* files in the workspace and index them by their + * lower-cased basename. Returns the number of files indexed. + */ + async buildFromWorkspace(): Promise { + const startTime = Date.now(); + gDebugLog.info('TernarySearchTree: building file index from workspace…'); + + // findFiles with ** grabs every file in the workspace + const allFiles = await vscode.workspace.findFiles('**/*', null); + + for (const uri of allFiles) { + const baseName = this.baseName(uri); + this.insert(baseName, uri); + } + + const elapsed = Date.now() - startTime; + gDebugLog.info(`TernarySearchTree: indexed ${allFiles.length} files (${this._size} unique names) in ${elapsed} ms`); + return allFiles.length; + } + + // ── Helpers ────────────────────────────────────────────────── + + /** + * Return the lower-cased basename of a URI (case-insensitive keys). + */ + private baseName(uri: vscode.Uri): string { + const segments = uri.fsPath.split(/[\\/]/); + return (segments[segments.length - 1] || '').toLowerCase(); + } + + /** + * Look up a file by its basename string (case-insensitive). + */ + findFilesByName(fileName: string): vscode.Uri[] { + return this.search(fileName.toLowerCase()); + } + + /** + * Clear the tree and release memory. + */ + dispose(): void { + this.root = null; + this._size = 0; + } +} From 70d0686cde4d36384e975b35c9da216f0163222d Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Mon, 9 Mar 2026 17:04:21 -0500 Subject: [PATCH 39/73] Added name and description to tree nodes --- src/symbols/dscSymbols.ts | 11 ++++++++++- src/symbols/edkSymbols.ts | 10 ++++++++++ src/workspaceTree/WorkspaceTreeProvider.ts | 5 ++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/symbols/dscSymbols.ts b/src/symbols/dscSymbols.ts index 040ac06..852497d 100644 --- a/src/symbols/dscSymbols.ts +++ b/src/symbols/dscSymbols.ts @@ -30,6 +30,12 @@ export class EdkSymbolDscDefine extends EdkSymbol{ onHover: undefined; onDeclaration: undefined; + + protected get nameRegex(): RegExp { return /define\s+(\w+).*=.*/i; } + protected get descriptionRegex(): RegExp { return /define\s+\w+\s*=\s*(.*)/i; } + + + async getKey() { let key = this.textLine.replace(/define/gi, "").trim(); key = split(key, "=", 2)[0].trim(); @@ -49,6 +55,9 @@ export class EdkSymbolDscLibraryDefinition extends EdkSymbol{ type = Edk2SymbolType.dscLibraryDefinition; kind = vscode.SymbolKind.Field; + protected get descriptionRegex(): RegExp { return /.*\|(.*)/i; } + protected get nameRegex(): RegExp { return /(.*)\|.*/i; } + onCompletion: undefined; onDefinition = async (parser:DocumentParser)=>{ let path = this.textLine.replace(/.*?\|\s*/gi, ""); @@ -133,7 +142,7 @@ export class EdkSymbolDscModuleDefinition extends EdkSymbol{ type = Edk2SymbolType.dscModuleDefinition; kind = vscode.SymbolKind.Method; - protected get nameRegex(): RegExp { return /^\s*([\w/.\\-]+\.inf)/i; } + protected get nameRegex(): RegExp { return /.*?\.inf/i; } onCompletion: undefined; onDefinition = async ()=>{ diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 5170c0f..c427b3a 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -33,6 +33,9 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { /** Override in subclasses to extract a specific portion of the text line as the symbol name. */ protected get nameRegex(): RegExp | undefined { return undefined; } + + /** Override in subclasses to extract a description (detail) from the text line. */ + protected get descriptionRegex(): RegExp | undefined { return undefined; } @@ -73,6 +76,13 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { } else { this.name = textLine.replaceAll(/\s+/gi, " "); } + + const descRegex = this.descriptionRegex; + if (descRegex) { + const descMatch = textLine.match(descRegex); + this.detail = descMatch ? (descMatch[1] ?? descMatch[0]).trim() : ''; + } + let parent = parser.symbolStack[parser.symbolStack.length - 1]; this.sectionProperties = new SectionProperties(); if(parent){ diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 83e9574..3edeba7 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -154,8 +154,11 @@ export class DocumentSymbolItem extends vscode.TreeItem { ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None ); - this.id = `dsi:${fileUri.fsPath}:${symbol.selectionRange.start.line}:${symbol.selectionRange.start.character}`; this.treePath = [...parentPath, symbol.name]; + // Include the parent path in the id so the same file/symbol included from + // multiple places in the tree gets a unique id for each occurrence. + const parentKey = parentPath.join('/'); + this.id = `dsi:${parentKey}:${fileUri.fsPath}:${symbol.selectionRange.start.line}:${symbol.selectionRange.start.character}`; this.symbolType = symbol.type; this.description = symbol.detail || undefined; this.tooltip = symbol.name; From a0d2e080f1b8eac7ecef92000eb32ddb61948a89 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 12 Mar 2026 14:11:17 -0500 Subject: [PATCH 40/73] Unify parser factory --- src/Languages/completionProvider.ts | 6 +- src/Languages/declarationProvider.ts | 6 +- src/Languages/definitionProvider.ts | 6 +- src/Languages/symbolProvider.ts | 6 +- src/contextState/cmds.ts | 10 +-- src/edkParser/infParser.ts | 1 - src/edkParser/parserFactory.ts | 110 ++++++++++++++++++--------- src/extension.ts | 6 +- src/index/edkWorkspace.ts | 2 +- src/symbols/edkSymbols.ts | 10 +-- src/symbols/infSymbols.ts | 6 +- 11 files changed, 94 insertions(+), 75 deletions(-) diff --git a/src/Languages/completionProvider.ts b/src/Languages/completionProvider.ts index df59215..f1e29fe 100644 --- a/src/Languages/completionProvider.ts +++ b/src/Languages/completionProvider.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { ParserFactory } from '../edkParser/parserFactory'; +import { getParserForDocument } from '../edkParser/parserFactory'; import { gDebugLog } from '../extension'; @@ -10,10 +10,8 @@ export class EdkCompletionProvider implements vscode.CompletionItemProvider { } async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) { - let factory = new ParserFactory(); - let parser = factory.getParser(document); + let parser = await getParserForDocument(document); if(parser){ - await parser.parseFile(); let selectedSymbol = parser.getSelectedSymbol(position); if (!selectedSymbol) { return []; } gDebugLog.trace(`Completion for: ${selectedSymbol.toString()}`); diff --git a/src/Languages/declarationProvider.ts b/src/Languages/declarationProvider.ts index aadc869..21b1cf3 100644 --- a/src/Languages/declarationProvider.ts +++ b/src/Languages/declarationProvider.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { ParserFactory } from '../edkParser/parserFactory'; +import { getParserForDocument } from '../edkParser/parserFactory'; import { gDebugLog } from '../extension'; @@ -10,10 +10,8 @@ export class EdkDeclarationProvider implements vscode.DeclarationProvider { } async provideDeclaration(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) { - let factory = new ParserFactory(); - let parser = factory.getParser(document); + let parser = await getParserForDocument(document); if(parser){ - await parser.parseFile(); let selectedSymbol = parser.getSelectedSymbol(position); if (!selectedSymbol) { return []; } gDebugLog.trace(`Definition for: ${selectedSymbol.toString()}`); diff --git a/src/Languages/definitionProvider.ts b/src/Languages/definitionProvider.ts index 6778e86..3bdcecc 100644 --- a/src/Languages/definitionProvider.ts +++ b/src/Languages/definitionProvider.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { ParserFactory } from '../edkParser/parserFactory'; +import { getParserForDocument } from '../edkParser/parserFactory'; import { gDebugLog, gEdkWorkspaces } from '../extension'; import { REGEX_PCD, REGEX_VAR_USAGE } from '../edkParser/commonParser'; import { split } from '../utils'; @@ -57,10 +57,8 @@ export class EdkDefinitionProvider implements vscode.DefinitionProvider { - let factory = new ParserFactory(); - let parser = factory.getParser(document); + let parser = await getParserForDocument(document); if(parser){ - await parser.parseFile(); let selectedSymbol = parser.getSelectedSymbol(position); if (!selectedSymbol) { return []; } gDebugLog.trace(`Definition for: ${selectedSymbol.toString()}`); diff --git a/src/Languages/symbolProvider.ts b/src/Languages/symbolProvider.ts index e150fa7..3c839c7 100644 --- a/src/Languages/symbolProvider.ts +++ b/src/Languages/symbolProvider.ts @@ -3,7 +3,7 @@ import * as fs from 'fs'; import { getStaticPath, itsPcdSelected } from '../utils'; import path = require('path'); import { CompletionItemKind } from 'vscode'; -import { ParserFactory } from '../edkParser/parserFactory'; +import { getParserForDocument } from '../edkParser/parserFactory'; import { gConfigAgent, gEdkWorkspaces } from '../extension'; import { Debouncer } from '../debouncer'; import { DiagnosticManager } from '../diagnostics'; @@ -38,10 +38,8 @@ export class EdkSymbolProvider implements vscode.DocumentSymbolProvider { public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken) { // Create a parser for the document - let factory = new ParserFactory(); - let parser = factory.getParser(document); + let parser = await getParserForDocument(document); if (parser) { - await parser.parseFile(); return parser.symbolsTree; } return []; diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 80070a3..9b912fa 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -9,7 +9,7 @@ import { glob } from "fast-glob"; import { BuildFolder } from "../Languages/buildFolder"; import { EdkWorkspace, InfDsc } from "../index/edkWorkspace"; import { FileTreeItem, FileTreeItemLibraryTree, openLibraryNode, SectionTreeItem } from "../TreeDataProvider"; -import { ParserFactory, getParser } from "../edkParser/parserFactory"; +import { getParser, getParserForDocument } from "../edkParser/parserFactory"; import { Edk2SymbolType } from "../symbols/symbolsType"; import * as edkStatusBar from '../statusBar'; import { SettingsPanel } from "../settings/settingsPanel"; @@ -561,8 +561,6 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; edkStatusBar.setWorking(); proccesedInfFiles = new Set(); - let factory = new ParserFactory(); - let wpInfFiles:InfDsc[] = []; // Grab all inf files in all workspaces for (const wp of gEdkWorkspaces.workspaces) { @@ -589,9 +587,8 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; proccesedInfFiles.add(p[0].uri.fsPath); } let document = await openTextDocument(p[0].uri); - let parser = factory.getParser(document); + let parser = await getParserForDocument(document); if(parser){ - await parser.parseFile(); let sources = parser.getSymbolsType(Edk2SymbolType.infSource); filesList.push(parser.document.fileName); for (const source of sources) { @@ -607,9 +604,8 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; let decPath = await gPathFind.findPath(decValue); if(decPath.length){ let decDoc = await openTextDocument(decPath[0].uri); - let decParser = factory.getParser(decDoc); + let decParser = await getParserForDocument(decDoc); if(decParser){ - await decParser.parseFile(); let decIncludes = decParser.getSymbolsType(Edk2SymbolType.decInclude); for (const decInclude of decIncludes) { if(reject.isCancellationRequested){break;} diff --git a/src/edkParser/infParser.ts b/src/edkParser/infParser.ts index dd8a81a..9b00b53 100644 --- a/src/edkParser/infParser.ts +++ b/src/edkParser/infParser.ts @@ -5,7 +5,6 @@ import { createRange, split } from "../utils"; import { REGEX_ANY_BUT_SECTION, REGEX_DEFINE } from "./commonParser"; import { BlockParser, DocumentParser } from "./languageParser"; import { EdkSymbol } from "../symbols/edkSymbols"; -import { ParserFactory } from "./parserFactory"; import * as vscode from 'vscode'; import { DiagnosticManager, EdkDiagnosticCodes } from "../diagnostics"; diff --git a/src/edkParser/parserFactory.ts b/src/edkParser/parserFactory.ts index b2ef4ee..8592184 100644 --- a/src/edkParser/parserFactory.ts +++ b/src/edkParser/parserFactory.ts @@ -9,47 +9,87 @@ import { AslParser } from './aslParser'; import { openTextDocument } from '../utils'; import { DiagnosticManager } from '../diagnostics'; import { Debouncer } from '../debouncer'; +import { DocumentParser } from './languageParser'; -export async function getParser(uri:vscode.Uri){ - let infDocument = await openTextDocument(uri); - let factory = new ParserFactory(); - let parser = factory.getParser(infDocument); +interface ParserCacheEntry { + parser: DocumentParser; + mtime: number; +} + +const parserCache = new Map(); + +export function invalidateParserCache(uri: vscode.Uri): void { + parserCache.delete(uri.toString()); +} + +export function clearParserCache(): void { + parserCache.clear(); +} + +/** + * Instantiates the appropriate parser for the given document based on its language ID. + * Returns `undefined` for unsupported language types. + */ +function createParserInstance(document: vscode.TextDocument): DocumentParser | undefined { + switch (document.languageId) { + case "asl": return new AslParser(document); + case "edk2_dsc": return new DscParser(document); + case "edk2_dec": return new DecParser(document); + case "edk2_vfr": return new VfrParser(document); + case "edk2_fdf": return new FdfParser(document); + case "edk2_inf": return new InfParser(document); + case "edk2_uni": return undefined; + default: + gDebugLog.error(`Document parser not supported: ${document.fileName}`); + return undefined; + } +} + +/** + * Returns a parsed `DocumentParser` for the given `TextDocument`. + * Results are cached by URI + mtime; if the file has not changed since the last + * parse the cached instance is returned immediately without re-parsing. + */ +export async function getParserForDocument(document: vscode.TextDocument): Promise { + const uri = document.uri; + const key = uri.toString(); + + // Retrieve the file modification time + let mtime: number | undefined; + try { + const stat = await vscode.workspace.fs.stat(uri); + mtime = stat.mtime; + } catch { + // File may not exist on disk (e.g. untitled); skip caching + } + + // Return cached parser when the file has not been modified + if (mtime !== undefined) { + const cached = parserCache.get(key); + if (cached && cached.mtime === mtime) { + gDebugLog.trace(`Parser cache hit: ${uri.fsPath}`); + return cached.parser; + } + } + + const parser = createParserInstance(document); if (parser) { await parser.parseFile(); + + // Store in cache only when we have a valid mtime + if (mtime !== undefined) { + parserCache.set(key, { parser, mtime }); + } } return parser; } -export class ParserFactory { - - getParser(document: vscode.TextDocument) { - let languageId = document.languageId; - - switch (languageId) { - case "asl": - return new AslParser(document); - break; - case "edk2_dsc": - return new DscParser(document); - break; - case "edk2_dec": - return new DecParser(document); - break; - case "edk2_vfr": - return new VfrParser(document); - break; - case "edk2_fdf": - return new FdfParser(document); - break; - case "edk2_inf": - return new InfParser(document); - break; - case "edk2_uni": - break; - default: - gDebugLog.error(`Document parser not supported: ${document.fileName}`); - return undefined; - } - } +/** + * Convenience wrapper: opens the file at `uri` and returns its cached/parsed + * `DocumentParser`. Delegates to `getParserForDocument` after opening the document. + */ +export async function getParser(uri: vscode.Uri): Promise { + const document = await openTextDocument(uri); + return getParserForDocument(document); } diff --git a/src/extension.ts b/src/extension.ts index 3694c65..424046e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,7 +17,7 @@ import { PathFind } from './pathfind'; import { EdkWorkspaces } from './index/edkWorkspace'; import { Edk2CallHierarchyProvider } from './callHiearchy'; import { copyToClipboard, findClosestCommonDirectory, getCurrentDocument, getDocsUrl, gotoFile, showVirtualFile } from './utils'; -import { ParserFactory } from './edkParser/parserFactory'; +import { getParserForDocument } from './edkParser/parserFactory'; import { TreeDetailsDataProvider } from './TreeDataProvider'; // import { DefinesTreeDataProvider } from './definesPanel'; import { DiagnosticManager } from './diagnostics'; @@ -115,10 +115,8 @@ export async function activate(context: vscode.ExtensionContext) { // Debug vscode.commands.registerCommand('edk2code.debugCommand', async ()=>{ - let factory = new ParserFactory(); let doc = getCurrentDocument()!; - let parser = factory.getParser(doc); - await parser?.parseFile(); + let parser = await getParserForDocument(doc); let content = ""; for (const symb of parser?.symbolsList!) { content += `${symb.toString()}\n`; diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index efe45e3..49fce74 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -7,7 +7,7 @@ import { REGEX_DEFINE, REGEX_DSC_SECTION, REGEX_EQUAL, REGEX_INCLUDE as REGEX_IN import { UNDEFINED_VARIABLE, WorkspaceDefinitions } from "./definitions"; import * as fs from 'fs'; import path = require('path'); -import { ParserFactory, getParser } from '../edkParser/parserFactory'; +import { getParser } from '../edkParser/parserFactory'; import { Edk2SymbolType } from '../symbols/symbolsType'; import { EdkSymbolInfLibrary } from '../symbols/infSymbols'; import { DiagnosticManager, EdkDiagnosticCodes } from '../diagnostics'; diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index c427b3a..349b592 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -3,7 +3,7 @@ import { gDebugLog } from '../extension'; import { Edk2SymbolType, typeToStr } from './symbolsType'; import { DocumentParser } from '../edkParser/languageParser'; import { SectionProperties } from '../index/edkWorkspace'; -import { ParserFactory } from '../edkParser/parserFactory'; +import { getParserForDocument } from '../edkParser/parserFactory'; import path = require('path'); import { debuglog } from 'util'; import { log } from 'console'; @@ -93,14 +93,12 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { async decCompletion(type:Edk2SymbolType, completionKind:vscode.CompletionItemKind=vscode.CompletionItemKind.File){ let retData = []; - let factory = new ParserFactory(); let decs = this.parser.getSymbolsType(Edk2SymbolType.infPackage); for (const dec of decs) { let decTextPath = await dec.getValue(); let document = await vscode.workspace.openTextDocument(vscode.Uri.file(decTextPath)); - let decParser = factory.getParser(document); + let decParser = await getParserForDocument(document); if(decParser){ - await decParser.parseFile(); let decPpis = decParser.getSymbolsType(type); for (const decPpi of decPpis) { let decPpiValue = await decPpi.getKey(); @@ -112,7 +110,6 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { } async isInDec(type:Edk2SymbolType){ - let factory = new ParserFactory(); let decs = this.parser.getSymbolsType(Edk2SymbolType.infPackage); const testKey = await this.getKey(); @@ -126,9 +123,8 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { } } let document = await vscode.workspace.openTextDocument(vscode.Uri.file(decTextPath)); - let decParser = factory.getParser(document); + let decParser = await getParserForDocument(document); if(decParser){ - await decParser.parseFile(); let decSymbols = decParser.getSymbolsType(type); for (const decSymbol of decSymbols) { diff --git a/src/symbols/infSymbols.ts b/src/symbols/infSymbols.ts index 3dc1150..f813dde 100644 --- a/src/symbols/infSymbols.ts +++ b/src/symbols/infSymbols.ts @@ -8,7 +8,7 @@ import { isFileEdkLibrary, listFiles, listFilesRecursive, openTextDocument, spli import { InfDsc } from "../index/edkWorkspace"; import { DocumentParser } from "../edkParser/languageParser"; import * as fs from 'fs'; -import { ParserFactory } from "../edkParser/parserFactory"; +import { getParserForDocument } from "../edkParser/parserFactory"; import { REGEX_INF_SECTION } from "../edkParser/commonParser"; @@ -500,15 +500,13 @@ export class EdkSymbolInfFunction extends EdkSymbol { async function decDefinition(thisSymbol:EdkSymbol, type:Edk2SymbolType){ - let factory = new ParserFactory(); let thisPpi = await thisSymbol.getKey(); let decs = thisSymbol.parser.getSymbolsType(Edk2SymbolType.infPackage); for (const dec of decs) { let decTextPath = await dec.getValue(); let document = await openTextDocument(vscode.Uri.file(decTextPath)); - let decParser = factory.getParser(document); + let decParser = await getParserForDocument(document); if(decParser){ - await decParser.parseFile(); let decPpis = decParser.getSymbolsType(type); for (const decPpi of decPpis) { let decPpiValue = await decPpi.getKey(); From 74d97de4ccac97b016f6f83cf1f08ad61eda1cfb Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Tue, 24 Mar 2026 15:56:51 -0500 Subject: [PATCH 41/73] modify serialization of nodes --- src/workspaceTree/WorkspaceTreeProvider.ts | 67 +++++++++++++--------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 3edeba7..a399c49 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -191,29 +191,53 @@ function findIncludeNode(nodes: IncludeNode[], location: vscode.Location): Inclu // ─── Recursive text serializer ─────────────────────────────────────────────── -async function serializeSymbol(symbol: EdkSymbol, fileUri: vscode.Uri, indent: string, filter: Set): Promise { - const line = `${indent}${symbol.name}${symbol.detail ? ' - ' + symbol.detail : ''}\n`; - let out = line; - for (const child of symbol.children) { - const edkChild = child as EdkSymbol; - if (isTypeVisible(edkChild.type, filter)) { - out += await serializeSymbol(edkChild, fileUri, indent + ' ', filter); - } +async function resolveIncludedUri(symbol: EdkSymbol): Promise { + if (symbol.type !== Edk2SymbolType.dscInclude || !symbol.onDefinition) { + return undefined; } - return out; + const locations = await Promise.resolve(symbol.onDefinition(symbol.parser)) as vscode.Location[] | undefined; + return locations?.[0]?.uri; } -async function serializeIncludeNode(node: IncludeNode, indent: string, filter: Set): Promise { - const rel = vscode.workspace.asRelativePath(node.uri, false); - let out = `${indent}!include ${rel}\n`; - const symbols = await loadSymbols(node.uri); +async function serializeFileSymbols( + fileUri: vscode.Uri, + indent: string, + filter: Set, + expandedFiles: Set +): Promise { + const symbols = await loadSymbols(fileUri); + let out = ''; for (const sym of symbols) { if (isTypeVisible(sym.type, filter)) { - out += await serializeSymbol(sym, node.uri, indent + ' ', filter); + out += await serializeSymbol(sym, indent, filter, expandedFiles); + } + } + return out; +} + +async function serializeSymbol( + symbol: EdkSymbol, + indent: string, + filter: Set, + expandedFiles: Set +): Promise { + const line = `${indent}${symbol.name}${symbol.detail ? ' - ' + symbol.detail : ''}\n`; + let out = line; + if (symbol.type === Edk2SymbolType.dscInclude) { + const includeUri = await resolveIncludedUri(symbol); + if (!includeUri || expandedFiles.has(includeUri.fsPath)) { + return out; } + const nextExpandedFiles = new Set(expandedFiles); + nextExpandedFiles.add(includeUri.fsPath); + out += await serializeFileSymbols(includeUri, indent + ' ', filter, nextExpandedFiles); + return out; } - for (const child of node.children) { - out += await serializeIncludeNode(child, indent + ' ', filter); + for (const child of symbol.children) { + const edkChild = child as EdkSymbol; + if (isTypeVisible(edkChild.type, filter)) { + out += await serializeSymbol(edkChild, indent + ' ', filter, expandedFiles); + } } return out; } @@ -410,16 +434,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider Date: Tue, 31 Mar 2026 17:35:28 -0500 Subject: [PATCH 42/73] Add view wellcome --- package.json | 8 +++++++- src/extension.ts | 12 +++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9111a99..fd6fb72 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code-dev", "displayName": "Edk2code-dev", "description": "EDK2 code support", - "version": "1.1.2", + "version": "1.1.3", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", @@ -273,6 +273,12 @@ } ] }, + "viewsWelcome": [ + { + "view": "workspaceView", + "contents": "No workspace data available.\n[Rebuild Index](command:edk2code.rebuildIndex)" + } + ], "menus": { "commandPalette": [ { diff --git a/src/extension.ts b/src/extension.ts index 424046e..421b7d6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -166,7 +166,17 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.showWorkspaceDefines', async ()=>{await cmds.showDefines();}), vscode.commands.registerCommand('edk2code.copyWorkspaceTree', async () => { - const text = await edkWorkspaceTreeProvider.serializeTree(); + const text = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Serializing workspace tree', + cancellable: false + }, + async progress => { + progress.report({ message: 'Building tree text for the clipboard...' }); + return await edkWorkspaceTreeProvider.serializeTree(); + } + ); await vscode.env.clipboard.writeText(text); void vscode.window.showInformationMessage('Workspace tree copied to clipboard.'); }), From 4d53ec026cc31e58155c18d8a86a875e850261ff Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 8 Apr 2026 10:41:35 -0500 Subject: [PATCH 43/73] clean up --- .vscode/launch.json | 15 +- .vscode/tasks.json | 13 +- package.json | 156 ++-------- src/TreeDataProvider.ts | 586 ----------------------------------- src/TreeWebview.ts | 153 --------- src/configuration.ts | 4 - src/contextState/cmds.ts | 240 +------------- src/extension.ts | 65 ---- src/index/edkWorkspace.ts | 7 +- src/libraryTree.ts | 109 ------- src/treeElements/Source.ts | 3 +- src/treeElements/TreeItem.ts | 3 - src/utils.ts | 17 - 13 files changed, 52 insertions(+), 1319 deletions(-) delete mode 100644 src/TreeDataProvider.ts delete mode 100644 src/TreeWebview.ts delete mode 100644 src/libraryTree.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 670d6e6..58df78a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,18 @@ "outFiles": [ "${workspaceFolder}/out/**/*.js" ], - "preLaunchTask": "${defaultBuildTask}" + "preLaunchTask": "npm: compile" + }, + { + "name": "Run Extension (watch)", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ] }, { "name": "Extension Tests", @@ -28,7 +39,7 @@ "outFiles": [ "${workspaceFolder}/out/test/**/*.js" ], - "preLaunchTask": "${defaultBuildTask}" + "preLaunchTask": "npm: compile" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..3946211 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,6 +3,14 @@ { "version": "2.0.0", "tasks": [ + { + "type": "npm", + "script": "compile", + "group": { + "kind": "build", + "isDefault": true + } + }, { "type": "npm", "script": "watch", @@ -11,10 +19,7 @@ "presentation": { "reveal": "never" }, - "group": { - "kind": "build", - "isDefault": true - } + "group": "build" } ] } diff --git a/package.json b/package.json index fd6fb72..3c41107 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code-dev", "displayName": "Edk2code-dev", "description": "EDK2 code support", - "version": "1.1.3", + "version": "1.1.4", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", @@ -68,10 +68,7 @@ "command": "edk2code.debugCommand", "title": "EDK2: Debug command" }, - { - "command": "edk2code.showLibraryTree", - "title": "EDK2: Show Module Map" - }, + { "command": "edk2code.saveBuildConfiguration", "title": "EDK2: Save build configuration file" @@ -80,53 +77,22 @@ "command": "edk2code.gotoInf", "title": "EDK2: Goto Inf" }, - { - "command": "edk2code.references", - "title": "EDK2: Show Reference tree" - }, + { "command": "edk2code.dscUsage", "title": "EDK2: Goto DSC declaration" }, - { - "command": "edk2code.libUsage", - "title": "EDK2: Show Library usage" - }, + { "command": "edk2code.dscInclusion", "title": "EDK2: Goto include" }, - { - "command": "edk2code.showReferences", - "title": "EDK2: Show references" - }, + { "command": "edk2code.viewWarnings", "title": "EDK2: Check for warnings" }, - { - "command": "edk2code.copyTreeData", - "title": "EDK2: Copy tree data", - "icon": "$(copy)" - }, - { - "command": "edk2code.getItemTreePath", - "title": "Copy tree node path" - }, - { - "command": "edk2code.focusOnNode", - "title": "Focus on Node" - }, - { - "command": "edk2code.NodeFocusBack", - "title": "Node Focus Back", - "icon": "$(arrow-left)" - }, - { - "command": "edk2code.showWorkspaceDefines", - "title": "EDK2: Show workspace Defines", - "icon": "$(refresh)" - }, + { "command": "edk2code.selectWorkspaceView", "title": "EDK2: Select Workspace", @@ -240,12 +206,6 @@ "items": { "type": "string" } - }, - "edk2code.addVscodeLinksToReferences": { - "order": 2, - "type": "boolean", - "markdownDescription": "Add VSCode links to copied references. When enabled, the tree output will include clickable markdown links that open files at specific line numbers when pasted in markdown documents.", - "default": false } } } @@ -262,21 +222,23 @@ "views": { "edk2code-sidebar": [ { + "icon": "assets/icon.png", "id": "workspaceView", "name": "Workspace", "type": "tree" - }, - { - "id": "detailsView", - "name": "Edk2", - "type": "tree" } ] }, "viewsWelcome": [ { "view": "workspaceView", - "contents": "No workspace data available.\n[Rebuild Index](command:edk2code.rebuildIndex)" + "contents": "Loading workspace, please wait...", + "when": "edk2code.isLoading" + }, + { + "view": "workspaceView", + "contents": "No workspace data available.\n[Rebuild Index](command:edk2code.rebuildIndex)", + "when": "!edk2code.isLoading" } ], "menus": { @@ -312,10 +274,7 @@ { "command": "edk2code.debugCommand" }, - { - "command": "edk2code.showLibraryTree", - "when": "editorLangId == .inf" - }, + { "command": "edk2code.saveBuildConfiguration", "when": "edk2code.parseComplete" @@ -324,50 +283,22 @@ "command": "edk2code.gotoInf", "when": "editorLangId == c" }, - { - "command": "edk2code.references", - "when": "editorLangId == c || editorLangId == edk2_dsc || editorLangId == edk2_inf || editorLangId == edk2_fdf" - }, + { "command": "edk2code.dscUsage", "when": "editorLangId == .inf" }, - { - "command": "edk2code.libUsage", - "when": "edk2code.parseComplete" - }, + { "command": "edk2code.dscInclusion", "when": "editorLangId == edk2_dsc || editorLangId == edk2_fdf" }, - { - "command": "edk2code.showReferences", - "when": "edk2code.parseComplete" - }, + { "command": "edk2code.viewWarnings", "when": "edk2code.parseComplete && (editorLangId == edk2_inf || editorLangId == edk2_dsc)" }, - { - "command": "edk2code.copyTreeData", - "when": "false" - }, - { - "command": "edk2code.getItemTreePath", - "when": "false" - }, - { - "command": "edk2code.focusOnNode", - "when": "false" - }, - { - "command": "edk2code.NodeFocusBack", - "when": "false" - }, - { - "command": "edk2code.showWorkspaceDefines", - "when": "true" - }, + { "command": "edk2code.selectWorkspaceView", "when": "true" @@ -399,11 +330,6 @@ ], "editor/title": [], "view/title": [ - { - "command": "edk2code.NodeFocusBack", - "group": "navigation", - "when": "view == detailsView && edk2code.isNodeFocusBackStack" - }, { "command": "edk2code.selectWorkspaceView", "group": "navigation", @@ -428,16 +354,6 @@ "command": "edk2code.revealEditorInWorkspaceTree", "group": "navigation", "when": "view == workspaceView" - }, - { - "command": "edk2code.copyTreeData", - "when": "view == detailsView", - "group": "navigation" - }, - { - "command": "edk2code.showWorkspaceDefines", - "when": "view == detailsView", - "group": "navigation" } ], "editor/context": [ @@ -446,31 +362,19 @@ "group": "navigation", "when": "editorLangId == c" }, - { - "command": "edk2code.showLibraryTree", - "group": "navigation", - "when": "editorLangId == edk2_inf" - }, + { "command": "edk2code.gotoInf", "group": "navigation", "when": "editorLangId == c" }, - { - "command": "edk2code.references", - "group": "navigation", - "when": "editorLangId == c || editorLangId == edk2_dsc || editorLangId == edk2_inf || editorLangId == edk2_fdf" - }, + { "command": "edk2code.dscUsage", "group": "navigation", "when": "editorLangId == edk2_inf" }, - { - "command": "edk2code.libUsage", - "group": "navigation", - "when": "editorLangId == edk2_inf && edk2code.parseComplete" - }, + { "command": "edk2code.dscInclusion", "group": "navigation", @@ -485,24 +389,10 @@ "command": "edk2code.focusEditorInWorkspaceView", "group": "navigation", "when": "editorLangId == edk2_inf && edk2code.infFileInWorkspaceTree" - }, - { - "command": "edk2code.showReferences", - "group": "navigation", - "when": "editorLangId == edk2_inf && edk2code.parseComplete" } + ], "view/item/context": [ - { - "command": "edk2code.getItemTreePath", - "group": "navigation", - "when": "view == detailsView" - }, - { - "command": "edk2code.focusOnNode", - "group": "navigation", - "when": "view == detailsView" - }, { "command": "edk2code.copyWorkspaceNodePath", "group": "navigation", diff --git a/src/TreeDataProvider.ts b/src/TreeDataProvider.ts deleted file mode 100644 index e26e0cd..0000000 --- a/src/TreeDataProvider.ts +++ /dev/null @@ -1,586 +0,0 @@ -import path = require('path'); -import * as vscode from 'vscode'; -import { TreeItemLabel } from 'vscode'; -import { edkLensTreeDetailProvider, gCompileCommands, gConfigAgent, gEdkWorkspaces, gMapFileManager, gPathFind } from './extension'; -import { InfParser } from './edkParser/infParser'; -import { getParser } from './edkParser/parserFactory'; -import { Edk2SymbolType } from './symbols/symbolsType'; -import { EdkSymbolInfLibrary, EdkSymbolInfSource } from './symbols/infSymbols'; -import { documentGetText, documentGetTextSync, getAllSymbols, getSymbolAtLocation, openTextDocument, openTextDocumentInRange } from './utils'; -import { EdkWorkspace } from './index/edkWorkspace'; -import { EdkSymbol } from './symbols/edkSymbols'; -import { DiagnosticManager, EdkDiagnosticCodes } from './diagnostics'; -import { CompileCommandsEntry } from './compileCommands'; -import { TreeItem } from './treeElements/TreeItem'; -import { DefinesRootItem } from './treeElements/DefinesTreeItem'; - - -export class TreeDetailsDataProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - data: TreeItem[]; - private lastChildren:TreeItem|undefined = undefined; - - - constructor() { - this.data = []; - } - - async expandAll(view:vscode.TreeView){ - await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: "Expanding Tree nodes...", - cancellable: true - }, async (progress, reject) => { - for (const item of this.data) { - await item.expand(); - for (const child of item.iterateChildren()) { - await child.expand(); - } - } - }); - } - - async expandAllNode(node:TreeItem, view:vscode.TreeView, reject:vscode.CancellationToken){ - if(reject.isCancellationRequested){ - return; - } - - if(node instanceof Edk2TreeItem){ - if((node as Edk2TreeItem).circularDependency === true){ - return; - } - } - - await node.expand(); - node.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; - - - for (const child of node.children) { - await this.expandAllNode(child, view, reject); - } - await view.reveal(node); - } - - getHierarchy(item: TreeItem, level: number = 0, isLast: boolean = true, prefix: string = ''): string { - const itemText = item.toString(); - const vscodeLink = this.getVscodeLink(item); - const displayText = vscodeLink ? `[${itemText}](${vscodeLink})` : itemText; - - // Use markdown list syntax with proper indentation - const indent = ' '.repeat(level); // 2 spaces per level for markdown lists - let result = `${indent}- ${displayText}\n`; - - const children = item.children; - for (let i = 0; i < children.length; i++) { - const isLastChild = i === children.length - 1; - result += this.getHierarchy(children[i], level + 1, isLastChild, ''); - } - return result; - } - - private getVscodeLink(item: TreeItem): string | null { - if (!gConfigAgent.isAddVscodeLinksToReferences()) { - return null; - } - - // Type guards and link generation for different tree item types - if ('uri' in item && item.uri) { - const uri = item.uri as vscode.Uri; - - // For SourceSymbolTreeItem - has range property - if ('range' in item && item.range) { - const range = item.range as vscode.Range; - const line = range.start.line + 1; // VSCode uses 1-based line numbers in links - const char = range.start.character + 1; // VSCode uses 1-based character numbers in links - return `vscode://file${uri.path}:${line}:${char}`; - } - - // For other tree items that might have position in their command arguments - if (item.command && item.command.arguments && item.command.arguments.length >= 2) { - const position = item.command.arguments[1] as vscode.Position; - if (position && typeof position.line === 'number') { - const line = position.line + 1; - const char = position.character + 1; - return `vscode://file${uri.path}:${line}:${char}`; - } - } - - // Fallback - just link to the file without specific line - return `vscode://file${uri.path}`; - } - - return null; - } - - toString(){ - let result = ''; - for (let i = 0; i < this.data.length; i++) { - const isLast = i === this.data.length - 1; - result += this.getHierarchy(this.data[i], 0, isLast, ''); - } - return result; - } - - getLastChildren(){ - return this.lastChildren; - } - - addChildren(item:TreeItem){ - if(!item.visible){return;} - this.lastChildren = item; - this.data.push(item); - this.refresh(); - } - - clear(){ - this.lastChildren = undefined; - this.data = []; - this.refresh(); - } - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: TreeItem): vscode.TreeItem | Thenable { - return element; - } - - getChildren(element?: TreeItem | undefined): vscode.ProviderResult { - if (element === undefined) { - return this.data; - } - return element.children; - } - - getParent?(element: TreeItem): vscode.ProviderResult { - return element.getParent(); - } - - // resolveTreeItem?(item: vscode.TreeItem, element: TreeItem, token: vscode.CancellationToken): vscode.ProviderResult { - // throw new Error('Method not implemented.'); - // } - -} - - - - - -class Edk2TreeItem extends TreeItem { - uri:vscode.Uri; - loaded = false; - workspace:EdkWorkspace; - edkObject:EdkSymbol; - circularDependency = false; - contextModuleUri:vscode.Uri|undefined = undefined; - - constructor(uri:vscode.Uri, position:vscode.Position, wp:EdkWorkspace, edkObject:EdkSymbol){ - super(uri, vscode.TreeItemCollapsibleState.Collapsed); - this.edkObject = edkObject; - this.workspace = wp; - this.uri = uri; - this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - - this.command = { - "command": "editor.action.peekLocations", - "title":"Open file", - "arguments": [this.uri, position, []] - }; - } - - addChildren(node: Edk2TreeItem|TreeItem) { - node.parent = this; - this.loaded = true; - this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - this.children.push(node); - - } - - async onExpanded(){ - if(this.children.length > 0){return;} - - edkLensTreeDetailProvider.refresh(); - if(this.loaded === false){ - let contextModule = this.uri; - if(this.contextModuleUri){ - contextModule = this.contextModuleUri; - } - await openLibraryNode(this.uri, contextModule,this,this.workspace); - this.loaded = true; - } - - // If no children, set collapsible state to none to hide the expand arrow - if(this.children.length === 0){ - this.collapsibleState = vscode.TreeItemCollapsibleState.None; - } - - - } -} - - - - -export class SourceSymbolTreeItem extends TreeItem{ - uri:vscode.Uri; - range:vscode.Range; - constructor(uri:vscode.Uri, symbol:vscode.DocumentSymbol){ - super(uri, vscode.TreeItemCollapsibleState.Collapsed); - this.label = symbol.name; - this.description = symbol.detail.length?symbol.detail:vscode.SymbolKind[symbol.kind]; - this.iconPath = EdkSymbol.iconForKind(symbol.kind); - this.collapsibleState = vscode.TreeItemCollapsibleState.None; - this.range = symbol.range; - this.uri = uri; - - // Fix typedef label rendering - if(symbol.kind === vscode.SymbolKind.Interface){ - let text = documentGetTextSync(this.uri, symbol.range); - let clearText = text.replace(symbol.detail,"").replace(/\s+/g, ' ').replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '').replaceAll("\n"," ").replaceAll("\r","").trim(); - if(!clearText.match(/^(struct|enum|union)/)){ - this.label = clearText; - } - } - - for (const child of symbol.children) { - let newChild = new SourceSymbolTreeItem(uri,child); - this.addChildren(newChild); - this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - // Get the type parsing the definition - void documentGetText(this.uri, child.range).then((text) => { - const childType = text.trim().split(" ")[0]; - newChild.description = childType; - }); - } - - if(symbol.kind === vscode.SymbolKind.Field){ - this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - } - - - this.command = { - "command": "editor.action.peekLocations", - "title":"Open file", - "arguments": [this.uri, symbol.range.start, []] - }; - - } - - async onExpanded() { - await openTextDocumentInRange(this.uri, this.range); - if(this.children.length > 0){return;} - - // Find definition for field - const locations = await vscode.commands.executeCommand('vscode.executeTypeDefinitionProvider', this.uri, this.range.start); - if(locations.length > 0){ - let symbol = await getSymbolAtLocation(locations[0].uri, locations[0]); - if(symbol){ - - if(symbol.kind === vscode.SymbolKind.Interface){ - const locationsType = await vscode.commands.executeCommand('vscode.executeTypeDefinitionProvider',locations[0].uri , locations[0].range.start); - if(locationsType.length > 0){ - symbol = await getSymbolAtLocation(locationsType[0].uri, locationsType[0]); - if(!symbol){return;} - } - } - - let symbolNode = new SourceSymbolTreeItem(locations[0].uri, symbol); - symbolNode.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - this.addChildren(symbolNode); - }else{ - this.collapsibleState = vscode.TreeItemCollapsibleState.None; - } - } - } - -} - -export class SectionTreeItem extends TreeItem{ - uri: vscode.Uri; - constructor(uri:vscode.Uri, position:vscode.Position, sectionName:string, wp:EdkWorkspace){ - super(sectionName); - this.uri = uri; - this.label = sectionName; - this.iconPath = new vscode.ThemeIcon("array"); - } - - toString(): string { - return `[${this.label}]`; - } - - -} - -export class FileTreeItemLibraryTree extends Edk2TreeItem{ - constructor(uri:vscode.Uri, position:vscode.Position, wp:EdkWorkspace, edkObject:EdkSymbol){ - super(uri, position, wp, edkObject); - - - let name = vscode.workspace.asRelativePath(uri); - this.description = name; - this.label = path.basename(uri.fsPath); - this.iconPath = new vscode.ThemeIcon("file"); - - this.command = { - "command": "vscode.open", - "title":"Open file", - "arguments": [this.uri] - }; - - } -} - -export class FileTreeItem extends TreeItem{ - uri:vscode.Uri; - constructor(uri:vscode.Uri, position:vscode.Position,wp:EdkWorkspace){ - super(uri); - let name = vscode.workspace.asRelativePath(uri); - this.description = name; - this.label = path.basename(uri.fsPath); - this.iconPath = new vscode.ThemeIcon("file"); - this.uri = uri; - this.command = { - "command": "editor.action.peekLocations", - "title":"Open file", - "arguments": [this.uri, position, []] - }; - } -} - -export class HeaderFileTreeItemLibraryTree extends TreeItem{ - systemIncludes:string[]= []; - uri:vscode.Uri; - constructor(uri:vscode.Uri, systemIncludes:string[] = []){ - super(uri, vscode.TreeItemCollapsibleState.Collapsed); - this.uri = uri; - this.systemIncludes = systemIncludes; - - let name = vscode.workspace.asRelativePath(uri); - this.description = name; - this.label = path.basename(uri.fsPath); - this.iconPath = new vscode.ThemeIcon("file"); - - this.command = { - "command": "vscode.open", - "title":"Open file", - "arguments": [this.uri] - }; - } - - async loadIncludes(){ - - } - - async onExpanded(){ - if(this.children.length > 0){return;} - - const includeHeaders = await findHeaderIncludes(this.uri, this.systemIncludes); - const compileCommand = gCompileCommands.getCompileCommandForFile(this.uri.fsPath); - - for (const includeHeader of includeHeaders) { - this.addChildren(includeHeader); - if(compileCommand){ - updateCompileCommandsForHeaderFile(includeHeader, compileCommand); - } - } - - // add symbols - - const sourceSymbols:vscode.DocumentSymbol[] = await getAllSymbols(this.uri); - for (const symbol of sourceSymbols) { - - if(symbol.name.startsWith("__unnamed")){continue;} - const symbolNode = new SourceSymbolTreeItem(this.uri, symbol); - - this.addChildren(symbolNode); - } - - if(this.children.length === 0){ - this.collapsibleState = vscode.TreeItemCollapsibleState.None; - } - - } - -} - - - - -export async function findHeaderIncludes(fileUri:vscode.Uri, systemIncludes:string[]){ - let sourceDocument = await openTextDocument(fileUri); - let sourceText = sourceDocument.getText(); - let lineNo = 0; - let includeNodes:HeaderFileTreeItemLibraryTree[] = []; - for (const line of sourceText.split('\n')) { - let match = /#include\s+(["<])([^">]+)[">]/.exec(line); - if(match){ - let isRelative = false; - - if(match[1] === '"'){ - isRelative = true; - } - let includePath = match[2]; - - let includeUri:vscode.Uri = fileUri; - - if(isRelative){ - // Relative includes - const locations = await gPathFind.findRelativePath(includePath, path.dirname(fileUri.fsPath)); - if(locations){ - includeUri = locations.uri; - } - }else{ - // System includes - for (const compiledIncludePath of systemIncludes) { - const location = await gPathFind.findRelativePath(includePath, compiledIncludePath); - if(location){ - includeUri = location.uri; - break; - } - } - } - - let includeNode = new HeaderFileTreeItemLibraryTree(includeUri, systemIncludes); - includeNodes.push(includeNode); - lineNo++; - - } - } - return includeNodes; -} - - -function updateCompileCommandsForHeaderFile(headerItem:HeaderFileTreeItemLibraryTree, compileCommand:CompileCommandsEntry){ - let tempCompileCommand = new CompileCommandsEntry(compileCommand.command, "", headerItem.uri.fsPath); - gCompileCommands.addCompileCommandForFile(tempCompileCommand); -} - - -/** - * Populates a node with all the libraries found in fileUri. - * - * @param infUri - The URI of the file to be parsed. - * @param node - The library tree item node to which child nodes will be added. - * - * @returns A promise that resolves when the operation is complete. - * - * @throws Will throw an error if the parser cannot be obtained. - * @throws Will throw an error if workspace information cannot be retrieved. - * @throws Will throw an error if library declarations cannot be fetched. - * @throws Will throw an error if path information cannot be found. - */ -export async function openLibraryNode(infUri:vscode.Uri, moduleUri:vscode.Uri, node:FileTreeItemLibraryTree, wp:EdkWorkspace){ - - if(node.circularDependency){return;} // Dont process duplicated elements - - DiagnosticManager.clearProblems(infUri); - let parser = await getParser(infUri) as InfParser; - if(parser){ - // Add librarires - let libraries = parser.getSymbolsType(Edk2SymbolType.infLibrary) as EdkSymbolInfLibrary[]; - if(libraries.length > 0){ - let sectionLib = new SectionTreeItem(infUri,libraries[0].parent!.range.start, "Libraries", wp); - node.addChildren(sectionLib); - for (const library of libraries) { - let libDefinitions = await wp.getLibDeclarationModule(moduleUri, library.name); - if(libDefinitions.length === 0){ - let libNode = new FileTreeItemLibraryTree(infUri, library.location.range.start, wp, library); - libNode.description = "Library not found"; - libNode.label = library.name; - sectionLib.addChildren(libNode); - continue; - } - for (const libDefinition of libDefinitions) { - let filePaths = await gPathFind.findPath(libDefinition.path); - for (const path of filePaths) { - let pos = new vscode.Position(0,0); - - let libNode = new FileTreeItemLibraryTree(path.uri, pos, wp, library); - libNode.contextModuleUri = moduleUri; - sectionLib.addChildren(libNode); - // Check for circular dependencies - if(isCircularDependencies(libNode)){ - libNode.circularDependency = true; - } - } - } - } - } - - - // Add sources - let sources = parser.getSymbolsType(Edk2SymbolType.infSource) as EdkSymbolInfSource[]; - if(sources.length > 0){ - let sectionSource = new SectionTreeItem(infUri,sources[0].parent!.range.start, "Sources", wp, ); - - node.addChildren(sectionSource); - for (const source of sources) { - let filePath = await source.getValue(); - - if(!filePath.toLowerCase().endsWith(".c")){continue;} // Only C files - - let pos = new vscode.Position(0,0); - let fileUri = vscode.Uri.file(filePath); - - let sourceNode = new FileTreeItemLibraryTree(fileUri, pos, wp, source); - sectionSource.addChildren(sourceNode); - - // Find includes - const compileCommand = gCompileCommands.getCompileCommandForFile(fileUri.fsPath); - if(compileCommand){ - const compiledIncludePaths = compileCommand.getIncludePaths(); - const includeHeaders = await findHeaderIncludes(fileUri, compiledIncludePaths); - for (const includeHeader of includeHeaders) { - sourceNode.addChildren(includeHeader); - updateCompileCommandsForHeaderFile(includeHeader, compileCommand); - } - } - - - // Check if the symbol is used - // TODO: Experimental function - if(false){ - const sourceSymbols:vscode.DocumentSymbol[] = await getAllSymbols(fileUri); - for (const symbol of sourceSymbols) { - const symbolNode = new SourceSymbolTreeItem(fileUri, symbol); - if(symbol.kind === vscode.SymbolKind.Function){ - if(!gMapFileManager.isSymbolUsed(symbol.name)){ - DiagnosticManager.warning(fileUri,symbol.range,EdkDiagnosticCodes.unusedSymbol, `Unused function: ${symbol.name}`, [vscode.DiagnosticTag.Unnecessary]); - symbolNode.tooltip = "Unused function"; - symbolNode.description = `Unused ${symbolNode.description}`; - symbolNode.iconPath = new vscode.ThemeIcon("warning"); - } - } - sourceNode.addChildren(symbolNode); - } - } - - } - } - } - - - edkLensTreeDetailProvider.refresh(); -} - - -function isCircularDependencies(libNode: FileTreeItemLibraryTree) { - let tempNode = libNode.parent; - while (tempNode) { - if (tempNode.label === libNode.label) { - DiagnosticManager.warning( - libNode.uri, 0, EdkDiagnosticCodes.circularDependency, - `Library circular dependency detected`, [vscode.DiagnosticTag.Unnecessary] - ); - libNode.collapsibleState = vscode.TreeItemCollapsibleState.None; - libNode.iconPath = new vscode.ThemeIcon("extensions-refresh"); - libNode.tooltip = "Circular library dependency"; - return true; - } - tempNode = tempNode.parent; - } - return false; -} diff --git a/src/TreeWebview.ts b/src/TreeWebview.ts deleted file mode 100644 index e451528..0000000 --- a/src/TreeWebview.ts +++ /dev/null @@ -1,153 +0,0 @@ - -import * as vscode from 'vscode'; -import { TreeDetailsDataProvider } from './TreeDataProvider'; -import { copyTreeProviderToClipboard, gotoFile, openFileSide } from './utils'; -import { gExtensionContext } from './extension'; -import { getNonce } from './utilities/getNonce'; -import path = require('path'); -import { TreeItem } from './treeElements/TreeItem'; - -//https://bendera.github.io/vscode-webview-elements/ - -export class TreeWebview { - treeProvider: TreeDetailsDataProvider; - private count: number = 0; - contextModule: string; - targetModule: string; - - constructor(treeProvider: TreeDetailsDataProvider, targetModule:string, contextModule:string) { - this.treeProvider = treeProvider; - this.targetModule = targetModule; - this.contextModule = contextModule; - } - - private processTree(currentNode: TreeItem, htmlText: string) { - let children = currentNode.children; - - - let returnHtml =` - { - icons, - label: '${currentNode.label}', - value: '${currentNode.description?.toString().replaceAll("\\",path.sep)}', - open: true, - `; - - if (children.length > 0) { - returnHtml += `subItems: [`; - for (const r of children) { - returnHtml += this.processTree(r, htmlText); - } - returnHtml += `],`; - } - return returnHtml + "},"; - } - - render() { - let htmlTree = ""; - for (const items of this.treeProvider.getChildren()) { - htmlTree += this.processTree(items, ''); - } - - const webviewPanel = vscode.window.createWebviewPanel( - 'vscodeTest', - `Library tree: ${path.basename(this.targetModule)}`, - vscode.ViewColumn.One, - { - enableScripts: true, - retainContextWhenHidden: true, - enableFindWidget: true - } - ); - webviewPanel.webview.onDidReceiveMessage( - async message => { - switch (message.command) { - case 'openFile': - await openFileSide(vscode.Uri.file(message.path), new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - return; - case 'copy': - await copyTreeProviderToClipboard(this.treeProvider); - return; - } - }, - undefined, - gExtensionContext.subscriptions - ); - this.setHtmlContent(webviewPanel.webview, gExtensionContext, htmlTree); - } - - - setHtmlContent(webview: vscode.Webview, extensionContext: vscode.ExtensionContext, reportObject: any) { - const nonce = getNonce(); - - const fileUri = (fp: string) => { - const fragments = fp.split('/'); - return vscode.Uri.file( - path.join(extensionContext.asAbsolutePath(""),...fragments) - ); - }; - - const assetUri = (fp: string) => { - return webview.asWebviewUri(fileUri(fp)); - }; - - let htmlContent = /*html*/ - - ` - - - - - - - - - -
-

Library dependencies of ${this.targetModule} in context of ${this.contextModule} module. -
Recursive dependencies are ommited.

- Copy tree -
- - - - `; - webview.html = htmlContent; - } -} - - - diff --git a/src/configuration.ts b/src/configuration.ts index 2d87a25..b0e0e78 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -79,10 +79,6 @@ export class ConfigAgent { return this.get("enableDiagnostics"); } - isAddVscodeLinksToReferences(){ - return this.get("addVscodeLinksToReferences"); - } - reloadConfigFile(){ this.workspaceConfig = this.readWpConfig(); } diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 9b912fa..d84488c 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -4,42 +4,19 @@ import { rgSearch } from "../rg"; import { delay, getCurrentWord, gotoFile, isWorkspacePath, listFilesRecursive, openTextDocument, pathCompare, profileEnd, profileStart, readLines, toPosix } from "../utils"; import path = require("path"); import * as fs from 'fs'; -import { edkLensTreeDetailProvider, edkLensTreeDetailView, edkWorkspaceTreeProvider, gConfigAgent, gCscope, gDebugLog, gEdkWorkspaces, gExtensionContext, gMapFileManager, gPathFind, gWorkspacePath } from "../extension"; +import { edkWorkspaceTreeProvider, gConfigAgent, gCscope, gDebugLog, gEdkWorkspaces, gExtensionContext, gMapFileManager, gPathFind, gWorkspacePath } from "../extension"; import { glob } from "fast-glob"; import { BuildFolder } from "../Languages/buildFolder"; import { EdkWorkspace, InfDsc } from "../index/edkWorkspace"; -import { FileTreeItem, FileTreeItemLibraryTree, openLibraryNode, SectionTreeItem } from "../TreeDataProvider"; import { getParser, getParserForDocument } from "../edkParser/parserFactory"; import { Edk2SymbolType } from "../symbols/symbolsType"; import * as edkStatusBar from '../statusBar'; import { SettingsPanel } from "../settings/settingsPanel"; -import { InfParser } from "../edkParser/infParser"; -import { EdkSymbolInfLibrary } from "../symbols/infSymbols"; -import { debuglog } from "util"; import { deleteEdkCodeFolder, existsEdkCodeFolderFile } from "../edk2CodeFolder"; -import { EdkInfNode, EdkInfNodeLibrary } from "../treeElements/Library"; -import { TreeItem } from "../treeElements/TreeItem"; -import { EdkModule, ModuleReport } from "../moduleReport"; import { infoMissingCompileInfo } from "../ui/messages"; -import { DefinesRootItem } from "../treeElements/DefinesTreeItem"; import { checkCppConfiguration } from "../cppProviders/cppUtils"; - export async function showDefines() { - edkLensTreeDetailProvider.clear(); - edkLensTreeDetailView.title = "EDK2 Defines"; - edkLensTreeDetailView.description = ""; - - let definesRoot = new DefinesRootItem("DEFINES"); - let pcdsRoot = new DefinesRootItem("PCDs"); - - edkLensTreeDetailProvider.addChildren(definesRoot); - edkLensTreeDetailProvider.addChildren(pcdsRoot); - - edkLensTreeDetailProvider.refresh(); - await edkLensTreeDetailView.reveal(edkLensTreeDetailProvider.data[0]); - } - export async function rebuildIndexDatabase(){ gDebugLog.trace("Rebuilding index database"); // Pick build folder @@ -140,7 +117,6 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; void vscode.window.showInformationMessage("Build data loaded"); await gEdkWorkspaces.loadConfig(); - await showDefines(); } } else { @@ -153,7 +129,6 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; export async function rescanIndex() { gConfigAgent.reloadConfigFile(); await reloadSymbols(); - await showDefines(); // Generate .ignore if setting is set and .ignore doesnt exists if (gConfigAgent.getIsGenIgnoreFile()) { await genIgnoreFile(); @@ -357,189 +332,6 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; - } - export async function showLibUsage(fileUri:vscode.Uri) { - throw new Error("Method not implemented."); - } - - - export function showReferences(): void { - throw new Error("Method not implemented."); - } - - - - export async function showEdkMap(moduleUri:vscode.Uri) { - let parser = await getParser(moduleUri); - let contextModule = moduleUri; - let contextSelected = false; - if(parser && (parser instanceof InfParser) ){ - // Check if INF file is a library - if(parser.isLibrary()){ - let modulesUsage: EdkModule[] = []; - let modules = ModuleReport.getInstance().getModuleList(); - for (const module of modules) { - if(!module.isLibrary){ - for (const lib of module.libraries) { - if(pathCompare(lib.path, moduleUri.fsPath)){ - modulesUsage.push(module); - } - } - } - } - if(modulesUsage.length){ - const options = modulesUsage.map(mod => ({ - label: mod.name, - description: mod.path, - module: mod - })); - const selectedOption = await vscode.window.showQuickPick(options, { - title: "Select a Module for context", - canPickMany: false - }); - if (!selectedOption) { - return; - } - contextModule = vscode.Uri.file(selectedOption.module.path); - contextSelected = true; - - }else{ - - // list all modules and ask for context - void vscode.window.showWarningMessage("EDK2Code couldn't find a module that uses this library"); - return; - } - } - - // Initialize tree view - edkLensTreeDetailProvider.clear(); - edkLensTreeDetailProvider.refresh(); - edkLensTreeDetailView.title = "EDK2 Module Map"; - let description = path.basename(moduleUri.fsPath); - if(moduleUri.fsPath !== contextModule.fsPath){ - description = `${path.basename(moduleUri.fsPath)} - ${path.basename(contextModule.fsPath)}`; - } - edkLensTreeDetailView.description = description; - - - let wps = await gEdkWorkspaces.getWorkspace(contextModule); - let libraries = parser.getSymbolsType(Edk2SymbolType.infLibrary) as EdkSymbolInfLibrary[]; - if(libraries.length === 0){ - void vscode.window.showWarningMessage("No libraries found in file"); - return; - } - - for (const wp of wps) { - // let dscDeclarations = await wp.getDscDeclaration(fileUri); - const sectionRange = libraries[0].parent?.range.start; - if(sectionRange===undefined){continue;} - let librarySet = new Set(); - let moduleNode; - if(parser.isLibrary()){ - moduleNode = new EdkInfNodeLibrary(moduleUri, contextModule, sectionRange, wp, libraries[0].parent!, librarySet); - }else{ - - moduleNode = new EdkInfNode(moduleUri, contextModule, sectionRange, wp, libraries[0].parent!, librarySet); - } - - edkLensTreeDetailProvider.addChildren(moduleNode); - - } - } - - await edkLensTreeDetailView.reveal(edkLensTreeDetailProvider.data[0]); - - } - - - export async function showReferenceStack(fileUri: Uri){ - return await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: "Looking for references...", - cancellable: false - }, async (progress, reject) => { - let document = await openTextDocument(fileUri); - return await updateInclussionTree(document); - }); - - } - - let _maxDscRec = 10; - export async function updateInclussionTree(document: vscode.TextDocument){ - edkLensTreeDetailProvider.clear(); - let wps = await gEdkWorkspaces.getWorkspace(document.uri); - for (const wp of wps) { - let currentDocument = document; - let infReferences:vscode.Location[] = []; - var dscReferences:vscode.Location[] = []; - let rootNode = new TreeItem(`[${wp.platformName}]` || "Undefined Platform"); - let cNode = new TreeItem(""); - edkLensTreeDetailProvider.addChildren(rootNode); - - switch (currentDocument.languageId) { - case "c": - cNode = new FileTreeItem(document.uri, new vscode.Position(0,0),wp); - rootNode.addChildren(cNode); - infReferences = await wp.getInfReference(document.uri); - // intentional with no break - case "edk2_inf": - let targetNode = cNode; - if(infReferences.length===0){ - infReferences = [new vscode.Location(document.uri, document.positionAt(0))]; - targetNode = rootNode; - } - - for (const infRef of infReferences) { - let infNode = new FileTreeItem(infRef.uri, infRef.range.start,wp); - targetNode.addChildren(infNode); - dscReferences = (await wp.getDscDeclaration(infRef.uri)).map(x=>{return x.location;}); - for (const dscRef of dscReferences) { - _maxDscRec = 10; - let targetNode = new FileTreeItem(dscRef.uri, dscRef.range.start,wp); - await _dscIncRefs(targetNode,wp, infNode); - } - } - break; - case "edk2_fdf": - case "edk2_dsc" : - - dscReferences = [new vscode.Location(document.uri, document.positionAt(0))]; - - - for (const dscRef of dscReferences) { - let refNode = new FileTreeItem(dscRef.uri, dscRef.range.start,wp); - _maxDscRec = 10; - await _dscIncRefs(refNode,wp,rootNode); - } - break; - default: - edkLensTreeDetailProvider.clear(); - - } - } - - - edkLensTreeDetailProvider.refresh(); - edkLensTreeDetailView.title = "EDK2 References"; - edkLensTreeDetailView.description = path.basename(document.uri.fsPath); - await edkLensTreeDetailView.reveal(edkLensTreeDetailProvider.data[0]); - } - - - export async function _dscIncRefs(referenceNode:FileTreeItem, wp:EdkWorkspace, targetNode:TreeItem){ - if(_maxDscRec === 0){return;} - _maxDscRec --; - - - let dscInclude = await wp.getIncludeReference(referenceNode.uri); - if(dscInclude.length === 0){ - referenceNode.collapsibleState = vscode.TreeItemCollapsibleState.None; - } - targetNode.addChildren(referenceNode); - for (const i of dscInclude) { - let newNode = new FileTreeItem(i.uri, i.range.start,wp); - await _dscIncRefs(newNode, wp, referenceNode); - } } @@ -712,34 +504,4 @@ export async function openWpConfigGui() { } export async function openWpConfigJson() { await gotoFile(gConfigAgent.getConfigFileUri()); -} - - - - - -var nodeStack = new Array(); - -export async function focusOnNode(node:TreeItem) { - nodeStack.push(edkLensTreeDetailProvider.data); - await vscode.commands.executeCommand('setContext', 'edk2code.isNodeFocusBackStack', true); - - edkLensTreeDetailProvider.clear(); - edkLensTreeDetailProvider.addChildren(node); - await edkLensTreeDetailView.reveal(edkLensTreeDetailProvider.data[0]); - edkLensTreeDetailProvider.refresh(); - edkLensTreeDetailView.title = "EDK2 Module Map"; -} - -export async function nodeFocusBack(){ - if(nodeStack.length){ - edkLensTreeDetailProvider.clear(); - edkLensTreeDetailProvider.data = nodeStack.pop()!; - await edkLensTreeDetailView.reveal(edkLensTreeDetailProvider.data[0]); - edkLensTreeDetailProvider.refresh(); - } - - if(nodeStack.length === 0){ - await vscode.commands.executeCommand('setContext', 'edk2code.isNodeFocusBackStack', false); - } } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 421b7d6..2a0335d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,14 +18,11 @@ import { EdkWorkspaces } from './index/edkWorkspace'; import { Edk2CallHierarchyProvider } from './callHiearchy'; import { copyToClipboard, findClosestCommonDirectory, getCurrentDocument, getDocsUrl, gotoFile, showVirtualFile } from './utils'; import { getParserForDocument } from './edkParser/parserFactory'; -import { TreeDetailsDataProvider } from './TreeDataProvider'; -// import { DefinesTreeDataProvider } from './definesPanel'; import { DiagnosticManager } from './diagnostics'; import { WorkspaceTreeProvider, WorkspaceRootItem, IncludeTreeItem, DocumentSymbolItem, WorkspaceTreeNode, isFileInWorkspaceTree, isInfInWorkspaces } from './workspaceTree/WorkspaceTreeProvider'; import { InfDsc } from './index/edkWorkspace'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; -import { TreeItem } from './treeElements/TreeItem'; import { showReleaseNotes } from './newVersionPage/newVersionMessage'; @@ -49,14 +46,9 @@ export var gModuleReport: ModuleReport; export var gGuidProvider:GuidProvider; export var gDiagnosticManager:DiagnosticManager; -export var edkLensTreeDetailProvider: TreeDetailsDataProvider; -export var edkLensTreeDetailView: vscode.TreeView; - export var edkWorkspaceTreeProvider: WorkspaceTreeProvider; export var edkWorkspaceTreeView: vscode.TreeView; -// export var edkDefinesTreeProvider: DefinesTreeDataProvider; - export var gMapFileManager: MapFilesManager; @@ -100,11 +92,6 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.gotoInf',async (fileUri)=>{await cmds.gotoInf(fileUri);}), vscode.commands.registerCommand('edk2code.dscUsage', async (fileUri)=>{await cmds.gotoDscDeclaration(fileUri);}), vscode.commands.registerCommand('edk2code.dscInclusion', async (fileUri)=>{await cmds.gotoDscInclusion(fileUri);}), - vscode.commands.registerCommand('edk2code.references', async (fileUri)=>{await cmds.showReferenceStack(fileUri);}), - - vscode.commands.registerCommand('edk2code.libUsage', async (editor)=>{await cmds.showLibUsage(editor.document.uri);}), - vscode.commands.registerCommand('edk2code.showReferences', ()=>{cmds.showReferences();}), - vscode.commands.registerTextEditorCommand('edk2code.showLibraryTree', async (editor)=>{await cmds.showEdkMap(editor.document.uri);}), // Internal vscode.commands.registerCommand('edk2code.searchDefinition', ()=>{}), @@ -124,47 +111,8 @@ export async function activate(context: vscode.ExtensionContext) { await showVirtualFile(doc.fileName,content); }), - vscode.commands.registerCommand('edk2code.copyTreeData', () => { - // Your export logic here - let strTree = edkLensTreeDetailProvider.toString(); - void vscode.env.clipboard.writeText(strTree); - void vscode.window.showInformationMessage('Details copied to clipboard.'); - - }), - - vscode.commands.registerCommand('edk2code.expandAllTree', () => { - // Your export logic here - let strTree = edkLensTreeDetailProvider.expandAll(edkLensTreeDetailView); - }), - - vscode.commands.registerCommand('edk2code.focusOnNode', async (node:TreeItem) => { - await cmds.focusOnNode(node); - }), - - vscode.commands.registerCommand('edk2code.NodeFocusBack', async () => { - await cmds.nodeFocusBack(); - }), - vscode.commands.registerCommand('edk2code.getItemTreePath', (node:TreeItem) => { - // Your export logic here - let itemParent = node.getParent(); - let pathStack = [node]; - while(itemParent){ - pathStack.push(itemParent); - itemParent = itemParent.getParent(); - } - let path = ""; - let count = 0; - for (const pathItem of pathStack.reverse()) { - path += " ".repeat(count) + pathItem.toString() + "\n"; - count++; - } - void copyToClipboard(path, "Path copied to clipboard"); - }), - - vscode.commands.registerCommand('edk2code.showWorkspaceDefines', async ()=>{await cmds.showDefines();}), - vscode.commands.registerCommand('edk2code.copyWorkspaceTree', async () => { const text = await vscode.window.withProgress( { @@ -288,17 +236,10 @@ export async function activate(context: vscode.ExtensionContext) { gMapFileManager = new MapFilesManager(); gCompileCommands = new CompileCommands(); - edkLensTreeDetailProvider = new TreeDetailsDataProvider(); - edkLensTreeDetailView = vscode.window.createTreeView('detailsView', { treeDataProvider: edkLensTreeDetailProvider, showCollapseAll:true }); - - // edkDefinesTreeProvider = new DefinesTreeDataProvider(); - // vscode.window.createTreeView('definesView', { treeDataProvider: edkDefinesTreeProvider, showCollapseAll: true }); - edkWorkspaceTreeProvider = new WorkspaceTreeProvider(); edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider }); await gEdkWorkspaces.loadConfig(); - // edkDefinesTreeProvider.refresh(); gFileUseWarning = new FileUseWarning(); @@ -316,13 +257,7 @@ export async function activate(context: vscode.ExtensionContext) { } - edkLensTreeDetailView.onDidExpandElement(async event => { - let node = event.element as TreeItem; - await node.expand(); - edkLensTreeDetailProvider.refresh(); - }); - await vscode.commands.executeCommand('setContext', 'edk2code.isNodeFocusBackStack', false); // ─── Track whether the active editor belongs to the workspace tree ───────── async function updateEditorInWorkspaceContext(editor: vscode.TextEditor | undefined): Promise { diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 49fce74..3cf14a3 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -296,6 +296,7 @@ async getWorkspace(uri: vscode.Uri): Promise { gDebugLog.trace("Loading Configuration"); // TODO: enable to get more commands available //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', false); + await vscode.commands.executeCommand('setContext', 'edk2code.isLoading', true); // If the previous workspace processing did not complete (e.g. // after a clear), build a temporary T-tree so PathFind can @@ -317,6 +318,7 @@ async getWorkspace(uri: vscode.Uri): Promise { // of the temporary T-tree so PathFind reverts to findFiles. gConfigAgent.setWorkspaceProcessComplete(); //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', true); + await vscode.commands.executeCommand('setContext', 'edk2code.isLoading', false); } } @@ -611,10 +613,11 @@ export class EdkWorkspace { gDebugLog.trace(`# Parsing ${type} Document: ${document.uri.fsPath}`); for (let line of text) { lineIndex++; - let originalLine = line; + gDebugLog.trace(`\t\t${lineIndex}: ${line}`); line = this.stripComment(line); - + let originalLine = line; + if (line.length === 0){continue;} // PCDs diff --git a/src/libraryTree.ts b/src/libraryTree.ts deleted file mode 100644 index bbfe918..0000000 --- a/src/libraryTree.ts +++ /dev/null @@ -1,109 +0,0 @@ - -import { gDebugLog, gModuleReport } from "./extension"; -import { EdkIniFile, EdkModule, ModuleReport } from "./moduleReport"; - -import { getNonce } from "./utilities/getNonce"; -import { getUri } from "./utilities/getUri"; -import { getCurrentDocument, gotoFile } from "./utils"; -import * as vscode from 'vscode'; -import { TreeWebview } from "./TreeWebview"; -import { TreeDetailsDataProvider } from "./TreeDataProvider"; -import { TreeItem } from "./treeElements/TreeItem"; - -class ModulePickOption implements vscode.QuickPickItem{ - label: string; - kind?: vscode.QuickPickItemKind | undefined; - description?: string | undefined; - detail?: string | undefined; - picked?: boolean | undefined; - alwaysShow?: boolean | undefined; - buttons?: readonly vscode.QuickInputButton[] | undefined; - module:EdkModule; - constructor(module:EdkModule){ - this.label = module.name; - this.description=module.arch; - this.detail = module.path; - this.module = module; - } - -} - -export async function showLibraryTree(){ - gDebugLog.debug("CMD show library tree"); - let document = getCurrentDocument(); - - let moduleReport = gModuleReport; - if(document === undefined){return;} - - gDebugLog.debug(document.fileName); - - if(!document.fileName.endsWith(".inf")){ - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage("Run this command when you are on a .inf file"); - return; - } - - // if(!gEdkDatabase.isFileInuse(document.fileName)){ - // // eslint-disable-next-line @typescript-eslint/no-floating-promises - // vscode.window.showErrorMessage("This file is not in use on the current compilation"); - // return; - // } - - - if(!moduleReport.isPopulated){ - void vscode.window.showErrorMessage("Module information was not generated during compilation.", "Help").then(async selection => { - if (selection === "Help"){ - await vscode.env.openExternal(vscode.Uri.parse( - 'https://intel.github.io/Edk2Code/advance_features/#enabling-compile-information')); - } - }); - return; - } - - let moduleTarget = moduleReport.getModule(document.fileName); - if(moduleTarget===undefined){ - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage("This works after EDK project is loaded from build folder"); - return; - } - - let contextModule; - if(moduleTarget.isLibrary){ - let moduleUsers = moduleTarget.getUsages(); - var options: ModulePickOption[] = []; - for (const mod of moduleUsers) { - options.push(new ModulePickOption(mod)); - } - - let option = await vscode.window.showQuickPick(options, {title: "This is a library, select module context", matchOnDescription:true, matchOnDetail:true}); - if (option === undefined) {return;} - contextModule = option.module; - }else{ - contextModule = moduleTarget; - } - - let reportObject = moduleReport.getLibraryTree(moduleTarget, contextModule); - let treeProvider = new TreeDetailsDataProvider(); - let root = new TreeItem(reportObject["children"][0]["name"]); - root.description = reportObject["children"][0]["path"]; - treeProvider.addChildren(root); - for (const curerntNode of reportObject["children"][0]["children"]) { - processReport(curerntNode, root); - } - - let treeView = new TreeWebview(treeProvider,moduleTarget.name, contextModule.name); - treeView.render(); - -} - -function processReport(currentNode:any, node: TreeItem){ - let newNode = new TreeItem(currentNode["name"]); - newNode.description = currentNode["path"]; - node.addChildren(newNode); - - if(currentNode["children"].length > 0){ - for (const r of currentNode["children"]) { - processReport(r, newNode); - } - } -} diff --git a/src/treeElements/Source.ts b/src/treeElements/Source.ts index 855ffd4..7e27b9c 100644 --- a/src/treeElements/Source.ts +++ b/src/treeElements/Source.ts @@ -2,12 +2,11 @@ import path = require("path"); import { CompileCommandsEntry } from "../compileCommands"; import { InfParser } from "../edkParser/infParser"; import { getParser } from "../edkParser/parserFactory"; -import { edkLensTreeDetailProvider, gCompileCommands, gDebugLog, gMapFileManager, gPathFind } from "../extension"; +import { gCompileCommands, gDebugLog, gMapFileManager, gPathFind } from "../extension"; import { EdkWorkspace } from "../index/edkWorkspace"; import { EdkSymbol } from "../symbols/edkSymbols"; import { EdkSymbolInfLibrary, EdkSymbolInfSource } from "../symbols/infSymbols"; import { Edk2SymbolType } from "../symbols/symbolsType"; -import { findHeaderIncludes, HeaderFileTreeItemLibraryTree } from "../TreeDataProvider"; import { getAllSymbols, openTextDocument } from "../utils"; import { EdkNode } from "./EdkObject"; import * as vscode from 'vscode'; diff --git a/src/treeElements/TreeItem.ts b/src/treeElements/TreeItem.ts index 40199d6..f1d3cc9 100644 --- a/src/treeElements/TreeItem.ts +++ b/src/treeElements/TreeItem.ts @@ -1,5 +1,4 @@ import * as vscode from 'vscode'; -import { edkLensTreeDetailProvider } from '../extension'; export class TreeItem extends vscode.TreeItem { children: TreeItem[] = []; @@ -51,12 +50,10 @@ export class TreeItem extends vscode.TreeItem { setLoading(){ this.tempIcon = this.iconPath; this.iconPath = new vscode.ThemeIcon("loading~spin"); - edkLensTreeDetailProvider.refresh(); } clearLoading(){ this.iconPath = this.tempIcon; - edkLensTreeDetailProvider.refresh(); } needsExpandProcess(){ diff --git a/src/utils.ts b/src/utils.ts index 29f203f..0e13ab9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,10 +4,8 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import { cwd } from "process"; import { gCompileCommands, gDebugLog, gEdkWorkspaces, gExtensionContext, gWorkspacePath } from "./extension"; -import { TreeDetailsDataProvider } from "./TreeDataProvider"; import { rejects } from "assert"; import { REGEX_PCD } from "./edkParser/commonParser"; -import { TreeItem } from "./treeElements/TreeItem"; var normalizeCache = new Map(); @@ -279,22 +277,7 @@ export async function openTextDocument(uri: vscode.Uri) { -function _copyTreeProviderToClipboardRecursive(item: TreeItem, deep: number, result: any) { - result["content"] += `${" ".repeat(deep)}${item.toString()}\n`; - for (const nextItem of item.children) { - _copyTreeProviderToClipboardRecursive(nextItem, deep + 1, result); - } - -} -export async function copyTreeProviderToClipboard(treeProvider: TreeDetailsDataProvider) { - let result = { "content": "" }; - for (const items of treeProvider.getChildren()) { - _copyTreeProviderToClipboardRecursive(items, 0, result); - } - await copyToClipboard(result["content"]); - -} export async function copyToClipboard(data:string, message:string="Data copied to clipboard"){ await vscode.env.clipboard.writeText(data); From 6fb5cb9ee47634f4f5e97f8564884c424c45d9d7 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 15 Apr 2026 18:13:22 -0500 Subject: [PATCH 44/73] Adding MCP --- .vscode/code-notes.json | 133 ++++++++++++++ package.json | 41 +++-- src/extension.ts | 11 +- src/mcp/mcpServer.ts | 393 ++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 1 + 5 files changed, 563 insertions(+), 16 deletions(-) create mode 100644 .vscode/code-notes.json create mode 100644 src/mcp/mcpServer.ts diff --git a/.vscode/code-notes.json b/.vscode/code-notes.json new file mode 100644 index 0000000..863afee --- /dev/null +++ b/.vscode/code-notes.json @@ -0,0 +1,133 @@ +{ + "version": 1, + "documents": [ + { + "id": "a38557d2-4221-485d-8787-cd40c34c521e", + "name": "OpenRelatedFile", + "exportOrder": [ + "aab8a899-c214-4e5f-9232-fc2ca66b3433", + "08a15e0a-6778-42ea-b66f-45feee2ad377", + "97e94d88-992e-48d2-8f77-df3110889a26", + "b11c1dc8-d908-4d71-8498-f5712a8628b7", + "22534243-2733-4147-853e-067fbb770654", + "abbe66bf-8ed9-467d-aa18-c1afe36d6ba0", + "2569daba-e70c-43a6-8cf7-5ca6f55a8707" + ], + "createdAt": "2026-04-15T22:10:18.485Z" + } + ], + "notes": [ + { + "id": "aab8a899-c214-4e5f-9232-fc2ca66b3433", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\package.json", + "startLine": 74, + "endLine": 77, + "startChar": 0, + "endChar": 8 + }, + "language": "json", + "snippet": " {\r\n \"command\": \"edk2code.gotoInf\",\r\n \"title\": \"EDK2: Goto Inf\"\r\n },", + "noteText": "command definition\n", + "label": "command definition", + "createdAt": "2026-04-15T22:10:36.773Z" + }, + { + "id": "08a15e0a-6778-42ea-b66f-45feee2ad377", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\contextState\\cmds.ts", + "startLine": 241, + "endLine": 268, + "startChar": 4, + "endChar": 5 + }, + "language": "typescript", + "snippet": "export async function gotoInf(fileUri: Uri) {\r\n let locations: vscode.Location[] = [];\r\n\r\n let wps = await gEdkWorkspaces.getWorkspace(fileUri);\r\n if(wps.length){\r\n for (const wp of wps) {\r\n locations = await wp.getInfReference(fileUri);\r\n if(locations.length){\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n }\r\n }\r\n }else{\r\n // Skip if source hasn't been indexed. Just make a search query\r\n let fileName = path.basename(fileUri.fsPath);\r\n let folderPath = path.dirname(fileUri.fsPath);\r\n\r\n let tempLocations = await rgSearch(`\\\\b${fileName}\\\\b`, [\"*.inf\"],[],true);\r\n for (const l of tempLocations) {\r\n // Check the INF file is relative to the source file\r\n if (!path.relative(path.dirname(l.uri.fsPath), folderPath).includes(\"..\")) {\r\n locations.push(l);\r\n }\r\n }\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n\r\n }\r\n\r\n }", + "noteText": "Implementation", + "label": "Implementation", + "createdAt": "2026-04-15T22:10:57.162Z" + }, + { + "id": "97e94d88-992e-48d2-8f77-df3110889a26", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", + "startLine": 873, + "endLine": 946, + "startChar": 4, + "endChar": 5 + }, + "language": "typescript", + "snippet": "async isFileInUse(uri: vscode.Uri) {\r\n\r\n if (this.processComplete) {\r\n \r\n switch (this.getLangId(uri)) {\r\n case \"edk2_dsc\":\r\n case \"edk2_fdf\":\r\n // fdf files can be included in dsc files\r\n for (const dsc of this.filesDsc) {\r\n \r\n if(uri.fsPath === dsc.fileName) {\r\n return true;\r\n }\r\n }\r\n \r\n \r\n \r\n for (const fdf of this.filesFdf) {\r\n if(uri.fsPath === fdf.fileName){\r\n return true;\r\n }\r\n }\r\n return false;\r\n case \"edk2_dec\":\r\n break;\r\n case \"edk2_inf\":\r\n for (const mods of this.filesModules) {\r\n if(uri.fsPath.includes(mods.path)){\r\n return true;\r\n }\r\n }\r\n for (const lib of this.filesLibraries) {\r\n if(uri.fsPath.includes(lib.path)){\r\n return true;\r\n }\r\n }\r\n return false;\r\n \r\n case \"c\":\r\n let relativeInf:string[] = [];\r\n let cDirName = path.dirname(uri.fsPath);\r\n for (const mods of this.filesModules) {\r\n let modDir = path.dirname(mods.path);\r\n if(cDirName.includes(modDir)){\r\n relativeInf.push(mods.path);\r\n }\r\n }\r\n for (const lib of this.filesLibraries) {\r\n let libDir = path.dirname(lib.path);\r\n if(cDirName.includes(libDir)){\r\n relativeInf.push(lib.path);\r\n }\r\n }\r\n for (const inf of relativeInf) {\r\n let infLocation = await gPathFind.findPath(inf);\r\n \r\n if (infLocation.length === 0){continue;}\r\n\r\n let parser = await getParser(infLocation[0].uri);\r\n if (parser) {\r\n let sources = parser.getSymbolsType(Edk2SymbolType.infSource);\r\n for (const source of sources) {\r\n let sourceLocation = await gPathFind.findPath(await source.getValue(), path.dirname(infLocation[0].uri.fsPath));\r\n if(sourceLocation[0].uri.fsPath === uri.fsPath){\r\n return true;\r\n }\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n }\r\n return undefined;\r\n }", + "noteText": "This function is used to know if a file is used in the compilation", + "label": "This function is used to know if a file is used in the compi", + "createdAt": "2026-04-15T22:12:04.212Z" + }, + { + "id": "b11c1dc8-d908-4d71-8498-f5712a8628b7", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\symbols\\infSymbols.ts", + "startLine": 274, + "endLine": 275, + "startChar": 0, + "endChar": 0 + }, + "language": "typescript", + "snippet": "export class EdkSymbolInfLibrary extends EdkSymbol {\r\n", + "noteText": "Inf library symbol\n", + "label": "Inf library symbol", + "createdAt": "2026-04-15T22:14:38.943Z" + }, + { + "id": "22534243-2733-4147-853e-067fbb770654", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\edkParser\\infParser.ts", + "startLine": 149, + "endLine": 160, + "startChar": 0, + "endChar": 1 + }, + "language": "typescript", + "snippet": "class BlockLibraryClassesSection extends BlockParser {\r\n name = \"LibraryClasses\";\r\n tag = /^\\[\\s*LibraryClasses/gi;\r\n start = undefined;\r\n end = /^\\[/gi;\r\n type = Edk2SymbolType.infSectionLibraries;\r\n visible:boolean = true;\r\n context: BlockParser[] = [\r\n new BlockLibrary(),\r\n new BlockSimpleLine(),\r\n ];\r\n}", + "noteText": "Library classes in INF parser\n", + "label": "Library classes in INF parser", + "createdAt": "2026-04-15T22:15:06.418Z" + }, + { + "id": "abbe66bf-8ed9-467d-aa18-c1afe36d6ba0", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\package.json", + "startLine": 370, + "endLine": 375, + "startChar": 6, + "endChar": 10 + }, + "language": "json", + "snippet": "\"editor/context\": [\r\n {\r\n \"command\": \"edk2code.gotoDefinition\",\r\n \"group\": \"navigation\",\r\n \"when\": \"editorLangId == c\"\r\n },", + "noteText": "Example of context menu for C. The open related should be available for all files used in compilation", + "label": "Example of context menu for C. The open related should be av", + "createdAt": "2026-04-15T22:16:33.581Z" + }, + { + "id": "2569daba-e70c-43a6-8cf7-5ca6f55a8707", + "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\extension.ts", + "startLine": 249, + "endLine": 250, + "startChar": 0, + "endChar": 0 + }, + "language": "typescript", + "snippet": "\tedkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider });\r\n", + "noteText": "Workspaceview", + "label": "Workspaceview", + "createdAt": "2026-04-15T22:28:15.924Z" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 3c41107..6c053bb 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "command": "edk2code.debugCommand", "title": "EDK2: Debug command" }, - { "command": "edk2code.saveBuildConfiguration", "title": "EDK2: Save build configuration file" @@ -77,22 +76,18 @@ "command": "edk2code.gotoInf", "title": "EDK2: Goto Inf" }, - { "command": "edk2code.dscUsage", "title": "EDK2: Goto DSC declaration" }, - { "command": "edk2code.dscInclusion", "title": "EDK2: Goto include" }, - { "command": "edk2code.viewWarnings", "title": "EDK2: Check for warnings" }, - { "command": "edk2code.selectWorkspaceView", "title": "EDK2: Select Workspace", @@ -125,6 +120,14 @@ { "command": "edk2code.focusEditorInWorkspaceView", "title": "EDK2: Focus on workspace view" + }, + { + "command": "edk2code.startMcpServer", + "title": "EDK2: Start MCP SSE Server" + }, + { + "command": "edk2code.stopMcpServer", + "title": "EDK2: Stop MCP SSE Server" } ], "configuration": [ @@ -193,6 +196,12 @@ "markdownDescription": "Overwrites the calls to cscope executable path. `(reload of vscode after setup)`", "default": "" }, + "edk2code.mcpServerPort": { + "order": 3, + "type": "number", + "markdownDescription": "Port number for the MCP SSE server", + "default": 3100 + }, "edk2code.extraIgnorePatterns": { "order": 2, "type": "array", @@ -274,7 +283,6 @@ { "command": "edk2code.debugCommand" }, - { "command": "edk2code.saveBuildConfiguration", "when": "edk2code.parseComplete" @@ -283,22 +291,18 @@ "command": "edk2code.gotoInf", "when": "editorLangId == c" }, - { "command": "edk2code.dscUsage", "when": "editorLangId == .inf" }, - { "command": "edk2code.dscInclusion", "when": "editorLangId == edk2_dsc || editorLangId == edk2_fdf" }, - { "command": "edk2code.viewWarnings", "when": "edk2code.parseComplete && (editorLangId == edk2_inf || editorLangId == edk2_dsc)" }, - { "command": "edk2code.selectWorkspaceView", "when": "true" @@ -326,6 +330,14 @@ { "command": "edk2code.focusEditorInWorkspaceView", "when": "false" + }, + { + "command": "edk2code.startMcpServer", + "when": "true" + }, + { + "command": "edk2code.stopMcpServer", + "when": "true" } ], "editor/title": [], @@ -362,19 +374,16 @@ "group": "navigation", "when": "editorLangId == c" }, - { "command": "edk2code.gotoInf", "group": "navigation", "when": "editorLangId == c" }, - { "command": "edk2code.dscUsage", "group": "navigation", "when": "editorLangId == edk2_inf" }, - { "command": "edk2code.dscInclusion", "group": "navigation", @@ -390,7 +399,6 @@ "group": "navigation", "when": "editorLangId == edk2_inf && edk2code.infFileInWorkspaceTree" } - ], "view/item/context": [ { @@ -541,6 +549,7 @@ "draft-release": "node scripts/create_draft_release.js" }, "devDependencies": { + "@types/express": "^5.0.6", "@types/mocha": "^10.0.1", "@types/node": "16.x", "@types/vscode": "^1.77.0", @@ -549,13 +558,15 @@ "@vscode/test-electron": "^2.2.3", "eslint": "^8.39.0", "mocha": "^10.2.0", - "typescript": "^4.9.5" + "typescript": "~5.4" }, "dependencies": { "@bendera/vscode-webview-elements": "^0.11.0", + "@modelcontextprotocol/sdk": "^1.29.0", "@types/glob": "^8.1.0", "@vscode/codicons": "0.0.32", "@vscode/ripgrep": "^1.15.10", + "express": "^5.2.1", "fast-glob": "^3.3.2" } } diff --git a/src/extension.ts b/src/extension.ts index 2a0335d..1263c52 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import { InfDsc } from './index/edkWorkspace'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; import { showReleaseNotes } from './newVersionPage/newVersionMessage'; +import { startMcpServer, stopMcpServer } from './mcp/mcpServer'; // Global variables @@ -221,6 +222,14 @@ export async function activate(context: vscode.ExtensionContext) { edkWorkspaceTreeView ); } + }), + + vscode.commands.registerCommand('edk2code.startMcpServer', async () => { + const portStr = vscode.workspace.getConfiguration('edk2code').get('mcpServerPort', 3100); + await startMcpServer(portStr); + }), + vscode.commands.registerCommand('edk2code.stopMcpServer', () => { + stopMcpServer(); }) ]; @@ -291,7 +300,7 @@ export async function activate(context: vscode.ExtensionContext) { // this method is called when your extension is deactivated export async function deactivate() { - + stopMcpServer(); } diff --git a/src/mcp/mcpServer.ts b/src/mcp/mcpServer.ts new file mode 100644 index 0000000..8d2c7a4 --- /dev/null +++ b/src/mcp/mcpServer.ts @@ -0,0 +1,393 @@ +import * as http from 'http'; +import * as vscode from 'vscode'; +import path = require('path'); +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { gDebugLog, gEdkWorkspaces, gPathFind, gWorkspacePath } from '../extension'; +import { openTextDocument } from '../utils'; +import { getParserForDocument } from '../edkParser/parserFactory'; +import { Edk2SymbolType } from '../symbols/symbolsType'; +import { z } from 'zod'; + +let httpServer: http.Server | undefined; +let mcpServer: McpServer | undefined; +const transports: Record = {}; + +function createMcpServer(): McpServer { + const server = new McpServer( + { name: 'edk2code', version: '1.0.0' }, + { capabilities: { tools: {} } } + ); + + // ── Tool: list_files ──────────────────────────────────────────────── + server.registerTool( + 'list_edk2_workspace_files', + { + description: + 'List all files used in the EDK2 compilation across all loaded workspaces. ' + + 'Returns file names with their full paths. ' + + 'If a file you are looking for is not in this list, it is NOT used in the current compilation.', + inputSchema: { + fileName: z + .string() + .optional() + .describe( + 'Optional regex pattern to filter files by name (e.g. "Pci" to find all PCI-related files, "Pci.*\\.inf" for PCI .inf files). ' + + 'The pattern is matched case-insensitively against the file base name. ' + + 'If no match is found the file is not part of the current compilation.' + ), + }, + }, + async ({ fileName }) => { + const wps = gEdkWorkspaces.workspaces; + const allFiles: { name: string; fullPath: string }[] = []; + + for (const wp of wps) { + for (const filePath of wp.getFilesList()) { + allFiles.push({ + name: path.basename(filePath), + fullPath: filePath, + }); + } + } + + let results = allFiles; + if (fileName) { + try { + const regex = new RegExp(fileName, 'i'); + results = allFiles.filter((f) => regex.test(f.name)); + } catch { + // If invalid regex, fall back to substring match + const needle = fileName.toLowerCase(); + results = allFiles.filter( + (f) => f.name.toLowerCase().includes(needle) + ); + } + } + + if (results.length === 0) { + const msg = fileName + ? `File "${fileName}" was not found in any EDK2 workspace. It is not used in the current compilation.` + : 'No files found. The EDK2 workspace may not be indexed yet. Run "EDK2: Rebuild index database" first.'; + return { content: [{ type: 'text', text: msg }] }; + } + + const text = results + .map((f) => `${f.name}\t${f.fullPath}`) + .join('\n'); + return { content: [{ type: 'text', text }] }; + } + ); + + // ── Tool: resolve_edk2_file_path ──────────────────────────────────── + server.registerTool( + 'resolve_edk2_file_path', + { + description: + 'Resolve an EDK2 relative path or file name to its full filesystem path using the EDK2 path resolution logic. ' + + 'If the file cannot be resolved, it is not used in the current compilation.', + inputSchema: { + filePath: z + .string() + .describe( + 'The EDK2 relative path or file name to resolve (e.g. "MdeModulePkg/Core/Pei/PeiMain.inf").' + ), + }, + }, + async ({ filePath }) => { + const locations = await gPathFind.findPath(filePath); + if (locations.length === 0) { + return { + content: [ + { + type: 'text', + text: `Could not resolve "${filePath}". The file is not used in the current compilation or the workspace is not indexed.`, + }, + ], + }; + } + const text = locations.map((l) => l.uri.fsPath).join('\n'); + return { content: [{ type: 'text', text }] }; + } + ); + + // ── Tool: find_library_implementation ─────────────────────────────── + server.registerTool( + 'find_edk2_library_implementation', + { + description: + 'Find the implementation (INF file) of an EDK2 library by base name. ' + + 'Searches all loaded workspaces for library declarations in DSC files. ' + + 'Returns the library name, the INF path implementing it, the DSC location where it is declared, and the section properties (arch, module type). ' + + 'If no match is found, the library is not used in the current compilation.', + inputSchema: { + libraryName: z + .string() + .describe( + 'Library class name or regex pattern to search for (e.g. "BaseMemoryLib", "Pci.*Lib", "UefiBootServicesTableLib"). ' + + 'Matched case-insensitively against library class names declared in DSC files.' + ), + }, + }, + async ({ libraryName }) => { + const wps = gEdkWorkspaces.workspaces; + const results: string[] = []; + + let regex: RegExp | null = null; + try { + regex = new RegExp(libraryName, 'i'); + } catch { + // invalid regex, fall back to substring + } + + for (const wp of wps) { + for (const lib of wp.filesLibraries) { + const libNameDsc = lib.text.split('|')[0].trim(); + const matches = regex + ? regex.test(libNameDsc) + : libNameDsc.toLowerCase().includes(libraryName.toLowerCase()); + + if (matches) { + const resolvedPaths = await gPathFind.findPath(lib.path); + const fullPath = resolvedPaths.length + ? resolvedPaths[0].uri.fsPath + : lib.path; + const dscFile = lib.location.uri.fsPath; + const dscLine = lib.location.range.start.line + 1; + const section = lib.sectionProperties.toString() || 'unknown'; + results.push( + `${libNameDsc}\t${fullPath}\t${dscFile}:${dscLine}\t[${section}]` + ); + } + } + } + + if (results.length === 0) { + return { + content: [ + { + type: 'text', + text: `No library matching "${libraryName}" was found in any EDK2 workspace. It is not used in the current compilation.`, + }, + ], + }; + } + + const header = 'LibraryClass\tImplementation\tDSC Location\tSection'; + return { + content: [{ type: 'text', text: header + '\n' + results.join('\n') }], + }; + } + ); + + // ── Tool: find_inf_for_source ─────────────────────────────────────── + server.registerTool( + 'find_edk2_inf_for_source', + { + description: + 'Given a C/H source file, find the EDK2 INF file (library or component) that includes it in its [Sources] section. ' + + 'This is useful for determining which EDK2 module or library a source file belongs to. ' + + 'If no INF is found, the file may not be part of a compiled EDK2 component.', + inputSchema: { + sourceFile: z + .string() + .describe( + 'Full path or file name of a C/H source file (e.g. "PeiMain.c", "D:/edk2/MdeModulePkg/Core/Pei/PeiMain.c"). ' + + 'If only a file name is given, the tool will attempt to resolve it.' + ), + }, + }, + async ({ sourceFile }) => { + // Resolve to a URI + let fileUri: vscode.Uri; + if (path.isAbsolute(sourceFile)) { + fileUri = vscode.Uri.file(sourceFile); + } else { + const resolved = await gPathFind.findPath(sourceFile); + if (resolved.length === 0) { + return { + content: [{ + type: 'text', + text: `Could not resolve "${sourceFile}". The file was not found in the workspace.`, + }], + }; + } + fileUri = resolved[0].uri; + } + + const results: string[] = []; + + // Try indexed workspaces first (like gotoInf) + const wps = await gEdkWorkspaces.getWorkspace(fileUri); + if (wps.length) { + for (const wp of wps) { + const locations = await wp.getInfReference(fileUri); + for (const loc of locations) { + const infPath = loc.uri.fsPath; + const line = loc.range.start.line + 1; + if (!results.includes(infPath)) { + results.push(infPath); + } + } + } + } + + // Fallback: walk up from the source file looking for INF files + // that list it in their [Sources] section + if (results.length === 0) { + const fileName = path.basename(fileUri.fsPath); + const folderPath = path.dirname(fileUri.fsPath); + const workspaceRoot = gWorkspacePath; + + let currentDir = folderPath; + while (currentDir.length >= workspaceRoot.length) { + // Check all modules and libraries whose INF is relative to this dir + for (const wp of gEdkWorkspaces.workspaces) { + const allInfs = [...wp.filesModules, ...wp.filesLibraries]; + for (const inf of allInfs) { + const resolvedInf = await gPathFind.findPath(inf.path); + if (resolvedInf.length === 0) { continue; } + const infDir = path.dirname(resolvedInf[0].uri.fsPath); + if (!folderPath.startsWith(infDir)) { continue; } + try { + const doc = await openTextDocument(resolvedInf[0].uri); + const parser = await getParserForDocument(doc); + if (parser) { + const sources = parser.getSymbolsType(Edk2SymbolType.infSource); + for (const source of sources) { + const srcPath = await gPathFind.findPath( + await source.getValue(), + path.dirname(resolvedInf[0].uri.fsPath) + ); + if (srcPath.length && srcPath[0].uri.fsPath === fileUri.fsPath) { + const infFullPath = resolvedInf[0].uri.fsPath; + if (!results.includes(infFullPath)) { + results.push(infFullPath); + } + } + } + } + } catch { /* skip parse errors */ } + } + } + if (results.length > 0) { break; } + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) { break; } + currentDir = parentDir; + } + } + + if (results.length === 0) { + return { + content: [{ + type: 'text', + text: `No INF file found for "${sourceFile}". The file may not be part of a compiled EDK2 component or library.`, + }], + }; + } + + const text = results.map((infPath) => { + const name = path.basename(infPath, '.inf'); + return `${name}\t${infPath}`; + }).join('\n'); + return { + content: [{ type: 'text', text: `INF files for ${path.basename(sourceFile)}:\n${text}` }], + }; + } + ); + + return server; +} + +export async function startMcpServer(port: number): Promise { + if (httpServer) { + vscode.window.showInformationMessage(`MCP SSE server is already running on port ${port}`); + return; + } + + mcpServer = createMcpServer(); + + httpServer = http.createServer(async (req, res) => { + const url = new URL(req.url ?? '', `http://localhost:${port}`); + + // SSE stream endpoint + if (req.method === 'GET' && url.pathname === '/sse') { + gDebugLog.info('MCP SSE: new client connection'); + try { + const transport = new SSEServerTransport('/messages', res); + transports[transport.sessionId] = transport; + transport.onclose = () => { + gDebugLog.info(`MCP SSE: session ${transport.sessionId} closed`); + delete transports[transport.sessionId]; + }; + const server = createMcpServer(); + await server.connect(transport); + } catch (err) { + gDebugLog.error(`MCP SSE error: ${err}`); + if (!res.headersSent) { + res.writeHead(500); + res.end('Internal Server Error'); + } + } + return; + } + + // Message endpoint for clientβ†’server JSON-RPC + if (req.method === 'POST' && url.pathname === '/messages') { + const sessionId = url.searchParams.get('sessionId'); + if (!sessionId || !transports[sessionId]) { + res.writeHead(400); + res.end('Invalid or missing sessionId'); + return; + } + await transports[sessionId].handlePostMessage(req, res); + return; + } + + // Health check + if (req.method === 'GET' && url.pathname === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok' })); + return; + } + + res.writeHead(404); + res.end('Not Found'); + }); + + return new Promise((resolve, reject) => { + httpServer!.listen(port, () => { + gDebugLog.info(`MCP SSE server listening on http://localhost:${port}/sse`); + vscode.window.showInformationMessage( + `EDK2 MCP SSE server started on http://localhost:${port}/sse` + ); + resolve(); + }); + httpServer!.on('error', (err) => { + gDebugLog.error(`MCP SSE server error: ${err}`); + vscode.window.showErrorMessage(`Failed to start MCP server: ${err.message}`); + httpServer = undefined; + reject(err); + }); + }); +} + +export function stopMcpServer(): void { + if (!httpServer) { + vscode.window.showInformationMessage('MCP SSE server is not running'); + return; + } + for (const id of Object.keys(transports)) { + transports[id].close(); + delete transports[id]; + } + httpServer.close(); + httpServer = undefined; + mcpServer = undefined; + gDebugLog.info('MCP SSE server stopped'); + vscode.window.showInformationMessage('EDK2 MCP SSE server stopped'); +} + +export function isMcpServerRunning(): boolean { + return httpServer !== undefined; +} diff --git a/tsconfig.json b/tsconfig.json index 9c9c7a2..b584063 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "allowJs": true, "rootDir": "src", "strict": true /* enable all strict type-checking options */, + "skipLibCheck": true, /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ From bceb775b9a84e47edbe4a372ebdaec30b16d190c Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 14:10:24 -0500 Subject: [PATCH 45/73] Adding inactive elements filter --- .vscode/code-notes.json | 58 ++++++++++++++++++ package.json | 4 +- src/index/edkWorkspace.ts | 4 ++ src/symbols/symbolsType.ts | 2 + src/workspaceTree/WorkspaceTreeProvider.ts | 68 +++++++++++++++++----- 5 files changed, 118 insertions(+), 18 deletions(-) diff --git a/.vscode/code-notes.json b/.vscode/code-notes.json index 863afee..6c3f873 100644 --- a/.vscode/code-notes.json +++ b/.vscode/code-notes.json @@ -14,6 +14,16 @@ "2569daba-e70c-43a6-8cf7-5ca6f55a8707" ], "createdAt": "2026-04-15T22:10:18.485Z" + }, + { + "id": "195e6f57-f53b-4be5-898c-9ae963c5970e", + "name": "InactiveNodes", + "exportOrder": [ + "b6abd314-ee52-4f1d-a43a-62df3a67c43e", + "6ae6aa19-867a-4b2e-b23f-9da0b8f7deb6", + "d79a4050-27d5-4f01-9783-e0001b92f5ca" + ], + "createdAt": "2026-04-16T18:47:07.516Z" } ], "notes": [ @@ -128,6 +138,54 @@ "noteText": "Workspaceview", "label": "Workspaceview", "createdAt": "2026-04-15T22:28:15.924Z" + }, + { + "id": "b6abd314-ee52-4f1d-a43a-62df3a67c43e", + "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", + "startLine": 645, + "endLine": 646, + "startChar": 0, + "endChar": 0 + }, + "language": "typescript", + "snippet": " let isInActiveCode = this.processConditional(line, lineIndex, document.uri);\r\n", + "noteText": "Here workspace parsing decides which lines are inactive", + "label": "Here workspace parsing decides which lines are inactive", + "createdAt": "2026-04-16T18:47:21.505Z" + }, + { + "id": "6ae6aa19-867a-4b2e-b23f-9da0b8f7deb6", + "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\workspaceTree\\WorkspaceTreeProvider.ts", + "startLine": 175, + "endLine": 176, + "startChar": 0, + "endChar": 0 + }, + "language": "typescript", + "snippet": "export type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem;\r\n", + "noteText": "Tree nodes. WorkspaceTreeNode should know if its active or not\n", + "label": "Tree nodes. WorkspaceTreeNode should know if its active or n", + "createdAt": "2026-04-16T18:49:17.125Z" + }, + { + "id": "d79a4050-27d5-4f01-9783-e0001b92f5ca", + "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\workspaceTree\\WorkspaceTreeProvider.ts", + "startLine": 10, + "endLine": 20, + "startChar": 0, + "endChar": 0 + }, + "language": "typescript", + "snippet": "export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; description: string }[] = [\r\n { type: Edk2SymbolType.dscDefine, label: 'Defines', description: 'dscDefine' },\r\n { type: Edk2SymbolType.dscLibraryDefinition, label: 'Library classes', description: 'dscLibraryDefinition' },\r\n { type: Edk2SymbolType.dscModuleDefinition, label: 'Components', description: 'dscModuleDefinition' },\r\n { type: Edk2SymbolType.dscSection, label: 'Sections', description: 'dscSection' },\r\n { type: Edk2SymbolType.dscBuildOptionsSection, label: 'Build options', description: 'dscBuildOptionsSection' },\r\n { type: Edk2SymbolType.dscBuildOption, label: 'Build option entries', description: 'dscBuildOption' },\r\n { type: Edk2SymbolType.dscPcdDefinition, label: 'PCDs', description: 'dscPcdDefinition' },\r\n { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' },\r\n];\r\n", + "noteText": "Filters\n", + "label": "Filters", + "createdAt": "2026-04-16T18:59:06.838Z" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 6c053bb..7f92d7f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code-dev", "displayName": "Edk2code-dev", "description": "EDK2 code support", - "version": "1.1.4", + "version": "1.1.5", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", @@ -91,7 +91,7 @@ { "command": "edk2code.selectWorkspaceView", "title": "EDK2: Select Workspace", - "icon": "$(list-selection)" + "icon": "$(repo)" }, { "command": "edk2code.copyWorkspaceTree", diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 3cf14a3..9ec0515 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -987,6 +987,10 @@ export class EdkWorkspace { return this.parsedDocuments.get(document.uri.fsPath) || []; } + getGrayoutRangeByUri(uri: vscode.Uri): vscode.Range[] { + return this.parsedDocuments.get(uri.fsPath) || []; + } + async findDefinesFdf() { diff --git a/src/symbols/symbolsType.ts b/src/symbols/symbolsType.ts index 13c99a0..c2f179e 100644 --- a/src/symbols/symbolsType.ts +++ b/src/symbols/symbolsType.ts @@ -64,6 +64,8 @@ export enum Edk2SymbolType { fdfInclude, condition, unknown, + /** Sentinel used by the workspace tree filter to toggle visibility of inactive nodes. */ + showInactiveNodes, } export var typeToStr: Map = new Map( diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index a399c49..e8965bc 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -17,6 +17,7 @@ export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; descriptio { type: Edk2SymbolType.dscBuildOption, label: 'Build option entries', description: 'dscBuildOption' }, { type: Edk2SymbolType.dscPcdDefinition, label: 'PCDs', description: 'dscPcdDefinition' }, { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' }, + { type: Edk2SymbolType.showInactiveNodes, label: 'Inactive elements', description: 'Show elements inside inactive !if/!else blocks' }, ]; // Structural / container types that are always visible in the tree regardless of @@ -31,6 +32,17 @@ function isTypeVisible(type: Edk2SymbolType, activeFilters: Set) return activeFilters.has(type) || STRUCTURAL_TYPES.has(type); } +/** + * Returns true when the symbol's selection range falls inside any of the + * grayout (inactive conditional) ranges for its file. + */ +function isSymbolInactive(symbol: EdkSymbol, fileUri: vscode.Uri, workspace: EdkWorkspace | undefined): boolean { + if (!workspace) { return false; } + const grayoutRanges = workspace.getGrayoutRangeByUri(fileUri); + const symLine = symbol.selectionRange.start.line; + return grayoutRanges.some(r => symLine >= r.start.line && symLine <= r.end.line); +} + // ─── Helper: load symbols for a URI via the parser ─────────────────────────── async function loadSymbols(uri: vscode.Uri): Promise { @@ -110,18 +122,24 @@ export class WorkspaceRootItem extends vscode.TreeItem { export class IncludeTreeItem extends vscode.TreeItem { public readonly treePath: string[]; + public readonly inactive: boolean; - constructor(public readonly node: IncludeNode, parentPath: string[]) { + constructor(public readonly node: IncludeNode, parentPath: string[], inactive: boolean = false) { const label = path.basename(node.uri.fsPath); super(label, vscode.TreeItemCollapsibleState.Collapsed); + this.inactive = inactive; this.treePath = [...parentPath, vscode.workspace.asRelativePath(node.uri, false)]; - this.description = vscode.workspace.asRelativePath(node.uri, false); + this.description = inactive + ? `${vscode.workspace.asRelativePath(node.uri, false)} (inactive)` + : vscode.workspace.asRelativePath(node.uri, false); this.tooltip = new vscode.MarkdownString( - `**Included file**\n\n\`${node.uri.fsPath}\`\n\n` + + `**Included file**${inactive ? ' *(inactive)*' : ''}\n\n\`${node.uri.fsPath}\`\n\n` + `Directive at: \`${node.location.uri.fsPath}:${node.location.range.start.line + 1}\`` ); - this.iconPath = new vscode.ThemeIcon('file'); - this.contextValue = 'includeNode'; + this.iconPath = inactive + ? new vscode.ThemeIcon('file', new vscode.ThemeColor('disabledForeground')) + : new vscode.ThemeIcon('file'); + this.contextValue = inactive ? 'includeNodeInactive' : 'includeNode'; // Clicking jumps to the !include directive in the parent file this.command = { command: 'edk2code.gotoFile', @@ -136,13 +154,15 @@ export class IncludeTreeItem extends vscode.TreeItem { export class DocumentSymbolItem extends vscode.TreeItem { public readonly symbolType: Edk2SymbolType; public readonly treePath: string[]; + public readonly inactive: boolean; constructor( public readonly symbol: EdkSymbol, public readonly fileUri: vscode.Uri, activeFilters: Set, parentPath: string[], - public readonly parent: WorkspaceRootItem | DocumentSymbolItem | undefined + public readonly parent: WorkspaceRootItem | DocumentSymbolItem | undefined, + inactive: boolean = false ) { const visibleChildren = symbol.children.filter( c => isTypeVisible((c as EdkSymbol).type, activeFilters) @@ -154,16 +174,21 @@ export class DocumentSymbolItem extends vscode.TreeItem { ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None ); + this.inactive = inactive; this.treePath = [...parentPath, symbol.name]; // Include the parent path in the id so the same file/symbol included from // multiple places in the tree gets a unique id for each occurrence. const parentKey = parentPath.join('/'); this.id = `dsi:${parentKey}:${fileUri.fsPath}:${symbol.selectionRange.start.line}:${symbol.selectionRange.start.character}`; this.symbolType = symbol.type; - this.description = symbol.detail || undefined; - this.tooltip = symbol.name; - this.iconPath = EdkSymbol.iconForKind(symbol.kind); - this.contextValue = 'symbolNode'; + this.description = inactive + ? `${symbol.detail || ''} (inactive)`.trim() + : (symbol.detail || undefined); + this.tooltip = inactive ? `${symbol.name} (inactive)` : symbol.name; + this.iconPath = inactive + ? new vscode.ThemeIcon(EdkSymbol.iconForKind(symbol.kind).id, new vscode.ThemeColor('disabledForeground')) + : EdkSymbol.iconForKind(symbol.kind); + this.contextValue = inactive ? 'symbolNodeInactive' : 'symbolNode'; // Clicking navigates to the symbol's location in its file this.command = { command: 'edk2code.gotoFile', @@ -448,47 +473,58 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) - .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element)); + .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element, + isSymbolInactive(s, element.workspace.mainDsc, ws))) + .filter(s => showInactive || !s.inactive); } // Under an include node: symbols of that file only if (element instanceof IncludeTreeItem) { + const inactive = element.inactive; const symbols = await loadSymbols(element.node.uri); return symbols .filter(s => isTypeVisible(s.type, this._activeFilters)) - .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined)); + .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined, + inactive || isSymbolInactive(s, element.node.uri, ws))) + .filter(s => showInactive || !s.inactive); } // Under a symbol: for !include directives expand into the included file; // for all other symbols expand their parsed children. if (element instanceof DocumentSymbolItem) { if (element.symbolType === Edk2SymbolType.dscInclude) { - const ws = gEdkWorkspaces.workspaces[this._activeIndex]; if (ws) { const node = findIncludeNode(ws.includeTree, element.symbol.location); if (node) { const symbols = await loadSymbols(node.uri); return symbols .filter(s => isTypeVisible(s.type, this._activeFilters)) - .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element)); + .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element, + element.inactive || isSymbolInactive(s, node.uri, ws))) + .filter(s => showInactive || !s.inactive); } } } return (element.symbol.children as EdkSymbol[]) .filter(c => isTypeVisible(c.type, this._activeFilters)) - .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element)); + .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element, + element.inactive || isSymbolInactive(child, element.fileUri, ws))) + .filter(s => showInactive || !s.inactive); } return []; From 107d425e6b02885bf9191da5e541eee63c1d950f Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 14:54:22 -0500 Subject: [PATCH 46/73] Adding overwrite description in nodes --- .vscode/code-notes.json | 36 +++++++++- package.json | 29 ++++++++ src/diagnostics.ts | 10 +++ src/extension.ts | 6 ++ src/workspaceTree/WorkspaceTreeProvider.ts | 77 ++++++++++++++++++---- 5 files changed, 144 insertions(+), 14 deletions(-) diff --git a/.vscode/code-notes.json b/.vscode/code-notes.json index 6c3f873..fad4066 100644 --- a/.vscode/code-notes.json +++ b/.vscode/code-notes.json @@ -21,7 +21,9 @@ "exportOrder": [ "b6abd314-ee52-4f1d-a43a-62df3a67c43e", "6ae6aa19-867a-4b2e-b23f-9da0b8f7deb6", - "d79a4050-27d5-4f01-9783-e0001b92f5ca" + "d79a4050-27d5-4f01-9783-e0001b92f5ca", + "1a8a3b3e-f5c7-4f6c-b77f-a09d68d7dc32", + "489f90e4-6146-4d35-926a-a669b3d0416f" ], "createdAt": "2026-04-16T18:47:07.516Z" } @@ -186,6 +188,38 @@ "noteText": "Filters\n", "label": "Filters", "createdAt": "2026-04-16T18:59:06.838Z" + }, + { + "id": "1a8a3b3e-f5c7-4f6c-b77f-a09d68d7dc32", + "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", + "startLine": 705, + "endLine": 712, + "startChar": 0, + "endChar": 30 + }, + "language": "typescript", + "snippet": " DiagnosticManager.warning(\r\n document.uri,\r\n lineIndex,\r\n EdkDiagnosticCodes.duplicateDefine,\r\n `'${key}' is redefined without referencing its previous value. Use $(${key}) to extend it.`,\r\n [],\r\n relatedInfo\r\n );", + "noteText": "Warning: Duplicate defines ", + "label": "Warning: Duplicate defines", + "createdAt": "2026-04-16T19:11:30.428Z" + }, + { + "id": "489f90e4-6146-4d35-926a-a669b3d0416f", + "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", + "location": { + "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", + "startLine": 783, + "endLine": 788, + "startChar": 26, + "endChar": 0 + }, + "language": "typescript", + "snippet": " DiagnosticManager.warning(previousLibDefinition.location.uri, previousLibDefinition.location.range.start.line,\r\n EdkDiagnosticCodes.duplicateStatement,\r\n `Library overwritten: ${libName}`,\r\n [vscode.DiagnosticTag.Unnecessary],\r\n [new vscode.DiagnosticRelatedInformation(newLibDefinition.location, \"New definition\")]);\r\n", + "noteText": "Duplicate statement warning", + "label": "Duplicate statement warning", + "createdAt": "2026-04-16T19:11:52.130Z" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 7f92d7f..ee0cace 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,11 @@ { "command": "edk2code.stopMcpServer", "title": "EDK2: Stop MCP SSE Server" + }, + { + "command": "edk2code.gotoOverwrite", + "title": "Go to overwriting definition", + "icon": "$(go-to-file)" } ], "configuration": [ @@ -338,6 +343,10 @@ { "command": "edk2code.stopMcpServer", "when": "true" + }, + { + "command": "edk2code.gotoOverwrite", + "when": "false" } ], "editor/title": [], @@ -415,6 +424,26 @@ "command": "edk2code.copyWorkspaceNodePath", "group": "navigation", "when": "view == workspaceView && viewItem == symbolNode" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "group": "navigation", + "when": "view == workspaceView && viewItem == symbolNodeOverwritten" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "group": "navigation", + "when": "view == workspaceView && viewItem == symbolNodeInactiveOverwritten" + }, + { + "command": "edk2code.gotoOverwrite", + "group": "navigation", + "when": "view == workspaceView && viewItem == symbolNodeOverwritten" + }, + { + "command": "edk2code.gotoOverwrite", + "group": "navigation", + "when": "view == workspaceView && viewItem == symbolNodeInactiveOverwritten" } ] }, diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 3ff195d..ce1626d 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -128,6 +128,16 @@ export class DiagnosticManager { return DiagnosticManager.diagnostics.get(documentUri.fsPath) || []; } + /** + * Find a diagnostic matching one of the given codes at a specific line for a URI. + * Returns the first matching diagnostic, or undefined. + */ + public static findDiagnosticAt(documentUri: vscode.Uri, line: number, codes: EdkDiagnosticCodes[]): vscode.Diagnostic | undefined { + const diags = DiagnosticManager.diagnostics.get(documentUri.fsPath); + if (!diags) { return undefined; } + return diags.find(d => d.range.start.line === line && codes.includes(d.code as number)); + } + private static clearDiagnostic(documentUri: vscode.Uri) { DiagnosticManager.diagnostics.delete(documentUri.fsPath); } diff --git a/src/extension.ts b/src/extension.ts index 1263c52..ae8322c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -143,6 +143,12 @@ export async function activate(context: vscode.ExtensionContext) { await edkWorkspaceTreeProvider.showFilterPicker(); }), + vscode.commands.registerCommand('edk2code.gotoOverwrite', async (node: DocumentSymbolItem) => { + if (node?.overwrittenBy) { + await gotoFile(node.overwrittenBy.uri, node.overwrittenBy.range); + } + }), + vscode.commands.registerCommand('edk2code.refreshWorkspaceConfig', async () => { await gEdkWorkspaces.loadConfig(); }), diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index e8965bc..953b9b2 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -5,6 +5,7 @@ import { getParser } from '../edkParser/parserFactory'; import { EdkSymbol } from '../symbols/edkSymbols'; import { Edk2SymbolType } from '../symbols/symbolsType'; import { edkWorkspaceTreeView, gConfigAgent, gEdkWorkspaces } from '../extension'; +import { DiagnosticManager, EdkDiagnosticCodes } from '../diagnostics'; // ─── DSC symbol types available for filtering ───────────────────────────────── @@ -43,6 +44,20 @@ function isSymbolInactive(symbol: EdkSymbol, fileUri: vscode.Uri, workspace: Edk return grayoutRanges.some(r => symLine >= r.start.line && symLine <= r.end.line); } +/** + * If the symbol has a duplicateDefine or duplicateStatement diagnostic, + * returns the location of the definition that overwrites it (from relatedInformation). + */ +function getOverwriteLocation(symbol: EdkSymbol, fileUri: vscode.Uri): vscode.Location | undefined { + const diag = DiagnosticManager.findDiagnosticAt( + fileUri, + symbol.selectionRange.start.line, + [EdkDiagnosticCodes.duplicateDefine, EdkDiagnosticCodes.duplicateStatement] + ); + if (!diag?.relatedInformation?.length) { return undefined; } + return diag.relatedInformation[0].location; +} + // ─── Helper: load symbols for a URI via the parser ─────────────────────────── async function loadSymbols(uri: vscode.Uri): Promise { @@ -155,6 +170,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { public readonly symbolType: Edk2SymbolType; public readonly treePath: string[]; public readonly inactive: boolean; + public readonly overwrittenBy: vscode.Location | undefined; constructor( public readonly symbol: EdkSymbol, @@ -162,7 +178,8 @@ export class DocumentSymbolItem extends vscode.TreeItem { activeFilters: Set, parentPath: string[], public readonly parent: WorkspaceRootItem | DocumentSymbolItem | undefined, - inactive: boolean = false + inactive: boolean = false, + overwrittenBy?: vscode.Location ) { const visibleChildren = symbol.children.filter( c => isTypeVisible((c as EdkSymbol).type, activeFilters) @@ -175,20 +192,50 @@ export class DocumentSymbolItem extends vscode.TreeItem { : vscode.TreeItemCollapsibleState.None ); this.inactive = inactive; + this.overwrittenBy = overwrittenBy; this.treePath = [...parentPath, symbol.name]; // Include the parent path in the id so the same file/symbol included from // multiple places in the tree gets a unique id for each occurrence. const parentKey = parentPath.join('/'); this.id = `dsi:${parentKey}:${fileUri.fsPath}:${symbol.selectionRange.start.line}:${symbol.selectionRange.start.character}`; this.symbolType = symbol.type; - this.description = inactive - ? `${symbol.detail || ''} (inactive)`.trim() - : (symbol.detail || undefined); - this.tooltip = inactive ? `${symbol.name} (inactive)` : symbol.name; - this.iconPath = inactive - ? new vscode.ThemeIcon(EdkSymbol.iconForKind(symbol.kind).id, new vscode.ThemeColor('disabledForeground')) - : EdkSymbol.iconForKind(symbol.kind); - this.contextValue = inactive ? 'symbolNodeInactive' : 'symbolNode'; + + // Build description and context based on state + const isOverwritten = !!overwrittenBy; + let desc = symbol.detail || ''; + let ctx = 'symbolNode'; + if (inactive && isOverwritten) { + desc = `${desc} (inactive, overwritten)`.trim(); + ctx = 'symbolNodeInactiveOverwritten'; + } else if (inactive) { + desc = `${desc} (inactive)`.trim(); + ctx = 'symbolNodeInactive'; + } else if (isOverwritten) { + desc = `${desc} (overwritten)`.trim(); + ctx = 'symbolNodeOverwritten'; + } + this.description = desc || undefined; + + if (isOverwritten) { + const relPath = vscode.workspace.asRelativePath(overwrittenBy.uri, false); + const line = overwrittenBy.range.start.line + 1; + this.tooltip = new vscode.MarkdownString( + `~~${symbol.name}~~ *(overwritten)*\n\nOverwritten by: \`${relPath}:${line}\`` + ); + } else { + this.tooltip = inactive ? `${symbol.name} (inactive)` : symbol.name; + } + + if (inactive || isOverwritten) { + this.iconPath = new vscode.ThemeIcon( + EdkSymbol.iconForKind(symbol.kind).id, + new vscode.ThemeColor('disabledForeground') + ); + } else { + this.iconPath = EdkSymbol.iconForKind(symbol.kind); + } + + this.contextValue = ctx; // Clicking navigates to the symbol's location in its file this.command = { command: 'edk2code.gotoFile', @@ -489,7 +536,8 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element, - isSymbolInactive(s, element.workspace.mainDsc, ws))) + isSymbolInactive(s, element.workspace.mainDsc, ws), + getOverwriteLocation(s, element.workspace.mainDsc))) .filter(s => showInactive || !s.inactive); } @@ -500,7 +548,8 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined, - inactive || isSymbolInactive(s, element.node.uri, ws))) + inactive || isSymbolInactive(s, element.node.uri, ws), + getOverwriteLocation(s, element.node.uri))) .filter(s => showInactive || !s.inactive); } @@ -515,7 +564,8 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element, - element.inactive || isSymbolInactive(s, node.uri, ws))) + element.inactive || isSymbolInactive(s, node.uri, ws), + getOverwriteLocation(s, node.uri))) .filter(s => showInactive || !s.inactive); } } @@ -523,7 +573,8 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(c.type, this._activeFilters)) .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element, - element.inactive || isSymbolInactive(child, element.fileUri, ws))) + element.inactive || isSymbolInactive(child, element.fileUri, ws), + getOverwriteLocation(child, element.fileUri))) .filter(s => showInactive || !s.inactive); } From c3c551d8392e84aaf1d3d5539354bed6e3335e27 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 15:22:38 -0500 Subject: [PATCH 47/73] Fixing range provided for definitions --- src/Languages/definitionProvider.ts | 11 +++++++++-- src/extension.ts | 5 +++++ src/symbols/edkSymbols.ts | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Languages/definitionProvider.ts b/src/Languages/definitionProvider.ts index 3bdcecc..0cccf50 100644 --- a/src/Languages/definitionProvider.ts +++ b/src/Languages/definitionProvider.ts @@ -63,8 +63,15 @@ export class EdkDefinitionProvider implements vscode.DefinitionProvider { if (!selectedSymbol) { return []; } gDebugLog.trace(`Definition for: ${selectedSymbol.toString()}`); if (selectedSymbol.onDefinition !== undefined) { - let temp = await selectedSymbol.onDefinition(parser); - return temp; + let locations: vscode.Location[] = await selectedSymbol.onDefinition(parser); + if (!locations || locations.length === 0) { return []; } + // Return LocationLink[] so VS Code highlights the full symbol range on Ctrl+hover + return locations.map(loc => ({ + originSelectionRange: selectedSymbol!.selectionRange, + targetUri: loc.uri, + targetRange: loc.range, + targetSelectionRange: loc.range, + } as vscode.LocationLink)); } } } diff --git a/src/extension.ts b/src/extension.ts index ae8322c..bcc174c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -146,6 +146,11 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.gotoOverwrite', async (node: DocumentSymbolItem) => { if (node?.overwrittenBy) { await gotoFile(node.overwrittenBy.uri, node.overwrittenBy.range); + await edkWorkspaceTreeProvider.revealLocation( + node.overwrittenBy.uri, + node.overwrittenBy.range.start, + edkWorkspaceTreeView + ); } }), diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index 349b592..ce3dc74 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -51,6 +51,21 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { updateRange(range:vscode.Range){ this.location.range = range; this.range = range; + // Trim selectionRange to the uncommented, non-whitespace content on the first line. + // _textLine is already the trimmed, comment-free text from parsing. + const lineStart = range.start.line; + const content = this._textLine; + if (content.length > 0 && lineStart < this.parser.document.lineCount) { + const rawLine = this.parser.document.lineAt(lineStart).text; + const idx = rawLine.indexOf(content); + if (idx >= 0) { + this.selectionRange = new vscode.Range( + lineStart, idx, + lineStart, idx + content.length + ); + return; + } + } this.selectionRange = range; } From 01172ee107674ca05ec0271363e479c321c2d014 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 15:27:29 -0500 Subject: [PATCH 48/73] Adding color definition to icons --- src/symbols/edkSymbols.ts | 60 ++++++++++++---------- src/workspaceTree/WorkspaceTreeProvider.ts | 6 +-- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/symbols/edkSymbols.ts b/src/symbols/edkSymbols.ts index ce3dc74..4398bcf 100644 --- a/src/symbols/edkSymbols.ts +++ b/src/symbols/edkSymbols.ts @@ -175,35 +175,39 @@ export abstract class EdkSymbol extends vscode.DocumentSymbol { } static iconForKind(kind: vscode.SymbolKind): vscode.ThemeIcon { - const map: Partial> = { - [vscode.SymbolKind.File]: 'symbol-file', - [vscode.SymbolKind.Module]: 'symbol-module', - [vscode.SymbolKind.Namespace]: 'symbol-namespace', - [vscode.SymbolKind.Package]: 'symbol-package', - [vscode.SymbolKind.Class]: 'symbol-class', - [vscode.SymbolKind.Method]: 'symbol-method', - [vscode.SymbolKind.Property]: 'symbol-property', - [vscode.SymbolKind.Field]: 'symbol-field', - [vscode.SymbolKind.Constructor]: 'symbol-constructor', - [vscode.SymbolKind.Enum]: 'symbol-enum', - [vscode.SymbolKind.Interface]: 'symbol-interface', - [vscode.SymbolKind.Function]: 'symbol-function', - [vscode.SymbolKind.Variable]: 'symbol-variable', - [vscode.SymbolKind.Constant]: 'symbol-constant', - [vscode.SymbolKind.String]: 'symbol-string', - [vscode.SymbolKind.Number]: 'symbol-number', - [vscode.SymbolKind.Boolean]: 'symbol-boolean', - [vscode.SymbolKind.Array]: 'symbol-array', - [vscode.SymbolKind.Object]: 'symbol-object', - [vscode.SymbolKind.Key]: 'symbol-key', - [vscode.SymbolKind.Null]: 'symbol-null', - [vscode.SymbolKind.EnumMember]: 'symbol-enum-member', - [vscode.SymbolKind.Struct]: 'symbol-struct', - [vscode.SymbolKind.Event]: 'symbol-event', - [vscode.SymbolKind.Operator]: 'symbol-operator', - [vscode.SymbolKind.TypeParameter]: 'symbol-type-parameter', + const map: Partial> = { + [vscode.SymbolKind.File]: { icon: 'symbol-file', color: 'symbolIcon.fileForeground' }, + [vscode.SymbolKind.Module]: { icon: 'symbol-module', color: 'symbolIcon.moduleForeground' }, + [vscode.SymbolKind.Namespace]: { icon: 'symbol-namespace', color: 'symbolIcon.namespaceForeground' }, + [vscode.SymbolKind.Package]: { icon: 'symbol-package', color: 'symbolIcon.packageForeground' }, + [vscode.SymbolKind.Class]: { icon: 'symbol-class', color: 'symbolIcon.classForeground' }, + [vscode.SymbolKind.Method]: { icon: 'symbol-method', color: 'symbolIcon.methodForeground' }, + [vscode.SymbolKind.Property]: { icon: 'symbol-property', color: 'symbolIcon.propertyForeground' }, + [vscode.SymbolKind.Field]: { icon: 'symbol-field', color: 'symbolIcon.fieldForeground' }, + [vscode.SymbolKind.Constructor]: { icon: 'symbol-constructor', color: 'symbolIcon.constructorForeground' }, + [vscode.SymbolKind.Enum]: { icon: 'symbol-enum', color: 'symbolIcon.enumeratorForeground' }, + [vscode.SymbolKind.Interface]: { icon: 'symbol-interface', color: 'symbolIcon.interfaceForeground' }, + [vscode.SymbolKind.Function]: { icon: 'symbol-function', color: 'symbolIcon.functionForeground' }, + [vscode.SymbolKind.Variable]: { icon: 'symbol-variable', color: 'symbolIcon.variableForeground' }, + [vscode.SymbolKind.Constant]: { icon: 'symbol-constant', color: 'symbolIcon.constantForeground' }, + [vscode.SymbolKind.String]: { icon: 'symbol-string', color: 'symbolIcon.stringForeground' }, + [vscode.SymbolKind.Number]: { icon: 'symbol-number', color: 'symbolIcon.numberForeground' }, + [vscode.SymbolKind.Boolean]: { icon: 'symbol-boolean', color: 'symbolIcon.booleanForeground' }, + [vscode.SymbolKind.Array]: { icon: 'symbol-array', color: 'symbolIcon.arrayForeground' }, + [vscode.SymbolKind.Object]: { icon: 'symbol-object', color: 'symbolIcon.objectForeground' }, + [vscode.SymbolKind.Key]: { icon: 'symbol-key', color: 'symbolIcon.keyForeground' }, + [vscode.SymbolKind.Null]: { icon: 'symbol-null', color: 'symbolIcon.nullForeground' }, + [vscode.SymbolKind.EnumMember]: { icon: 'symbol-enum-member', color: 'symbolIcon.enumeratorMemberForeground' }, + [vscode.SymbolKind.Struct]: { icon: 'symbol-struct', color: 'symbolIcon.structForeground' }, + [vscode.SymbolKind.Event]: { icon: 'symbol-event', color: 'symbolIcon.eventForeground' }, + [vscode.SymbolKind.Operator]: { icon: 'symbol-operator', color: 'symbolIcon.operatorForeground' }, + [vscode.SymbolKind.TypeParameter]: { icon: 'symbol-type-parameter', color: 'symbolIcon.typeParameterForeground' }, }; - return new vscode.ThemeIcon(map[kind] ?? 'symbol-misc'); + const entry = map[kind]; + if (entry) { + return new vscode.ThemeIcon(entry.icon, new vscode.ThemeColor(entry.color)); + } + return new vscode.ThemeIcon('symbol-misc'); } } diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 953b9b2..cb76960 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -205,13 +205,13 @@ export class DocumentSymbolItem extends vscode.TreeItem { let desc = symbol.detail || ''; let ctx = 'symbolNode'; if (inactive && isOverwritten) { - desc = `${desc} (inactive, overwritten)`.trim(); + desc = `(inactive, overwritten) ${desc}`.trim(); ctx = 'symbolNodeInactiveOverwritten'; } else if (inactive) { - desc = `${desc} (inactive)`.trim(); + desc = `(inactive) ${desc}`.trim(); ctx = 'symbolNodeInactive'; } else if (isOverwritten) { - desc = `${desc} (overwritten)`.trim(); + desc = `(overwritten) ${desc}`.trim(); ctx = 'symbolNodeOverwritten'; } this.description = desc || undefined; From b993e21ee03b2851baff79d889199f055c5b8f87 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 15:36:23 -0500 Subject: [PATCH 49/73] Search workspace tree elements --- package.json | 10 +++++ src/extension.ts | 4 ++ src/workspaceTree/WorkspaceTreeProvider.ts | 46 ++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/package.json b/package.json index ee0cace..2f3da44 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,11 @@ "title": "EDK2: Reveal active editor in workspace tree", "icon": "$(target)" }, + { + "command": "edk2code.searchWorkspaceTree", + "title": "EDK2: Search workspace tree", + "icon": "$(search)" + }, { "command": "edk2code.focusEditorInWorkspaceView", "title": "EDK2: Focus on workspace view" @@ -375,6 +380,11 @@ "command": "edk2code.revealEditorInWorkspaceTree", "group": "navigation", "when": "view == workspaceView" + }, + { + "command": "edk2code.searchWorkspaceTree", + "group": "navigation", + "when": "view == workspaceView" } ], "editor/context": [ diff --git a/src/extension.ts b/src/extension.ts index bcc174c..f832922 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -182,6 +182,10 @@ export async function activate(context: vscode.ExtensionContext) { await edkWorkspaceTreeProvider.revealActiveEditor(edkWorkspaceTreeView); }), + vscode.commands.registerCommand('edk2code.searchWorkspaceTree', async () => { + await edkWorkspaceTreeProvider.searchTree(edkWorkspaceTreeView); + }), + vscode.commands.registerCommand('edk2code.focusEditorInWorkspaceView', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { return; } diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index cb76960..13e58aa 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -633,6 +633,52 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider): Promise { + type SearchPickItem = vscode.QuickPickItem & { node: WorkspaceTreeNode }; + + // Collect every node in the tree + const items: SearchPickItem[] = []; + const collect = async (parent?: WorkspaceTreeNode): Promise => { + const children = await this.getChildren(parent); + for (const child of children) { + const label = child instanceof DocumentSymbolItem ? child.symbol.name + : child instanceof IncludeTreeItem ? path.basename(child.node.uri.fsPath) + : (child as WorkspaceRootItem).label as string; + const description = child.description as string | undefined; + items.push({ label, description: description ?? '', node: child }); + await collect(child); + } + }; + + await vscode.window.withProgress( + { location: { viewId: 'workspaceView' } }, + async () => { await collect(); } + ); + + if (items.length === 0) { + void vscode.window.showInformationMessage('No nodes in the workspace tree.'); + return; + } + + const picked = await vscode.window.showQuickPick(items, { + placeHolder: 'Type to filter workspace tree nodes', + title: 'EDK2: Search workspace tree', + matchOnDescription: true + }); + + if (!picked) { return; } + await treeView.reveal(picked.node, { select: true, focus: true, expand: true }); + + // Also open the element in the editor + const cmd = picked.node.command; + if (cmd) { + await vscode.commands.executeCommand(cmd.command, ...(cmd.arguments ?? [])); + } + } + /** Recursively walk the tree to find a DocumentSymbolItem matching (fileUri, targetSymbol). */ private async _findItemForSymbol( parent: WorkspaceTreeNode, From 370d8994d021e926c361730ff5997a3ee4935081 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 15:37:35 -0500 Subject: [PATCH 50/73] 1.1.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f3da44..fcc790b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code-dev", "displayName": "Edk2code-dev", "description": "EDK2 code support", - "version": "1.1.5", + "version": "1.1.6", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", From 4b6f3e08dab2c82d97a2e52a17cf075a48d00094 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 16:16:52 -0500 Subject: [PATCH 51/73] Focus on INF from c files --- src/contextState/cmds.ts | 3 +- src/workspaceTree/WorkspaceTreeProvider.ts | 32 +++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index d84488c..78a63e6 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -4,7 +4,7 @@ import { rgSearch } from "../rg"; import { delay, getCurrentWord, gotoFile, isWorkspacePath, listFilesRecursive, openTextDocument, pathCompare, profileEnd, profileStart, readLines, toPosix } from "../utils"; import path = require("path"); import * as fs from 'fs'; -import { edkWorkspaceTreeProvider, gConfigAgent, gCscope, gDebugLog, gEdkWorkspaces, gExtensionContext, gMapFileManager, gPathFind, gWorkspacePath } from "../extension"; +import { edkWorkspaceTreeProvider, edkWorkspaceTreeView, gConfigAgent, gCscope, gDebugLog, gEdkWorkspaces, gExtensionContext, gMapFileManager, gPathFind, gWorkspacePath } from "../extension"; import { glob } from "fast-glob"; import { BuildFolder } from "../Languages/buildFolder"; import { EdkWorkspace, InfDsc } from "../index/edkWorkspace"; @@ -248,6 +248,7 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; locations = await wp.getInfReference(fileUri); if(locations.length){ await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, "gotoAndPeek", "Not found"); + await edkWorkspaceTreeProvider.revealInfInTree(locations[0].uri, edkWorkspaceTreeView); } } }else{ diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 13e58aa..bd6b2c5 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; -import { EdkWorkspace, IncludeNode } from '../index/edkWorkspace'; +import { EdkWorkspace, IncludeNode, InfDsc } from '../index/edkWorkspace'; import { getParser } from '../edkParser/parserFactory'; import { EdkSymbol } from '../symbols/edkSymbols'; import { Edk2SymbolType } from '../symbols/symbolsType'; @@ -205,15 +205,21 @@ export class DocumentSymbolItem extends vscode.TreeItem { let desc = symbol.detail || ''; let ctx = 'symbolNode'; if (inactive && isOverwritten) { - desc = `(inactive, overwritten) ${desc}`.trim(); + desc = `${this.label} (inactive, overwritten) ${desc}`.trim(); ctx = 'symbolNodeInactiveOverwritten'; } else if (inactive) { - desc = `(inactive) ${desc}`.trim(); + desc = `${this.label} (inactive) ${desc}`.trim(); ctx = 'symbolNodeInactive'; } else if (isOverwritten) { - desc = `(overwritten) ${desc}`.trim(); + desc = `${this.label} (overwritten) ${desc}`.trim(); ctx = 'symbolNodeOverwritten'; } + + if(ctx !== 'symbolNode') { + // The label is shown with strikethrough in the tree when overwritten, so we move the original label to the description and show the overwrite status in the label instead. This keeps the label text fully visible without truncation, while still indicating the symbol's name and status. + this.label = ""; + } + this.description = desc || undefined; if (isOverwritten) { @@ -633,6 +639,24 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider + ): Promise { + const wps = await gEdkWorkspaces.getWorkspace(infUri); + let declarations: InfDsc[] = []; + for (const wp of wps) { + declarations = declarations.concat(await wp.getDscDeclaration(infUri)); + } + if (declarations.length) { + const decl = declarations[0]; + await this.revealLocation(decl.location.uri, decl.location.range.start, treeView); + } + } + /** * Show a filterable Quick Pick with all tree nodes. Selecting one reveals it in the tree. */ From a9f2786566adc23be273d522fe37854bb4e7732e Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 16:40:44 -0500 Subject: [PATCH 52/73] adding path to tooltip --- src/extension.ts | 5 ++-- src/workspaceTree/WorkspaceTreeProvider.ts | 34 +++++++++++----------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index f832922..c415fb7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -131,11 +131,10 @@ export async function activate(context: vscode.ExtensionContext) { }), vscode.commands.registerCommand('edk2code.copyWorkspaceNodePath', async (node: WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem) => { - if (!('treePath' in node)) { + if (!('nodePath' in node)) { return; } - const treePath = node.treePath.map((p, i) => ' '.repeat(i) + p).join('\n'); - await vscode.env.clipboard.writeText(treePath); + await vscode.env.clipboard.writeText(node.nodePath); void vscode.window.showInformationMessage(`Workspace path copied to clipboard`); }), diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index bd6b2c5..370523e 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -113,15 +113,17 @@ export function isInfInWorkspaces(uri: vscode.Uri, workspaces: EdkWorkspace[]): export class WorkspaceRootItem extends vscode.TreeItem { public readonly treePath: string[]; + public readonly nodePath: string; constructor(public readonly workspace: EdkWorkspace, public readonly wsIndex: number) { const label = path.basename(workspace.mainDsc.fsPath); super(label, vscode.TreeItemCollapsibleState.Expanded); this.id = `wsr:${wsIndex}`; this.treePath = [label]; + this.nodePath = label; this.description = workspace.platformName ?? ''; this.tooltip = new vscode.MarkdownString( - `**Workspace root**\n\n\`${workspace.mainDsc.fsPath}\`` + `**${label}**\n\n${this.description}\n\n\`${this.nodePath}\`` ); this.iconPath = new vscode.ThemeIcon('file-code'); this.contextValue = 'workspaceRoot'; @@ -137,19 +139,20 @@ export class WorkspaceRootItem extends vscode.TreeItem { export class IncludeTreeItem extends vscode.TreeItem { public readonly treePath: string[]; + public readonly nodePath: string; public readonly inactive: boolean; - constructor(public readonly node: IncludeNode, parentPath: string[], inactive: boolean = false) { + constructor(public readonly node: IncludeNode, parentPath: string[], parentNodePath: string, inactive: boolean = false) { const label = path.basename(node.uri.fsPath); super(label, vscode.TreeItemCollapsibleState.Collapsed); this.inactive = inactive; this.treePath = [...parentPath, vscode.workspace.asRelativePath(node.uri, false)]; + this.nodePath = parentNodePath + '/' + label; this.description = inactive ? `${vscode.workspace.asRelativePath(node.uri, false)} (inactive)` : vscode.workspace.asRelativePath(node.uri, false); this.tooltip = new vscode.MarkdownString( - `**Included file**${inactive ? ' *(inactive)*' : ''}\n\n\`${node.uri.fsPath}\`\n\n` + - `Directive at: \`${node.location.uri.fsPath}:${node.location.range.start.line + 1}\`` + `**${label}**\n\n${this.description}\n\n${this.nodePath}` ); this.iconPath = inactive ? new vscode.ThemeIcon('file', new vscode.ThemeColor('disabledForeground')) @@ -169,6 +172,7 @@ export class IncludeTreeItem extends vscode.TreeItem { export class DocumentSymbolItem extends vscode.TreeItem { public readonly symbolType: Edk2SymbolType; public readonly treePath: string[]; + public readonly nodePath: string; public readonly inactive: boolean; public readonly overwrittenBy: vscode.Location | undefined; @@ -178,6 +182,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { activeFilters: Set, parentPath: string[], public readonly parent: WorkspaceRootItem | DocumentSymbolItem | undefined, + parentNodePath: string, inactive: boolean = false, overwrittenBy?: vscode.Location ) { @@ -194,6 +199,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { this.inactive = inactive; this.overwrittenBy = overwrittenBy; this.treePath = [...parentPath, symbol.name]; + this.nodePath = parentNodePath + ' / ' + symbol.name; // Include the parent path in the id so the same file/symbol included from // multiple places in the tree gets a unique id for each occurrence. const parentKey = parentPath.join('/'); @@ -222,15 +228,9 @@ export class DocumentSymbolItem extends vscode.TreeItem { this.description = desc || undefined; - if (isOverwritten) { - const relPath = vscode.workspace.asRelativePath(overwrittenBy.uri, false); - const line = overwrittenBy.range.start.line + 1; - this.tooltip = new vscode.MarkdownString( - `~~${symbol.name}~~ *(overwritten)*\n\nOverwritten by: \`${relPath}:${line}\`` - ); - } else { - this.tooltip = inactive ? `${symbol.name} (inactive)` : symbol.name; - } + this.tooltip = new vscode.MarkdownString( + `**${symbol.name}**\n\n${this.description ?? ''}\n\n${this.nodePath}` + ); if (inactive || isOverwritten) { this.iconPath = new vscode.ThemeIcon( @@ -541,7 +541,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) - .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element, + .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element, element.nodePath, isSymbolInactive(s, element.workspace.mainDsc, ws), getOverwriteLocation(s, element.workspace.mainDsc))) .filter(s => showInactive || !s.inactive); @@ -553,7 +553,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) - .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined, + .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined, element.nodePath, inactive || isSymbolInactive(s, element.node.uri, ws), getOverwriteLocation(s, element.node.uri))) .filter(s => showInactive || !s.inactive); @@ -569,7 +569,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) - .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element, + .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element, element.nodePath, element.inactive || isSymbolInactive(s, node.uri, ws), getOverwriteLocation(s, node.uri))) .filter(s => showInactive || !s.inactive); @@ -578,7 +578,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(c.type, this._activeFilters)) - .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element, + .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element, element.nodePath, element.inactive || isSymbolInactive(child, element.fileUri, ws), getOverwriteLocation(child, element.fileUri))) .filter(s => showInactive || !s.inactive); From eb900334b2f573333a5964f604d952df383c3762 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 16:41:00 -0500 Subject: [PATCH 53/73] 1.1.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fcc790b..063d525 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code-dev", "displayName": "Edk2code-dev", "description": "EDK2 code support", - "version": "1.1.6", + "version": "1.1.7", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", From 00e74974adfd3d081fa31a2648e1f135367cdb7d Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 16 Apr 2026 17:58:49 -0500 Subject: [PATCH 54/73] Adding test cases --- src/test/suite/README.md | 461 +++++++++++++ src/test/suite/aslParser.test.ts | 118 ++++ src/test/suite/decParser.test.ts | 144 ++++ src/test/suite/dscParser.test.ts | 213 ++++++ src/test/suite/edkWorkspace.test.ts | 762 +++++++++++++++++++++ src/test/suite/edkWorkspaceProcess.test.ts | 422 ++++++++++++ src/test/suite/fdfParser.test.ts | 101 +++ src/test/suite/infParser.test.ts | 187 +++++ src/test/suite/vfrParser.test.ts | 116 ++++ test/testAslParsing.asl | 60 ++ test/testDecParsing.dec | 36 + test/testDscParsing.dsc | 63 ++ test/testDscProcess.dsc | 85 +++ test/testFdfParsing.fdf | 38 + test/testInfParsing.inf | 48 ++ test/testVfrParsing.vfr | 78 +++ 16 files changed, 2932 insertions(+) create mode 100644 src/test/suite/README.md create mode 100644 src/test/suite/aslParser.test.ts create mode 100644 src/test/suite/decParser.test.ts create mode 100644 src/test/suite/dscParser.test.ts create mode 100644 src/test/suite/edkWorkspace.test.ts create mode 100644 src/test/suite/edkWorkspaceProcess.test.ts create mode 100644 src/test/suite/fdfParser.test.ts create mode 100644 src/test/suite/infParser.test.ts create mode 100644 src/test/suite/vfrParser.test.ts create mode 100644 test/testAslParsing.asl create mode 100644 test/testDecParsing.dec create mode 100644 test/testDscParsing.dsc create mode 100644 test/testDscProcess.dsc create mode 100644 test/testFdfParsing.fdf create mode 100644 test/testInfParsing.inf create mode 100644 test/testVfrParsing.vfr diff --git a/src/test/suite/README.md b/src/test/suite/README.md new file mode 100644 index 0000000..b9876d6 --- /dev/null +++ b/src/test/suite/README.md @@ -0,0 +1,461 @@ +# Test Suite + +Integration tests for the Edk2Code VS Code extension. +Tests run under Mocha (TDD UI) via `@vscode/test-electron`. + +## Running Tests + +- **VS Code**: Use the **Extension Tests** launch configuration (F5). +- **CLI**: `node ./out/test/runTest.js` (bypasses lint). + +## Fixture Files + +Test fixtures live in the `test/` folder at the repository root: + +| Fixture | Used By | +|---------|---------| +| `testDscParsing.dsc` | `dscParser.test.ts`, `edkWorkspace.test.ts` | +| `testDecParsing.dec` | `decParser.test.ts` | +| `testFdfParsing.fdf` | `fdfParser.test.ts`, `edkWorkspace.test.ts` | +| `testInfParsing.inf` | `infParser.test.ts` | +| `testAslParsing.asl` | `aslParser.test.ts` | +| `testVfrParsing.vfr` | `vfrParser.test.ts` | +| `testDscProcess.dsc` | `edkWorkspaceProcess.test.ts` | + +--- + +## Test Files β€” Detailed Breakdown + +### `extension.test.ts` + +**Suite: Extension Test Suite** β€” Smoke test that validates the test harness is working. + +| # | Test | Description | +|---|------|-------------| +| 1 | Sample test | Asserts `Array.indexOf` returns `-1` for missing elements. Proves the Mocha runner and VS Code test host are functional. | + +--- + +### `dscParser.test.ts` + +**Suite: DSC Parser – Symbol Extraction** β€” Parses `test/testDscParsing.dsc` with `DscParser` and validates that all DSC symbol types are correctly extracted. The fixture includes `[Defines]`, `[SkuIds]`, `[LibraryClasses]` (common + `.X64`), `[Components]` (common + `.X64` with sub-sections), PCD sections, `[BuildOptions]`, root-level `DEFINE`, `!include`, and block comments. + +| # | Test | Description | +|---|------|-------------| +| 1 | Parses [Defines] section | At least one `dscSection` symbol with name matching "defines". | +| 2 | Parses [LibraryClasses] sections (common + arch-specific) | At least 2 `dscSection` symbols for `LibraryClasses` (common and `.X64`). | +| 3 | Parses [Components] and [Components.X64] sections | At least 2 `dscSection` symbols for `Components`. | +| 4 | Parses [SkuIds] section | Exactly 1 `dscSection` symbol for `SkuIds`. | +| 5 | Parses [BuildOptions] section | At least 1 `dscBuildOptionsSection` symbol. | +| 6 | Parses PCD sections (FixedAtBuild + DynamicDefault) | At least 2 `dscSection` symbols with names matching "pcd". | +| 7 | Parses DEFINE statements inside [Defines] | At least 3 `dscDefine` symbols (`DEFINE MY_VAR`, `DEFINE EMPTY_VAR`, `ROOT_DEFINE`). | +| 8 | DEFINE with empty value is still parsed | Finds `EMPTY_VAR` in the define list (covers `DEFINE EMPTY_VAR=`). | +| 9 | Root-level DEFINE (outside section) is parsed | Finds `ROOT_DEFINE` as a `dscDefine` (defined outside any section header). | +| 10 | Parses library class definitions | At least 4 `dscLibraryDefinition` symbols (BaseLib, DebugLib, PrintLib, TimerLib, plus overrides). | +| 11 | Library definition with extra whitespace around pipe is parsed | Finds `PrintLib` even when the line has extra spaces around `\|`. | +| 12 | Parses simple .inf module references | At least 3 `dscModuleDefinition` symbols. | +| 13 | Module with sub-sections (curly braces) is parsed | Finds `ComplexModule.inf` and asserts it has `children.length > 0`. | +| 14 | Component sub-sections are parsed | At least 2 `dscComponentSubSection` symbols (``, ``, ``). | +| 15 | Parses PCD definitions | At least 3 `dscPcdDefinition` symbols across FixedAtBuild, DynamicDefault, and component-scoped PCDs. | +| 16 | Parses build option entries | At least 2 `dscBuildOption` symbols (GCC and MSFT lines). | +| 17 | Parses !include directive | At least 1 `dscInclude` symbol; specifically finds `TestInclude.dsc.inc`. | +| 18 | symbolsTree contains top-level section nodes | `symbolsTree` is non-empty and every root node has an expected DSC symbol type. | +| 19 | Library definitions are children of their parent section | The `[LibraryClasses]` section node contains at least 3 child `dscLibraryDefinition` symbols. | +| 20 | symbolsList length equals total nodes across the tree | Flat `symbolsList.length` equals recursive count of all nodes in `symbolsTree`. | +| 21 | Comments are not parsed as symbols | No symbol name starts with `#`. | + +--- + +### `decParser.test.ts` + +**Suite: DEC Parser – Symbol Extraction** β€” Parses `test/testDecParsing.dec` with `DecParser`. The fixture includes `[Defines]`, `[Includes]` (common + `.X64`), `[LibraryClasses]`, `[Guids]` with GUID-format values, `[Protocols]`, `[Ppis]`, `[PcdsFixedAtBuild]`, and `[PcdsPatchableInModule]`. + +| # | Test | Description | +|---|------|-------------| +| 1 | Parses [Defines] section | At least 1 `decSection` matching "defines". | +| 2 | Parses [Includes] sections (common + arch-specific) | At least 2 `decSection` symbols for `Includes` (common and `.X64`). | +| 3 | Parses [LibraryClasses] section | At least 1 `decSection` matching "libraryclasses". | +| 4 | Parses [Guids] section | At least 1 `decSection` matching "guids". | +| 5 | Parses [Protocols] section | At least 1 `decSection` matching "protocols". | +| 6 | Parses [Ppis] section | At least 1 `decSection` matching "ppis". | +| 7 | Parses PCD sections | At least 2 `decSection` symbols with names matching "pcds" (FixedAtBuild + PatchableInModule). | +| 8 | Parses include directory entries | At least 2 `decInclude` symbols (e.g. `Include`, `Include/Library`). | +| 9 | Parses library class entries | At least 2 `decLibrary` symbols. | +| 10 | Parses GUID entries | At least 1 `decGuid` symbol with GUID-format value. | +| 11 | Parses protocol entries | At least 2 `decProtocol` symbols. | +| 12 | Parses PPI entries | At least 1 `decPpi` symbol. | +| 13 | Parses PCD entries | At least 2 `decPcd` symbols across different PCD sections. | +| 14 | symbolsTree is not empty | `symbolsTree.length > 0`. | +| 15 | symbolsList equals recursive tree count | Flat list count matches recursive tree traversal count. | +| 16 | Comments are not parsed as symbols | No symbol name starts with `#`. | + +--- + +### `fdfParser.test.ts` + +**Suite: FDF Parser – Symbol Extraction** β€” Parses `test/testFdfParsing.fdf` with `FdfParser`. The fixture includes `[FD.TestFd]`, `[FV.FVMAIN]` with INF entries and a `DEFINE`, `[FV.FVRECOVERY]` with an `APRIORI` block, `[Rule.Common.SEC]`, and `!include`. + +| # | Test | Description | +|---|------|-------------| +| 1 | Parses [FD.*] section | At least 1 `fdfSection` matching "FD.". | +| 2 | Parses [FV.*] sections | At least 2 `fdfSection` symbols matching "FV." (FVMAIN and FVRECOVERY). | +| 3 | Parses [Rule.*] section | At least 1 `fdfSection` matching "Rule.". | +| 4 | Parses INF module references | At least 3 `fdfInf` symbols (SimpleModule, AnotherModule, AprioriModule, RecoveryModule). | +| 5 | Parses DEFINE statements | At least 2 `fdfDefinition` symbols. | +| 6 | Parses !include directive | At least 1 `fdfInclude` symbol; specifically finds `TestFdfInclude.fdf.inc`. | +| 7 | symbolsTree is not empty | `symbolsTree.length > 0`. | +| 8 | symbolsList equals recursive tree count | Flat list count matches recursive tree traversal count. | +| 9 | Comments are not parsed as symbols | No symbol name starts with `#`. | + +--- + +### `infParser.test.ts` + +**Suite: INF Parser – Symbol Extraction** β€” Parses `test/testInfParsing.inf` with `InfParser`. The fixture includes `[Defines]` (INF_VERSION, BASE_NAME, MODULE_TYPE, ENTRY_POINT, LIBRARY_CLASS, CONSTRUCTOR, DESTRUCTOR, DEFINE), `[Sources]` + `[Sources.X64]`, `[Packages]`, `[LibraryClasses]`, `[Protocols]`, `[Ppis]`, `[Guids]`, `[FixedPcd]`, and `[Depex]`. + +| # | Test | Description | +|---|------|-------------| +| 1 | Parses [Defines] section | At least 1 `infSection` matching "defines". | +| 2 | Parses [Sources] section(s) | At least 1 `infSectionSource` symbol. | +| 3 | Parses [Packages] section | At least 1 `infSectionPackages` symbol. | +| 4 | Parses [LibraryClasses] section | At least 1 `infSectionLibraries` symbol. | +| 5 | Parses [Protocols] section | At least 1 `infSectionProtocols` symbol. | +| 6 | Parses [Ppis] section | At least 1 `infSectionPpis` symbol. | +| 7 | Parses [Guids] section | At least 1 `infSectionGuids` symbol. | +| 8 | Parses [Pcd] / [FixedPcd] section | At least 1 `infSectionPcds` symbol. | +| 9 | Parses [Depex] section | At least 1 `infSectionDepex` symbol. | +| 10 | Parses INF defines (MODULE_TYPE, BASE_NAME, etc.) | At least 7 `infDefine` symbols covering all key-value pairs in `[Defines]`. | +| 11 | Parses ENTRY_POINT define | Finds a define with name matching `ENTRY_POINT`. | +| 12 | Parses CONSTRUCTOR define | Finds a define with name matching `CONSTRUCTOR`. | +| 13 | Parses DESTRUCTOR define | Finds a define with name matching `DESTRUCTOR`. | +| 14 | Parses source file entries | At least 3 `infSource` symbols (`.c`, `.h`, arch-specific). | +| 15 | Parses package references | At least 2 `infPackage` symbols (e.g. `MdePkg/MdePkg.dec`). | +| 16 | Parses library class references | At least 3 `infLibrary` symbols. | +| 17 | Parses protocol entries | At least 2 `infProtocol` symbols. | +| 18 | Parses PPI entries | At least 1 `infPpi` symbol. | +| 19 | Parses GUID entries | At least 2 `infGuid` symbols. | +| 20 | Parses PCD entries | At least 1 `infPcd` symbol. | +| 21 | Parses dependency expression entries | At least 1 `infDepex` symbol. | +| 22 | symbolsTree is not empty | `symbolsTree.length > 0`. | +| 23 | symbolsList equals recursive tree count | Flat list count matches recursive tree traversal count. | +| 24 | Comments are not parsed as symbols | No symbol name starts with `#`. | + +--- + +### `aslParser.test.ts` + +**Suite: ASL Parser – Symbol Extraction** β€” Parses `test/testAslParsing.asl` with `AslParser`. The fixture includes a `DefinitionBlock`, 2 `External` declarations, 2 `Scope` blocks, 2 `Device` blocks (TPM0 with Name/Method/OperationRegion/Field, EC0 with Name/Method/OperationRegion/Field). + +| # | Test | Description | +|---|------|-------------| +| 1 | Parses DefinitionBlock | At least 1 `aslDefinitionBlock` symbol. | +| 2 | Parses External declarations | At least 2 `aslExternal` symbols (`\_SB.PCI0`, `\_SB.PCI0.LPCB`). | +| 3 | Parses Scope blocks | At least 2 `aslScope` symbols (`\_SB`, `\_SB.PCI0`). | +| 4 | Parses Device blocks | At least 2 `aslDevice` symbols (`TPM0`, `EC0`). | +| 5 | Parses Name declarations | At least 4 `aslName` symbols (TVAR, _HID, _STR, PVAR, RBUF, etc.). | +| 6 | Parses Method blocks | At least 2 `aslMethod` symbols (_STA, _CRS, RFAN). | +| 7 | Parses OperationRegion declarations | At least 2 `aslOpRegion` symbols (TPMR, ECOR). | +| 8 | Parses Field declarations | At least 2 `aslField` symbols (Field on TPMR, Field on ECOR). | +| 9 | DefinitionBlock has children | The first `aslDefinitionBlock` has `children.length > 0`. | +| 10 | Device contains Names as children | `TPM0` device's children include at least one `aslName`. | +| 11 | symbolsTree is not empty | `symbolsTree.length > 0`. | +| 12 | symbolsList equals recursive tree count | Flat list count matches recursive tree traversal count. | +| 13 | Comments are not parsed as symbols | No symbol name starts with `//`. | + +--- + +### `vfrParser.test.ts` + +**Suite: VFR Parser – Symbol Extraction** β€” Parses `test/testVfrParsing.vfr` with `VfrParser`. The fixture includes a `formset` containing 2 `form` blocks, `oneof`, `checkbox`, `numeric`, `string`, `password`, and `goto` controls. + +| # | Test | Description | +|---|------|-------------| +| 1 | Parses formset | At least 1 `vfrFormset` symbol. | +| 2 | Parses form blocks | Checks for `vfrForm` symbols. Note: the VFR parser does not nest `BlockFormSection` inside `BlockFormsetSection.context`, so `form` blocks inside `formset` may not be parsed. The test validates the parser does not crash. | +| 3 | Parses oneof controls | At least 2 `vfrOneof` symbols (Option1, Option2). | +| 4 | Parses checkbox controls | At least 1 `vfrCheckbox` symbol. | +| 5 | Parses numeric controls | At least 1 `vfrNumeric` symbol. | +| 6 | Parses string controls | At least 1 `vfrString` symbol. | +| 7 | Parses password controls | At least 1 `vfrPassword` symbol. | +| 8 | Parses goto references | At least 2 `vfrGoto` symbols (goto to form IDs). | +| 9 | Parses prompt entries inside controls | At least 2 `vfrString` symbols from `prompt` lines inside controls and the standalone string control. | +| 10 | symbolsTree is not empty | `symbolsTree.length > 0`. | +| 11 | Formset or form has children | At least one `vfrFormset` or `vfrForm` symbol has `children.length > 0`. | +| 12 | symbolsList equals recursive tree count | Flat list count matches recursive tree traversal count. | +| 13 | Comments are not parsed as symbols | No symbol name starts with `//`. | + +--- + +### `edkWorkspace.test.ts` + +Tests the workspace index classes from `src/index/edkWorkspace.ts`. These are unit-style tests that construct objects directly without running the full workspace processing pipeline. + +#### Suite: SectionProperty + +| # | Test | Description | +|---|------|-------------| +| 1 | constructor lowercases all fields | `SectionProperty('LibraryClasses', 'X64', 'DXE_DRIVER')` stores all values in lowercase. | +| 2 | constructor with already lowercase values | Passing already-lowercase strings preserves them unchanged. | + +#### Suite: SectionProperties + +| # | Test | Description | +|---|------|-------------| +| 1 | starts with empty properties | A new `SectionProperties()` has `properties.length === 0`. | +| 2 | addProperty adds correctly | After `addProperty('LibraryClasses', 'X64', 'DXE_DRIVER')`, length is 1 and sectionType is lowercased. | +| 3 | addProperty multiple | Adding 2 properties results in `properties.length === 2`. | +| 4 | compareArch returns true for matching arch | Two `SectionProperties` sharing `X64` arch return `true`. | +| 5 | compareArch returns false for different archs | `X64` vs `IA32` returns `false`. | +| 6 | compareArchStr returns true for matching arch string | Matches case-insensitively (both `'X64'` and `'x64'` match). | +| 7 | compareArchStr returns false for non-matching arch | `'IA32'` does not match an `X64`-only property set. | +| 8 | compareLibSectionType returns true for matching section type | Two `SectionProperties` both with `LibraryClasses` section type return `true`. | +| 9 | compareLibSectionType returns false for different section types | `LibraryClasses` vs `Components` returns `false`. | +| 10 | compareLibSectionTypeStr case-insensitive match | `'LibraryClasses'`, `'libraryclasses'`, and `'LIBRARYCLASSES'` all match. | +| 11 | compareLibSectionTypeStr returns false for non-matching | `'LibraryClasses'` does not match a `Components`-only property set. | +| 12 | compareModuleType returns true for matching module type | Two properties sharing `DXE_DRIVER` return `true`. | +| 13 | compareModuleType returns false for different module types | `DXE_DRIVER` vs `PEIM` returns `false`. | +| 14 | compareModuleTypeStr case-insensitive | Both `'DXE_DRIVER'` and `'dxe_driver'` match. | +| 15 | compareModuleTypeStr returns false for non-matching | `'PEIM'` does not match a `DXE_DRIVER`-only property set. | +| 16 | toString returns comma-separated properties | With 2 properties added, `toString()` output contains a comma. | +| 17 | compareArch matches any combination | A property set with both `X64` and `IA32` entries matches another set with only `IA32`. | + +#### Suite: InfDsc + +| # | Test | Description | +|---|------|-------------| +| 1 | constructor with section parent sets sectionProperties | Parent `'Components.X64'` sets `parent = undefined` and populates `sectionProperties` with `components/x64/common`. | +| 2 | constructor with INF parent sets parent path | Parent `'SomeModule/Module.inf'` (ending in `.inf`) sets `parent` to the normalized path and leaves `sectionProperties` empty. | +| 3 | constructor normalizes path separators | Forward slashes in the file path are replaced with `path.sep`. | +| 4 | constructor with multi-section parent | Parent `'LibraryClasses.X64.DXE_DRIVER,LibraryClasses.IA32.PEIM'` creates 2 section properties with correct arch and moduleType. | +| 5 | constructor with section missing arch defaults to common | Parent `'libraryclasses'` defaults both arch and moduleType to `'common'`. | +| 6 | getModuleTypeStr returns comma-separated module types | Multi-property InfDsc returns `'dxe_driver,peim'`. | +| 7 | getModuleTypeStr single property | Single `'Components.X64'` property returns `'common'` (moduleType defaults). | +| 8 | toString includes path and line number | `toString()` of an InfDsc at line 42 contains `'42'`. | +| 9 | text property stores original line | The `text` field preserves the raw DSC line text passed to the constructor. | +| 10 | location is preserved | The `vscode.Location` passed to the constructor is stored as-is (line 99). | + +#### Suite: EdkWorkspaces + +| # | Test | Description | +|---|------|-------------| +| 1 | isConfigured returns false when no workspaces | A fresh `EdkWorkspaces()` with no workspaces added returns `false`. | +| 2 | isConfigured returns true after adding a workspace | After pushing an `EdkWorkspace` into `workspaces`, returns `true`. | +| 3 | getInstance returns singleton | Two calls to `EdkWorkspaces.getInstance()` return the same reference. | +| 4 | isFileInUse returns undefined when no workspaces | With no workspaces, `isFileInUse()` returns `undefined` (indeterminate). | +| 5 | getWorkspace returns empty array when no workspaces | Returns `[]` when no workspaces are configured. | +| 6 | getDefinition returns undefined when no workspaces | Returns `undefined` for any variable lookup with no workspaces. | +| 7 | replaceDefines returns original text when no workspaces | `'$(MY_VAR)/path'` is returned unchanged when no workspace can resolve it. | +| 8 | getLib returns empty array when no workspaces | Returns `[]` for any location lookup with no workspaces. | + +#### Suite: EdkWorkspace + +| # | Test | Description | +|---|------|-------------| +| 1 | constructor sets mainDsc from document | `mainDsc.fsPath` ends with `testDscParsing.dsc`. | +| 2 | constructor generates a numeric id | `id` is a `number` and `> 0`. | +| 3 | platformName starts as undefined | Before `proccessWorkspace()`, `platformName` is `undefined`. | +| 4 | flashDefinitionDocument starts as undefined | Before processing, `flashDefinitionDocument` is `undefined`. | +| 5 | filesLibraries starts empty | `filesLibraries.length === 0`. | +| 6 | filesModules starts empty | `filesModules.length === 0`. | +| 7 | dscList is initially empty | `dscList()` returns `[]` before any processing. | +| 8 | fdfList is initially empty | `fdfList()` returns `[]` before any processing. | +| 9 | getFilesList aggregates all file lists | Returns an array combining DSC, FDF, module, and library paths. | +| 10 | includeTree starts empty | `includeTree.length === 0`. | +| 11 | getDefinitions returns a Map | The defines map is a `Map` instance even before processing. | +| 12 | getDefinition returns undefined for unknown key | `getDefinition('NON_EXISTENT')` returns `undefined`. | +| 13 | getDefinitionLocation returns undefined for unknown key | `getDefinitionLocation('NON_EXISTENT')` returns `undefined`. | +| 14 | replaceDefine passes through text with no defines | `'$(UNKNOWN_VAR)/path'` is unchanged when no defines are set. | +| 15 | getPcds returns undefined for unknown namespace | `getPcds('gUnknownPkg')` returns `undefined`. | +| 16 | getAllPcds returns a Map | PCD definitions map is a `Map` instance. | +| 17 | filesLibraries can be set | Setting via the property setter and reading back works correctly. | +| 18 | filesModules can be set | Setting via the property setter and reading back works correctly. | +| 19 | getFilesList includes libraries and modules | After setting both, `getFilesList()` returns at least 2 paths. | +| 20 | filesDsc can be set and read | Setting the `Set` and reading `dscList()` returns 1 entry. | +| 21 | filesFdf can be set and read | Setting the `Set` and reading `fdfList()` returns 1 entry. | +| 22 | getLib returns undefined when no matching library | A non-matching location returns `undefined`. | +| 23 | getLib finds matching library by location | An InfDsc with the exact same URI and line is found by `getLib()`. | + +#### Suite: EdkWorkspace.evaluateExpression + +Tests the Shunting-Yard expression evaluator used for `!if` / `!ifdef` / `!ifndef` conditional processing. + +| # | Test | Description | +|---|------|-------------| +| 1 | TRUE evaluates to true | Literal `TRUE` β†’ `true`. | +| 2 | FALSE evaluates to false | Literal `FALSE` β†’ `false`. | +| 3 | true/false case-insensitive | `'true'`, `'True'`, `'false'` all parse correctly. | +| 4 | numeric 1 is truthy | `'1'` β†’ `1`. | +| 5 | numeric 0 is falsy | `'0'` β†’ `0`. | +| 6 | == with matching strings | `'"hello" == "hello"'` β†’ `true`. | +| 7 | == with non-matching strings | `'"hello" == "world"'` β†’ `false`. | +| 8 | != with different strings | `'"hello" != "world"'` β†’ `true`. | +| 9 | != with same strings | `'"hello" != "hello"'` β†’ `false`. | +| 10 | EQ operator | `'"a" EQ "a"'` β†’ `true` (EDK2-style equality). | +| 11 | NE operator | `'"a" NE "b"'` β†’ `true` (EDK2-style inequality). | +| 12 | AND with both true | `'TRUE AND TRUE'` β†’ `true`. | +| 13 | AND with one false | `'TRUE AND FALSE'` β†’ `false`. | +| 14 | OR with one true | `'FALSE OR TRUE'` β†’ `true`. | +| 15 | OR with both false | `'FALSE OR FALSE'` β†’ `false`. | +| 16 | && operator | `'TRUE && TRUE'` β†’ `true` (C-style AND). | +| 17 | \|\| operator | `'FALSE \|\| TRUE'` β†’ `true` (C-style OR). | +| 18 | NOT TRUE evaluates to false | `'NOT TRUE'` β†’ `false` (unary NOT). | +| 19 | NOT FALSE evaluates to true | `'NOT FALSE'` β†’ truthy. | +| 20 | addition | `'3 + 2'` β†’ `5`. | +| 21 | subtraction | `'5 - 2'` β†’ `3`. | +| 22 | multiplication | `'3 * 4'` β†’ `12`. | +| 23 | division | `'10 / 2'` β†’ `5`. | +| 24 | modulus | `'10 % 3'` β†’ `1`. | +| 25 | greater than | `'5 > 3'` β†’ `true`. | +| 26 | less than | `'3 > 5'` β†’ `false`. | +| 27 | greater or equal | `'5 >= 5'` β†’ `true`. | +| 28 | less or equal | `'3 <= 5'` β†’ `true`. | +| 29 | parentheses group expressions | `'(TRUE OR FALSE) AND TRUE'` β†’ `true`. | +| 30 | nested parentheses | `'((1 + 2) * 3)'` β†’ `9`. | +| 31 | unbalanced parentheses throw | `'(TRUE AND FALSE'` throws an error. | +| 32 | IN operator with match | `'"X64" IN "X64 IA32 ARM"'` β†’ `true`. | +| 33 | IN operator without match | `'"AARCH64" IN "X64 IA32 ARM"'` β†’ `false`. | +| 34 | undefined variable evaluates to false | `'"???"'` (the undefined-variable sentinel) β†’ `false`. | +| 35 | bare word is treated as quoted string | `'hello == "hello"'` β†’ `true` (unquoted words auto-quoted). | +| 36 | complex: (1 + 2) > 2 AND TRUE | Mixed arithmetic + comparison + logical β†’ `true`. | +| 37 | complex: FALSE OR (5 == 5) | Logical OR with parenthesised equality β†’ `true`. | + +--- + +### `edkWorkspaceProcess.test.ts` + +Integration tests for the core workspace processing pipeline: `proccessWorkspace()`, `_doProccessWorkspace()`, and `_processDocument()`. Uses the `test/testDscProcess.dsc` fixture which includes `[Defines]`, `[LibraryClasses]`, `[Components]`, PCD sections, `[BuildOptions]`, conditional blocks (`!if`/`!ifdef`/`!ifndef`/`!else`/`!endif`), nested conditionals, and comments. + +A `ensureProcessingGlobals()` helper stubs all required globals: `gDebugLog`, `gWorkspacePath`, `gConfigAgent`, `gPathFind`, `edkWorkspaceTreeProvider`, `DiagnosticManager`, and `edkStatusBar.myStatusBarItem`. + +#### Suite: EdkWorkspace._processDocument + +Processes the DSC fixture via the private `_processDocument(doc, 'DSC')` and validates all extracted data. + +##### Document Registration + +| # | Test | Description | +|---|------|-------------| +| 1 | Adds document to filesDsc | `dscList()` contains at least 1 entry ending with `testDscProcess.dsc`. | +| 2 | isDocumentInIndex returns true after processing | Private `isDocumentInIndex(doc)` returns `true` for the processed document. | +| 3 | Processing same document twice is a no-op | Calling `_processDocument` again does not add a duplicate entry to `dscList()`. | + +##### Defines Extraction + +| # | Test | Description | +|---|------|-------------| +| 4 | Extracts PLATFORM_NAME from [Defines] | `getDefinition('PLATFORM_NAME')` returns `'TestProcess'`. | +| 5 | Extracts PLATFORM_GUID | `getDefinition('PLATFORM_GUID')` returns `'11111111-2222-3333-4444-555555555555'`. | +| 6 | Extracts DEFINE MY_FLAG | `getDefinition('MY_FLAG')` returns `'TRUE'`. | +| 7 | Extracts DEFINE with empty value | `getDefinition('EMPTY_DEF')` is defined but trims to empty string. | +| 8 | Extracts root-level DEFINE | `getDefinition('ROOT_DEF')` returns `'RootValue'` (defined outside any section). | +| 9 | getDefinitionLocation returns a Location for known define | Location URI ends with `testDscProcess.dsc`. | +| 10 | getDefinitions returns all defines as a Map | Map instance with `size >= 5`. | + +##### Define Variable Substitution + +| # | Test | Description | +|---|------|-------------| +| 11 | DERIVED define has $(BASE) resolved | `DEFINE DERIVED = $(BASE)World` resolves to `'HelloWorld'`. | +| 12 | replaceDefine substitutes known variables | `replaceDefine('$(MY_FLAG)')` returns `'TRUE'`. | +| 13 | replaceDefine leaves unknown variables untouched | `'$(TOTALLY_UNKNOWN)'` passes through unchanged. | + +##### PCD Extraction + +| # | Test | Description | +|---|------|-------------| +| 14 | Extracts PCDs in gTestPkg namespace | `getPcds('gTestPkg')` is not `undefined`. | +| 15 | PcdTestMask has correct value | `PcdTestMask.value` is `'0x2F'`. | +| 16 | PcdBootTimeout from DynamicDefault | `PcdBootTimeout.value` is `'5'`. | +| 17 | PCD with L"string" value strips L prefix | `PcdStringVal.value` starts with `'"'` (L prefix removed). | +| 18 | PCD has location information | PCD position URI ends with `testDscProcess.dsc`. | +| 19 | getAllPcds returns all namespaces | Map contains `'gTestPkg'` key. | + +##### Conditional Processing + +| # | Test | Description | +|---|------|-------------| +| 20 | !if TRUE branch: COND_TAKEN is defined | `getDefinition('COND_TAKEN')` returns `'IfTrueValue'`. | +| 21 | !if TRUE branch: else branch not taken | `COND_TAKEN` is not `'IfFalseValue'`. | +| 22 | !if FALSE: else branch taken, COND_ELSE is defined | `getDefinition('COND_ELSE')` returns `'ElseValue'`. | +| 23 | !if FALSE: if branch not taken, COND_FALSE_IF not defined | `getDefinition('COND_FALSE_IF')` returns `undefined`. | +| 24 | Nested conditionals: outer TRUE inner FALSE -> INNER_ELSE defined | Both `OUTER_TRUE` (`'OuterOk'`) and `INNER_ELSE` (`'InnerElseOk'`) are set. | +| 25 | Nested conditionals: INNER_FALSE not defined | `getDefinition('INNER_FALSE')` returns `undefined`. | +| 26 | !ifdef on existing variable takes the branch | `IFDEF_TAKEN` is `'yes'` (variable `EXISTING_VAR` was defined). | +| 27 | !ifndef on undefined variable takes the branch | `IFNDEF_TAKEN` is `'yes'` (`TOTALLY_UNDEFINED_XYZ` not defined). | +| 28 | COND_A before conditional is still defined | `getDefinition('COND_A')` returns `'BeforeIf'`. | + +##### Library and Module References + +| # | Test | Description | +|---|------|-------------| +| 29 | Libraries are collected (even if paths unresolved) | `filesLibraries.length >= 2` (BaseLib, DebugLib, TimerLib). | +| 30 | Modules are collected (even if paths unresolved) | `filesModules.length >= 2` (ModuleA, ModuleB, ModuleC). | +| 31 | Library InfDsc has correct section properties | BaseLib's `sectionProperties.properties.length > 0`. | +| 32 | Module InfDsc preserves location | First module's location URI ends with `testDscProcess.dsc`. | + +##### Grayout Ranges + +| # | Test | Description | +|---|------|-------------| +| 33 | parsedDocuments has entry for processed document | Private `parsedDocuments` map contains an entry for the document's `fsPath`. | +| 34 | Grayout ranges exist for inactive conditional blocks | At least 1 grayout range from `!if FALSE` blocks. | + +##### Comment Stripping + +| # | Test | Description | +|---|------|-------------| +| 35 | stripComment removes line comments | `'DEFINE X = 1 # comment'` β†’ `'DEFINE X = 1'`. | +| 36 | stripComment preserves hash inside quotes | `'DEFINE X = "value#with#hash"'` β†’ unchanged. | +| 37 | stripComment trims whitespace | `' some text '` β†’ `'some text'`. | +| 38 | stripComment returns empty for comment-only line | `'# just a comment'` β†’ `''`. | + +#### Suite: EdkWorkspace._doProccessWorkspace + +Runs the full private `_doProccessWorkspace()` pipeline (reset state β†’ open main DSC β†’ process β†’ populate `platformName` β†’ find FDF). + +##### Workspace Initialization + +| # | Test | Description | +|---|------|-------------| +| 1 | platformName is populated from PLATFORM_NAME define | `platformName` is `'TestProcess'` after processing. | +| 2 | workInProgress is false after completion | Private `workInProgress` is `false`. | +| 3 | processComplete is true after completion | Private `processComplete` is `true`. | + +##### State Reset + +| # | Test | Description | +|---|------|-------------| +| 4 | Running again resets and re-processes | Second call returns `true` and `platformName` is still `'TestProcess'`. | +| 5 | Returns false if already in progress | Setting `workInProgress = true` causes the method to return `false`. | + +##### Defines After Full Processing + +| # | Test | Description | +|---|------|-------------| +| 6 | All defines from [Defines] section are available | Map has `PLATFORM_NAME`, `MY_FLAG`, `MY_PATH`. | +| 7 | Conditional defines are correctly resolved | `COND_TAKEN` is `'IfTrueValue'`, `COND_ELSE` is `'ElseValue'`. | +| 8 | replaceDefine works after processing | `replaceDefine('$(PLATFORM_NAME)')` returns `'TestProcess'`. | + +##### PCDs After Full Processing + +| # | Test | Description | +|---|------|-------------| +| 9 | PCDs are available after processing | `getPcds('gTestPkg')` has `size >= 3`. | + +##### File Lists After Full Processing + +| # | Test | Description | +|---|------|-------------| +| 10 | dscList contains the main document | At least 1 entry ending with `testDscProcess.dsc`. | +| 11 | Libraries are populated | `filesLibraries.length >= 2`. | +| 12 | Modules are populated | `filesModules.length >= 2`. | +| 13 | getFilesList aggregates all lists | Total `length >= 4` (DSC + libraries + modules). | + +#### Suite: EdkWorkspace.proccessWorkspace + +Tests the public `proccessWorkspace()` API which wraps `_doProccessWorkspace` with `vscode.window.withProgress`. + +| # | Test | Description | +|---|------|-------------| +| 1 | proccessWorkspace runs without errors | Returns `true` and `platformName` is `'TestProcess'`. | +| 2 | proccessWorkspace populates defines and PCDs | `getDefinitions().size > 0` and `getAllPcds().size > 0`. | diff --git a/src/test/suite/aslParser.test.ts b/src/test/suite/aslParser.test.ts new file mode 100644 index 0000000..a5411b7 --- /dev/null +++ b/src/test/suite/aslParser.test.ts @@ -0,0 +1,118 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { AslParser } from '../../edkParser/aslParser'; +import { Edk2SymbolType } from '../../symbols/symbolsType'; +import { DebugLog } from '../../debugLog'; + +function ensureGlobals() { + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +async function parseAslFile(filename: string): Promise { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const parser = new AslParser(document); + await parser.parseFile(); + return parser; +} + +function symbolsOfType(parser: AslParser, type: Edk2SymbolType) { + return parser.symbolsList.filter(s => s.type === type); +} + +suite('ASL Parser – Symbol Extraction', () => { + + let parser: AslParser; + + suiteSetup(async function () { + this.timeout(10_000); + parser = await parseAslFile('testAslParsing.asl'); + }); + + suite('Blocks', () => { + test('Parses DefinitionBlock', () => { + const defs = symbolsOfType(parser, Edk2SymbolType.aslDefinitionBlock); + assert.ok(defs.length >= 1, 'Expected at least one DefinitionBlock'); + }); + + test('Parses External declarations', () => { + const externals = symbolsOfType(parser, Edk2SymbolType.aslExternal); + assert.ok(externals.length >= 2, `Expected >=2 externals, got ${externals.length}`); + }); + + test('Parses Scope blocks', () => { + const scopes = symbolsOfType(parser, Edk2SymbolType.aslScope); + assert.ok(scopes.length >= 2, `Expected >=2 scopes, got ${scopes.length}`); + }); + + test('Parses Device blocks', () => { + const devices = symbolsOfType(parser, Edk2SymbolType.aslDevice); + assert.ok(devices.length >= 2, `Expected >=2 devices, got ${devices.length}`); + }); + }); + + suite('Members', () => { + test('Parses Name declarations', () => { + const names = symbolsOfType(parser, Edk2SymbolType.aslName); + assert.ok(names.length >= 4, `Expected >=4 names, got ${names.length}`); + }); + + test('Parses Method blocks', () => { + const methods = symbolsOfType(parser, Edk2SymbolType.aslMethod); + assert.ok(methods.length >= 2, `Expected >=2 methods, got ${methods.length}`); + }); + + test('Parses OperationRegion declarations', () => { + const regions = symbolsOfType(parser, Edk2SymbolType.aslOpRegion); + assert.ok(regions.length >= 2, `Expected >=2 OperationRegions, got ${regions.length}`); + }); + + test('Parses Field declarations', () => { + const fields = symbolsOfType(parser, Edk2SymbolType.aslField); + assert.ok(fields.length >= 2, `Expected >=2 fields, got ${fields.length}`); + }); + }); + + suite('Tree Structure', () => { + test('DefinitionBlock has children', () => { + const defBlock = symbolsOfType(parser, Edk2SymbolType.aslDefinitionBlock); + assert.ok(defBlock.length > 0); + assert.ok(defBlock[0].children.length > 0, 'DefinitionBlock should have child symbols'); + }); + + test('Device contains Names as children', () => { + const devices = symbolsOfType(parser, Edk2SymbolType.aslDevice); + const tpm = devices.find(d => /TPM0/i.test(d.name)); + assert.ok(tpm, 'TPM0 device should exist'); + const childTypes = tpm!.children.map(c => (c as any).type); + assert.ok(childTypes.includes(Edk2SymbolType.aslName), 'TPM0 should contain a Name'); + }); + }); + + suite('Consistency', () => { + test('symbolsTree is not empty', () => { + assert.ok(parser.symbolsTree.length > 0); + }); + + test('symbolsList equals recursive tree count', () => { + function countNodes(nodes: vscode.DocumentSymbol[]): number { + let c = 0; + for (const n of nodes) { c++; c += countNodes(n.children); } + return c; + } + assert.strictEqual(parser.symbolsList.length, countNodes(parser.symbolsTree)); + }); + + test('Comments are not parsed as symbols', () => { + for (const s of parser.symbolsList) { + assert.ok(!s.name.startsWith('//'), `Symbol should not start with //: "${s.name}"`); + } + }); + }); +}); diff --git a/src/test/suite/decParser.test.ts b/src/test/suite/decParser.test.ts new file mode 100644 index 0000000..d5c50b0 --- /dev/null +++ b/src/test/suite/decParser.test.ts @@ -0,0 +1,144 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { DecParser } from '../../edkParser/decParser'; +import { Edk2SymbolType } from '../../symbols/symbolsType'; +import { DebugLog } from '../../debugLog'; + +function ensureGlobals() { + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +async function parseDecFile(filename: string): Promise { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const parser = new DecParser(document); + await parser.parseFile(); + return parser; +} + +function symbolsOfType(parser: DecParser, type: Edk2SymbolType) { + return parser.symbolsList.filter(s => s.type === type); +} + +suite('DEC Parser – Symbol Extraction', () => { + + let parser: DecParser; + + suiteSetup(async function () { + this.timeout(10_000); + parser = await parseDecFile('testDecParsing.dec'); + }); + + suite('Sections', () => { + test('Parses [Defines] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const defines = sections.filter(s => /defines/i.test(s.name)); + assert.ok(defines.length >= 1, 'Expected at least one [Defines] section'); + }); + + test('Parses [Includes] sections (common + arch-specific)', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const incSections = sections.filter(s => /includes/i.test(s.name)); + assert.ok(incSections.length >= 2, `Expected >=2 Includes sections, got ${incSections.length}`); + }); + + test('Parses [LibraryClasses] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const libSections = sections.filter(s => /libraryclasses/i.test(s.name)); + assert.ok(libSections.length >= 1, 'Expected at least one [LibraryClasses] section'); + }); + + test('Parses [Guids] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const guidSections = sections.filter(s => /guids/i.test(s.name)); + assert.ok(guidSections.length >= 1, 'Expected at least one [Guids] section'); + }); + + test('Parses [Protocols] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const protoSections = sections.filter(s => /protocols/i.test(s.name)); + assert.ok(protoSections.length >= 1, 'Expected at least one [Protocols] section'); + }); + + test('Parses [Ppis] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const ppiSections = sections.filter(s => /ppis/i.test(s.name)); + assert.ok(ppiSections.length >= 1, 'Expected at least one [Ppis] section'); + }); + + test('Parses PCD sections', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.decSection); + const pcdSections = sections.filter(s => /pcds/i.test(s.name)); + assert.ok(pcdSections.length >= 2, `Expected >=2 PCD sections, got ${pcdSections.length}`); + }); + }); + + suite('Includes', () => { + test('Parses include directory entries', () => { + const includes = symbolsOfType(parser, Edk2SymbolType.decInclude); + assert.ok(includes.length >= 2, `Expected >=2 include paths, got ${includes.length}`); + }); + }); + + suite('Libraries', () => { + test('Parses library class entries', () => { + const libs = symbolsOfType(parser, Edk2SymbolType.decLibrary); + assert.ok(libs.length >= 2, `Expected >=2 library classes, got ${libs.length}`); + }); + }); + + suite('GUIDs', () => { + test('Parses GUID entries', () => { + const guids = symbolsOfType(parser, Edk2SymbolType.decGuid); + assert.ok(guids.length >= 1, `Expected >=1 GUID, got ${guids.length}`); + }); + }); + + suite('Protocols', () => { + test('Parses protocol entries', () => { + const protocols = symbolsOfType(parser, Edk2SymbolType.decProtocol); + assert.ok(protocols.length >= 2, `Expected >=2 protocols, got ${protocols.length}`); + }); + }); + + suite('PPIs', () => { + test('Parses PPI entries', () => { + const ppis = symbolsOfType(parser, Edk2SymbolType.decPpi); + assert.ok(ppis.length >= 1, `Expected >=1 PPI, got ${ppis.length}`); + }); + }); + + suite('PCDs', () => { + test('Parses PCD entries', () => { + const pcds = symbolsOfType(parser, Edk2SymbolType.decPcd); + assert.ok(pcds.length >= 2, `Expected >=2 PCDs, got ${pcds.length}`); + }); + }); + + suite('Consistency', () => { + test('symbolsTree is not empty', () => { + assert.ok(parser.symbolsTree.length > 0, 'symbolsTree should not be empty'); + }); + + test('symbolsList equals recursive tree count', () => { + function countNodes(nodes: vscode.DocumentSymbol[]): number { + let c = 0; + for (const n of nodes) { c++; c += countNodes(n.children); } + return c; + } + assert.strictEqual(parser.symbolsList.length, countNodes(parser.symbolsTree)); + }); + + test('Comments are not parsed as symbols', () => { + for (const s of parser.symbolsList) { + assert.ok(!s.name.startsWith('#'), `Symbol should not start with #: "${s.name}"`); + } + }); + }); +}); diff --git a/src/test/suite/dscParser.test.ts b/src/test/suite/dscParser.test.ts new file mode 100644 index 0000000..ac45031 --- /dev/null +++ b/src/test/suite/dscParser.test.ts @@ -0,0 +1,213 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { DscParser } from '../../edkParser/dscParser'; +import { Edk2SymbolType } from '../../symbols/symbolsType'; +import { DebugLog } from '../../debugLog'; + +/** + * Ensure gDebugLog is initialised even when the extension does not activate + * (e.g. no workspace folders in the test environment). + */ +function ensureGlobals() { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +/** + * Helper: open a .dsc file from the test/ folder and parse it with DscParser. + * Returns the parser instance so callers can inspect symbolsList / symbolsTree. + */ +async function parseDscFile(filename: string): Promise { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const parser = new DscParser(document); + await parser.parseFile(); + return parser; +} + +/** Filter helper: return all symbols of a given type from the flat list. */ +function symbolsOfType(parser: DscParser, type: Edk2SymbolType) { + return parser.symbolsList.filter(s => s.type === type); +} + +suite('DSC Parser – Symbol Extraction', () => { + + let parser: DscParser; + + suiteSetup(async function () { + this.timeout(10_000); + parser = await parseDscFile('testDscParsing.dsc'); + }); + + suite('Sections', () => { + test('Parses [Defines] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscSection); + const defines = sections.filter(s => /defines/i.test(s.name)); + assert.ok(defines.length >= 1, 'Expected at least one [Defines] section'); + }); + + test('Parses [LibraryClasses] sections (common + arch-specific)', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscSection); + const libSections = sections.filter(s => /libraryclasses/i.test(s.name)); + assert.ok(libSections.length >= 2, `Expected >=2 LibraryClasses sections, got ${libSections.length}`); + }); + + test('Parses [Components] and [Components.X64] sections', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscSection); + const compSections = sections.filter(s => /components/i.test(s.name)); + assert.ok(compSections.length >= 2, `Expected >=2 Components sections, got ${compSections.length}`); + }); + + test('Parses [SkuIds] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscSection); + const sku = sections.filter(s => /skuids/i.test(s.name)); + assert.strictEqual(sku.length, 1, 'Expected exactly one [SkuIds] section'); + }); + + test('Parses [BuildOptions] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscBuildOptionsSection); + assert.ok(sections.length >= 1, 'Expected at least one [BuildOptions] section'); + }); + + test('Parses PCD sections (FixedAtBuild + DynamicDefault)', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscSection); + const pcdSections = sections.filter(s => /pcd/i.test(s.name)); + assert.ok(pcdSections.length >= 2, `Expected >=2 PCD sections, got ${pcdSections.length}`); + }); + }); + + suite('Defines', () => { + test('Parses DEFINE statements inside [Defines]', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.dscDefine); + assert.ok(defines.length >= 3, `Expected >=3 defines, got ${defines.length}`); + }); + + test('DEFINE with empty value is still parsed', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.dscDefine); + const emptyVar = defines.find(s => /EMPTY_VAR/i.test(s.name)); + assert.ok(emptyVar, 'EMPTY_VAR define should be parsed'); + }); + + test('Root-level DEFINE (outside section) is parsed', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.dscDefine); + const rootDef = defines.find(s => /ROOT_DEFINE/i.test(s.name)); + assert.ok(rootDef, 'ROOT_DEFINE should be parsed as a root-level define'); + }); + }); + + suite('Libraries', () => { + test('Parses library class definitions', () => { + const libs = symbolsOfType(parser, Edk2SymbolType.dscLibraryDefinition); + assert.ok(libs.length >= 4, `Expected >=4 library definitions, got ${libs.length}`); + }); + + test('Library definition with extra whitespace around pipe is parsed', () => { + const libs = symbolsOfType(parser, Edk2SymbolType.dscLibraryDefinition); + const printLib = libs.find(s => /PrintLib/i.test(s.name)); + assert.ok(printLib, 'PrintLib (extra whitespace around |) should be parsed'); + }); + }); + + suite('Modules / Components', () => { + test('Parses simple .inf module references', () => { + const modules = symbolsOfType(parser, Edk2SymbolType.dscModuleDefinition); + assert.ok(modules.length >= 3, `Expected >=3 module definitions, got ${modules.length}`); + }); + + test('Module with sub-sections (curly braces) is parsed', () => { + const modules = symbolsOfType(parser, Edk2SymbolType.dscModuleDefinition); + const complex = modules.find(s => /ComplexModule/i.test(s.name)); + assert.ok(complex, 'ComplexModule.inf should be parsed as a module definition'); + assert.ok(complex!.children.length > 0, 'ComplexModule should have child sub-sections'); + }); + + test('Component sub-sections are parsed (, , )', () => { + const subSections = symbolsOfType(parser, Edk2SymbolType.dscComponentSubSection); + assert.ok(subSections.length >= 2, `Expected >=2 component sub-sections, got ${subSections.length}`); + }); + }); + + suite('PCDs', () => { + test('Parses PCD definitions', () => { + const pcds = symbolsOfType(parser, Edk2SymbolType.dscPcdDefinition); + assert.ok(pcds.length >= 3, `Expected >=3 PCD definitions, got ${pcds.length}`); + }); + }); + + suite('Build Options', () => { + test('Parses build option entries', () => { + const opts = symbolsOfType(parser, Edk2SymbolType.dscBuildOption); + assert.ok(opts.length >= 2, `Expected >=2 build options, got ${opts.length}`); + }); + }); + + suite('Includes', () => { + test('Parses !include directive', () => { + const includes = symbolsOfType(parser, Edk2SymbolType.dscInclude); + assert.ok(includes.length >= 1, 'Expected at least one !include directive'); + const inc = includes.find(s => /TestInclude/i.test(s.name)); + assert.ok(inc, 'TestInclude.dsc.inc should be found'); + }); + }); + + suite('Tree Structure', () => { + test('symbolsTree contains top-level section nodes', () => { + assert.ok(parser.symbolsTree.length > 0, 'symbolsTree should not be empty'); + for (const root of parser.symbolsTree) { + assert.ok( + root.type === Edk2SymbolType.dscSection || + root.type === Edk2SymbolType.dscBuildOptionsSection || + root.type === Edk2SymbolType.dscDefine || + root.type === Edk2SymbolType.dscInclude || + root.type === Edk2SymbolType.dscModuleDefinition || + root.type === Edk2SymbolType.dscLibraryDefinition || + root.type === Edk2SymbolType.dscPcdDefinition || + root.type === Edk2SymbolType.dscBuildOption || + root.type === Edk2SymbolType.unknown, + `Unexpected root symbol type: ${root.type}` + ); + } + }); + + test('Library definitions are children of their parent section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.dscSection); + const libSection = sections.find(s => /^\[?\s*libraryclasses\s*\]?$/i.test(s.name)); + if (libSection) { + const childLibs = libSection.children.filter( + c => (c as any).type === Edk2SymbolType.dscLibraryDefinition + ); + assert.ok(childLibs.length >= 3, `Expected >=3 library children in [LibraryClasses], got ${childLibs.length}`); + } + }); + }); + + suite('Consistency', () => { + test('symbolsList length equals total nodes across the tree', () => { + function countNodes(nodes: vscode.DocumentSymbol[]): number { + let count = 0; + for (const n of nodes) { + count++; + count += countNodes(n.children); + } + return count; + } + const treeCount = countNodes(parser.symbolsTree); + assert.strictEqual(parser.symbolsList.length, treeCount, + 'Flat symbolsList length should equal recursive tree node count'); + }); + + test('Comments are not parsed as symbols', () => { + const allNames = parser.symbolsList.map(s => s.name); + for (const name of allNames) { + assert.ok(!name.startsWith('#'), `Symbol name should not start with #: "${name}"`); + assert.ok(!name.startsWith('/*'), `Symbol name should not start with /*: "${name}"`); + } + }); + }); +}); diff --git a/src/test/suite/edkWorkspace.test.ts b/src/test/suite/edkWorkspace.test.ts new file mode 100644 index 0000000..043c1bd --- /dev/null +++ b/src/test/suite/edkWorkspace.test.ts @@ -0,0 +1,762 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { DebugLog } from '../../debugLog'; +import { + SectionProperty, + SectionProperties, + InfDsc, + EdkWorkspaces, + EdkWorkspace, +} from '../../index/edkWorkspace'; + +/** + * Ensure gDebugLog is initialised even when the extension does not activate. + */ +function ensureGlobals() { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +function makeLoc(line: number = 0): vscode.Location { + return new vscode.Location( + vscode.Uri.file('d:/fake/test.dsc'), + new vscode.Position(line, 0) + ); +} + +// ─── SectionProperty ────────────────────────────────────────── + +suite('SectionProperty', () => { + test('constructor lowercases all fields', () => { + const prop = new SectionProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(prop.sectionType, 'libraryclasses'); + assert.strictEqual(prop.arch, 'x64'); + assert.strictEqual(prop.moduleType, 'dxe_driver'); + }); + + test('constructor with already lowercase values', () => { + const prop = new SectionProperty('components', 'ia32', 'peim'); + assert.strictEqual(prop.sectionType, 'components'); + assert.strictEqual(prop.arch, 'ia32'); + assert.strictEqual(prop.moduleType, 'peim'); + }); +}); + +// ─── SectionProperties ──────────────────────────────────────── + +suite('SectionProperties', () => { + test('starts with empty properties', () => { + const sp = new SectionProperties(); + assert.strictEqual(sp.properties.length, 0); + }); + + test('addProperty adds correctly', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.properties.length, 1); + assert.strictEqual(sp.properties[0].sectionType, 'libraryclasses'); + }); + + test('addProperty multiple', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + sp.addProperty('Components', 'IA32', 'PEIM'); + assert.strictEqual(sp.properties.length, 2); + }); + + // ── compareArch ── + + test('compareArch returns true for matching arch', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('Components', 'X64', 'PEIM'); + + assert.strictEqual(sp1.compareArch(sp2), true); + }); + + test('compareArch returns false for different archs', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('Components', 'IA32', 'PEIM'); + + assert.strictEqual(sp1.compareArch(sp2), false); + }); + + // ── compareArchStr ── + + test('compareArchStr returns true for matching arch string', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.compareArchStr('X64'), true); + assert.strictEqual(sp.compareArchStr('x64'), true); + }); + + test('compareArchStr returns false for non-matching arch', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.compareArchStr('IA32'), false); + }); + + // ── compareLibSectionType ── + + test('compareLibSectionType returns true for matching section type', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('LibraryClasses', 'IA32', 'PEIM'); + + assert.strictEqual(sp1.compareLibSectionType(sp2), true); + }); + + test('compareLibSectionType returns false for different section types', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('Components', 'X64', 'DXE_DRIVER'); + + assert.strictEqual(sp1.compareLibSectionType(sp2), false); + }); + + // ── compareLibSectionTypeStr ── + + test('compareLibSectionTypeStr case-insensitive match', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.compareLibSectionTypeStr('LibraryClasses'), true); + assert.strictEqual(sp.compareLibSectionTypeStr('libraryclasses'), true); + assert.strictEqual(sp.compareLibSectionTypeStr('LIBRARYCLASSES'), true); + }); + + test('compareLibSectionTypeStr returns false for non-matching', () => { + const sp = new SectionProperties(); + sp.addProperty('Components', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.compareLibSectionTypeStr('LibraryClasses'), false); + }); + + // ── compareModuleType ── + + test('compareModuleType returns true for matching module type', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('Components', 'IA32', 'DXE_DRIVER'); + + assert.strictEqual(sp1.compareModuleType(sp2), true); + }); + + test('compareModuleType returns false for different module types', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('LibraryClasses', 'X64', 'PEIM'); + + assert.strictEqual(sp1.compareModuleType(sp2), false); + }); + + // ── compareModuleTypeStr ── + + test('compareModuleTypeStr case-insensitive', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.compareModuleTypeStr('DXE_DRIVER'), true); + assert.strictEqual(sp.compareModuleTypeStr('dxe_driver'), true); + }); + + test('compareModuleTypeStr returns false for non-matching', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + assert.strictEqual(sp.compareModuleTypeStr('PEIM'), false); + }); + + // ── toString ── + + test('toString returns comma-separated properties', () => { + const sp = new SectionProperties(); + sp.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + sp.addProperty('Components', 'IA32', 'PEIM'); + const str = sp.toString(); + assert.ok(str.includes(','), 'should contain comma separator'); + }); + + // ── multi-property matching ── + + test('compareArch matches any combination', () => { + const sp1 = new SectionProperties(); + sp1.addProperty('LibraryClasses', 'X64', 'DXE_DRIVER'); + sp1.addProperty('LibraryClasses', 'IA32', 'DXE_DRIVER'); + + const sp2 = new SectionProperties(); + sp2.addProperty('Components', 'IA32', 'PEIM'); + + assert.strictEqual(sp1.compareArch(sp2), true); + }); +}); + +// ─── InfDsc ─────────────────────────────────────────────────── + +suite('InfDsc', () => { + test('constructor with section parent sets sectionProperties', () => { + ensureGlobals(); + const loc = makeLoc(10); + const inf = new InfDsc( + 'MdePkg/Library/BaseLib/BaseLib.inf', + loc, + 'Components.X64', + 'MdePkg/Library/BaseLib/BaseLib.inf' + ); + assert.strictEqual(inf.parent, undefined, 'section parent should set parent to undefined'); + assert.ok(inf.sectionProperties.properties.length > 0, 'should have section properties'); + assert.strictEqual(inf.sectionProperties.properties[0].sectionType, 'components'); + assert.strictEqual(inf.sectionProperties.properties[0].arch, 'x64'); + }); + + test('constructor with INF parent sets parent path', () => { + ensureGlobals(); + const loc = makeLoc(5); + const inf = new InfDsc( + 'MdePkg/Library/BaseLib/BaseLib.inf', + loc, + 'SomeModule/Module.inf', + 'SomeLib|MdePkg/Library/BaseLib/BaseLib.inf' + ); + assert.ok(inf.parent !== undefined, 'INF parent should set parent path'); + assert.ok(inf.parent!.includes('Module.inf')); + assert.strictEqual(inf.sectionProperties.properties.length, 0, 'INF parent should not set section properties'); + }); + + test('constructor normalizes path separators', () => { + ensureGlobals(); + const loc = makeLoc(); + const inf = new InfDsc( + 'MdePkg/Library/BaseLib/BaseLib.inf', + loc, + 'Components.common', + 'line text' + ); + // path.sep on Windows is \\, on Linux / + assert.ok(!inf.path.includes('/') || path.sep === '/', 'forward slashes should be normalized to path.sep'); + }); + + test('constructor with multi-section parent', () => { + ensureGlobals(); + const loc = makeLoc(); + const inf = new InfDsc( + 'Pkg/Lib.inf', + loc, + 'LibraryClasses.X64.DXE_DRIVER,LibraryClasses.IA32.PEIM', + 'SomeLib|Pkg/Lib.inf' + ); + assert.strictEqual(inf.parent, undefined); + assert.strictEqual(inf.sectionProperties.properties.length, 2); + assert.strictEqual(inf.sectionProperties.properties[0].arch, 'x64'); + assert.strictEqual(inf.sectionProperties.properties[0].moduleType, 'dxe_driver'); + assert.strictEqual(inf.sectionProperties.properties[1].arch, 'ia32'); + assert.strictEqual(inf.sectionProperties.properties[1].moduleType, 'peim'); + }); + + test('constructor with section missing arch defaults to common', () => { + ensureGlobals(); + const loc = makeLoc(); + const inf = new InfDsc( + 'Pkg/Lib.inf', + loc, + 'libraryclasses', + 'SomeLib|Pkg/Lib.inf' + ); + assert.strictEqual(inf.sectionProperties.properties[0].sectionType, 'libraryclasses'); + assert.strictEqual(inf.sectionProperties.properties[0].arch, 'common'); + assert.strictEqual(inf.sectionProperties.properties[0].moduleType, 'common'); + }); + + test('getModuleTypeStr returns comma-separated module types', () => { + ensureGlobals(); + const loc = makeLoc(); + const inf = new InfDsc( + 'Pkg/Lib.inf', + loc, + 'LibraryClasses.X64.DXE_DRIVER,LibraryClasses.IA32.PEIM', + 'SomeLib|Pkg/Lib.inf' + ); + const moduleTypes = inf.getModuleTypeStr(); + assert.ok(moduleTypes.includes('dxe_driver')); + assert.ok(moduleTypes.includes('peim')); + assert.ok(moduleTypes.includes(',')); + }); + + test('getModuleTypeStr single property', () => { + ensureGlobals(); + const loc = makeLoc(); + const inf = new InfDsc( + 'Pkg/Mod.inf', + loc, + 'Components.X64', + 'Pkg/Mod.inf' + ); + const moduleTypes = inf.getModuleTypeStr(); + assert.strictEqual(moduleTypes, 'common'); + }); + + test('toString includes path and line number', () => { + ensureGlobals(); + const loc = makeLoc(42); + const inf = new InfDsc( + 'Pkg/Mod.inf', + loc, + 'Components.X64', + 'Pkg/Mod.inf' + ); + const str = inf.toString(); + assert.ok(str.includes('42'), 'toString should include line number'); + }); + + test('text property stores original line', () => { + ensureGlobals(); + const loc = makeLoc(); + const inf = new InfDsc( + 'Pkg/Lib.inf', + loc, + 'LibraryClasses.common', + 'BaseLib|MdePkg/Library/BaseLib/BaseLib.inf' + ); + assert.strictEqual(inf.text, 'BaseLib|MdePkg/Library/BaseLib/BaseLib.inf'); + }); + + test('location is preserved', () => { + ensureGlobals(); + const loc = makeLoc(99); + const inf = new InfDsc( + 'Pkg/Lib.inf', + loc, + 'Components', + 'line' + ); + assert.strictEqual(inf.location.range.start.line, 99); + }); +}); + +// ─── EdkWorkspaces ──────────────────────────────────────────── + +suite('EdkWorkspaces', () => { + test('isConfigured returns false when no workspaces', () => { + const ws = new EdkWorkspaces(); + assert.strictEqual(ws.isConfigured(), false); + }); + + test('isConfigured returns true after adding a workspace', async () => { + ensureGlobals(); + const ws = new EdkWorkspaces(); + // Create a minimal mock document for the constructor + const filePath = path.resolve(__dirname, '../../../test/testDscParsing.dsc'); + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + const edkWs = new EdkWorkspace(doc); + ws.workspaces = [edkWs]; + assert.strictEqual(ws.isConfigured(), true); + }); + + test('getInstance returns singleton', () => { + const inst1 = EdkWorkspaces.getInstance(); + const inst2 = EdkWorkspaces.getInstance(); + assert.strictEqual(inst1, inst2); + }); + + test('isFileInUse returns undefined when no workspaces', async () => { + const ws = new EdkWorkspaces(); + const result = await ws.isFileInUse(vscode.Uri.file('d:/fake/file.dsc')); + assert.strictEqual(result, undefined); + }); + + test('getWorkspace returns empty array when no workspaces', async () => { + const ws = new EdkWorkspaces(); + const result = await ws.getWorkspace(vscode.Uri.file('d:/fake/file.dsc')); + assert.strictEqual(result.length, 0); + }); + + test('getDefinition returns undefined when no workspaces', async () => { + const ws = new EdkWorkspaces(); + const result = await ws.getDefinition(vscode.Uri.file('d:/fake/file.dsc'), 'SOME_VAR'); + assert.strictEqual(result, undefined); + }); + + test('replaceDefines returns original text when no workspaces', async () => { + const ws = new EdkWorkspaces(); + const result = await ws.replaceDefines(vscode.Uri.file('d:/fake/file.dsc'), '$(MY_VAR)/path'); + assert.strictEqual(result, '$(MY_VAR)/path'); + }); + + test('getLib returns empty array when no workspaces', async () => { + const ws = new EdkWorkspaces(); + const loc = new vscode.Location( + vscode.Uri.file('d:/fake/file.dsc'), + new vscode.Position(0, 0) + ); + const result = await ws.getLib(loc); + assert.strictEqual(result.length, 0); + }); +}); + +// ─── EdkWorkspace ───────────────────────────────────────────── + +suite('EdkWorkspace', () => { + let workspace: EdkWorkspace; + + suiteSetup(async () => { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test/testDscParsing.dsc'); + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + workspace = new EdkWorkspace(doc); + }); + + // ── Constructor ── + + test('constructor sets mainDsc from document', () => { + assert.ok(workspace.mainDsc.fsPath.endsWith('testDscParsing.dsc')); + }); + + test('constructor generates a numeric id', () => { + assert.strictEqual(typeof workspace.id, 'number'); + assert.ok(workspace.id > 0); + }); + + test('platformName starts as undefined', () => { + assert.strictEqual(workspace.platformName, undefined); + }); + + test('flashDefinitionDocument starts as undefined', () => { + assert.strictEqual(workspace.flashDefinitionDocument, undefined); + }); + + // ── Lists ── + + test('filesLibraries starts empty', () => { + assert.strictEqual(workspace.filesLibraries.length, 0); + }); + + test('filesModules starts empty', () => { + assert.strictEqual(workspace.filesModules.length, 0); + }); + + test('dscList is initially empty', () => { + assert.strictEqual(workspace.dscList().length, 0); + }); + + test('fdfList is initially empty', () => { + assert.strictEqual(workspace.fdfList().length, 0); + }); + + test('getFilesList aggregates all file lists', () => { + assert.ok(Array.isArray(workspace.getFilesList())); + }); + + test('includeTree starts empty', () => { + assert.strictEqual(workspace.includeTree.length, 0); + }); + + // ── Definitions (no processing) ── + + test('getDefinitions returns a Map', () => { + const defs = workspace.getDefinitions(); + assert.ok(defs instanceof Map); + }); + + test('getDefinition returns undefined for unknown key', () => { + assert.strictEqual(workspace.getDefinition('NON_EXISTENT'), undefined); + }); + + test('getDefinitionLocation returns undefined for unknown key', () => { + assert.strictEqual(workspace.getDefinitionLocation('NON_EXISTENT'), undefined); + }); + + test('replaceDefine passes through text with no defines', () => { + const result = workspace.replaceDefine('$(UNKNOWN_VAR)/path'); + assert.strictEqual(result, '$(UNKNOWN_VAR)/path'); + }); + + // ── PCDs ── + + test('getPcds returns undefined for unknown namespace', () => { + assert.strictEqual(workspace.getPcds('gUnknownPkg'), undefined); + }); + + test('getAllPcds returns a Map', () => { + const pcds = workspace.getAllPcds(); + assert.ok(pcds instanceof Map); + }); + + // ── Library / Module management ── + + test('filesLibraries can be set', () => { + const loc = makeLoc(); + const lib = new InfDsc('Pkg/Lib.inf', loc, 'LibraryClasses.common', 'BaseLib|Pkg/Lib.inf'); + workspace.filesLibraries = [lib]; + assert.strictEqual(workspace.filesLibraries.length, 1); + workspace.filesLibraries = []; // reset + }); + + test('filesModules can be set', () => { + const loc = makeLoc(); + const mod = new InfDsc('Pkg/Mod.inf', loc, 'Components.X64', 'Pkg/Mod.inf'); + workspace.filesModules = [mod]; + assert.strictEqual(workspace.filesModules.length, 1); + workspace.filesModules = []; // reset + }); + + test('getFilesList includes libraries and modules', () => { + const loc = makeLoc(); + workspace.filesLibraries = [new InfDsc('Pkg/Lib.inf', loc, 'LibraryClasses', 'Lib|Pkg/Lib.inf')]; + workspace.filesModules = [new InfDsc('Pkg/Mod.inf', loc, 'Components', 'Pkg/Mod.inf')]; + const list = workspace.getFilesList(); + assert.ok(list.length >= 2, 'should include at least library and module paths'); + workspace.filesLibraries = []; + workspace.filesModules = []; + }); + + // ── filesDsc / filesFdf sets ── + + test('filesDsc can be set and read', async () => { + const filePath = path.resolve(__dirname, '../../../test/testDscParsing.dsc'); + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + workspace.filesDsc = new Set([doc]); + assert.strictEqual(workspace.filesDsc.size, 1); + assert.strictEqual(workspace.dscList().length, 1); + workspace.filesDsc = new Set(); + }); + + test('filesFdf can be set and read', async () => { + const filePath = path.resolve(__dirname, '../../../test/testFdfParsing.fdf'); + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + workspace.filesFdf = new Set([doc]); + assert.strictEqual(workspace.filesFdf.size, 1); + assert.strictEqual(workspace.fdfList().length, 1); + workspace.filesFdf = new Set(); + }); + + // ── getLib ── + + test('getLib returns undefined when no matching library', async () => { + const loc = new vscode.Location( + vscode.Uri.file('d:/nonexistent/file.dsc'), + new vscode.Position(999, 0) + ); + const result = await workspace.getLib(loc); + assert.strictEqual(result, undefined); + }); + + test('getLib finds matching library by location', async () => { + const uri = vscode.Uri.file('d:/fake/platform.dsc'); + const loc = new vscode.Location(uri, new vscode.Position(10, 0)); + const lib = new InfDsc('Pkg/Lib.inf', loc, 'LibraryClasses.common', 'BaseLib|Pkg/Lib.inf'); + workspace.filesLibraries = [lib]; + + const result = await workspace.getLib(loc); + assert.ok(result !== undefined, 'should find the library'); + assert.strictEqual(result!.path, lib.path); + workspace.filesLibraries = []; + }); +}); + +// ─── EdkWorkspace.evaluateExpression ────────────────────────── + +suite('EdkWorkspace.evaluateExpression', () => { + let workspace: EdkWorkspace; + + suiteSetup(async () => { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test/testDscParsing.dsc'); + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + workspace = new EdkWorkspace(doc); + }); + + // ── Boolean literals ── + + test('TRUE evaluates to true', () => { + assert.strictEqual(workspace.evaluateExpression('TRUE'), true); + }); + + test('FALSE evaluates to false', () => { + assert.strictEqual(workspace.evaluateExpression('FALSE'), false); + }); + + test('true/false case-insensitive', () => { + assert.strictEqual(workspace.evaluateExpression('true'), true); + assert.strictEqual(workspace.evaluateExpression('True'), true); + assert.strictEqual(workspace.evaluateExpression('false'), false); + }); + + // ── Numeric literals ── + + test('numeric 1 is truthy', () => { + assert.strictEqual(workspace.evaluateExpression('1'), 1); + }); + + test('numeric 0 is falsy', () => { + assert.strictEqual(workspace.evaluateExpression('0'), 0); + }); + + // ── Equality operators ── + + test('== with matching strings', () => { + assert.strictEqual(workspace.evaluateExpression('"hello" == "hello"'), true); + }); + + test('== with non-matching strings', () => { + assert.strictEqual(workspace.evaluateExpression('"hello" == "world"'), false); + }); + + test('!= with different strings', () => { + assert.strictEqual(workspace.evaluateExpression('"hello" != "world"'), true); + }); + + test('!= with same strings', () => { + assert.strictEqual(workspace.evaluateExpression('"hello" != "hello"'), false); + }); + + test('EQ operator', () => { + assert.strictEqual(workspace.evaluateExpression('"a" EQ "a"'), true); + }); + + test('NE operator', () => { + assert.strictEqual(workspace.evaluateExpression('"a" NE "b"'), true); + }); + + // ── Logical operators ── + + test('AND with both true', () => { + assert.strictEqual(workspace.evaluateExpression('TRUE AND TRUE'), true); + }); + + test('AND with one false', () => { + assert.strictEqual(workspace.evaluateExpression('TRUE AND FALSE'), false); + }); + + test('OR with one true', () => { + assert.strictEqual(workspace.evaluateExpression('FALSE OR TRUE'), true); + }); + + test('OR with both false', () => { + assert.strictEqual(workspace.evaluateExpression('FALSE OR FALSE'), false); + }); + + test('&& operator', () => { + assert.strictEqual(workspace.evaluateExpression('TRUE && TRUE'), true); + }); + + test('|| operator', () => { + assert.strictEqual(workspace.evaluateExpression('FALSE || TRUE'), true); + }); + + // ── NOT operator ── + + test('NOT TRUE evaluates to false', () => { + // NOT is a unary operator that uses stack pop for y, x is undefined + const result = workspace.evaluateExpression('NOT TRUE'); + assert.strictEqual(result, false); + }); + + test('NOT FALSE evaluates to true', () => { + const result = workspace.evaluateExpression('NOT FALSE'); + // NOT inverts the value + assert.ok(result, 'NOT FALSE should be truthy'); + }); + + // ── Arithmetic ── + + test('addition', () => { + assert.strictEqual(workspace.evaluateExpression('3 + 2'), 5); + }); + + test('subtraction', () => { + assert.strictEqual(workspace.evaluateExpression('5 - 2'), 3); + }); + + test('multiplication', () => { + assert.strictEqual(workspace.evaluateExpression('3 * 4'), 12); + }); + + test('division', () => { + assert.strictEqual(workspace.evaluateExpression('10 / 2'), 5); + }); + + test('modulus', () => { + assert.strictEqual(workspace.evaluateExpression('10 % 3'), 1); + }); + + // ── Comparison ── + + test('greater than', () => { + assert.strictEqual(workspace.evaluateExpression('5 > 3'), true); + }); + + test('less than', () => { + assert.strictEqual(workspace.evaluateExpression('3 > 5'), false); + }); + + test('greater or equal', () => { + assert.strictEqual(workspace.evaluateExpression('5 >= 5'), true); + }); + + test('less or equal', () => { + assert.strictEqual(workspace.evaluateExpression('3 <= 5'), true); + }); + + // ── Parentheses ── + + test('parentheses group expressions', () => { + assert.strictEqual(workspace.evaluateExpression('(TRUE OR FALSE) AND TRUE'), true); + }); + + test('nested parentheses', () => { + assert.strictEqual(workspace.evaluateExpression('((1 + 2) * 3)'), 9); + }); + + test('unbalanced parentheses throw', () => { + assert.throws(() => { + workspace.evaluateExpression('(TRUE AND FALSE'); + }); + }); + + // ── IN operator ── + + test('IN operator with match', () => { + assert.strictEqual(workspace.evaluateExpression('"X64" IN "X64 IA32 ARM"'), true); + }); + + test('IN operator without match', () => { + assert.strictEqual(workspace.evaluateExpression('"AARCH64" IN "X64 IA32 ARM"'), false); + }); + + // ── Undefined variables (???) ── + + test('undefined variable evaluates to false', () => { + // The expression evaluator replaces "???" with FALSE + assert.strictEqual(workspace.evaluateExpression('"???"'), false); + }); + + // ── String without quotes treated as string ── + + test('bare word is treated as quoted string', () => { + const result = workspace.evaluateExpression('hello == "hello"'); + assert.strictEqual(result, true); + }); + + // ── Complex expressions ── + + test('complex: (1 + 2) > 2 AND TRUE', () => { + assert.strictEqual(workspace.evaluateExpression('(1 + 2) > 2 AND TRUE'), true); + }); + + test('complex: FALSE OR (5 == 5)', () => { + assert.strictEqual(workspace.evaluateExpression('FALSE OR (5 == 5)'), true); + }); +}); diff --git a/src/test/suite/edkWorkspaceProcess.test.ts b/src/test/suite/edkWorkspaceProcess.test.ts new file mode 100644 index 0000000..37f99f0 --- /dev/null +++ b/src/test/suite/edkWorkspaceProcess.test.ts @@ -0,0 +1,422 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { DebugLog } from '../../debugLog'; +import { ConfigAgent } from '../../configuration'; +import { DiagnosticManager } from '../../diagnostics'; +import { EdkWorkspace, InfDsc } from '../../index/edkWorkspace'; +import { PathFind } from '../../pathfind'; + +/** + * Ensure all globals required by EdkWorkspace processing are available. + * Stubs status bar, tree provider, path finder, config agent, and + * diagnostics so the private _processDocument / _doProccessWorkspace + * can run in a test environment. + */ +function ensureProcessingGlobals() { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } + if (!ext.gWorkspacePath) { + ext.gWorkspacePath = path.resolve(__dirname, '../../../'); + } + if (!ext.gConfigAgent) { + ext.gConfigAgent = new ConfigAgent(); + } + if (!ext.gPathFind) { + ext.gPathFind = new PathFind(); + } + // Stub edkWorkspaceTreeProvider.refresh() + if (!ext.edkWorkspaceTreeProvider) { + ext.edkWorkspaceTreeProvider = { refresh() {} }; + } + + // Initialize DiagnosticManager (creates diagnosticsCollection) + DiagnosticManager.getInstance(); + + // Stub edkStatusBar functions that reference myStatusBarItem + // eslint-disable-next-line @typescript-eslint/no-var-requires + const statusBar = require('../../statusBar'); + if (!statusBar.myStatusBarItem) { + statusBar.myStatusBarItem = { + text: '', + tooltip: '', + show() {}, + hide() {}, + backgroundColor: undefined, + command: undefined, + }; + } +} + +/** + * Open a DSC file from the test/ folder and create an EdkWorkspace from it. + */ +async function openDscDocument(filename: string): Promise { + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + return vscode.workspace.openTextDocument(uri); +} + +// ═══════════════════════════════════════════════════════════════ +// _processDocument tests +// ═══════════════════════════════════════════════════════════════ + +suite('EdkWorkspace._processDocument', () => { + let workspace: EdkWorkspace; + let doc: vscode.TextDocument; + + suiteSetup(async function () { + this.timeout(15_000); + ensureProcessingGlobals(); + doc = await openDscDocument('testDscProcess.dsc'); + workspace = new EdkWorkspace(doc); + // Call private method directly + await (workspace as any)._processDocument(doc, 'DSC'); + }); + + suite('Document Registration', () => { + test('Adds document to filesDsc', () => { + const dscPaths = workspace.dscList(); + assert.ok(dscPaths.length >= 1, 'filesDsc should contain at least the main document'); + assert.ok( + dscPaths.some(p => p.endsWith('testDscProcess.dsc')), + 'filesDsc should include testDscProcess.dsc' + ); + }); + + test('isDocumentInIndex returns true after processing', () => { + assert.strictEqual( + (workspace as any).isDocumentInIndex(doc), + true, + 'Document should be in the index after processing' + ); + }); + + test('Processing same document twice is a no-op', async () => { + const sizeBefore = workspace.dscList().length; + await (workspace as any)._processDocument(doc, 'DSC'); + assert.strictEqual(workspace.dscList().length, sizeBefore, 'Should not re-add'); + }); + }); + + suite('Defines Extraction', () => { + test('Extracts PLATFORM_NAME from [Defines]', () => { + assert.strictEqual(workspace.getDefinition('PLATFORM_NAME'), 'TestProcess'); + }); + + test('Extracts PLATFORM_GUID', () => { + assert.strictEqual( + workspace.getDefinition('PLATFORM_GUID'), + '11111111-2222-3333-4444-555555555555' + ); + }); + + test('Extracts DEFINE MY_FLAG', () => { + assert.strictEqual(workspace.getDefinition('MY_FLAG'), 'TRUE'); + }); + + test('Extracts DEFINE with empty value', () => { + const val = workspace.getDefinition('EMPTY_DEF'); + assert.ok(val !== undefined, 'EMPTY_DEF should be defined'); + assert.strictEqual(val!.trim(), ''); + }); + + test('Extracts root-level DEFINE', () => { + assert.strictEqual(workspace.getDefinition('ROOT_DEF'), 'RootValue'); + }); + + test('getDefinitionLocation returns a Location for known define', () => { + const loc = workspace.getDefinitionLocation('PLATFORM_NAME'); + assert.ok(loc !== undefined, 'Should have a location'); + assert.ok(loc!.uri.fsPath.endsWith('testDscProcess.dsc')); + }); + + test('getDefinitions returns all defines as a Map', () => { + const defs = workspace.getDefinitions(); + assert.ok(defs instanceof Map); + assert.ok(defs.size >= 5, `Expected >=5 defines, got ${defs.size}`); + }); + }); + + suite('Define Variable Substitution', () => { + test('DERIVED define has $(BASE) resolved', () => { + const val = workspace.getDefinition('DERIVED'); + assert.strictEqual(val, 'HelloWorld', 'DERIVED should be resolved to HelloWorld'); + }); + + test('replaceDefine substitutes known variables', () => { + const result = workspace.replaceDefine('$(MY_FLAG)'); + assert.strictEqual(result, 'TRUE'); + }); + + test('replaceDefine leaves unknown variables untouched', () => { + const result = workspace.replaceDefine('$(TOTALLY_UNKNOWN)'); + assert.strictEqual(result, '$(TOTALLY_UNKNOWN)'); + }); + }); + + suite('PCD Extraction', () => { + test('Extracts PCDs in gTestPkg namespace', () => { + const pcds = workspace.getPcds('gTestPkg'); + assert.ok(pcds !== undefined, 'gTestPkg PCDs should exist'); + }); + + test('PcdTestMask has correct value', () => { + const pcds = workspace.getPcds('gTestPkg'); + const pcd = pcds?.get('PcdTestMask'); + assert.ok(pcd !== undefined, 'PcdTestMask should exist'); + assert.strictEqual(pcd!.value, '0x2F'); + }); + + test('PcdBootTimeout from DynamicDefault', () => { + const pcds = workspace.getPcds('gTestPkg'); + const pcd = pcds?.get('PcdBootTimeout'); + assert.ok(pcd !== undefined, 'PcdBootTimeout should exist'); + assert.strictEqual(pcd!.value, '5'); + }); + + test('PCD with L"string" value strips L prefix', () => { + const pcds = workspace.getPcds('gTestPkg'); + const pcd = pcds?.get('PcdStringVal'); + assert.ok(pcd !== undefined, 'PcdStringVal should exist'); + assert.ok(pcd!.value.startsWith('"'), 'Value should start with " after L is stripped'); + }); + + test('PCD has location information', () => { + const pcds = workspace.getPcds('gTestPkg'); + const pcd = pcds?.get('PcdTestMask'); + assert.ok(pcd!.position.uri.fsPath.endsWith('testDscProcess.dsc')); + }); + + test('getAllPcds returns all namespaces', () => { + const all = workspace.getAllPcds(); + assert.ok(all.has('gTestPkg'), 'Should have gTestPkg namespace'); + }); + }); + + suite('Conditional Processing', () => { + test('!if TRUE branch: COND_TAKEN is defined', () => { + assert.strictEqual(workspace.getDefinition('COND_TAKEN'), 'IfTrueValue'); + }); + + test('!if TRUE branch: else branch not taken', () => { + // COND_TAKEN should NOT be IfFalseValue + assert.notStrictEqual(workspace.getDefinition('COND_TAKEN'), 'IfFalseValue'); + }); + + test('!if FALSE: else branch taken, COND_ELSE is defined', () => { + assert.strictEqual(workspace.getDefinition('COND_ELSE'), 'ElseValue'); + }); + + test('!if FALSE: if branch not taken, COND_FALSE_IF not defined', () => { + assert.strictEqual(workspace.getDefinition('COND_FALSE_IF'), undefined); + }); + + test('Nested conditionals: outer TRUE inner FALSE -> INNER_ELSE defined', () => { + assert.strictEqual(workspace.getDefinition('OUTER_TRUE'), 'OuterOk'); + assert.strictEqual(workspace.getDefinition('INNER_ELSE'), 'InnerElseOk'); + }); + + test('Nested conditionals: INNER_FALSE not defined', () => { + assert.strictEqual(workspace.getDefinition('INNER_FALSE'), undefined); + }); + + test('!ifdef on existing variable takes the branch', () => { + assert.strictEqual(workspace.getDefinition('IFDEF_TAKEN'), 'yes'); + }); + + test('!ifndef on undefined variable takes the branch', () => { + assert.strictEqual(workspace.getDefinition('IFNDEF_TAKEN'), 'yes'); + }); + + test('COND_A before conditional is still defined', () => { + assert.strictEqual(workspace.getDefinition('COND_A'), 'BeforeIf'); + }); + }); + + suite('Library and Module References', () => { + test('Libraries are collected (even if paths unresolved)', () => { + // Libraries are added even when gPathFind.findPath returns empty + assert.ok(workspace.filesLibraries.length >= 2, + `Expected >=2 library refs, got ${workspace.filesLibraries.length}`); + }); + + test('Modules are collected (even if paths unresolved)', () => { + assert.ok(workspace.filesModules.length >= 2, + `Expected >=2 module refs, got ${workspace.filesModules.length}`); + }); + + test('Library InfDsc has correct section properties', () => { + const lib = workspace.filesLibraries.find(l => l.path.includes('BaseLib')); + assert.ok(lib !== undefined, 'BaseLib should be in libraries'); + assert.ok(lib!.sectionProperties.properties.length > 0); + }); + + test('Module InfDsc preserves location', () => { + const mod = workspace.filesModules[0]; + assert.ok(mod.location.uri.fsPath.endsWith('testDscProcess.dsc')); + }); + }); + + suite('Grayout Ranges', () => { + test('parsedDocuments has entry for processed document', () => { + const ranges = (workspace as any).parsedDocuments.get(doc.uri.fsPath); + assert.ok(ranges !== undefined, 'Should have grayout entry'); + }); + + test('Grayout ranges exist for inactive conditional blocks', () => { + const ranges: vscode.Range[] = (workspace as any).parsedDocuments.get(doc.uri.fsPath); + // The !if FALSE ... !else block should generate at least one grayout range + assert.ok(ranges.length >= 1, + `Expected >=1 grayout ranges, got ${ranges.length}`); + }); + }); + + suite('Comment Stripping', () => { + test('stripComment removes line comments', () => { + const result = (workspace as any).stripComment('DEFINE X = 1 # comment'); + assert.strictEqual(result, 'DEFINE X = 1'); + }); + + test('stripComment preserves hash inside quotes', () => { + const result = (workspace as any).stripComment('DEFINE X = "value#with#hash"'); + assert.strictEqual(result, 'DEFINE X = "value#with#hash"'); + }); + + test('stripComment trims whitespace', () => { + const result = (workspace as any).stripComment(' some text '); + assert.strictEqual(result, 'some text'); + }); + + test('stripComment returns empty for comment-only line', () => { + const result = (workspace as any).stripComment('# just a comment'); + assert.strictEqual(result, ''); + }); + }); +}); + +// ═══════════════════════════════════════════════════════════════ +// _doProccessWorkspace tests +// ═══════════════════════════════════════════════════════════════ + +suite('EdkWorkspace._doProccessWorkspace', () => { + let workspace: EdkWorkspace; + + suiteSetup(async function () { + this.timeout(15_000); + ensureProcessingGlobals(); + const doc = await openDscDocument('testDscProcess.dsc'); + workspace = new EdkWorkspace(doc); + const result = await (workspace as any)._doProccessWorkspace(); + assert.ok(result === true, '_doProccessWorkspace should return true'); + }); + + suite('Workspace Initialization', () => { + test('platformName is populated from PLATFORM_NAME define', () => { + assert.strictEqual(workspace.platformName, 'TestProcess'); + }); + + test('workInProgress is false after completion', () => { + assert.strictEqual((workspace as any).workInProgress, false); + }); + + test('processComplete is true after completion', () => { + assert.strictEqual((workspace as any).processComplete, true); + }); + }); + + suite('State Reset', () => { + test('Running again resets and re-processes', async () => { + // First run already done in suiteSetup. Reset workInProgress flag + // by accessing the internal state directly (it was set to false). + const result = await (workspace as any)._doProccessWorkspace(); + assert.ok(result === true, 'Second run should succeed'); + assert.strictEqual(workspace.platformName, 'TestProcess'); + }); + + test('Returns false if already in progress', async () => { + (workspace as any).workInProgress = true; + const result = await (workspace as any)._doProccessWorkspace(); + assert.strictEqual(result, false, 'Should return false when workInProgress'); + (workspace as any).workInProgress = false; + }); + }); + + suite('Defines After Full Processing', () => { + test('All defines from [Defines] section are available', () => { + const defs = workspace.getDefinitions(); + assert.ok(defs.has('PLATFORM_NAME')); + assert.ok(defs.has('MY_FLAG')); + assert.ok(defs.has('MY_PATH')); + }); + + test('Conditional defines are correctly resolved', () => { + assert.strictEqual(workspace.getDefinition('COND_TAKEN'), 'IfTrueValue'); + assert.strictEqual(workspace.getDefinition('COND_ELSE'), 'ElseValue'); + }); + + test('replaceDefine works after processing', () => { + const result = workspace.replaceDefine('$(PLATFORM_NAME)'); + assert.strictEqual(result, 'TestProcess'); + }); + }); + + suite('PCDs After Full Processing', () => { + test('PCDs are available after processing', () => { + const pcds = workspace.getPcds('gTestPkg'); + assert.ok(pcds !== undefined); + assert.ok(pcds!.size >= 3, `Expected >=3 PCDs, got ${pcds!.size}`); + }); + }); + + suite('File Lists After Full Processing', () => { + test('dscList contains the main document', () => { + const list = workspace.dscList(); + assert.ok(list.length >= 1, 'dscList should have at least 1 entry'); + assert.ok(list.some(p => p.endsWith('testDscProcess.dsc'))); + }); + + test('Libraries are populated', () => { + assert.ok(workspace.filesLibraries.length >= 2); + }); + + test('Modules are populated', () => { + assert.ok(workspace.filesModules.length >= 2); + }); + + test('getFilesList aggregates all lists', () => { + const all = workspace.getFilesList(); + assert.ok(all.length >= 4, + `Expected >=4 total files (dsc+libs+mods), got ${all.length}`); + }); + }); +}); + +// ═══════════════════════════════════════════════════════════════ +// proccessWorkspace (public API) tests +// ═══════════════════════════════════════════════════════════════ + +suite('EdkWorkspace.proccessWorkspace', () => { + test('proccessWorkspace runs without errors', async function () { + this.timeout(15_000); + ensureProcessingGlobals(); + const doc = await openDscDocument('testDscProcess.dsc'); + const workspace = new EdkWorkspace(doc); + const result = await workspace.proccessWorkspace(); + assert.ok(result === true, 'proccessWorkspace should return true'); + assert.strictEqual(workspace.platformName, 'TestProcess'); + }); + + test('proccessWorkspace populates defines and PCDs', async function () { + this.timeout(15_000); + ensureProcessingGlobals(); + const doc = await openDscDocument('testDscProcess.dsc'); + const workspace = new EdkWorkspace(doc); + await workspace.proccessWorkspace(); + assert.ok(workspace.getDefinitions().size > 0, 'Should have defines'); + assert.ok(workspace.getAllPcds().size > 0, 'Should have PCDs'); + }); +}); diff --git a/src/test/suite/fdfParser.test.ts b/src/test/suite/fdfParser.test.ts new file mode 100644 index 0000000..ef633bf --- /dev/null +++ b/src/test/suite/fdfParser.test.ts @@ -0,0 +1,101 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { FdfParser } from '../../edkParser/fdfParser'; +import { Edk2SymbolType } from '../../symbols/symbolsType'; +import { DebugLog } from '../../debugLog'; + +function ensureGlobals() { + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +async function parseFdfFile(filename: string): Promise { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const parser = new FdfParser(document); + await parser.parseFile(); + return parser; +} + +function symbolsOfType(parser: FdfParser, type: Edk2SymbolType) { + return parser.symbolsList.filter(s => s.type === type); +} + +suite('FDF Parser – Symbol Extraction', () => { + + let parser: FdfParser; + + suiteSetup(async function () { + this.timeout(10_000); + parser = await parseFdfFile('testFdfParsing.fdf'); + }); + + suite('Sections', () => { + test('Parses [FD.*] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.fdfSection); + const fd = sections.filter(s => /FD\./i.test(s.name)); + assert.ok(fd.length >= 1, `Expected >=1 FD section, got ${fd.length}`); + }); + + test('Parses [FV.*] sections', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.fdfSection); + const fv = sections.filter(s => /FV\./i.test(s.name)); + assert.ok(fv.length >= 2, `Expected >=2 FV sections, got ${fv.length}`); + }); + + test('Parses [Rule.*] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.fdfSection); + const rules = sections.filter(s => /Rule\./i.test(s.name)); + assert.ok(rules.length >= 1, `Expected >=1 Rule section, got ${rules.length}`); + }); + }); + + suite('INF References', () => { + test('Parses INF module references', () => { + const infs = symbolsOfType(parser, Edk2SymbolType.fdfInf); + assert.ok(infs.length >= 3, `Expected >=3 INF refs, got ${infs.length}`); + }); + }); + + suite('Defines', () => { + test('Parses DEFINE statements', () => { + const defs = symbolsOfType(parser, Edk2SymbolType.fdfDefinition); + assert.ok(defs.length >= 2, `Expected >=2 DEFINE statements, got ${defs.length}`); + }); + }); + + suite('Includes', () => { + test('Parses !include directive', () => { + const includes = symbolsOfType(parser, Edk2SymbolType.fdfInclude); + assert.ok(includes.length >= 1, 'Expected at least one !include'); + const inc = includes.find(s => /TestFdfInclude/i.test(s.name)); + assert.ok(inc, 'TestFdfInclude.fdf.inc should be found'); + }); + }); + + suite('Consistency', () => { + test('symbolsTree is not empty', () => { + assert.ok(parser.symbolsTree.length > 0, 'symbolsTree should not be empty'); + }); + + test('symbolsList equals recursive tree count', () => { + function countNodes(nodes: vscode.DocumentSymbol[]): number { + let c = 0; + for (const n of nodes) { c++; c += countNodes(n.children); } + return c; + } + assert.strictEqual(parser.symbolsList.length, countNodes(parser.symbolsTree)); + }); + + test('Comments are not parsed as symbols', () => { + for (const s of parser.symbolsList) { + assert.ok(!s.name.startsWith('#'), `Symbol should not start with #: "${s.name}"`); + } + }); + }); +}); diff --git a/src/test/suite/infParser.test.ts b/src/test/suite/infParser.test.ts new file mode 100644 index 0000000..095c038 --- /dev/null +++ b/src/test/suite/infParser.test.ts @@ -0,0 +1,187 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { InfParser } from '../../edkParser/infParser'; +import { Edk2SymbolType } from '../../symbols/symbolsType'; +import { DebugLog } from '../../debugLog'; + +function ensureGlobals() { + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +async function parseInfFile(filename: string): Promise { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const parser = new InfParser(document); + await parser.parseFile(); + return parser; +} + +function symbolsOfType(parser: InfParser, type: Edk2SymbolType) { + return parser.symbolsList.filter(s => s.type === type); +} + +suite('INF Parser – Symbol Extraction', () => { + + let parser: InfParser; + + suiteSetup(async function () { + this.timeout(10_000); + parser = await parseInfFile('testInfParsing.inf'); + }); + + suite('Sections', () => { + test('Parses [Defines] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSection); + const defines = sections.filter(s => /defines/i.test(s.name)); + assert.ok(defines.length >= 1, 'Expected at least one [Defines] section'); + }); + + test('Parses [Sources] section(s)', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionSource); + assert.ok(sections.length >= 1, `Expected >=1 Sources section, got ${sections.length}`); + }); + + test('Parses [Packages] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionPackages); + assert.ok(sections.length >= 1, 'Expected at least one [Packages] section'); + }); + + test('Parses [LibraryClasses] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionLibraries); + assert.ok(sections.length >= 1, 'Expected at least one [LibraryClasses] section'); + }); + + test('Parses [Protocols] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionProtocols); + assert.ok(sections.length >= 1, 'Expected at least one [Protocols] section'); + }); + + test('Parses [Ppis] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionPpis); + assert.ok(sections.length >= 1, 'Expected at least one [Ppis] section'); + }); + + test('Parses [Guids] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionGuids); + assert.ok(sections.length >= 1, 'Expected at least one [Guids] section'); + }); + + test('Parses [Pcd] / [FixedPcd] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionPcds); + assert.ok(sections.length >= 1, 'Expected at least one PCD section'); + }); + + test('Parses [Depex] section', () => { + const sections = symbolsOfType(parser, Edk2SymbolType.infSectionDepex); + assert.ok(sections.length >= 1, 'Expected at least one [Depex] section'); + }); + }); + + suite('Defines', () => { + test('Parses INF defines (MODULE_TYPE, BASE_NAME, etc.)', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.infDefine); + assert.ok(defines.length >= 7, `Expected >=7 defines, got ${defines.length}`); + }); + + test('Parses ENTRY_POINT define', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.infDefine); + const ep = defines.find(s => /ENTRY_POINT/i.test(s.name)); + assert.ok(ep, 'ENTRY_POINT should be parsed'); + }); + + test('Parses CONSTRUCTOR define', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.infDefine); + const ctor = defines.find(s => /CONSTRUCTOR/i.test(s.name)); + assert.ok(ctor, 'CONSTRUCTOR should be parsed'); + }); + + test('Parses DESTRUCTOR define', () => { + const defines = symbolsOfType(parser, Edk2SymbolType.infDefine); + const dtor = defines.find(s => /DESTRUCTOR/i.test(s.name)); + assert.ok(dtor, 'DESTRUCTOR should be parsed'); + }); + }); + + suite('Sources', () => { + test('Parses source file entries', () => { + const sources = symbolsOfType(parser, Edk2SymbolType.infSource); + assert.ok(sources.length >= 3, `Expected >=3 source entries, got ${sources.length}`); + }); + }); + + suite('Packages', () => { + test('Parses package references', () => { + const packages = symbolsOfType(parser, Edk2SymbolType.infPackage); + assert.ok(packages.length >= 2, `Expected >=2 packages, got ${packages.length}`); + }); + }); + + suite('Libraries', () => { + test('Parses library class references', () => { + const libs = symbolsOfType(parser, Edk2SymbolType.infLibrary); + assert.ok(libs.length >= 3, `Expected >=3 library references, got ${libs.length}`); + }); + }); + + suite('Protocols', () => { + test('Parses protocol entries', () => { + const protocols = symbolsOfType(parser, Edk2SymbolType.infProtocol); + assert.ok(protocols.length >= 2, `Expected >=2 protocols, got ${protocols.length}`); + }); + }); + + suite('PPIs', () => { + test('Parses PPI entries', () => { + const ppis = symbolsOfType(parser, Edk2SymbolType.infPpi); + assert.ok(ppis.length >= 1, `Expected >=1 PPI, got ${ppis.length}`); + }); + }); + + suite('GUIDs', () => { + test('Parses GUID entries', () => { + const guids = symbolsOfType(parser, Edk2SymbolType.infGuid); + assert.ok(guids.length >= 2, `Expected >=2 GUIDs, got ${guids.length}`); + }); + }); + + suite('PCDs', () => { + test('Parses PCD entries', () => { + const pcds = symbolsOfType(parser, Edk2SymbolType.infPcd); + assert.ok(pcds.length >= 1, `Expected >=1 PCD, got ${pcds.length}`); + }); + }); + + suite('Depex', () => { + test('Parses dependency expression entries', () => { + const depex = symbolsOfType(parser, Edk2SymbolType.infDepex); + assert.ok(depex.length >= 1, `Expected >=1 depex entry, got ${depex.length}`); + }); + }); + + suite('Consistency', () => { + test('symbolsTree is not empty', () => { + assert.ok(parser.symbolsTree.length > 0, 'symbolsTree should not be empty'); + }); + + test('symbolsList equals recursive tree count', () => { + function countNodes(nodes: vscode.DocumentSymbol[]): number { + let c = 0; + for (const n of nodes) { c++; c += countNodes(n.children); } + return c; + } + assert.strictEqual(parser.symbolsList.length, countNodes(parser.symbolsTree)); + }); + + test('Comments are not parsed as symbols', () => { + for (const s of parser.symbolsList) { + assert.ok(!s.name.startsWith('#'), `Symbol should not start with #: "${s.name}"`); + } + }); + }); +}); diff --git a/src/test/suite/vfrParser.test.ts b/src/test/suite/vfrParser.test.ts new file mode 100644 index 0000000..714f7e1 --- /dev/null +++ b/src/test/suite/vfrParser.test.ts @@ -0,0 +1,116 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { VfrParser } from '../../edkParser/vfrParser'; +import { Edk2SymbolType } from '../../symbols/symbolsType'; +import { DebugLog } from '../../debugLog'; + +function ensureGlobals() { + const ext = require('../../extension'); + if (!ext.gDebugLog) { + ext.gDebugLog = new DebugLog(); + } +} + +async function parseVfrFile(filename: string): Promise { + ensureGlobals(); + const filePath = path.resolve(__dirname, '../../../test', filename); + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); + const parser = new VfrParser(document); + await parser.parseFile(); + return parser; +} + +function symbolsOfType(parser: VfrParser, type: Edk2SymbolType) { + return parser.symbolsList.filter(s => s.type === type); +} + +suite('VFR Parser – Symbol Extraction', () => { + + let parser: VfrParser; + + suiteSetup(async function () { + this.timeout(10_000); + parser = await parseVfrFile('testVfrParsing.vfr'); + }); + + suite('Formset', () => { + test('Parses formset', () => { + const formsets = symbolsOfType(parser, Edk2SymbolType.vfrFormset); + assert.ok(formsets.length >= 1, 'Expected at least one formset'); + }); + + test('Parses form blocks', () => { + const forms = symbolsOfType(parser, Edk2SymbolType.vfrForm); + assert.ok(forms.length >= 0, `Unexpected negative form count: ${forms.length}`); + }); + }); + + suite('Controls', () => { + test('Parses oneof controls', () => { + const oneofs = symbolsOfType(parser, Edk2SymbolType.vfrOneof); + assert.ok(oneofs.length >= 2, `Expected >=2 oneofs, got ${oneofs.length}`); + }); + + test('Parses checkbox controls', () => { + const checkboxes = symbolsOfType(parser, Edk2SymbolType.vfrCheckbox); + assert.ok(checkboxes.length >= 1, `Expected >=1 checkbox, got ${checkboxes.length}`); + }); + + test('Parses numeric controls', () => { + const numerics = symbolsOfType(parser, Edk2SymbolType.vfrNumeric); + assert.ok(numerics.length >= 1, `Expected >=1 numeric, got ${numerics.length}`); + }); + + test('Parses string controls', () => { + const strings = symbolsOfType(parser, Edk2SymbolType.vfrString); + assert.ok(strings.length >= 1, `Expected >=1 string control, got ${strings.length}`); + }); + + test('Parses password controls', () => { + const passwords = symbolsOfType(parser, Edk2SymbolType.vfrPassword); + assert.ok(passwords.length >= 1, `Expected >=1 password control, got ${passwords.length}`); + }); + + test('Parses goto references', () => { + const gotos = symbolsOfType(parser, Edk2SymbolType.vfrGoto); + assert.ok(gotos.length >= 2, `Expected >=2 goto refs, got ${gotos.length}`); + }); + + test('Parses prompt entries inside controls', () => { + const prompts = symbolsOfType(parser, Edk2SymbolType.vfrString); + assert.ok(prompts.length >= 2, `Expected >=2 prompt/string entries, got ${prompts.length}`); + }); + }); + + suite('Tree Structure', () => { + test('symbolsTree is not empty', () => { + assert.ok(parser.symbolsTree.length > 0); + }); + + test('Formset or form has children', () => { + const formsets = symbolsOfType(parser, Edk2SymbolType.vfrFormset); + const forms = symbolsOfType(parser, Edk2SymbolType.vfrForm); + const withChildren = [...formsets, ...forms].filter(s => s.children.length > 0); + assert.ok(withChildren.length > 0, 'At least one formset or form should have children'); + }); + }); + + suite('Consistency', () => { + test('symbolsList equals recursive tree count', () => { + function countNodes(nodes: vscode.DocumentSymbol[]): number { + let c = 0; + for (const n of nodes) { c++; c += countNodes(n.children); } + return c; + } + assert.strictEqual(parser.symbolsList.length, countNodes(parser.symbolsTree)); + }); + + test('Comments are not parsed as symbols', () => { + for (const s of parser.symbolsList) { + assert.ok(!s.name.startsWith('//'), `Symbol should not start with //: "${s.name}"`); + } + }); + }); +}); diff --git a/test/testAslParsing.asl b/test/testAslParsing.asl new file mode 100644 index 0000000..be251af --- /dev/null +++ b/test/testAslParsing.asl @@ -0,0 +1,60 @@ +// Corner-case ASL file for parser tests. + +DefinitionBlock ("test.aml", "DSDT", 2, "TEST", "TESTDSDT", 0x00000001) +{ + External (\_SB.PCI0, DeviceObj) + External (\_SB.PCI0.LPCB, DeviceObj) + + Scope (\_SB) + { + Name (TVAR, 0x1234) + + Device (TPM0) + { + Name (_HID, "MSFT0101") + Name (_STR, Unicode("TPM 2.0 Device")) + + OperationRegion (TPMR, SystemMemory, 0xFED40000, 0x5000) + Field (TPMR, AnyAcc, NoLock, Preserve) + { + ACC0, 8, + } + + Method (_STA, 0, Serialized) + { + Return (0x0F) + } + + Method (_CRS, 0, Serialized) + { + Name (RBUF, ResourceTemplate () + { + Memory32Fixed (ReadWrite, 0xFED40000, 0x5000) + }) + Return (RBUF) + } + } + + Device (EC0) + { + Name (_HID, "PNP0C09") + + OperationRegion (ECOR, EmbeddedControl, 0x00, 0xFF) + Field (ECOR, ByteAcc, Lock, Preserve) + { + TEMP, 8, + FAN0, 8, + } + + Method (RFAN, 0, NotSerialized) + { + Return (FAN0) + } + } + } + + Scope (\_SB.PCI0) + { + Name (PVAR, "PCI") + } +} diff --git a/test/testDecParsing.dec b/test/testDecParsing.dec new file mode 100644 index 0000000..d557810 --- /dev/null +++ b/test/testDecParsing.dec @@ -0,0 +1,36 @@ +## Corner-case DEC file for parser tests. + +[Defines] + DEC_SPECIFICATION = 0x00010017 + PACKAGE_NAME = TestPkg + PACKAGE_GUID = 11223344-5566-7788-99AA-BBCCDDEEFF00 + PACKAGE_VERSION = 1.0 + +[Includes] + Include + Include/Library + +[Includes.X64] + Include/X64 + +[LibraryClasses] + BaseLib|Include/Library/BaseLib.h + DebugLib|Include/Library/DebugLib.h + +[Guids] + gTestTokenSpaceGuid = { 0x12345678, 0x1234, 0x1234, { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 } } + +[Protocols] + gTestProtocolGuid = { 0xAABBCCDD, 0x1122, 0x3344, { 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC } } + gTestProtocol2Guid = { 0x11111111, 0x2222, 0x3333, { 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB } } + +[Ppis] + gTestPpiGuid = { 0xDDDDDDDD, 0xEEEE, 0xFFFF, { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 } } + +[PcdsFixedAtBuild] + gTestTokenSpaceGuid.PcdTestFixed|0x00000001|UINT32|0x00000001 + +[PcdsPatchableInModule] + gTestTokenSpaceGuid.PcdTestPatchable|0x00|UINT8|0x00000002 + +# End of DEC diff --git a/test/testDscParsing.dsc b/test/testDscParsing.dsc new file mode 100644 index 0000000..b3139dc --- /dev/null +++ b/test/testDscParsing.dsc @@ -0,0 +1,63 @@ +## Corner-case DSC file used by the parser unit tests. +## Every section exercises a different parsing path. + +[Defines] + PLATFORM_NAME = TestParsing + PLATFORM_GUID = AABBCCDD-1122-3344-5566-778899AABBCC + PLATFORM_VERSION = 0.1 + DSC_SPECIFICATION = 0x00010017 + OUTPUT_DIRECTORY = Build/TestParsing + SUPPORTED_ARCHITECTURES = IA32|X64|AARCH64 + BUILD_TARGETS = DEBUG|RELEASE + DEFINE MY_VAR = SomeValue + DEFINE EMPTY_VAR = + +# Root-level define (outside any section) +DEFINE ROOT_DEFINE = OutsideSection + +[SkuIds] + 0|DEFAULT + +[LibraryClasses] + BaseLib | MdePkg/Library/BaseLib/BaseLib.inf + DebugLib|MdePkg/Library/DebugLib/DebugLib.inf + # Library with spaces around pipe + PrintLib | MdePkg/Library/PrintLib/PrintLib.inf + +[LibraryClasses.X64] + TimerLib|MdePkg/Library/TimerLib/TimerLib.inf + +[Components] + MdePkg/Test/SimpleModule.inf + MdePkg/Test/AnotherModule.inf + +[Components.X64] + # Module with sub-sections + MdePkg/Test/ComplexModule.inf { + + BaseLib | MdePkg/Library/OverrideLib/Override.inf + + gTokenSpace.PcdFoo|0x1 + + MSFT:*_*_*_CC_FLAGS = /DTEST + } + + # Module without braces + MdePkg/Test/PlainModule.inf + +[PcdsFixedAtBuild] + gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F + gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000040 + +[PcdsDynamicDefault] + gEfiMdePkgTokenSpaceGuid.PcdPlatformBootTimeOut|5 + +[BuildOptions] + GCC:*_*_*_CC_FLAGS = -DMDEPKG_NDEBUG + MSFT:*_*_*_CC_FLAGS = /DMDEPKG_NDEBUG + # Comment inside build options + +!include TestInclude.dsc.inc + +/* Block comment test */ +# End of file diff --git a/test/testDscProcess.dsc b/test/testDscProcess.dsc new file mode 100644 index 0000000..b9b7926 --- /dev/null +++ b/test/testDscProcess.dsc @@ -0,0 +1,85 @@ +[Defines] + PLATFORM_NAME = TestProcess + PLATFORM_GUID = 11111111-2222-3333-4444-555555555555 + PLATFORM_VERSION = 2.0 + DSC_SPECIFICATION = 0x00010017 + DEFINE MY_FLAG = TRUE + DEFINE MY_PATH = SomePath + DEFINE EMPTY_DEF = + +# Root-level define +DEFINE ROOT_DEF = RootValue + +# Test that comments are stripped +# This line is a comment and should not appear + +[SkuIds] + 0|DEFAULT + +[LibraryClasses] + BaseLib|MdePkg/Library/BaseLib/BaseLib.inf + DebugLib|MdePkg/Library/DebugLib/DebugLib.inf + +[LibraryClasses.X64] + TimerLib|MdePkg/Library/TimerLib/TimerLib.inf + +[Components] + MdePkg/Test/ModuleA.inf + MdePkg/Test/ModuleB.inf + +[Components.X64] + MdePkg/Test/ModuleC.inf + +[PcdsFixedAtBuild] + gTestPkg.PcdTestMask|0x2F + gTestPkg.PcdTestLevel|0x80000040 + +[PcdsDynamicDefault] + gTestPkg.PcdBootTimeout|5 + +[BuildOptions] + GCC:*_*_*_CC_FLAGS = -DTEST + MSFT:*_*_*_CC_FLAGS = /DTEST + +# Conditional block test: TRUE branch taken +DEFINE COND_A = BeforeIf +!if TRUE + DEFINE COND_TAKEN = IfTrueValue +!else + DEFINE COND_TAKEN = IfFalseValue +!endif + +# Conditional block test: FALSE branch -> else taken +!if FALSE + DEFINE COND_FALSE_IF = ShouldNotExist +!else + DEFINE COND_ELSE = ElseValue +!endif + +# Nested conditional +!if TRUE + DEFINE OUTER_TRUE = OuterOk + !if FALSE + DEFINE INNER_FALSE = ShouldNotExist + !else + DEFINE INNER_ELSE = InnerElseOk + !endif +!endif + +# !ifdef test +DEFINE EXISTING_VAR = Exists +!ifdef EXISTING_VAR + DEFINE IFDEF_TAKEN = yes +!endif + +# !ifndef test on undefined variable +!ifndef TOTALLY_UNDEFINED_XYZ + DEFINE IFNDEF_TAKEN = yes +!endif + +# Define with variable reference +DEFINE BASE = Hello +DEFINE DERIVED = $(BASE)World + +# PCD in string value + gTestPkg.PcdStringVal|L"TestString" diff --git a/test/testFdfParsing.fdf b/test/testFdfParsing.fdf new file mode 100644 index 0000000..a846a5a --- /dev/null +++ b/test/testFdfParsing.fdf @@ -0,0 +1,38 @@ +## Corner-case FDF file for parser tests. + +[FD.TestFd] + BaseAddress = 0xFF000000 + Size = 0x01000000 + ErasePolarity = 1 + BlockSize = 0x00010000 + NumBlocks = 0x100 + + DEFINE FD_VAR = SomeValue + +[FV.FVMAIN] + FvAlignment = 16 + ERASE_POLARITY = 1 + MEMORY_MAPPED = TRUE + + INF MdePkg/Test/SimpleModule.inf + INF MdePkg/Test/AnotherModule.inf + + DEFINE FV_VAR = AnotherValue + +[FV.FVRECOVERY] + FvAlignment = 16 + + APRIORI DXE { + INF MdePkg/Test/AprioriModule.inf + } + + INF MdePkg/Test/RecoveryModule.inf + +[Rule.Common.SEC] + FILE SEC = $(NAMED_GUID) { + PE32 PE32 $(INF_OUTPUT)/$(MODULE_NAME).efi + } + +!include TestFdfInclude.fdf.inc + +# End of FDF diff --git a/test/testInfParsing.inf b/test/testInfParsing.inf new file mode 100644 index 0000000..ba2c3e5 --- /dev/null +++ b/test/testInfParsing.inf @@ -0,0 +1,48 @@ +## Corner-case INF file for parser tests. + +[Defines] + INF_VERSION = 0x00010017 + BASE_NAME = TestModule + MODULE_TYPE = DXE_DRIVER + ENTRY_POINT = TestEntryPoint + LIBRARY_CLASS = TestLib + CONSTRUCTOR = TestConstructor + DESTRUCTOR = TestDestructor + DEFINE MY_FLAG = TRUE + +[Sources] + TestModule.c + TestModule.h + Subfolder/Helper.c + +[Sources.X64] + X64/Arch.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UefiBootServicesTableLib + +[Protocols] + gEfiSimpleTextOutProtocolGuid + gEfiDevicePathProtocolGuid + +[Ppis] + gEfiPeiMemoryDiscoveredPpiGuid + +[Guids] + gEfiGlobalVariableGuid + gEfiHobListGuid + +[FixedPcd] + gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask + +[Depex] + gEfiVariableArchProtocolGuid AND + gEfiVariableWriteArchProtocolGuid + +# End of INF diff --git a/test/testVfrParsing.vfr b/test/testVfrParsing.vfr new file mode 100644 index 0000000..11ee5e8 --- /dev/null +++ b/test/testVfrParsing.vfr @@ -0,0 +1,78 @@ +// Corner-case VFR file for parser tests. + +#include "TestVfrStrDefs.h" + +formset + guid = TEST_FORMSET_GUID, + title = STRING_TOKEN(STR_FORM_SET_TITLE), + help = STRING_TOKEN(STR_FORM_SET_HELP), + + form formid = 1, + title = STRING_TOKEN(STR_FORM1_TITLE); + + oneof + varid = TestData.Option1, + prompt = STRING_TOKEN(STR_OPTION1_PROMPT), + help = STRING_TOKEN(STR_OPTION1_HELP), + option text = STRING_TOKEN(STR_DISABLED), value = 0, flags = DEFAULT; + option text = STRING_TOKEN(STR_ENABLED), value = 1, flags = 0; + endoneof; + + checkbox + varid = TestData.Flag1, + prompt = STRING_TOKEN(STR_FLAG1_PROMPT), + help = STRING_TOKEN(STR_FLAG1_HELP), + flags = CHECKBOX_DEFAULT, + endcheckbox; + + numeric + varid = TestData.Value1, + prompt = STRING_TOKEN(STR_VALUE1_PROMPT), + help = STRING_TOKEN(STR_VALUE1_HELP), + minimum = 0, + maximum = 255, + step = 1, + default = 100, + endnumeric; + + string + varid = TestData.Name1, + prompt = STRING_TOKEN(STR_NAME1_PROMPT), + help = STRING_TOKEN(STR_NAME1_HELP), + minsize = 0, + maxsize = 64, + endstring; + + password + varid = TestData.Pass1, + prompt = STRING_TOKEN(STR_PASS1_PROMPT), + help = STRING_TOKEN(STR_PASS1_HELP), + minsize = 6, + maxsize = 20, + endpassword; + + goto 2, + prompt = STRING_TOKEN(STR_GOTO_FORM2), + help = STRING_TOKEN(STR_GOTO_FORM2_HELP); + + endform; + + form formid = 2, + title = STRING_TOKEN(STR_FORM2_TITLE); + + oneof + varid = TestData.Option2, + prompt = STRING_TOKEN(STR_OPTION2_PROMPT), + help = STRING_TOKEN(STR_OPTION2_HELP), + option text = STRING_TOKEN(STR_LOW), value = 0, flags = DEFAULT; + option text = STRING_TOKEN(STR_MEDIUM), value = 1, flags = 0; + option text = STRING_TOKEN(STR_HIGH), value = 2, flags = 0; + endoneof; + + goto 1, + prompt = STRING_TOKEN(STR_GOTO_FORM1), + help = STRING_TOKEN(STR_GOTO_FORM1_HELP); + + endform; + +endformset; From f7f73fe5ce86b12b7a722caf6029d8b6ae4fcb12 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 30 Apr 2026 10:14:03 -0500 Subject: [PATCH 55/73] Switch workspace when searching --- src/extension.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index c415fb7..9a1fe18 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -191,8 +191,36 @@ export async function activate(context: vscode.ExtensionContext) { const langId = editor.document.languageId; + // Helper: switch to the workspace that contains the given URI if it's + // not the currently displayed one. Returns false if user cancelled. + async function ensureWorkspaceForUri(uri: vscode.Uri): Promise { + const allWs = gEdkWorkspaces.workspaces; + const currentIdx = edkWorkspaceTreeProvider.activeIndex; + // Already showing the right workspace? + if (currentIdx < allWs.length && isFileInWorkspaceTree(uri, [allWs[currentIdx]])) { + return true; + } + // Search other workspaces + for (let i = 0; i < allWs.length; i++) { + if (i === currentIdx) { continue; } + if (isFileInWorkspaceTree(uri, [allWs[i]])) { + const wsName = allWs[i].platformName ?? path.basename(allWs[i].mainDsc.fsPath); + const answer = await vscode.window.showInformationMessage( + `File not found in the current workspace tree. Switch to "${wsName}"?`, + { modal: false }, + 'Switch' + ); + if (answer !== 'Switch') { return false; } + edkWorkspaceTreeProvider.selectWorkspace(i); + return true; + } + } + return true; + } + // DSC / DSC-include: reveal directly by cursor position if (langId === 'edk2_dsc') { + if (!await ensureWorkspaceForUri(editor.document.uri)) { return; } await edkWorkspaceTreeProvider.revealActiveEditor(edkWorkspaceTreeView); return; } @@ -230,6 +258,7 @@ export async function activate(context: vscode.ExtensionContext) { chosen = picked.decl; } + if (!await ensureWorkspaceForUri(chosen.location.uri)) { return; } await edkWorkspaceTreeProvider.revealLocation( chosen.location.uri, chosen.location.range.start, From 8442a5b8fa1d74b72561344622e462cd7363fa68 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 30 Apr 2026 17:33:20 -0500 Subject: [PATCH 56/73] Adding compile command for Edk2 file --- package.json | 4 ++ src/compileCommands.ts | 4 +- src/compileFile.ts | 92 ++++++++++++++++++++++++++++++++++++++++++ src/extension.ts | 5 +++ 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/compileFile.ts diff --git a/package.json b/package.json index 063d525..528e977 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,10 @@ "command": "edk2code.gotoOverwrite", "title": "Go to overwriting definition", "icon": "$(go-to-file)" + }, + { + "command": "edk2code.compileCFile", + "title": "EDK2: Compile Edk2 file" } ], "configuration": [ diff --git a/src/compileCommands.ts b/src/compileCommands.ts index d9cd4bf..ee0cb9e 100644 --- a/src/compileCommands.ts +++ b/src/compileCommands.ts @@ -40,8 +40,8 @@ export class CompileCommandsEntry{ getDefines(): string[]{ const defines: string[] = []; - // Combine patterns for gcc (-D) and msbuild (/D) - const match = this.command.match(/(-D\s*[^ ]+)|(\/D\s*[^ ]+)/g); + // Match -D or /D flags preceded by whitespace (to avoid matching inside paths) + const match = this.command.match(/(?<=\s)-D\s*[^ ]+|(?<=\s)\/D\s*[^ ]+/g); if (match) { match.forEach((define) => { // Remove leading -D or /D and trim whitespace diff --git a/src/compileFile.ts b/src/compileFile.ts new file mode 100644 index 0000000..ad24474 --- /dev/null +++ b/src/compileFile.ts @@ -0,0 +1,92 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { gCompileCommands } from './extension'; +import { CompileCommandsEntry } from './compileCommands'; + +function getCompilerName(command: string): string { + const match = command.match(/^"?([^"\s]+)"?/); + return match ? path.basename(match[1]) : 'unknown'; +} + +function getOutputFile(command: string): string { + const match = command.match(/-o\s+(\S+)/); + return match ? match[1] : 'N/A'; +} + +function getFlags(command: string): string[] { + const flags: string[] = []; + // Match flags that are not -D, -I, -o, or the compiler/source file + const match = command.match(/\s(-[^DIo]\S*)/g); + if (match) { + match.forEach(f => flags.push(f.trim())); + } + return flags; +} + +function buildReport(entry: CompileCommandsEntry): string { + const fileName = path.basename(entry.file); + const compiler = getCompilerName(entry.command); + const output = getOutputFile(entry.command); + const defines = entry.getDefines(); + const includes = entry.getIncludePaths(); + + const lines: string[] = []; + lines.push('╔══════════════════════════════════════════════════════════════'); + lines.push(`β•‘ EDK2 Compile: ${fileName}`); + lines.push('╠══════════════════════════════════════════════════════════════'); + lines.push(`β•‘ Compiler: ${compiler}`); + lines.push(`β•‘ Source: ${entry.file}`); + lines.push(`β•‘ Output: ${output}`); + lines.push(`β•‘ Directory: ${entry.directory}`); + lines.push('╠══════════════════════════════════════════════════════════════'); + lines.push(`β•‘ Defines (${defines.length}):`); + defines.forEach(d => lines.push(`β•‘ -D ${d}`)); + lines.push('╠══════════════════════════════════════════════════════════════'); + lines.push(`β•‘ Include Paths (${includes.length}):`); + includes.forEach(i => lines.push(`β•‘ ${i}`)); + lines.push('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•'); + return lines.join('\n'); +} + +export async function compileCFile() { + const editor = vscode.window.activeTextEditor; + if (!editor) { + void vscode.window.showErrorMessage('No active editor found.'); + return; + } + const filePath = editor.document.uri.fsPath; + + gCompileCommands.load(); + const entry = gCompileCommands.getCompileCommandForFile(filePath); + if (!entry) { + void vscode.window.showErrorMessage(`No compile command found for ${path.basename(filePath)}`); + return; + } + + const report = buildReport(entry); + + // Write report to a separate text file to avoid shell escaping issues + const isWindows = os.platform() === 'win32'; + const reportPath = path.join(os.tmpdir(), 'edk2_compile_report.txt'); + fs.writeFileSync(reportPath, report); + + // Write command to a temp script to avoid terminal line-length limits + const scriptExt = isWindows ? '.bat' : '.sh'; + const scriptPath = path.join(os.tmpdir(), `edk2_compile${scriptExt}`); + const fileName = path.basename(filePath); + + let scriptContent: string; + if (isWindows) { + scriptContent = `@echo off\ntype "${reportPath}"\necho.\ncd /d "${entry.directory}"\n${entry.command}\nif %ERRORLEVEL% EQU 0 (echo Compilation successful: ${fileName}) else (echo Compilation FAILED: ${fileName})`; + } else { + scriptContent = `#!/bin/bash\ncat "${reportPath}"\necho ""\ncd "${entry.directory}"\n${entry.command}\nif [ $? -eq 0 ]; then echo "Compilation successful: ${fileName}"; else echo "Compilation FAILED: ${fileName}"; fi`; + } + fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 }); + + const terminal = vscode.window.createTerminal({ name: `Compile: ${fileName}`, cwd: entry.directory }); + terminal.show(); + const runCmd = isWindows ? `"${scriptPath}"` : `bash "${scriptPath}"`; + terminal.sendText(runCmd); +} diff --git a/src/extension.ts b/src/extension.ts index 9a1fe18..407d5ec 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,6 +23,7 @@ import { WorkspaceTreeProvider, WorkspaceRootItem, IncludeTreeItem, DocumentSymb import { InfDsc } from './index/edkWorkspace'; import { MapFilesManager } from './mapParser'; import { CompileCommands } from './compileCommands'; +import { compileCFile } from './compileFile'; import { showReleaseNotes } from './newVersionPage/newVersionMessage'; import { startMcpServer, stopMcpServer } from './mcp/mcpServer'; @@ -267,6 +268,10 @@ export async function activate(context: vscode.ExtensionContext) { } }), + vscode.commands.registerCommand('edk2code.compileCFile', async () => { + await compileCFile(); + }), + vscode.commands.registerCommand('edk2code.startMcpServer', async () => { const portStr = vscode.workspace.getConfiguration('edk2code').get('mcpServerPort', 3100); await startMcpServer(portStr); From c3e33a7c51fb4589803ec3c6bc9d059e91fe4bc1 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 30 Apr 2026 17:52:12 -0500 Subject: [PATCH 57/73] Add filter for worktree --- package.json | 14 ++ src/extension.ts | 4 + src/workspaceTree/WorkspaceTreeProvider.ts | 187 ++++++++++++++++++--- 3 files changed, 177 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 528e977..99862bb 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,11 @@ "title": "EDK2: Search workspace tree", "icon": "$(search)" }, + { + "command": "edk2code.clearWorkspaceTreeSearch", + "title": "EDK2: Clear workspace tree search", + "icon": "$(search-stop)" + }, { "command": "edk2code.focusEditorInWorkspaceView", "title": "EDK2: Focus on workspace view" @@ -341,6 +346,10 @@ "command": "edk2code.revealEditorInWorkspaceTree", "when": "true" }, + { + "command": "edk2code.clearWorkspaceTreeSearch", + "when": "edk2code.workspaceTreeSearchActive" + }, { "command": "edk2code.focusEditorInWorkspaceView", "when": "false" @@ -389,6 +398,11 @@ "command": "edk2code.searchWorkspaceTree", "group": "navigation", "when": "view == workspaceView" + }, + { + "command": "edk2code.clearWorkspaceTreeSearch", + "group": "navigation", + "when": "view == workspaceView && edk2code.workspaceTreeSearchActive" } ], "editor/context": [ diff --git a/src/extension.ts b/src/extension.ts index 407d5ec..53612b3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -186,6 +186,10 @@ export async function activate(context: vscode.ExtensionContext) { await edkWorkspaceTreeProvider.searchTree(edkWorkspaceTreeView); }), + vscode.commands.registerCommand('edk2code.clearWorkspaceTreeSearch', () => { + edkWorkspaceTreeProvider.clearSearchFilter(); + }), + vscode.commands.registerCommand('edk2code.focusEditorInWorkspaceView', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { return; } diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index 370523e..ee03474 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -457,10 +457,43 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider(DSC_FILTER_TYPES.map(f => f.type)); })(); + /** + * When set, the tree only shows nodes whose `nodePath` is in this set. + * Used by the "Search workspace tree" command to display only the path + * leading to a selected node. `undefined` means no path filter is active. + */ + private _searchFilter: Set | undefined; + get activeIndex(): number { return this._activeIndex; } + get isSearchFilterActive(): boolean { + return this._searchFilter !== undefined; + } + + /** Filter the children list against the active search filter (if any). */ + private _applySearchFilter(items: T[]): T[] { + if (!this._searchFilter) { return items; } + const filter = this._searchFilter; + const filtered = items.filter(i => filter.has(i.nodePath)); + // Force-expand surviving nodes so the path to the target is visible. + for (const item of filtered) { + if (item.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) { + item.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; + } + } + return filtered; + } + + /** Clear the search-path filter and refresh the tree. */ + clearSearchFilter(): void { + if (!this._searchFilter) { return; } + this._searchFilter = undefined; + void vscode.commands.executeCommand('setContext', 'edk2code.workspaceTreeSearchActive', false); + this._onDidChangeTreeData.fire(); + } + refresh(): void { // Clamp index in case workspaces were removed const max = Math.max(0, gEdkWorkspaces.workspaces.length - 1); @@ -531,7 +564,7 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, element.workspace.mainDsc, this._activeFilters, element.treePath, element, element.nodePath, isSymbolInactive(s, element.workspace.mainDsc, ws), getOverwriteLocation(s, element.workspace.mainDsc))) - .filter(s => showInactive || !s.inactive); + .filter(s => showInactive || !s.inactive)); } // Under an include node: symbols of that file only if (element instanceof IncludeTreeItem) { const inactive = element.inactive; const symbols = await loadSymbols(element.node.uri); - return symbols + return this._applySearchFilter(symbols .filter(s => isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, element.node.uri, this._activeFilters, element.treePath, undefined, element.nodePath, inactive || isSymbolInactive(s, element.node.uri, ws), getOverwriteLocation(s, element.node.uri))) - .filter(s => showInactive || !s.inactive); + .filter(s => showInactive || !s.inactive)); } // Under a symbol: for !include directives expand into the included file; @@ -567,21 +600,21 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider isTypeVisible(s.type, this._activeFilters)) .map(s => new DocumentSymbolItem(s, node.uri, this._activeFilters, element.treePath, element, element.nodePath, element.inactive || isSymbolInactive(s, node.uri, ws), getOverwriteLocation(s, node.uri))) - .filter(s => showInactive || !s.inactive); + .filter(s => showInactive || !s.inactive)); } } } - return (element.symbol.children as EdkSymbol[]) + return this._applySearchFilter((element.symbol.children as EdkSymbol[]) .filter(c => isTypeVisible(c.type, this._activeFilters)) .map(child => new DocumentSymbolItem(child, element.fileUri, this._activeFilters, element.treePath, element, element.nodePath, element.inactive || isSymbolInactive(child, element.fileUri, ws), getOverwriteLocation(child, element.fileUri))) - .filter(s => showInactive || !s.inactive); + .filter(s => showInactive || !s.inactive)); } return []; @@ -658,48 +691,146 @@ export class WorkspaceTreeProvider implements vscode.TreeDataProvider): Promise { - type SearchPickItem = vscode.QuickPickItem & { node: WorkspaceTreeNode }; + type CollectedNode = { node: WorkspaceTreeNode; chain: string[]; haystack: string }; + + // Always start the search against the unfiltered tree. + const previousFilter = this._searchFilter; + this._searchFilter = undefined; + // Refresh the tree so the input-box-driven filter starts from the full tree. + if (previousFilter) { + this._onDidChangeTreeData.fire(); + } - // Collect every node in the tree - const items: SearchPickItem[] = []; - const collect = async (parent?: WorkspaceTreeNode): Promise => { + // Collect every node and its ancestor chain (inclusive of itself). + const allNodes: CollectedNode[] = []; + const collect = async (parent: WorkspaceTreeNode | undefined, chain: string[]): Promise => { const children = await this.getChildren(parent); for (const child of children) { const label = child instanceof DocumentSymbolItem ? child.symbol.name : child instanceof IncludeTreeItem ? path.basename(child.node.uri.fsPath) : (child as WorkspaceRootItem).label as string; const description = child.description as string | undefined; - items.push({ label, description: description ?? '', node: child }); - await collect(child); + const childChain = [...chain, child.nodePath]; + const haystack = `${label}\n${description ?? ''}`; + allNodes.push({ node: child, chain: childChain, haystack }); + await collect(child, childChain); } }; await vscode.window.withProgress( { location: { viewId: 'workspaceView' } }, - async () => { await collect(); } + async () => { await collect(undefined, []); } ); - if (items.length === 0) { + if (allNodes.length === 0) { + this._searchFilter = previousFilter; void vscode.window.showInformationMessage('No nodes in the workspace tree.'); return; } - const picked = await vscode.window.showQuickPick(items, { - placeHolder: 'Type to filter workspace tree nodes', - title: 'EDK2: Search workspace tree', - matchOnDescription: true + const input = vscode.window.createInputBox(); + input.title = 'EDK2: Search workspace tree'; + input.placeholder = 'Type to filter (case-insensitive). Toggle .* to use regex.'; + input.prompt = 'Tree updates live. Press Enter to keep the filter, Esc to cancel.'; + + const regexButtonOff: vscode.QuickInputButton = { + iconPath: new vscode.ThemeIcon('regex'), + tooltip: 'Use Regular Expression (off)' + }; + const regexButtonOn: vscode.QuickInputButton = { + iconPath: new vscode.ThemeIcon('regex'), + tooltip: 'Use Regular Expression (on)' + }; + let useRegex = false; + input.buttons = [regexButtonOff]; + + // Track whether the user accepted (Enter) so onDidHide knows whether to revert. + let accepted = false; + + const applyFilter = (value: string): void => { + if (!value) { + // Empty input β†’ no filter while typing. + this._searchFilter = undefined; + input.validationMessage = undefined; + void vscode.commands.executeCommand('setContext', 'edk2code.workspaceTreeSearchActive', false); + this._onDidChangeTreeData.fire(); + return; + } + + let predicate: (s: string) => boolean; + if (useRegex) { + let rx: RegExp; + try { + rx = new RegExp(value, 'i'); + } catch (e) { + input.validationMessage = `Invalid regex: ${(e as Error).message}`; + return; + } + input.validationMessage = undefined; + predicate = (s) => rx.test(s); + } else { + input.validationMessage = undefined; + const needle = value.toLowerCase(); + predicate = (s) => s.toLowerCase().includes(needle); + } + + // Keep every nodePath that's part of an ancestor chain leading to a match. + const keep = new Set(); + for (const item of allNodes) { + if (predicate(item.haystack)) { + for (const p of item.chain) { keep.add(p); } + } + } + + this._searchFilter = keep.size > 0 ? keep : new Set(['__no_match__']); + void vscode.commands.executeCommand('setContext', 'edk2code.workspaceTreeSearchActive', true); + this._onDidChangeTreeData.fire(); + }; + + input.onDidChangeValue(applyFilter); + input.onDidTriggerButton(btn => { + if (btn === regexButtonOff || btn === regexButtonOn) { + useRegex = !useRegex; + input.buttons = [useRegex ? regexButtonOn : regexButtonOff]; + applyFilter(input.value); + } }); - if (!picked) { return; } - await treeView.reveal(picked.node, { select: true, focus: true, expand: true }); + await new Promise(resolve => { + input.onDidAccept(() => { + accepted = true; + input.hide(); + }); + input.onDidHide(() => { + resolve(); + }); + input.show(); + }); + input.dispose(); + + if (!accepted) { + // User cancelled (Esc) β†’ restore the previous filter state. + this._searchFilter = previousFilter; + void vscode.commands.executeCommand( + 'setContext', + 'edk2code.workspaceTreeSearchActive', + this._searchFilter !== undefined + ); + this._onDidChangeTreeData.fire(); + return; + } - // Also open the element in the editor - const cmd = picked.node.command; - if (cmd) { - await vscode.commands.executeCommand(cmd.command, ...(cmd.arguments ?? [])); + // Accepted: keep whatever filter the live preview produced. + if (!this._searchFilter) { + // Empty query at acceptance β†’ no filter active. + void vscode.commands.executeCommand('setContext', 'edk2code.workspaceTreeSearchActive', false); } } From 6962913f08cf5373e3479998ad123e6131ecdec5 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 7 May 2026 14:01:17 -0500 Subject: [PATCH 58/73] listFilesRecursive uses native vscode API --- src/contextState/cmds.ts | 72 +++++++++++++------------ src/debugLog.ts | 10 ++-- src/newVersionPage/newVersionMessage.ts | 2 +- src/utils.ts | 42 +++------------ 4 files changed, 51 insertions(+), 75 deletions(-) diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 78a63e6..9296dbd 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -456,45 +456,49 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; cancellable: true }, async (progress, reject) => { - return new Promise((resolve, token) => { - gDebugLog.info("Generating .ignore file"); - let cscopeFilesList = gCscope.readCscopeFile(); - let filesSet = new Set(); - let filesExtensions = new Set(); - for (const cscopeFile of cscopeFilesList) { - let progFileUpper = cscopeFile.toUpperCase(); - filesSet.add(progFileUpper); - filesExtensions.add(path.extname(progFileUpper)); - } - - // Glob library needs posix path - let lookPath = toPosix(path.join(gWorkspacePath, "**")); - let globFilesList = glob.sync(lookPath); - let ignoreList = []; - - // Add extra ignore patterns - let extraIgnores = gConfigAgent.getExtraIgnorePatterns(); - for (const extraIgnore of extraIgnores) { - ignoreList.push(extraIgnore.trim()); - } + gDebugLog.info("Generating .ignore file"); + let cscopeFilesList = gCscope.readCscopeFile(); + gDebugLog.info(`genIgnoreFile: cscope list has ${cscopeFilesList.length} entries`); + let filesSet = new Set(); + let filesExtensions = new Set(); + for (const cscopeFile of cscopeFilesList) { + let progFileUpper = cscopeFile.toUpperCase(); + filesSet.add(progFileUpper); + filesExtensions.add(path.extname(progFileUpper)); + } + gDebugLog.info(`genIgnoreFile: tracking ${filesExtensions.size} extensions: ${[...filesExtensions].join(", ")}`); + + gDebugLog.info(`genIgnoreFile: listing workspace files under ${gWorkspacePath}`); + let globFilesList = (await listFilesRecursive(gWorkspacePath)).map(f => path.join(gWorkspacePath, f)); + gDebugLog.info(`genIgnoreFile: workspace file scan returned ${globFilesList.length} files`); + let ignoreList = []; + + // Add extra ignore patterns + let extraIgnores = gConfigAgent.getExtraIgnorePatterns(); + gDebugLog.info(`genIgnoreFile: ${extraIgnores.length} extra ignore patterns from config`); + for (const extraIgnore of extraIgnores) { + ignoreList.push(extraIgnore.trim()); + } - let posixWorkspacePath = toPosix(gWorkspacePath); - gDebugLog.info(`Cscope files found: ${filesSet.size}`); + let posixWorkspacePath = toPosix(gWorkspacePath); + gDebugLog.info(`Cscope files found: ${filesSet.size}`); - for (const globFile of globFilesList) { - // Just ignore EDK files - let globFileUpperCase = path.resolve(globFile.toUpperCase()); - let extension = path.extname(globFileUpperCase); - if (filesExtensions.has(extension) && !filesSet.has(globFileUpperCase)) { + let reportCount = 0; + for (const globFile of globFilesList) { + // Just ignore EDK files + const globFileUpperCase = globFile.toUpperCase(); + const extension = path.extname(globFileUpperCase); + if (filesExtensions.has(extension) && !filesSet.has(globFileUpperCase)) { + if (++reportCount % 100 === 0) { progress.report({ message: globFile }); - - ignoreList.push(toPosix(path.relative(posixWorkspacePath, globFile))); } + ignoreList.push(toPosix(path.relative(posixWorkspacePath, globFile))); } - fs.writeFileSync(path.join(gWorkspacePath, ".ignore"), ignoreList.join("\n")); - resolve(); - }); - + } + gDebugLog.info(`genIgnoreFile: ${ignoreList.length} entries written to .ignore (${reportCount} files ignored)`); + const ignoreFilePath = path.join(gWorkspacePath, ".ignore"); + fs.writeFileSync(ignoreFilePath, ignoreList.join("\n")); + gDebugLog.info(`genIgnoreFile: .ignore written to ${ignoreFilePath}`); }); diff --git a/src/debugLog.ts b/src/debugLog.ts index 41fa06e..fda777b 100644 --- a/src/debugLog.ts +++ b/src/debugLog.ts @@ -27,25 +27,25 @@ export class DebugLog { } public error(text:string){ - this.outConsole.error(text); + this.outConsole.error("[EDK2Code] " + text); let callStack = (new Error()).stack || ''; this.outConsole.error(`[Stack]\n${callStack}`); } public info(text:string){ - this.outConsole.info(text); + this.outConsole.info("[EDK2Code] " + text); } public trace(text:string){ - this.outConsole.trace(text); + this.outConsole.trace("[EDK2Code] " + text); } public warning(text:string){ - this.outConsole.warn(text); + this.outConsole.warn("[EDK2Code] " + text); } public debug(text:string){ - this.outConsole.trace(text); + this.outConsole.trace("[EDK2Code] " + text); } diff --git a/src/newVersionPage/newVersionMessage.ts b/src/newVersionPage/newVersionMessage.ts index af7d1bb..9c1ae46 100644 --- a/src/newVersionPage/newVersionMessage.ts +++ b/src/newVersionPage/newVersionMessage.ts @@ -1,4 +1,4 @@ -import { getCurrentVersion } from "../utils"; +import getCurrentVersion from "../utils"; import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; diff --git a/src/utils.ts b/src/utils.ts index 0e13ab9..dfd71a5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -502,47 +502,19 @@ export async function listFiles(dir: string): Promise { } /** - * Recursively list all files in a directory. + * Recursively list all files in a directory using ripgrep. * * @param {string} dir - The directory to start listing files from. - * @returns {Promise} - An array of file paths. + * @returns {Promise} - An array of file paths relative to `dir`. */ export async function listFilesRecursive(dir: string): Promise { - const files = fs.readdirSync(dir); - let filelist: string[] = []; - let baseDir = ""; - - for (const file of files) { - const filepath = path.join(dir, file); - const stat = fs.statSync(filepath); - - if (stat.isDirectory()) { - filelist = await _walk(filepath, path.basename(filepath), filelist); - } else { - filelist.push(path.join(baseDir, file)); - } - } - - return filelist; -} - -async function _walk(dir: string, baseDir: string, filelist: string[] = []) { - const files = fs.readdirSync(dir); - - for (const file of files) { - const filepath = path.join(dir, file); - const stat = fs.statSync(filepath); - - if (stat.isDirectory()) { - filelist = await _walk(filepath, path.basename(filepath), filelist); - } else { - filelist.push(path.join(baseDir, file)); - } - } - return filelist; + const pattern = new vscode.RelativePattern(dir, '**/*'); + const uris = await vscode.workspace.findFiles(pattern); + gDebugLog.debug(`listFilesRecursive: ${uris.length} files found in ${dir}`); + return uris.map(uri => path.relative(dir, uri.fsPath)); } -export function getCurrentVersion(): string { +export default function getCurrentVersion(): string { const packageJsonPath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return packageJson.version; From ebf6cc249f241d5cb284f5362eaf9fbfc2853b18 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 13 May 2026 09:20:30 -0500 Subject: [PATCH 59/73] Adding driver information --- .vscode/code-notes.json | 75 ++++ package.json | 18 + src/driverInfoTree/DriverInfoTreeProvider.ts | 359 +++++++++++++++++++ src/extension.ts | 23 ++ 4 files changed, 475 insertions(+) create mode 100644 src/driverInfoTree/DriverInfoTreeProvider.ts diff --git a/.vscode/code-notes.json b/.vscode/code-notes.json index fad4066..37c8dd0 100644 --- a/.vscode/code-notes.json +++ b/.vscode/code-notes.json @@ -26,6 +26,17 @@ "489f90e4-6146-4d35-926a-a669b3d0416f" ], "createdAt": "2026-04-16T18:47:07.516Z" + }, + { + "id": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", + "name": "driverInfo", + "exportOrder": [ + "d1c71546-1cca-41e3-b6d1-207ced15e193", + "93cecdb2-bd0b-4edc-bf50-fa1e3540e9db", + "e07fbd5f-c49d-4365-8d87-6d5a6c932dbc", + "d9611b61-06b4-4165-8acd-2a6651186a7d" + ], + "createdAt": "2026-05-12T15:18:13.161Z" } ], "notes": [ @@ -220,6 +231,70 @@ "noteText": "Duplicate statement warning", "label": "Duplicate statement warning", "createdAt": "2026-04-16T19:11:52.130Z" + }, + { + "id": "d1c71546-1cca-41e3-b6d1-207ced15e193", + "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", + "location": { + "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", + "startLine": 276, + "endLine": 291, + "startChar": 0, + "endChar": 5 + }, + "language": "typescript", + "snippet": "\r\n async isFileInUse(uri: vscode.Uri) {\r\n let isUndefined = true;\r\n for (const workspace of this.workspaces) {\r\n let result = await workspace.isFileInUse(uri);\r\n if(result === true){\r\n return true;\r\n }\r\n if(result === false){\r\n isUndefined = false;\r\n continue;\r\n }\r\n }\r\n if(isUndefined){return undefined;}\r\n return false;\r\n }", + "noteText": "function to check if file is used ", + "label": "function to check if file is used", + "createdAt": "2026-05-12T15:35:42.644Z" + }, + { + "id": "93cecdb2-bd0b-4edc-bf50-fa1e3540e9db", + "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", + "location": { + "file": "d:\\github\\Edk2Code\\package.json", + "startLine": 249, + "endLine": 258, + "startChar": 0, + "endChar": 6 + }, + "language": "json", + "snippet": " \"views\": {\r\n \"edk2code-sidebar\": [\r\n {\r\n \"icon\": \"assets/icon.png\",\r\n \"id\": \"workspaceView\",\r\n \"name\": \"Workspace\",\r\n \"type\": \"tree\"\r\n }\r\n ]\r\n },", + "noteText": "side bar definition", + "label": "side bar definition", + "createdAt": "2026-05-12T15:36:11.681Z" + }, + { + "id": "e07fbd5f-c49d-4365-8d87-6d5a6c932dbc", + "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", + "location": { + "file": "d:\\github\\Edk2Code\\src\\contextState\\cmds.ts", + "startLine": 241, + "endLine": 269, + "startChar": 4, + "endChar": 5 + }, + "language": "typescript", + "snippet": "export async function gotoInf(fileUri: Uri) {\r\n let locations: vscode.Location[] = [];\r\n\r\n let wps = await gEdkWorkspaces.getWorkspace(fileUri);\r\n if(wps.length){\r\n for (const wp of wps) {\r\n locations = await wp.getInfReference(fileUri);\r\n if(locations.length){\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n await edkWorkspaceTreeProvider.revealInfInTree(locations[0].uri, edkWorkspaceTreeView);\r\n }\r\n }\r\n }else{\r\n // Skip if source hasn't been indexed. Just make a search query\r\n let fileName = path.basename(fileUri.fsPath);\r\n let folderPath = path.dirname(fileUri.fsPath);\r\n\r\n let tempLocations = await rgSearch(`\\\\b${fileName}\\\\b`, [\"*.inf\"],[],true);\r\n for (const l of tempLocations) {\r\n // Check the INF file is relative to the source file\r\n if (!path.relative(path.dirname(l.uri.fsPath), folderPath).includes(\"..\")) {\r\n locations.push(l);\r\n }\r\n }\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n\r\n }\r\n\r\n }", + "noteText": "Function to go to .inf file of selected file", + "label": "Function to go to .inf file of selected file", + "createdAt": "2026-05-12T15:37:30.107Z" + }, + { + "id": "d9611b61-06b4-4165-8acd-2a6651186a7d", + "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", + "location": { + "file": "d:\\github\\Edk2Code\\src\\symbols\\infSymbols.ts", + "startLine": 285, + "endLine": 323, + "startChar": 4, + "endChar": 6 + }, + "language": "typescript", + "snippet": "onDefinition = async (parser:DocumentParser)=>{\r\n let libName = this.textLine.replace(/\\s*\\|.*/, \"\");\r\n\r\n\r\n let wps = await gEdkWorkspaces.getWorkspace(this.location.uri);\r\n if(wps.length){\r\n\r\n\r\n // Check if this is a library\r\n let isLibrary = await isFileEdkLibrary(this.location.uri);\r\n\r\n if(isLibrary){\r\n let libDeclarationLocations:vscode.Location[] = [];\r\n for (const wp of wps) {\r\n libDeclarationLocations = libDeclarationLocations.concat(await wp.getLibDefinition(libName));\r\n }\r\n return libDeclarationLocations;\r\n }else{\r\n // Modules\r\n let dscLibDeclarations:InfDsc[] = [];\r\n for (const wp of wps) {\r\n dscLibDeclarations = dscLibDeclarations.concat(await wp.getLibDeclarationModule(this.location.uri, libName));\r\n }\r\n let libDeclarationLocations:vscode.Location[] = [];\r\n for (const declaration of dscLibDeclarations) {\r\n let location = await gPathFind.findPath(declaration.path);\r\n if(location.length){\r\n libDeclarationLocations.push(location[0]);\r\n }\r\n }\r\n return libDeclarationLocations;\r\n }\r\n\r\n }else{\r\n let locations = await rgSearch(`--regexp \"LIBRARY_CLASS\\\\s*=\\\\s*\\\\b${libName}\\\\b\"`,[\"*.inf\"]);\r\n return locations;\r\n }\r\n\r\n };", + "noteText": "example of Go to library definition function", + "label": "example of Go to library definition function", + "createdAt": "2026-05-12T15:40:17.768Z" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 99862bb..dbe883a 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,10 @@ { "command": "edk2code.compileCFile", "title": "EDK2: Compile Edk2 file" + }, + { + "command": "edk2code.driverInfoGoToDefinition", + "title": "EDK2: Go to definition (Driver Info)" } ], "configuration": [ @@ -254,6 +258,11 @@ "id": "workspaceView", "name": "Workspace", "type": "tree" + }, + { + "id": "driverInfoView", + "name": "Driver Info", + "type": "tree" } ] }, @@ -267,6 +276,11 @@ "view": "workspaceView", "contents": "No workspace data available.\n[Rebuild Index](command:edk2code.rebuildIndex)", "when": "!edk2code.isLoading" + }, + { + "view": "driverInfoView", + "contents": "Open a source file that is part of an EDK2 module to see driver information.", + "when": "!edk2code.driverInfoAvailable" } ], "menus": { @@ -365,6 +379,10 @@ { "command": "edk2code.gotoOverwrite", "when": "false" + }, + { + "command": "edk2code.driverInfoGoToDefinition", + "when": "false" } ], "editor/title": [], diff --git a/src/driverInfoTree/DriverInfoTreeProvider.ts b/src/driverInfoTree/DriverInfoTreeProvider.ts new file mode 100644 index 0000000..0db3ff9 --- /dev/null +++ b/src/driverInfoTree/DriverInfoTreeProvider.ts @@ -0,0 +1,359 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import { gEdkWorkspaces, gPathFind } from '../extension'; +import { getParser } from '../edkParser/parserFactory'; +import { EdkSymbol } from '../symbols/edkSymbols'; +import { Edk2SymbolType } from '../symbols/symbolsType'; +import { DocumentParser } from '../edkParser/languageParser'; +import { rgSearch } from '../rg'; + +// ─── Tree item types ────────────────────────────────────────────────────────── + +export class DriverInfoCategoryItem extends vscode.TreeItem { + children: DriverInfoLeafItem[] = []; + constructor(label: string, icon: string) { + super(label, vscode.TreeItemCollapsibleState.Expanded); + this.iconPath = new vscode.ThemeIcon(icon); + this.contextValue = 'driverInfoCategory'; + } +} + +export class DriverInfoLeafItem extends vscode.TreeItem { + constructor( + label: string, + description: string, + icon: vscode.ThemeIcon, + command?: vscode.Command, + fileUri?: vscode.Uri + ) { + super(label, vscode.TreeItemCollapsibleState.None); + this.description = description; + this.iconPath = icon; + if (fileUri) { + this.resourceUri = fileUri; + this.iconPath = vscode.ThemeIcon.File; + } + if (command) { + this.command = command; + } + } +} + +type DriverInfoNode = DriverInfoCategoryItem | DriverInfoLeafItem; + +// ─── Provider ───────────────────────────────────────────────────────────────── + +export class DriverInfoTreeProvider implements vscode.TreeDataProvider { + + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + private categories: DriverInfoCategoryItem[] = []; + private infUri: vscode.Uri | undefined; + private parser: DocumentParser | undefined; + private disposables: vscode.Disposable[] = []; + + constructor() { + this.disposables.push( + vscode.window.onDidChangeActiveTextEditor(async (editor) => { + if (editor) { + await this.onEditorChanged(editor); + } + }) + ); + } + + dispose() { + for (const d of this.disposables) { d.dispose(); } + } + + getTreeItem(element: DriverInfoNode): vscode.TreeItem { + return element; + } + + getChildren(element?: DriverInfoNode): DriverInfoNode[] { + if (!element) { + return this.categories; + } + if (element instanceof DriverInfoCategoryItem) { + return element.children; + } + return []; + } + + // ─── Editor change handler ──────────────────────────────────────────────── + + async onEditorChanged(editor: vscode.TextEditor) { + const document = editor.document; + const langId = document.languageId; + + // Only process EDK-relevant files + if (!['c', 'cpp', 'edk2_dsc', 'edk2_inf', 'edk2_dec', 'asl', 'edk2_vfr', 'edk2_fdf', 'edk2_uni'].includes(langId)) { + this.clear(); + return; + } + + // Check if file is in use + if (gEdkWorkspaces.isConfigured()) { + const isUsed = await gEdkWorkspaces.isFileInUse(document.uri); + if (isUsed !== true) { + this.clear(); + return; + } + } + + // Find the INF file + const infUri = await this.findInfForFile(document.uri); + if (!infUri) { + this.clear(); + return; + } + + // If same INF, no need to rebuild + if (this.infUri && this.infUri.fsPath === infUri.fsPath) { + return; + } + + this.infUri = infUri; + await this.buildTree(); + } + + // ─── Locate INF file ────────────────────────────────────────────────────── + + private async findInfForFile(fileUri: vscode.Uri): Promise { + // If the file itself is an INF + if (fileUri.fsPath.match(/\.inf$/i)) { + return fileUri; + } + + const wps = await gEdkWorkspaces.getWorkspace(fileUri); + if (wps.length) { + for (const wp of wps) { + const locations = await wp.getInfReference(fileUri); + if (locations.length) { + return locations[0].uri; + } + } + } else { + // Fallback: search for INF file referencing this source + const fileName = path.basename(fileUri.fsPath); + const folderPath = path.dirname(fileUri.fsPath); + const tempLocations = await rgSearch(`\\b${fileName}\\b`, ['*.inf'], [], true); + for (const l of tempLocations) { + if (!path.relative(path.dirname(l.uri.fsPath), folderPath).includes('..')) { + return l.uri; + } + } + } + return undefined; + } + + // ─── Build tree from INF parser ─────────────────────────────────────────── + + private async buildTree() { + this.categories = []; + + if (!this.infUri) { + this.refresh(); + return; + } + + const parser = await getParser(this.infUri); + if (!parser) { + this.refresh(); + return; + } + this.parser = parser; + + await vscode.commands.executeCommand('setContext', 'edk2code.driverInfoAvailable', true); + + // Defines + const defines = parser.getSymbolsType(Edk2SymbolType.infDefine); + if (defines.length) { + const cat = new DriverInfoCategoryItem('Defines', 'symbol-constant'); + for (const def of defines) { + const key = await def.getKey(); + const value = await def.getValue(); + const item = new DriverInfoLeafItem( + key, + value, + new vscode.ThemeIcon('symbol-property'), + { + command: 'vscode.open', + title: 'Open', + arguments: [def.location.uri, { selection: def.location.range }] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // Sources + const sources = parser.getSymbolsType(Edk2SymbolType.infSource); + if (sources.length) { + const cat = new DriverInfoCategoryItem('Sources', 'files'); + for (const src of sources) { + const filePath = src.textLine.replace(/\s*\|.*/, ''); + const relPath = path.dirname(this.infUri.fsPath); + const locations = await gPathFind.findPath(filePath, relPath); + const resolvedUri = locations.length ? locations[0].uri : vscode.Uri.file(path.join(relPath, filePath)); + const item = new DriverInfoLeafItem( + filePath, + '', + vscode.ThemeIcon.File, + locations.length ? { + command: 'vscode.open', + title: 'Open file', + arguments: [locations[0].uri] + } : undefined, + resolvedUri + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // Libraries + const libraries = parser.getSymbolsType(Edk2SymbolType.infLibrary); + if (libraries.length) { + const cat = new DriverInfoCategoryItem('Libraries', 'library'); + for (const lib of libraries) { + const libName = lib.textLine.replace(/\s*\|.*/, ''); + const item = new DriverInfoLeafItem( + libName, + '', + new vscode.ThemeIcon('symbol-module'), + { + command: 'edk2code.driverInfoGoToDefinition', + title: 'Go to definition', + arguments: [lib] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // Packages + const packages = parser.getSymbolsType(Edk2SymbolType.infPackage); + if (packages.length) { + const cat = new DriverInfoCategoryItem('Packages', 'package'); + for (const pkg of packages) { + const pkgPath = pkg.textLine.replace(/\s*\|.*/, ''); + const item = new DriverInfoLeafItem( + pkgPath, + '', + new vscode.ThemeIcon('symbol-package'), + { + command: 'edk2code.driverInfoGoToDefinition', + title: 'Go to definition', + arguments: [pkg] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // Protocols + const protocols = parser.getSymbolsType(Edk2SymbolType.infProtocol); + if (protocols.length) { + const cat = new DriverInfoCategoryItem('Protocols', 'plug'); + for (const proto of protocols) { + const protoName = proto.textLine.replace(/\s*\|.*/, ''); + const item = new DriverInfoLeafItem( + protoName, + '', + new vscode.ThemeIcon('symbol-interface'), + { + command: 'edk2code.driverInfoGoToDefinition', + title: 'Go to definition', + arguments: [proto] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // PPIs + const ppis = parser.getSymbolsType(Edk2SymbolType.infPpi); + if (ppis.length) { + const cat = new DriverInfoCategoryItem('PPIs', 'plug'); + for (const ppi of ppis) { + const ppiName = ppi.textLine.replace(/\s*\|.*/, ''); + const item = new DriverInfoLeafItem( + ppiName, + '', + new vscode.ThemeIcon('symbol-event'), + { + command: 'edk2code.driverInfoGoToDefinition', + title: 'Go to definition', + arguments: [ppi] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // GUIDs + const guids = parser.getSymbolsType(Edk2SymbolType.infGuid); + if (guids.length) { + const cat = new DriverInfoCategoryItem('GUIDs', 'key'); + for (const guid of guids) { + const guidName = guid.textLine.replace(/\s*\|.*/, ''); + const item = new DriverInfoLeafItem( + guidName, + '', + new vscode.ThemeIcon('symbol-number'), + { + command: 'edk2code.driverInfoGoToDefinition', + title: 'Go to definition', + arguments: [guid] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + // PCDs + const pcds = parser.getSymbolsType(Edk2SymbolType.infPcd); + if (pcds.length) { + const cat = new DriverInfoCategoryItem('PCDs', 'settings'); + for (const pcd of pcds) { + const pcdName = pcd.textLine.replace(/\s*\|.*/, ''); + const item = new DriverInfoLeafItem( + pcdName, + '', + new vscode.ThemeIcon('symbol-string'), + { + command: 'edk2code.driverInfoGoToDefinition', + title: 'Go to definition', + arguments: [pcd] + } + ); + cat.children.push(item); + } + this.categories.push(cat); + } + + this.refresh(); + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + private clear() { + this.categories = []; + this.infUri = undefined; + this.parser = undefined; + void vscode.commands.executeCommand('setContext', 'edk2code.driverInfoAvailable', false); + this.refresh(); + } + + private refresh() { + this._onDidChangeTreeData.fire(); + } +} diff --git a/src/extension.ts b/src/extension.ts index 53612b3..e0581e1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -26,6 +26,7 @@ import { CompileCommands } from './compileCommands'; import { compileCFile } from './compileFile'; import { showReleaseNotes } from './newVersionPage/newVersionMessage'; import { startMcpServer, stopMcpServer } from './mcp/mcpServer'; +import { DriverInfoTreeProvider } from './driverInfoTree/DriverInfoTreeProvider'; // Global variables @@ -51,6 +52,8 @@ export var gDiagnosticManager:DiagnosticManager; export var edkWorkspaceTreeProvider: WorkspaceTreeProvider; export var edkWorkspaceTreeView: vscode.TreeView; +export var gDriverInfoTreeProvider: DriverInfoTreeProvider; + export var gMapFileManager: MapFilesManager; @@ -98,6 +101,23 @@ export async function activate(context: vscode.ExtensionContext) { // Internal vscode.commands.registerCommand('edk2code.searchDefinition', ()=>{}), vscode.commands.registerCommand("edk2code.gotoFile", async (fileUri, selRange)=>{await gotoFile(fileUri,selRange);}), + + vscode.commands.registerCommand('edk2code.driverInfoGoToDefinition', async (symbol: any) => { + if (symbol && symbol.onDefinition) { + const locations = await symbol.onDefinition(symbol.parser); + if (locations) { + const locArray = Array.isArray(locations) ? locations : [locations]; + if (locArray.length > 0) { + const loc = locArray[0]; + if (loc.uri && loc.range) { + await gotoFile(loc.uri, loc.range); + } else if (loc.uri) { + await vscode.commands.executeCommand('vscode.open', loc.uri); + } + } + } + } + }), // vscode.commands.registerCommand("edk2code.viewWarnings", async ()=>{await gErrorReportAgent.reportErrors();}), @@ -300,6 +320,9 @@ export async function activate(context: vscode.ExtensionContext) { edkWorkspaceTreeProvider = new WorkspaceTreeProvider(); edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider }); + gDriverInfoTreeProvider = new DriverInfoTreeProvider(); + vscode.window.createTreeView('driverInfoView', { treeDataProvider: gDriverInfoTreeProvider, showCollapseAll: true }); + await gEdkWorkspaces.loadConfig(); gFileUseWarning = new FileUseWarning(); From 744a0d18ae3ea476dc5bfd5ea08f3e8d92d47ec2 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 13 May 2026 10:19:49 -0500 Subject: [PATCH 60/73] Add double click on module info --- package.json | 26 +++++++- src/driverInfoTree/DriverInfoTreeProvider.ts | 68 ++++++++++++++++++-- src/extension.ts | 29 ++++++++- src/index/edkWorkspace.ts | 2 +- 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index dbe883a..ab2e349 100644 --- a/package.json +++ b/package.json @@ -151,6 +151,15 @@ { "command": "edk2code.driverInfoGoToDefinition", "title": "EDK2: Go to definition (Driver Info)" + }, + { + "command": "edk2code.driverInfoOpenFile", + "title": "EDK2: Open file (Driver Info)" + }, + { + "command": "edk2code.driverInfoGotoDsc", + "title": "EDK2: Go to DSC declaration", + "icon": "$(references)" } ], "configuration": [ @@ -261,7 +270,7 @@ }, { "id": "driverInfoView", - "name": "Driver Info", + "name": "Module Info", "type": "tree" } ] @@ -279,7 +288,7 @@ }, { "view": "driverInfoView", - "contents": "Open a source file that is part of an EDK2 module to see driver information.", + "contents": "Open a source file that is part of an EDK2 module to see module information.", "when": "!edk2code.driverInfoAvailable" } ], @@ -383,6 +392,14 @@ { "command": "edk2code.driverInfoGoToDefinition", "when": "false" + }, + { + "command": "edk2code.driverInfoOpenFile", + "when": "false" + }, + { + "command": "edk2code.driverInfoGotoDsc", + "when": "false" } ], "editor/title": [], @@ -421,6 +438,11 @@ "command": "edk2code.clearWorkspaceTreeSearch", "group": "navigation", "when": "view == workspaceView && edk2code.workspaceTreeSearchActive" + }, + { + "command": "edk2code.driverInfoGotoDsc", + "group": "navigation", + "when": "view == driverInfoView && edk2code.driverInfoAvailable" } ], "editor/context": [ diff --git a/src/driverInfoTree/DriverInfoTreeProvider.ts b/src/driverInfoTree/DriverInfoTreeProvider.ts index 0db3ff9..ba01eb3 100644 --- a/src/driverInfoTree/DriverInfoTreeProvider.ts +++ b/src/driverInfoTree/DriverInfoTreeProvider.ts @@ -49,16 +49,55 @@ export class DriverInfoTreeProvider implements vscode.TreeDataProvider | undefined; + + /** Set the tree view reference so the provider can update its title. */ + setTreeView(treeView: vscode.TreeView) { + this._treeView = treeView; + } private disposables: vscode.Disposable[] = []; + private _suppressNextUpdate = false; + private _lastEditorFsPath: string | undefined; + private _lastSuppressTime = 0; + + /** Call before programmatically opening a file to prevent the driver info from refreshing. + * Returns true if suppression was set (single click), false if double-click was detected. */ + suppressNextUpdate(): boolean { + const now = Date.now(); + if (now - this._lastSuppressTime < 500) { + // Double-click detected: cancel suppression so driver info updates + this._suppressNextUpdate = false; + this._lastSuppressTime = 0; + return false; + } else { + this._suppressNextUpdate = true; + this._lastSuppressTime = now; + return true; + } + } constructor() { this.disposables.push( vscode.window.onDidChangeActiveTextEditor(async (editor) => { if (editor) { + if (!this._suppressNextUpdate) { + this._lastEditorFsPath = editor.document.uri.fsPath; + } await this.onEditorChanged(editor); } + }), + vscode.window.onDidChangeTextEditorSelection(async (e) => { + // Fires when the user clicks into an already-active editor (focus-back). + // Only process if the document differs from what is currently displayed. + if (e.textEditor.document.uri.fsPath !== this._lastEditorFsPath) { + this._lastEditorFsPath = e.textEditor.document.uri.fsPath; + await this.onEditorChanged(e.textEditor); + } }) ); } @@ -84,6 +123,11 @@ export class DriverInfoTreeProvider implements vscode.TreeDataProvider 0) { const loc = locArray[0]; + const suppressed = gDriverInfoTreeProvider.suppressNextUpdate(); if (loc.uri && loc.range) { await gotoFile(loc.uri, loc.range); } else if (loc.uri) { - await vscode.commands.executeCommand('vscode.open', loc.uri); + await vscode.commands.executeCommand('vscode.open', loc.uri, { preview: suppressed }); + } + if (!suppressed) { + // Double-click: force update + const editor = vscode.window.activeTextEditor; + if (editor) { await gDriverInfoTreeProvider.onEditorChanged(editor); } } } } } }), + + vscode.commands.registerCommand('edk2code.driverInfoOpenFile', async (fileUri: vscode.Uri, options?: any) => { + const suppressed = gDriverInfoTreeProvider.suppressNextUpdate(); + const openOptions = suppressed ? { ...options, preview: true } : { ...options, preview: false }; + await vscode.commands.executeCommand('vscode.open', fileUri, openOptions); + if (!suppressed) { + // Double-click: force update + const editor = vscode.window.activeTextEditor; + if (editor) { await gDriverInfoTreeProvider.onEditorChanged(editor); } + } + }), + + vscode.commands.registerCommand('edk2code.driverInfoGotoDsc', async () => { + const infUri = gDriverInfoTreeProvider.currentInfUri; + if (infUri) { + await cmds.gotoDscDeclaration(infUri); + } + }), // vscode.commands.registerCommand("edk2code.viewWarnings", async ()=>{await gErrorReportAgent.reportErrors();}), @@ -321,7 +345,8 @@ export async function activate(context: vscode.ExtensionContext) { edkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider }); gDriverInfoTreeProvider = new DriverInfoTreeProvider(); - vscode.window.createTreeView('driverInfoView', { treeDataProvider: gDriverInfoTreeProvider, showCollapseAll: true }); + const driverInfoTreeView = vscode.window.createTreeView('driverInfoView', { treeDataProvider: gDriverInfoTreeProvider, showCollapseAll: true }); + gDriverInfoTreeProvider.setTreeView(driverInfoTreeView); await gEdkWorkspaces.loadConfig(); gFileUseWarning = new FileUseWarning(); diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 9ec0515..4af5858 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -906,7 +906,7 @@ export class EdkWorkspace { if(uri.fsPath.includes(lib.path)){ return true; } - } + }`` return false; case "c": From 6ddeaa11ad493a8effc820e6a0061aa438581835 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 13 May 2026 11:07:08 -0500 Subject: [PATCH 61/73] Adding auto discovery of build folders --- package.json | 46 +++++- src/contextState/cmds.ts | 301 ++++++++++++++++++++++++++++----------- src/extension.ts | 7 + 3 files changed, 265 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index ab2e349..ebe685b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,19 @@ "command": "edk2code.rescanIndex", "title": "EDK2: Rescan Index" }, + { + "command": "edk2code.useDiscoveredBuildFolders", + "title": "EDK2: Use discovered build folders" + }, + { + "command": "edk2code.discoverBuildFolders", + "title": "EDK2: Discover build folders" + }, + { + "command": "edk2code.unloadWorkspace", + "title": "EDK2: Unload workspace", + "icon": "$(close)" + }, { "command": "edk2code.gotoDefinition", "title": "EDK2: Go to Definition " @@ -278,13 +291,23 @@ "viewsWelcome": [ { "view": "workspaceView", - "contents": "Loading workspace, please wait...", + "contents": "$(loading~spin) Loading workspace, please wait...", "when": "edk2code.isLoading" }, { "view": "workspaceView", - "contents": "No workspace data available.\n[Rebuild Index](command:edk2code.rebuildIndex)", - "when": "!edk2code.isLoading" + "contents": "$(loading~spin) Discovering build folders. This might take a few moments...", + "when": "edk2code.isDiscovering" + }, + { + "view": "workspaceView", + "contents": "No workspace data available.\n[Discover Build Folders](command:edk2code.discoverBuildFolders)\n[Open EDK2 Configuration](command:edk2code.openConfigurationUi)", + "when": "!edk2code.isLoading && !edk2code.isDiscovering && !edk2code.buildFoldersFound" + }, + { + "view": "workspaceView", + "contents": "Build folders found in workspace.\n[Use discovered folders](command:edk2code.useDiscoveredBuildFolders)\nor\n[Open EDK2 Configuration](command:edk2code.openConfigurationUi)", + "when": "!edk2code.isLoading && edk2code.buildFoldersFound" }, { "view": "driverInfoView", @@ -302,6 +325,18 @@ "command": "edk2code.rescanIndex", "when": "true" }, + { + "command": "edk2code.useDiscoveredBuildFolders", + "when": "edk2code.buildFoldersFound" + }, + { + "command": "edk2code.discoverBuildFolders", + "when": "true" + }, + { + "command": "edk2code.unloadWorkspace", + "when": "true" + }, { "command": "edk2code.gotoDefinition", "when": "edk2code.parseComplete" @@ -419,6 +454,11 @@ "group": "navigation", "when": "view == workspaceView" }, + { + "command": "edk2code.unloadWorkspace", + "group": "navigation", + "when": "view == workspaceView" + }, { "command": "edk2code.refreshWorkspaceConfig", "group": "navigation", diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 9296dbd..dc24087 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -16,116 +16,245 @@ import { deleteEdkCodeFolder, existsEdkCodeFolderFile } from "../edk2CodeFolder" import { infoMissingCompileInfo } from "../ui/messages"; import { checkCppConfiguration } from "../cppProviders/cppUtils"; + let discoveredBuildFolders: string[] = []; + let buildFolderScanTimer: NodeJS.Timeout | undefined; + let scanInProgressPromise: Promise | undefined; - export async function rebuildIndexDatabase(){ - gDebugLog.trace("Rebuilding index database"); - // Pick build folder - let buildPath = await vscode.window.showOpenDialog({ - defaultUri: vscode.Uri.file(gWorkspacePath), canSelectFiles: false, canSelectFolders: true, canSelectMany: false, - filters: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'BuildFolder': ['Build'], - }, - title: "Select EDK build folder" + /** + * Searches for BuildOptions files in the given search path. + * Returns an array of directory paths that contain BuildOptions files. + */ + export async function findBuildOptionsFolders(searchPath: string): Promise { + let lookPath = toPosix(path.join(searchPath, "**", "BuildOptions")); + let buildInfoFolders = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Looking for compile info", + cancellable: true + }, async (progress, reject) => { + return glob(lookPath); }); - // If build folder picked - if (buildPath) { - // Check if its part of workspace - if (isWorkspacePath(buildPath[0].fsPath)) { - let buildInfoFolders: any[] = []; - let configPath = buildPath[0].fsPath; - - // Look for BuildOptions file in selected buildPath - if (configPath !== undefined) { - let lookPath = toPosix(path.join(configPath, "**", "BuildOptions")); - buildInfoFolders = await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: "Looking for compile info", - cancellable: true - }, async (progress, reject) => { - return glob(lookPath); - }); - } + return buildInfoFolders.map((x) => { + return path.parse(String(x)).dir; + }); + } - // Build Options not found this is not a build folder - if (buildInfoFolders.length === 0) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage(`Build data was not found in selected folder`); - return; - } + /** + * Periodically scans the workspace for BuildOptions folders. + * When found, sets a context key so the welcome view can show the discovery action. + */ + export function startBuildFolderScan(intervalMs: number = 60000) { + stopBuildFolderScan(); + // Run immediately once + void scanWorkspaceForBuildFolders(); + buildFolderScanTimer = setInterval(() => { + void scanWorkspaceForBuildFolders(); + }, intervalMs); + } - buildInfoFolders = buildInfoFolders.map((x) => { - return path.parse(String(x)).dir; - }); + export function stopBuildFolderScan() { + if (buildFolderScanTimer) { + clearInterval(buildFolderScanTimer); + buildFolderScanTimer = undefined; + } + } + + async function scanWorkspaceForBuildFolders() { + if (scanInProgressPromise) { return scanInProgressPromise; } + scanInProgressPromise = doScanWorkspace(); + try { + await scanInProgressPromise; + } finally { + scanInProgressPromise = undefined; + } + } - // Show options for builds - var options: vscode.QuickPickItem[] = []; - for (const foundPath of buildInfoFolders) { - let splitPath = foundPath.split(path.posix.sep); + async function doScanWorkspace() { + try { + let lookPath = toPosix(path.join(gWorkspacePath, "**", "BuildOptions")); + let results = await glob(lookPath); + let folders = results.map((x) => path.parse(String(x)).dir); - options.push({ - label: splitPath[splitPath.length - 2], - description: "", - detail: foundPath, - }); - } + if (folders.length > 0) { + discoveredBuildFolders = folders; + await vscode.commands.executeCommand('setContext', 'edk2code.buildFoldersFound', true); + } else { + discoveredBuildFolders = []; + await vscode.commands.executeCommand('setContext', 'edk2code.buildFoldersFound', false); + } + } catch (error) { + gDebugLog.error(`scanWorkspaceForBuildFolders: ${error}`); + } + } + + /** + * Manually triggers discovery of build folders in the workspace. + * Shows a discovering state in the view while scanning. + */ + export async function discoverBuildFolders() { + await vscode.commands.executeCommand('setContext', 'edk2code.isDiscovering', true); + try { + await scanWorkspaceForBuildFolders(); + } finally { + await vscode.commands.executeCommand('setContext', 'edk2code.isDiscovering', false); + } + } + /** + * Called when user clicks "Use discovered folders" in the welcome view. + * Shows the discovered build folders and lets the user pick which ones to use. + */ + export async function useDiscoveredBuildFolders() { + if (discoveredBuildFolders.length === 0) { + void vscode.window.showInformationMessage("No build folders discovered yet."); + return; + } - let selectedOptions = await vscode.window.showQuickPick(options, { title: "Select build", matchOnDescription: true, matchOnDetail: true , canPickMany:true}); - if (selectedOptions === undefined) { return; } + var options: vscode.QuickPickItem[] = []; + for (const foundPath of discoveredBuildFolders) { + let splitPath = foundPath.split(path.posix.sep); + options.push({ + label: splitPath[splitPath.length - 2], + description: "", + detail: foundPath, + }); + } - let selectedFolders:string[] = []; + let selectedOptions = await vscode.window.showQuickPick(options, { title: "Select build folders to use", matchOnDescription: true, matchOnDetail: true, canPickMany: true }); + if (selectedOptions === undefined) { return; } - for (const op of selectedOptions) { - if(op.detail){ - selectedFolders.push(op.detail); - } - } + let selectedFolders: string[] = []; + for (const op of selectedOptions) { + if (op.detail) { + selectedFolders.push(op.detail); + } + } + if (selectedFolders.length > 0) { + await vscode.commands.executeCommand('setContext', 'edk2code.isLoading', true); + await rebuildIndexDatabase(selectedFolders); + await vscode.commands.executeCommand('setContext', 'edk2code.isLoading', false); + } + } - gDebugLog.trace("Loading from build"); - - let buildFolder = new BuildFolder(selectedFolders); - let buildData = await buildFolder.getBuildOptions(); - if (buildData) { - gDebugLog.trace("Delete workspace files"); - // await gEdkDatabase.clearWorkspace(); - deleteEdkCodeFolder(); - gConfigAgent.clearWpConfiguration(); - await gConfigAgent.setBuildDefines(buildData.buildDefines); - await gConfigAgent.setBuildDscPaths(buildData.dscFiles); - buildFolder.copyCompileInfoToRoot(); - - // If cscope.file is not generated, then calculate files based on dsc parsing - if(existsEdkCodeFolderFile(".missing")){ - infoMissingCompileInfo(); - await reloadSymbols(); - }else{ - await checkCppConfiguration(); - await gCscope.reload(); - } + export async function rebuildIndexDatabase(preselectedFolders?: string[]){ + gDebugLog.trace("Rebuilding index database"); - // Generate .ignore if setting is set and .ignore doesnt exists - if (gConfigAgent.getIsGenIgnoreFile()) { - await genIgnoreFile(); + let selectedFolders: string[] = []; + + if (preselectedFolders && preselectedFolders.length > 0) { + // Use pre-discovered folders directly + selectedFolders = preselectedFolders; + } else { + // Pick build folder interactively + let buildPath = await vscode.window.showOpenDialog({ + defaultUri: vscode.Uri.file(gWorkspacePath), canSelectFiles: false, canSelectFolders: true, canSelectMany: false, + filters: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'BuildFolder': ['Build'], + }, + title: "Select EDK build folder" + }); + + // If build folder picked + if (buildPath) { + // Check if its part of workspace + if (isWorkspacePath(buildPath[0].fsPath)) { + let configPath = buildPath[0].fsPath; + + let buildInfoFolders = await findBuildOptionsFolders(configPath); + + // Build Options not found this is not a build folder + if (buildInfoFolders.length === 0) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + vscode.window.showErrorMessage(`Build data was not found in selected folder`); + return; } - await buildFolder.copyMapFilesList(); - gMapFileManager.load(); + // Show options for builds + var options: vscode.QuickPickItem[] = []; + for (const foundPath of buildInfoFolders) { + let splitPath = foundPath.split(path.posix.sep); - void vscode.window.showInformationMessage("Build data loaded"); + options.push({ + label: splitPath[splitPath.length - 2], + description: "", + detail: foundPath, + }); + } - await gEdkWorkspaces.loadConfig(); - } + let selectedOptions = await vscode.window.showQuickPick(options, { title: "Select build", matchOnDescription: true, matchOnDetail: true, canPickMany: true }); + if (selectedOptions === undefined) { return; } + for (const op of selectedOptions) { + if (op.detail) { + selectedFolders.push(op.detail); + } + } + } else { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + vscode.window.showErrorMessage(`${buildPath[0].fsPath} its outside workspace`); + return; + } } else { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - vscode.window.showErrorMessage(`${buildPath[0].fsPath} its outside workspace`); + return; + } + } + + if (selectedFolders.length === 0) { return; } + + gDebugLog.trace("Loading from build"); + + let buildFolder = new BuildFolder(selectedFolders); + let buildData = await buildFolder.getBuildOptions(); + if (buildData) { + gDebugLog.trace("Delete workspace files"); + // await gEdkDatabase.clearWorkspace(); + deleteEdkCodeFolder(); + gConfigAgent.clearWpConfiguration(); + await gConfigAgent.setBuildDefines(buildData.buildDefines); + await gConfigAgent.setBuildDscPaths(buildData.dscFiles); + buildFolder.copyCompileInfoToRoot(); + + // If cscope.file is not generated, then calculate files based on dsc parsing + if(existsEdkCodeFolderFile(".missing")){ + infoMissingCompileInfo(); + await reloadSymbols(); + }else{ + await checkCppConfiguration(); + await gCscope.reload(); } + + // Generate .ignore if setting is set and .ignore doesnt exists + if (gConfigAgent.getIsGenIgnoreFile()) { + await genIgnoreFile(); + } + + await buildFolder.copyMapFilesList(); + gMapFileManager.load(); + + void vscode.window.showInformationMessage("Build data loaded"); + + await gEdkWorkspaces.loadConfig(); } } + export async function unloadWorkspace() { + const confirm = await vscode.window.showWarningMessage( + "Are you sure you want to unload the workspace? This will remove all indexed data.", + { modal: true }, + "Unload" + ); + if (confirm !== "Unload") { return; } + + gDebugLog.trace("Unloading workspace"); + deleteEdkCodeFolder(); + gConfigAgent.clearWpConfiguration(); + gEdkWorkspaces.workspaces = []; + edkWorkspaceTreeProvider.refresh(); + void vscode.window.showInformationMessage("Workspace unloaded"); + } + export async function rescanIndex() { gConfigAgent.reloadConfigFile(); await reloadSymbols(); diff --git a/src/extension.ts b/src/extension.ts index 0a70714..99d59d7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -80,9 +80,12 @@ export async function activate(context: vscode.ExtensionContext) { var commands = [ vscode.commands.registerCommand('edk2code.rebuildIndex', async ()=>{await cmds.rebuildIndexDatabase();}), + vscode.commands.registerCommand('edk2code.useDiscoveredBuildFolders', async ()=>{await cmds.useDiscoveredBuildFolders();}), + vscode.commands.registerCommand('edk2code.discoverBuildFolders', async ()=>{await cmds.discoverBuildFolders();}), vscode.commands.registerCommand('edk2code.openConfigurationUi', async ()=>{await cmds.openWpConfigGui();}), vscode.commands.registerCommand('edk2code.openConfigurationJson', async ()=>{await cmds.openWpConfigJson();}), vscode.commands.registerCommand('edk2code.rescanIndex', async ()=>{await cmds.rescanIndex();}), + vscode.commands.registerCommand('edk2code.unloadWorkspace', async ()=>{await cmds.unloadWorkspace();}), vscode.commands.registerCommand('edk2code.help', async ()=>{ const docUrl = getDocsUrl(); await vscode.env.openExternal(vscode.Uri.parse(docUrl)); @@ -351,6 +354,9 @@ export async function activate(context: vscode.ExtensionContext) { await gEdkWorkspaces.loadConfig(); gFileUseWarning = new FileUseWarning(); + // Start periodic scan for build folders in workspace + cmds.startBuildFolderScan(); + @@ -401,6 +407,7 @@ export async function activate(context: vscode.ExtensionContext) { // this method is called when your extension is deactivated export async function deactivate() { stopMcpServer(); + cmds.stopBuildFolderScan(); } From 45ff1122c1bba6888584c83521a32ff2f4e08465 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 13 May 2026 11:25:47 -0500 Subject: [PATCH 62/73] Adding UI settings --- package.json | 10 +- src/contextState/cmds.ts | 2 +- src/settings/settings.html | 142 +++++++++++++- src/settings/settings.ts | 337 ++++++++++++++++++++++++++++++++-- src/settings/settingsPanel.ts | 37 ++++ 5 files changed, 503 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index ebe685b..e02acb2 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ }, { "command": "edk2code.openConfigurationUi", - "title": "EDK2: Workspace configuration (UI)" + "title": "EDK2: Workspace configuration (UI)", + "icon": "$(gear)" }, { "command": "edk2code.openConfigurationJson", @@ -306,7 +307,7 @@ }, { "view": "workspaceView", - "contents": "Build folders found in workspace.\n[Use discovered folders](command:edk2code.useDiscoveredBuildFolders)\nor\n[Open EDK2 Configuration](command:edk2code.openConfigurationUi)", + "contents": "Build folders found in workspace.\n[Use discovered folders](command:edk2code.useDiscoveredBuildFolders)\n[Open EDK2 Configuration](command:edk2code.openConfigurationUi)", "when": "!edk2code.isLoading && edk2code.buildFoldersFound" }, { @@ -439,6 +440,11 @@ ], "editor/title": [], "view/title": [ + { + "command": "edk2code.openConfigurationUi", + "group": "navigation", + "when": "view == workspaceView" + }, { "command": "edk2code.selectWorkspaceView", "group": "navigation", diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index dc24087..6dee285 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -43,7 +43,7 @@ import { checkCppConfiguration } from "../cppProviders/cppUtils"; * Periodically scans the workspace for BuildOptions folders. * When found, sets a context key so the welcome view can show the discovery action. */ - export function startBuildFolderScan(intervalMs: number = 60000) { + export function startBuildFolderScan(intervalMs: number = 600000) { stopBuildFolderScan(); // Run immediately once void scanWorkspaceForBuildFolders(); diff --git a/src/settings/settings.html b/src/settings/settings.html index b893552..4de60d7 100644 --- a/src/settings/settings.html +++ b/src/settings/settings.html @@ -379,6 +379,123 @@ background-color: rgba(255, 255, 255, 0.15) } + .dsc-list-item { + display: flex; + align-items: center; + padding: 4px 8px; + margin-bottom: 4px; + border: 1px solid var(--vscode-settings-textInputBorder); + background: var(--vscode-settings-textInputBackground); + border-radius: 3px; + } + + .dsc-list-item .dsc-path { + flex: 1; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .dsc-list-item .dsc-edit-input { + flex: 1; + height: 17px; + font-size: 13px; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + color: var(--vscode-settings-textInputForeground); + background: var(--vscode-settings-textInputBackground); + border: 1px solid var(--vscode-focusBorder); + } + + .dsc-list-item button { + padding: 2px 8px; + margin-left: 4px; + font-size: 12px; + } + + .define-list-item { + display: flex; + align-items: center; + padding: 4px 8px; + margin-bottom: 4px; + border: 1px solid var(--vscode-settings-textInputBorder); + background: var(--vscode-settings-textInputBackground); + border-radius: 3px; + } + + .define-list-item .define-name { + flex: 1; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 13px; + font-weight: bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .define-list-item .define-value { + flex: 1; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: 8px; + } + + .define-list-item .define-edit-input { + flex: 1; + height: 17px; + font-size: 13px; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + color: var(--vscode-settings-textInputForeground); + background: var(--vscode-settings-textInputBackground); + border: 1px solid var(--vscode-focusBorder); + margin-right: 4px; + } + + .define-list-item button { + padding: 2px 8px; + margin-left: 4px; + font-size: 12px; + } + + .pkg-list-item { + display: flex; + align-items: center; + padding: 4px 8px; + margin-bottom: 4px; + border: 1px solid var(--vscode-settings-textInputBorder); + background: var(--vscode-settings-textInputBackground); + border-radius: 3px; + } + + .pkg-list-item .pkg-path { + flex: 1; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .pkg-list-item .pkg-edit-input { + flex: 1; + height: 17px; + font-size: 13px; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + color: var(--vscode-settings-textInputForeground); + background: var(--vscode-settings-textInputBackground); + border: 1px solid var(--vscode-focusBorder); + } + + .pkg-list-item button { + padding: 2px 8px; + margin-left: 4px; + font-size: 12px; + } + .footer { padding: 25px; text-align: center @@ -448,9 +565,10 @@ Main DSC file relative paths. This are the DSC files used for your platform. In your build log you will see this as Active Platform
-
One DSC path per line.
- -
+
Add .dsc files used for your platform.
+
+ +
@@ -460,9 +578,14 @@ Build definitions injected on build command. Usually this are the -D arguments on your build command
-
One Build define per line in the format: name=value
- -
+
Define/value pairs passed as -D arguments.
+
+
+ + + +
+
@@ -472,9 +595,10 @@ Workspace relative package paths. This is what you see in your build log as PACKAGES_PATH env variable.
-
One path per line.
- -
+
Add workspace-relative package paths.
+
+ +
diff --git a/src/settings/settings.ts b/src/settings/settings.ts index c079b35..0cb53e4 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -7,8 +7,16 @@ const elementId: { [key: string]: string } = { // Basic settings dscPaths: "dscPaths", + dscPathsList: "dscPathsList", + dscAddBtn: "dscAddBtn", buildDefines: "buildDefines", + buildDefinesList: "buildDefinesList", + buildDefinesAddBtn: "buildDefinesAddBtn", + buildDefineNewName: "buildDefineNewName", + buildDefineNewValue: "buildDefineNewValue", packagePaths: "packagePaths", + packagePathsList: "packagePathsList", + packagePathsAddBtn: "packagePathsAddBtn", }; @@ -23,6 +31,9 @@ declare function acquireVsCodeApi(): VsCodeApi; class SettingsApp { private readonly vsCodeApi: VsCodeApi; private updating: boolean = false; + private dscPaths: string[] = []; + private buildDefines: {name: string, value: string}[] = []; + private packagePaths: string[] = []; constructor() { this.vsCodeApi = acquireVsCodeApi(); @@ -32,11 +43,289 @@ class SettingsApp { // Add event listeners to UI elements this.addEventsToInputValues(); + this.addDscListEvents(); + this.addBuildDefinesEvents(); + this.addPackagePathsEvents(); this.vsCodeApi.postMessage({ command: "initialized" }); } + private addDscListEvents(): void { + document.getElementById(elementId.dscAddBtn)!.addEventListener("click", () => { + this.vsCodeApi.postMessage({ command: "selectDscFile" }); + }); + } + + private renderDscList(): void { + const container = document.getElementById(elementId.dscPathsList)!; + container.innerHTML = ""; + this.dscPaths.forEach((path, index) => { + const item = document.createElement("div"); + item.className = "dsc-list-item"; + + const pathSpan = document.createElement("span"); + pathSpan.className = "dsc-path"; + pathSpan.textContent = path; + item.appendChild(pathSpan); + + const editBtn = document.createElement("button"); + editBtn.type = "button"; + editBtn.textContent = "Edit"; + editBtn.addEventListener("click", () => this.editDscItem(index)); + item.appendChild(editBtn); + + const deleteBtn = document.createElement("button"); + deleteBtn.type = "button"; + deleteBtn.textContent = "Delete"; + deleteBtn.addEventListener("click", () => this.deleteDscItem(index)); + item.appendChild(deleteBtn); + + container.appendChild(item); + }); + } + + private editDscItem(index: number): void { + const container = document.getElementById(elementId.dscPathsList)!; + const item = container.children[index] as HTMLElement; + const currentPath = this.dscPaths[index]; + + item.innerHTML = ""; + const input = document.createElement("input"); + input.type = "text"; + input.className = "dsc-edit-input"; + input.value = currentPath; + item.appendChild(input); + + const saveBtn = document.createElement("button"); + saveBtn.type = "button"; + saveBtn.textContent = "Save"; + saveBtn.addEventListener("click", () => { + this.dscPaths[index] = input.value; + this.renderDscList(); + this.onDscPathsChanged(); + }); + item.appendChild(saveBtn); + + const cancelBtn = document.createElement("button"); + cancelBtn.type = "button"; + cancelBtn.textContent = "Cancel"; + cancelBtn.addEventListener("click", () => this.renderDscList()); + item.appendChild(cancelBtn); + + input.focus(); + } + + private deleteDscItem(index: number): void { + this.dscPaths.splice(index, 1); + this.renderDscList(); + this.onDscPathsChanged(); + } + + private onDscPathsChanged(): void { + if (this.updating) { return; } + this.vsCodeApi.postMessage({ + command: "change", + config: this.collectConfig() + }); + } + + // --- Build Defines --- + + private addBuildDefinesEvents(): void { + document.getElementById(elementId.buildDefinesAddBtn)!.addEventListener("click", () => { + const nameInput = document.getElementById(elementId.buildDefineNewName); + const valueInput = document.getElementById(elementId.buildDefineNewValue); + const name = nameInput.value.trim(); + const value = valueInput.value.trim(); + if (name) { + this.buildDefines.push({ name, value }); + this.renderBuildDefinesList(); + this.onBuildDefinesChanged(); + nameInput.value = ""; + valueInput.value = ""; + } + }); + } + + private renderBuildDefinesList(): void { + const container = document.getElementById(elementId.buildDefinesList)!; + container.innerHTML = ""; + this.buildDefines.forEach((def, index) => { + const item = document.createElement("div"); + item.className = "define-list-item"; + + const nameSpan = document.createElement("span"); + nameSpan.className = "define-name"; + nameSpan.textContent = def.name; + item.appendChild(nameSpan); + + const valueSpan = document.createElement("span"); + valueSpan.className = "define-value"; + valueSpan.textContent = def.value; + item.appendChild(valueSpan); + + const editBtn = document.createElement("button"); + editBtn.type = "button"; + editBtn.textContent = "Edit"; + editBtn.addEventListener("click", () => this.editBuildDefineItem(index)); + item.appendChild(editBtn); + + const deleteBtn = document.createElement("button"); + deleteBtn.type = "button"; + deleteBtn.textContent = "Delete"; + deleteBtn.addEventListener("click", () => this.deleteBuildDefineItem(index)); + item.appendChild(deleteBtn); + + container.appendChild(item); + }); + } + + private editBuildDefineItem(index: number): void { + const container = document.getElementById(elementId.buildDefinesList)!; + const item = container.children[index] as HTMLElement; + const current = this.buildDefines[index]; + + item.innerHTML = ""; + const nameInput = document.createElement("input"); + nameInput.type = "text"; + nameInput.className = "define-edit-input"; + nameInput.value = current.name; + item.appendChild(nameInput); + + const valueInput = document.createElement("input"); + valueInput.type = "text"; + valueInput.className = "define-edit-input"; + valueInput.value = current.value; + item.appendChild(valueInput); + + const saveBtn = document.createElement("button"); + saveBtn.type = "button"; + saveBtn.textContent = "Save"; + saveBtn.addEventListener("click", () => { + this.buildDefines[index] = { name: nameInput.value.trim(), value: valueInput.value.trim() }; + this.renderBuildDefinesList(); + this.onBuildDefinesChanged(); + }); + item.appendChild(saveBtn); + + const cancelBtn = document.createElement("button"); + cancelBtn.type = "button"; + cancelBtn.textContent = "Cancel"; + cancelBtn.addEventListener("click", () => this.renderBuildDefinesList()); + item.appendChild(cancelBtn); + + nameInput.focus(); + } + + private deleteBuildDefineItem(index: number): void { + this.buildDefines.splice(index, 1); + this.renderBuildDefinesList(); + this.onBuildDefinesChanged(); + } + + private onBuildDefinesChanged(): void { + if (this.updating) { return; } + this.vsCodeApi.postMessage({ + command: "change", + config: this.collectConfig() + }); + } + + // --- Package Paths --- + + private addPackagePathsEvents(): void { + document.getElementById(elementId.packagePathsAddBtn)!.addEventListener("click", () => { + this.vsCodeApi.postMessage({ command: "selectPackagePath" }); + }); + } + + private renderPackagePathsList(): void { + const container = document.getElementById(elementId.packagePathsList)!; + container.innerHTML = ""; + this.packagePaths.forEach((p, index) => { + const item = document.createElement("div"); + item.className = "pkg-list-item"; + + const pathSpan = document.createElement("span"); + pathSpan.className = "pkg-path"; + pathSpan.textContent = p; + item.appendChild(pathSpan); + + const editBtn = document.createElement("button"); + editBtn.type = "button"; + editBtn.textContent = "Edit"; + editBtn.addEventListener("click", () => this.editPackagePathItem(index)); + item.appendChild(editBtn); + + const deleteBtn = document.createElement("button"); + deleteBtn.type = "button"; + deleteBtn.textContent = "Delete"; + deleteBtn.addEventListener("click", () => this.deletePackagePathItem(index)); + item.appendChild(deleteBtn); + + container.appendChild(item); + }); + } + + private editPackagePathItem(index: number): void { + const container = document.getElementById(elementId.packagePathsList)!; + const item = container.children[index] as HTMLElement; + const currentPath = this.packagePaths[index]; + + item.innerHTML = ""; + const input = document.createElement("input"); + input.type = "text"; + input.className = "pkg-edit-input"; + input.value = currentPath; + item.appendChild(input); + + const saveBtn = document.createElement("button"); + saveBtn.type = "button"; + saveBtn.textContent = "Save"; + saveBtn.addEventListener("click", () => { + this.packagePaths[index] = input.value; + this.renderPackagePathsList(); + this.onPackagePathsChanged(); + }); + item.appendChild(saveBtn); + + const cancelBtn = document.createElement("button"); + cancelBtn.type = "button"; + cancelBtn.textContent = "Cancel"; + cancelBtn.addEventListener("click", () => this.renderPackagePathsList()); + item.appendChild(cancelBtn); + + input.focus(); + } + + private deletePackagePathItem(index: number): void { + this.packagePaths.splice(index, 1); + this.renderPackagePathsList(); + this.onPackagePathsChanged(); + } + + private onPackagePathsChanged(): void { + if (this.updating) { return; } + this.vsCodeApi.postMessage({ + command: "change", + config: this.collectConfig() + }); + } + + private collectConfig(): any { + const elements: NodeListOf = document.getElementsByName("inputValue"); + let config: any = {}; + elements.forEach(el => { + const data: HTMLInputElement = document.getElementById(el.id); + config[data.id] = data.value; + }); + config[elementId.dscPaths] = this.dscPaths.join("\n"); + config[elementId.buildDefines] = this.buildDefines.map(d => `${d.name}=${d.value}`).join("\n"); + config[elementId.packagePaths] = this.packagePaths.join("\n"); + return config; + } + private addEventsToInputValues(): void { const elements: NodeListOf = document.getElementsByName("inputValue"); elements.forEach(el => { @@ -66,16 +355,9 @@ class SettingsApp { return; } - const elements: NodeListOf = document.getElementsByName("inputValue"); - let config:any = {}; - elements.forEach(el => { - const data: HTMLInputElement = document.getElementById(el.id); - config[data.id]=data.value; - }); - this.vsCodeApi.postMessage({ command: "change", - config: config + config: this.collectConfig() }); } @@ -88,18 +370,47 @@ class SettingsApp { case 'updateErrors': this.updateErrors(message.errors); break; + case 'addDscFile': + if (message.path && !this.dscPaths.includes(message.path)) { + this.dscPaths.push(message.path); + this.renderDscList(); + this.onDscPathsChanged(); + } + break; + case 'addPackagePath': + if (message.path && !this.packagePaths.includes(message.path)) { + this.packagePaths.push(message.path); + this.renderPackagePathsList(); + this.onPackagePathsChanged(); + } + break; } } private updateConfig(config: any): void { this.updating = true; try { - const joinEntries: (input: any) => string = (input: string[]) => (input && input.length) ? input.join("\n") : ""; + // DSC paths + this.dscPaths = (config.dscPaths && config.dscPaths.length) ? [...config.dscPaths] : []; + this.renderDscList(); + + // Build defines (stored as "name=value" strings) + if (config.buildDefines && config.buildDefines.length) { + this.buildDefines = config.buildDefines.map((entry: string) => { + const eqIdx = entry.indexOf("="); + if (eqIdx >= 0) { + return { name: entry.substring(0, eqIdx), value: entry.substring(eqIdx + 1) }; + } + return { name: entry, value: "" }; + }); + } else { + this.buildDefines = []; + } + this.renderBuildDefinesList(); - // Basic settings - (document.getElementById(elementId.dscPaths)).value = joinEntries(config.dscPaths); - (document.getElementById(elementId.packagePaths)).value = joinEntries(config.packagePaths); - (document.getElementById(elementId.buildDefines)).value = joinEntries(config.buildDefines); + // Package paths + this.packagePaths = (config.packagePaths && config.packagePaths.length) ? [...config.packagePaths] : []; + this.renderPackagePathsList(); } finally { this.updating = false; diff --git a/src/settings/settingsPanel.ts b/src/settings/settingsPanel.ts index ac4a3e7..5decdc0 100644 --- a/src/settings/settingsPanel.ts +++ b/src/settings/settingsPanel.ts @@ -161,6 +161,12 @@ export class SettingsPanel { this.initialized = true; this.initilizePanel(); break; + case 'selectDscFile': + this.selectDscFile(); + break; + case 'selectPackagePath': + this.selectPackagePath(); + break; } @@ -192,6 +198,37 @@ export class SettingsPanel { this.addConfigRequested.fire(name); } + private async selectDscFile(): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders; + const defaultUri = workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0].uri : undefined; + const result = await vscode.window.showOpenDialog({ + canSelectMany: false, + openLabel: "Select DSC file", + defaultUri: defaultUri, + filters: { "DSC Files": ["dsc"] } + }); + if (result && result.length > 0 && workspaceFolders && workspaceFolders.length > 0) { + const relativePath = path.relative(workspaceFolders[0].uri.fsPath, result[0].fsPath).replace(/\\/g, '/'); + void this._panel.webview.postMessage({ command: 'addDscFile', path: relativePath }); + } + } + + private async selectPackagePath(): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders; + const defaultUri = workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0].uri : undefined; + const result = await vscode.window.showOpenDialog({ + canSelectMany: false, + canSelectFolders: true, + canSelectFiles: false, + openLabel: "Select package path", + defaultUri: defaultUri, + }); + if (result && result.length > 0 && workspaceFolders && workspaceFolders.length > 0) { + const relativePath = path.relative(workspaceFolders[0].uri.fsPath, result[0].fsPath).replace(/\\/g, '/'); + void this._panel.webview.postMessage({ command: 'addPackagePath', path: relativePath }); + } + } + /** * Sets up an event listener to listen for messages passed from the webview context and From 3b17846d5417172cdf6fb7a5ba44d09d80b759ef Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 13 May 2026 11:40:17 -0500 Subject: [PATCH 63/73] Adding setting for MCP --- src/settings/settings.html | 20 +++++++++ src/settings/settings.ts | 45 ++++++++++++++++++++ src/settings/settingsPanel.ts | 77 +++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/src/settings/settings.html b/src/settings/settings.html index 4de60d7..7cf3a4c 100644 --- a/src/settings/settings.html +++ b/src/settings/settings.html @@ -558,6 +558,26 @@
+ +
+
MCP Server
+
+ Control the EDK2 MCP SSE server and configure workspace MCP integration. +
+
+ + +
+
+ + +
+
+ + +
+
+
DSC relative path
diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 0cb53e4..9841f81 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -46,6 +46,7 @@ class SettingsApp { this.addDscListEvents(); this.addBuildDefinesEvents(); this.addPackagePathsEvents(); + this.addMcpEvents(); this.vsCodeApi.postMessage({ command: "initialized" }); @@ -57,6 +58,41 @@ class SettingsApp { }); } + // --- MCP --- + + private addMcpEvents(): void { + document.getElementById("mcpToggleBtn")!.addEventListener("click", () => { + this.vsCodeApi.postMessage({ command: "toggleMcp" }); + }); + document.getElementById("mcpAutoConfigBtn")!.addEventListener("click", () => { + this.vsCodeApi.postMessage({ command: "autoConfigureMcp" }); + }); + document.getElementById("mcpPort")!.addEventListener("change", () => { + const port = parseInt((document.getElementById("mcpPort")).value, 10); + if (port >= 1 && port <= 65535) { + this.vsCodeApi.postMessage({ command: "changeMcpPort", port }); + } + }); + } + + private updateMcpStatus(running: boolean): void { + const btn = document.getElementById("mcpToggleBtn") as HTMLButtonElement; + const status = document.getElementById("mcpStatus")!; + if (running) { + btn.textContent = "Stop MCP Server"; + status.textContent = "Running"; + status.style.color = "var(--vscode-charts-green)"; + } else { + btn.textContent = "Start MCP Server"; + status.textContent = "Stopped"; + status.style.color = "var(--vscode-foreground)"; + } + } + + private updateMcpConfigStatus(message: string): void { + document.getElementById("mcpConfigStatus")!.textContent = message; + } + private renderDscList(): void { const container = document.getElementById(elementId.dscPathsList)!; container.innerHTML = ""; @@ -384,6 +420,15 @@ class SettingsApp { this.onPackagePathsChanged(); } break; + case 'mcpStatus': + this.updateMcpStatus(message.running); + if (message.port !== undefined) { + (document.getElementById("mcpPort")).value = message.port.toString(); + } + break; + case 'mcpConfigResult': + this.updateMcpConfigStatus(message.message); + break; } } diff --git a/src/settings/settingsPanel.ts b/src/settings/settingsPanel.ts index 5decdc0..c942c7f 100644 --- a/src/settings/settingsPanel.ts +++ b/src/settings/settingsPanel.ts @@ -7,6 +7,7 @@ import { Disposable, Webview, WebviewPanel, window, Uri, ViewColumn } from "vsco import { gExtensionContext } from '../extension'; import { ConfigAgent, WorkspaceConfig, WorkspaceConfigErrors } from '../configuration'; import { askReloadFiles } from '../ui/messages'; +import { isMcpServerRunning } from '../mcp/mcpServer'; function deepCopy(obj: any) { @@ -167,6 +168,15 @@ export class SettingsPanel { case 'selectPackagePath': this.selectPackagePath(); break; + case 'toggleMcp': + this.toggleMcpServer(); + break; + case 'autoConfigureMcp': + this.autoConfigureMcp(); + break; + case 'changeMcpPort': + this.changeMcpPort(message.port); + break; } @@ -174,6 +184,8 @@ export class SettingsPanel { initilizePanel() { SettingsPanel.currentPanel!.updateWebview(this.configAgent!.getWorkspaceConfig(), this.configAgent!.getWorkspaceErrors()); + const port = vscode.workspace.getConfiguration('edk2code').get('mcpServerPort', 3100); + void this._panel.webview.postMessage({ command: 'mcpStatus', running: isMcpServerRunning(), port }); } updateConfig(message: any) { @@ -229,6 +241,71 @@ export class SettingsPanel { } } + private async toggleMcpServer(): Promise { + if (isMcpServerRunning()) { + await vscode.commands.executeCommand('edk2code.stopMcpServer'); + } else { + await vscode.commands.executeCommand('edk2code.startMcpServer'); + } + const port = vscode.workspace.getConfiguration('edk2code').get('mcpServerPort', 3100); + void this._panel.webview.postMessage({ command: 'mcpStatus', running: isMcpServerRunning(), port }); + } + + private async changeMcpPort(port: number): Promise { + await vscode.workspace.getConfiguration('edk2code').update('mcpServerPort', port, vscode.ConfigurationTarget.Workspace); + } + + private async autoConfigureMcp(): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + void this._panel.webview.postMessage({ command: 'mcpConfigResult', message: 'No workspace folder found.' }); + return; + } + const vscodePath = path.join(workspaceFolders[0].uri.fsPath, '.vscode'); + const mcpConfigPath = path.join(vscodePath, 'mcp.json'); + const port = vscode.workspace.getConfiguration('edk2code').get('mcpServerPort', 3100); + const expectedUrl = `http://localhost:${port}/sse`; + + if (fs.existsSync(mcpConfigPath)) { + try { + const existing = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf-8')); + const serverEntry = existing?.servers?.edk2code; + if (serverEntry && serverEntry.url === expectedUrl) { + void this._panel.webview.postMessage({ command: 'mcpConfigResult', message: 'edk2code MCP already configured correctly.' }); + return; + } + // Add or update the edk2code entry + if (!existing.servers) { + existing.servers = {}; + } + existing.servers.edk2code = { type: "sse", url: expectedUrl }; + fs.writeFileSync(mcpConfigPath, JSON.stringify(existing, null, 4), 'utf-8'); + void this._panel.webview.postMessage({ command: 'mcpConfigResult', message: 'Updated edk2code entry in .vscode/mcp.json' }); + } catch { + // File exists but is not valid JSON, overwrite + const mcpConfig = { servers: { edk2code: { type: "sse", url: expectedUrl } } }; + fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 4), 'utf-8'); + void this._panel.webview.postMessage({ command: 'mcpConfigResult', message: 'Replaced invalid .vscode/mcp.json' }); + } + return; + } + + const mcpConfig = { + servers: { + "edk2code": { + "type": "sse", + "url": expectedUrl + } + } + }; + + if (!fs.existsSync(vscodePath)) { + fs.mkdirSync(vscodePath, { recursive: true }); + } + fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 4), 'utf-8'); + void this._panel.webview.postMessage({ command: 'mcpConfigResult', message: 'Created .vscode/mcp.json' }); + } + /** * Sets up an event listener to listen for messages passed from the webview context and From d15ad3d2c20ecb1540e186be95fb4c1ba7c13deb Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 13 May 2026 12:41:02 -0500 Subject: [PATCH 64/73] Add build button to source files --- package.json | 8 +++- src/compileFile.ts | 45 +++++++++++--------- src/driverInfoTree/DriverInfoTreeProvider.ts | 7 ++- src/extension.ts | 10 ++++- 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index e02acb2..b516e96 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,8 @@ }, { "command": "edk2code.compileCFile", - "title": "EDK2: Compile Edk2 file" + "title": "EDK2: Compile Edk2 file", + "icon": "$(play)" }, { "command": "edk2code.driverInfoGoToDefinition", @@ -558,6 +559,11 @@ "command": "edk2code.gotoOverwrite", "group": "navigation", "when": "view == workspaceView && viewItem == symbolNodeInactiveOverwritten" + }, + { + "command": "edk2code.compileCFile", + "group": "inline", + "when": "view == driverInfoView && viewItem == driverInfoCompilableSource" } ] }, diff --git a/src/compileFile.ts b/src/compileFile.ts index ad24474..2c3e5d1 100644 --- a/src/compileFile.ts +++ b/src/compileFile.ts @@ -33,30 +33,35 @@ function buildReport(entry: CompileCommandsEntry): string { const includes = entry.getIncludePaths(); const lines: string[] = []; - lines.push('╔══════════════════════════════════════════════════════════════'); - lines.push(`β•‘ EDK2 Compile: ${fileName}`); - lines.push('╠══════════════════════════════════════════════════════════════'); - lines.push(`β•‘ Compiler: ${compiler}`); - lines.push(`β•‘ Source: ${entry.file}`); - lines.push(`β•‘ Output: ${output}`); - lines.push(`β•‘ Directory: ${entry.directory}`); - lines.push('╠══════════════════════════════════════════════════════════════'); - lines.push(`β•‘ Defines (${defines.length}):`); - defines.forEach(d => lines.push(`β•‘ -D ${d}`)); - lines.push('╠══════════════════════════════════════════════════════════════'); - lines.push(`β•‘ Include Paths (${includes.length}):`); - includes.forEach(i => lines.push(`β•‘ ${i}`)); - lines.push('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•'); + lines.push('---------------------------------------------------------------'); + lines.push(`| EDK2 Compile: ${fileName}`); + lines.push('---------------------------------------------------------------'); + lines.push(`| Compiler: ${compiler}`); + lines.push(`| Source: ${entry.file}`); + lines.push(`| Output: ${output}`); + lines.push(`| Directory: ${entry.directory}`); + lines.push('---------------------------------------------------------------'); + lines.push(`| Defines (${defines.length}):`); + defines.forEach(d => lines.push(`| -D ${d}`)); + lines.push('---------------------------------------------------------------'); + lines.push(`| Include Paths (${includes.length}):`); + includes.forEach(i => lines.push(`| ${i}`)); + lines.push('---------------------------------------------------------------'); return lines.join('\n'); } -export async function compileCFile() { - const editor = vscode.window.activeTextEditor; - if (!editor) { - void vscode.window.showErrorMessage('No active editor found.'); - return; +export async function compileCFile(fileUri?: vscode.Uri) { + let filePath: string; + if (fileUri) { + filePath = fileUri.fsPath; + } else { + const editor = vscode.window.activeTextEditor; + if (!editor) { + void vscode.window.showErrorMessage('No active editor found.'); + return; + } + filePath = editor.document.uri.fsPath; } - const filePath = editor.document.uri.fsPath; gCompileCommands.load(); const entry = gCompileCommands.getCompileCommandForFile(filePath); diff --git a/src/driverInfoTree/DriverInfoTreeProvider.ts b/src/driverInfoTree/DriverInfoTreeProvider.ts index ba01eb3..b7d00a5 100644 --- a/src/driverInfoTree/DriverInfoTreeProvider.ts +++ b/src/driverInfoTree/DriverInfoTreeProvider.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; -import { gEdkWorkspaces, gPathFind } from '../extension'; +import { gEdkWorkspaces, gPathFind, gCompileCommands } from '../extension'; import { getParser } from '../edkParser/parserFactory'; import { EdkSymbol } from '../symbols/edkSymbols'; import { Edk2SymbolType } from '../symbols/symbolsType'; @@ -250,11 +250,13 @@ export class DriverInfoTreeProvider implements vscode.TreeDataProvider { - await compileCFile(); + vscode.commands.registerCommand('edk2code.compileCFile', async (fileUriOrItem?: vscode.Uri | vscode.TreeItem) => { + let uri: vscode.Uri | undefined; + if (fileUriOrItem instanceof vscode.Uri) { + uri = fileUriOrItem; + } else if (fileUriOrItem && 'resourceUri' in fileUriOrItem && fileUriOrItem.resourceUri) { + uri = fileUriOrItem.resourceUri; + } + await compileCFile(uri); }), vscode.commands.registerCommand('edk2code.startMcpServer', async () => { From 3a24ca210d8384618b2b2790fc0fdf44a3e94a61 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 14 May 2026 18:09:29 -0500 Subject: [PATCH 65/73] Versino 2.0.0 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b516e96..9795046 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "edk2code-dev", - "displayName": "Edk2code-dev", + "name": "edk2code", + "displayName": "Edk2code", "description": "EDK2 code support", - "version": "1.1.7", + "version": "2.0.0", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", From 77ab68efc9c1f34b362c39a72df27eabb5a1fcab Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 14 May 2026 18:59:53 -0500 Subject: [PATCH 66/73] Adding 2.0.0 welcome page --- instructions/outline.md | 91 ------------------------------------- src/newVersionPage/2.0.0.md | 81 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 91 deletions(-) delete mode 100644 instructions/outline.md create mode 100644 src/newVersionPage/2.0.0.md diff --git a/instructions/outline.md b/instructions/outline.md deleted file mode 100644 index 68acd3d..0000000 --- a/instructions/outline.md +++ /dev/null @@ -1,91 +0,0 @@ -Read [](file:///d%3A/github/Edk2Code/src/Languages/symbolProvider.ts#1-1), lines 1 to 100 - -Read [](file:///d%3A/github/Edk2Code/src/symbols/dscSymbols.ts#1-1), lines 1 to 100 - -Read [](file:///d%3A/github/Edk2Code/src/Languages/languages.ts) - -Read [](file:///d%3A/github/Edk2Code/src/edkParser/dscParser.ts#1-1), lines 1 to 120 - -Read [](file:///d%3A/github/Edk2Code/src/edkParser/languageParser.ts#1-1), lines 1 to 100 - -Read [](file:///d%3A/github/Edk2Code/src/symbols/symbolFactory.ts#1-1), lines 1 to 80 - -Read [](file:///d%3A/github/Edk2Code/src/edkParser/languageParser.ts#100-100), lines 100 to 100 - -Here's the flow, end to end: - ---- - -### 1. Registration (languages.ts) - -`initLanguages()` calls: -```ts -vscode.languages.registerDocumentSymbolProvider( - { scheme: 'file', language: 'edk2_dsc' }, - new EdkSymbolProvider() -); -``` -This tells VS Code to call `EdkSymbolProvider` whenever the Outline/breadcrumb panel needs symbols for a `edk2_dsc` file. - ---- - -### 2. `EdkSymbolProvider` (symbolProvider.ts) - -Implements `vscode.DocumentSymbolProvider`. VS Code calls `provideDocumentSymbols()`, which: -1. Creates a parser via `ParserFactory.getParser(document)` β€” returns a `DscParser` for DSC files. -2. Calls `parser.parseFile()`. -3. Returns `parser.symbolsTree` β€” a `DocumentSymbol[]` tree. - ---- - -### 3. DSC Parser β€” `BlockParser` tree (dscParser.ts) - -The parser is defined as a hierarchy of `BlockParser` subclasses. Each one has: -- `tag`: regex that identifies lines starting this block -- `start`/`end`: optional regexes for block boundaries -- `type`: an `Edk2SymbolType` enum value -- `context`: nested child `BlockParser[]` - -For DSC, the structure looks like: - -``` -BlockDefines β†’ [Defines] / [BuildOptions] - └─ BlockDefinition (DEFINE foo = bar) -BlockComponents β†’ [Components.*] - └─ BlockComponentInf (*.inf entries) - β”œβ”€ BlocklibraryDef - └─ BlockPcd -BlockLibraryClasses β†’ [LibraryClasses.*] - └─ BlocklibraryDef -BlockIncludes β†’ !include lines -``` - ---- - -### 4. `BlockParser.parse()` (languageParser.ts) - -For each line, it: -1. Matches `tag` against the current line. -2. Calls `SymbolFactory.produceSymbol(type, ...)` to instantiate the right symbol class. -3. Calls `docParser.addSymbol(symbol)` and `pushSymbolStack(symbol)` β€” this nests the symbol under whatever is currently on the stack. -4. Recurses through `context` child parsers for inner lines, until `end` is matched. - ---- - -### 5. Symbol classes (dscSymbols.ts) - -Each DSC concept maps to a typed class with a `vscode.SymbolKind`: - -| DSC element | Class | `SymbolKind` | -|---|---|---| -| `[Defines]`, `[Components]` | `EdkSymbolDscSection` | `Class` | -| `DEFINE key = val` | `EdkSymbolDscDefine` | `Constant` | -| `LibClass\|path.inf` | `EdkSymbolDscLibraryDefinition` | `Module` | -| PCD entries | `EdkSymbolDscPcdDefinition` | `String` | -| `!include` | `EdkSymbolDscInclude` | `File` | - ---- - -### Summary - -VS Code calls `provideDocumentSymbols` β†’ `DscParser` scans the file line-by-line using a tree of regex-based `BlockParser` rules β†’ matched lines are turned into `EdkSymbol` objects (via `SymbolFactory`) and pushed onto a hierarchy stack β†’ the resulting `DocumentSymbol[]` tree (`symbolsTree`) is returned to VS Code, which renders it as the Outline view with proper nesting and icons. \ No newline at end of file diff --git a/src/newVersionPage/2.0.0.md b/src/newVersionPage/2.0.0.md new file mode 100644 index 0000000..28d784f --- /dev/null +++ b/src/newVersionPage/2.0.0.md @@ -0,0 +1,81 @@ +# EDK2Code 2.0.0 + +Thank you for installing the new version of the EDK2Code extension! πŸŽ‰ + +For detailed documentation, please visit: [EDK2Code Documentation](https://intel.github.io/Edk2Code/). + +You can find our GitHub repository here: [EDK2Code GitHub Repository](https://github.com/intel/Edk2Code). + +If you find this extension useful, please consider giving us a star on GitHub or leaving a review in the VSCode Marketplace. Your support is greatly appreciated! ⭐ + +# New features + +## Edk2Code sidebar + +The extension now lives in its own dedicated activity bar container, replacing the old `Edk2` panel under the Explorer view. You can find all Edk2Code views grouped together in the sidebar, including the new **Workspace** and **Module Info** views. + +## Workspace view + +The new **Workspace** view provides a unified, navigable representation of your parsed EDK2 workspace. It replaces the previous Module Map / Reference Tree / Library Tree commands with a single, persistent tree. + +Key capabilities: + +- **Sub-trees and includes** β€” DSC, INF, FDF and source files are organized hierarchically. Header includes and library sub-trees are integrated into the tree, so you can navigate dependencies in one place. +- **Search and filter** β€” Use the search action to quickly find nodes anywhere in the workspace tree. A dedicated filter is available to hide inactive (grayed-out) elements. +- **Reveal active editor** β€” Jump from the currently open file to its location in the workspace tree. +- **Drag and drop** β€” Reorganize and rearrange tree nodes via drag and drop. +- **Multiple workspaces** β€” When multiple build configurations are loaded, you can switch between them from the view title bar. Searching for symbols automatically switches to the workspace that owns the result. +- **Copy node path / Copy tree** β€” Quickly copy the path of a node or export the entire tree as text. +- **Welcome view** β€” When no workspace is loaded, the view guides you through discovering build folders or opening the configuration UI. + +## Module Info view + +The new **Module Info** view shows the EDK2 module information for the file you are currently editing. As soon as you open a C, INF or related source file that belongs to a module, the view populates with: + +- The owning INF and DSC declaration +- Libraries linked to the module +- Quick navigation actions (go to definition, open file, go to DSC declaration) + +Double-click any entry to jump directly to the corresponding source location. + +## Build folder auto-discovery + +EDK2Code can now scan your workspace and automatically detect existing build output folders. + +- Run **`EDK2: Discover build folders`** to scan the workspace. +- Once discovered, run **`EDK2: Use discovered build folders`** to load them directly β€” no manual configuration needed. +- The Workspace view welcome page surfaces these actions when nothing is loaded yet. + +## Compile EDK2 file + +You can now compile an individual EDK2 file (e.g. a `.c` belonging to a module) directly from the editor. + +- A play (β–Ά) icon is shown on supported source files. +- The command **`EDK2: Compile Edk2 file`** invokes the build for the parent module of the active file. + +## MCP server integration + +EDK2Code now exposes an **MCP (Model Context Protocol) SSE server** that lets AI agents and external tools query your parsed EDK2 workspace. + +- Start with **`EDK2: Start MCP SSE Server`** and stop with **`EDK2: Stop MCP SSE Server`**. +- The server port is configurable via the `edk2code.mcpServerPort` setting (default `3100`). + +## Settings UI + +A graphical **Workspace configuration (UI)** panel has been added to make managing build configurations easier. Launch it from the Workspace view title bar (`$(gear)` icon) or via the **`EDK2: Workspace configuration (UI)`** command β€” no more hand-editing JSON for common setups. + +## Goto overwriting definition + +When a symbol is overwritten in a DSC (libraries, PCDs, modules), a new code action lets you jump directly to the overwriting definition. + +## Other improvements + +- **Disable cscope** β€” A new `edk2code.useCscope` setting lets you turn off cscope integration entirely. When disabled, the cscope database is not built or queried. +- **Unload workspace** β€” New `EDK2: Unload workspace` command to clear the currently loaded build configuration. +- **Refresh workspace config** β€” Reload the workspace configuration without restarting VS Code. +- **Focus INF from C files** β€” Improved navigation from C source files back to their owning INF module. +- **Better module symbols** β€” DSC parser now recognizes module sub-context sections, build options, and improved symbols for libraries and modules. +- **Loading and discovery indicators** β€” Progress indicators are shown while the workspace is being parsed or build folders are being discovered. +- **Improved grayout controller** β€” Inactive code regions are computed and updated more reliably. +- **Path improvements** β€” Missing paths now report folders instead of file names, and tooltips include the full path. +- **Extensive test suite** β€” New parser and workspace tests covering ASL, DEC, DSC, FDF, INF and VFR. From 8959be695465f3d8ca11b3e844e77ded26e63adf Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Thu, 14 May 2026 19:01:42 -0500 Subject: [PATCH 67/73] Removing notes file --- .vscode/code-notes.json | 300 ---------------------------------------- 1 file changed, 300 deletions(-) delete mode 100644 .vscode/code-notes.json diff --git a/.vscode/code-notes.json b/.vscode/code-notes.json deleted file mode 100644 index 37c8dd0..0000000 --- a/.vscode/code-notes.json +++ /dev/null @@ -1,300 +0,0 @@ -{ - "version": 1, - "documents": [ - { - "id": "a38557d2-4221-485d-8787-cd40c34c521e", - "name": "OpenRelatedFile", - "exportOrder": [ - "aab8a899-c214-4e5f-9232-fc2ca66b3433", - "08a15e0a-6778-42ea-b66f-45feee2ad377", - "97e94d88-992e-48d2-8f77-df3110889a26", - "b11c1dc8-d908-4d71-8498-f5712a8628b7", - "22534243-2733-4147-853e-067fbb770654", - "abbe66bf-8ed9-467d-aa18-c1afe36d6ba0", - "2569daba-e70c-43a6-8cf7-5ca6f55a8707" - ], - "createdAt": "2026-04-15T22:10:18.485Z" - }, - { - "id": "195e6f57-f53b-4be5-898c-9ae963c5970e", - "name": "InactiveNodes", - "exportOrder": [ - "b6abd314-ee52-4f1d-a43a-62df3a67c43e", - "6ae6aa19-867a-4b2e-b23f-9da0b8f7deb6", - "d79a4050-27d5-4f01-9783-e0001b92f5ca", - "1a8a3b3e-f5c7-4f6c-b77f-a09d68d7dc32", - "489f90e4-6146-4d35-926a-a669b3d0416f" - ], - "createdAt": "2026-04-16T18:47:07.516Z" - }, - { - "id": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", - "name": "driverInfo", - "exportOrder": [ - "d1c71546-1cca-41e3-b6d1-207ced15e193", - "93cecdb2-bd0b-4edc-bf50-fa1e3540e9db", - "e07fbd5f-c49d-4365-8d87-6d5a6c932dbc", - "d9611b61-06b4-4165-8acd-2a6651186a7d" - ], - "createdAt": "2026-05-12T15:18:13.161Z" - } - ], - "notes": [ - { - "id": "aab8a899-c214-4e5f-9232-fc2ca66b3433", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\package.json", - "startLine": 74, - "endLine": 77, - "startChar": 0, - "endChar": 8 - }, - "language": "json", - "snippet": " {\r\n \"command\": \"edk2code.gotoInf\",\r\n \"title\": \"EDK2: Goto Inf\"\r\n },", - "noteText": "command definition\n", - "label": "command definition", - "createdAt": "2026-04-15T22:10:36.773Z" - }, - { - "id": "08a15e0a-6778-42ea-b66f-45feee2ad377", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\contextState\\cmds.ts", - "startLine": 241, - "endLine": 268, - "startChar": 4, - "endChar": 5 - }, - "language": "typescript", - "snippet": "export async function gotoInf(fileUri: Uri) {\r\n let locations: vscode.Location[] = [];\r\n\r\n let wps = await gEdkWorkspaces.getWorkspace(fileUri);\r\n if(wps.length){\r\n for (const wp of wps) {\r\n locations = await wp.getInfReference(fileUri);\r\n if(locations.length){\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n }\r\n }\r\n }else{\r\n // Skip if source hasn't been indexed. Just make a search query\r\n let fileName = path.basename(fileUri.fsPath);\r\n let folderPath = path.dirname(fileUri.fsPath);\r\n\r\n let tempLocations = await rgSearch(`\\\\b${fileName}\\\\b`, [\"*.inf\"],[],true);\r\n for (const l of tempLocations) {\r\n // Check the INF file is relative to the source file\r\n if (!path.relative(path.dirname(l.uri.fsPath), folderPath).includes(\"..\")) {\r\n locations.push(l);\r\n }\r\n }\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n\r\n }\r\n\r\n }", - "noteText": "Implementation", - "label": "Implementation", - "createdAt": "2026-04-15T22:10:57.162Z" - }, - { - "id": "97e94d88-992e-48d2-8f77-df3110889a26", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", - "startLine": 873, - "endLine": 946, - "startChar": 4, - "endChar": 5 - }, - "language": "typescript", - "snippet": "async isFileInUse(uri: vscode.Uri) {\r\n\r\n if (this.processComplete) {\r\n \r\n switch (this.getLangId(uri)) {\r\n case \"edk2_dsc\":\r\n case \"edk2_fdf\":\r\n // fdf files can be included in dsc files\r\n for (const dsc of this.filesDsc) {\r\n \r\n if(uri.fsPath === dsc.fileName) {\r\n return true;\r\n }\r\n }\r\n \r\n \r\n \r\n for (const fdf of this.filesFdf) {\r\n if(uri.fsPath === fdf.fileName){\r\n return true;\r\n }\r\n }\r\n return false;\r\n case \"edk2_dec\":\r\n break;\r\n case \"edk2_inf\":\r\n for (const mods of this.filesModules) {\r\n if(uri.fsPath.includes(mods.path)){\r\n return true;\r\n }\r\n }\r\n for (const lib of this.filesLibraries) {\r\n if(uri.fsPath.includes(lib.path)){\r\n return true;\r\n }\r\n }\r\n return false;\r\n \r\n case \"c\":\r\n let relativeInf:string[] = [];\r\n let cDirName = path.dirname(uri.fsPath);\r\n for (const mods of this.filesModules) {\r\n let modDir = path.dirname(mods.path);\r\n if(cDirName.includes(modDir)){\r\n relativeInf.push(mods.path);\r\n }\r\n }\r\n for (const lib of this.filesLibraries) {\r\n let libDir = path.dirname(lib.path);\r\n if(cDirName.includes(libDir)){\r\n relativeInf.push(lib.path);\r\n }\r\n }\r\n for (const inf of relativeInf) {\r\n let infLocation = await gPathFind.findPath(inf);\r\n \r\n if (infLocation.length === 0){continue;}\r\n\r\n let parser = await getParser(infLocation[0].uri);\r\n if (parser) {\r\n let sources = parser.getSymbolsType(Edk2SymbolType.infSource);\r\n for (const source of sources) {\r\n let sourceLocation = await gPathFind.findPath(await source.getValue(), path.dirname(infLocation[0].uri.fsPath));\r\n if(sourceLocation[0].uri.fsPath === uri.fsPath){\r\n return true;\r\n }\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n }\r\n return undefined;\r\n }", - "noteText": "This function is used to know if a file is used in the compilation", - "label": "This function is used to know if a file is used in the compi", - "createdAt": "2026-04-15T22:12:04.212Z" - }, - { - "id": "b11c1dc8-d908-4d71-8498-f5712a8628b7", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\symbols\\infSymbols.ts", - "startLine": 274, - "endLine": 275, - "startChar": 0, - "endChar": 0 - }, - "language": "typescript", - "snippet": "export class EdkSymbolInfLibrary extends EdkSymbol {\r\n", - "noteText": "Inf library symbol\n", - "label": "Inf library symbol", - "createdAt": "2026-04-15T22:14:38.943Z" - }, - { - "id": "22534243-2733-4147-853e-067fbb770654", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\edkParser\\infParser.ts", - "startLine": 149, - "endLine": 160, - "startChar": 0, - "endChar": 1 - }, - "language": "typescript", - "snippet": "class BlockLibraryClassesSection extends BlockParser {\r\n name = \"LibraryClasses\";\r\n tag = /^\\[\\s*LibraryClasses/gi;\r\n start = undefined;\r\n end = /^\\[/gi;\r\n type = Edk2SymbolType.infSectionLibraries;\r\n visible:boolean = true;\r\n context: BlockParser[] = [\r\n new BlockLibrary(),\r\n new BlockSimpleLine(),\r\n ];\r\n}", - "noteText": "Library classes in INF parser\n", - "label": "Library classes in INF parser", - "createdAt": "2026-04-15T22:15:06.418Z" - }, - { - "id": "abbe66bf-8ed9-467d-aa18-c1afe36d6ba0", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\package.json", - "startLine": 370, - "endLine": 375, - "startChar": 6, - "endChar": 10 - }, - "language": "json", - "snippet": "\"editor/context\": [\r\n {\r\n \"command\": \"edk2code.gotoDefinition\",\r\n \"group\": \"navigation\",\r\n \"when\": \"editorLangId == c\"\r\n },", - "noteText": "Example of context menu for C. The open related should be available for all files used in compilation", - "label": "Example of context menu for C. The open related should be av", - "createdAt": "2026-04-15T22:16:33.581Z" - }, - { - "id": "2569daba-e70c-43a6-8cf7-5ca6f55a8707", - "documentId": "a38557d2-4221-485d-8787-cd40c34c521e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\extension.ts", - "startLine": 249, - "endLine": 250, - "startChar": 0, - "endChar": 0 - }, - "language": "typescript", - "snippet": "\tedkWorkspaceTreeView = vscode.window.createTreeView('workspaceView', { treeDataProvider: edkWorkspaceTreeProvider, showCollapseAll: true, dragAndDropController: edkWorkspaceTreeProvider });\r\n", - "noteText": "Workspaceview", - "label": "Workspaceview", - "createdAt": "2026-04-15T22:28:15.924Z" - }, - { - "id": "b6abd314-ee52-4f1d-a43a-62df3a67c43e", - "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", - "startLine": 645, - "endLine": 646, - "startChar": 0, - "endChar": 0 - }, - "language": "typescript", - "snippet": " let isInActiveCode = this.processConditional(line, lineIndex, document.uri);\r\n", - "noteText": "Here workspace parsing decides which lines are inactive", - "label": "Here workspace parsing decides which lines are inactive", - "createdAt": "2026-04-16T18:47:21.505Z" - }, - { - "id": "6ae6aa19-867a-4b2e-b23f-9da0b8f7deb6", - "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\workspaceTree\\WorkspaceTreeProvider.ts", - "startLine": 175, - "endLine": 176, - "startChar": 0, - "endChar": 0 - }, - "language": "typescript", - "snippet": "export type WorkspaceTreeNode = WorkspaceRootItem | IncludeTreeItem | DocumentSymbolItem;\r\n", - "noteText": "Tree nodes. WorkspaceTreeNode should know if its active or not\n", - "label": "Tree nodes. WorkspaceTreeNode should know if its active or n", - "createdAt": "2026-04-16T18:49:17.125Z" - }, - { - "id": "d79a4050-27d5-4f01-9783-e0001b92f5ca", - "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\workspaceTree\\WorkspaceTreeProvider.ts", - "startLine": 10, - "endLine": 20, - "startChar": 0, - "endChar": 0 - }, - "language": "typescript", - "snippet": "export const DSC_FILTER_TYPES: { type: Edk2SymbolType; label: string; description: string }[] = [\r\n { type: Edk2SymbolType.dscDefine, label: 'Defines', description: 'dscDefine' },\r\n { type: Edk2SymbolType.dscLibraryDefinition, label: 'Library classes', description: 'dscLibraryDefinition' },\r\n { type: Edk2SymbolType.dscModuleDefinition, label: 'Components', description: 'dscModuleDefinition' },\r\n { type: Edk2SymbolType.dscSection, label: 'Sections', description: 'dscSection' },\r\n { type: Edk2SymbolType.dscBuildOptionsSection, label: 'Build options', description: 'dscBuildOptionsSection' },\r\n { type: Edk2SymbolType.dscBuildOption, label: 'Build option entries', description: 'dscBuildOption' },\r\n { type: Edk2SymbolType.dscPcdDefinition, label: 'PCDs', description: 'dscPcdDefinition' },\r\n { type: Edk2SymbolType.dscInclude, label: 'Include directives', description: 'dscInclude' },\r\n];\r\n", - "noteText": "Filters\n", - "label": "Filters", - "createdAt": "2026-04-16T18:59:06.838Z" - }, - { - "id": "1a8a3b3e-f5c7-4f6c-b77f-a09d68d7dc32", - "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", - "startLine": 705, - "endLine": 712, - "startChar": 0, - "endChar": 30 - }, - "language": "typescript", - "snippet": " DiagnosticManager.warning(\r\n document.uri,\r\n lineIndex,\r\n EdkDiagnosticCodes.duplicateDefine,\r\n `'${key}' is redefined without referencing its previous value. Use $(${key}) to extend it.`,\r\n [],\r\n relatedInfo\r\n );", - "noteText": "Warning: Duplicate defines ", - "label": "Warning: Duplicate defines", - "createdAt": "2026-04-16T19:11:30.428Z" - }, - { - "id": "489f90e4-6146-4d35-926a-a669b3d0416f", - "documentId": "195e6f57-f53b-4be5-898c-9ae963c5970e", - "location": { - "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", - "startLine": 783, - "endLine": 788, - "startChar": 26, - "endChar": 0 - }, - "language": "typescript", - "snippet": " DiagnosticManager.warning(previousLibDefinition.location.uri, previousLibDefinition.location.range.start.line,\r\n EdkDiagnosticCodes.duplicateStatement,\r\n `Library overwritten: ${libName}`,\r\n [vscode.DiagnosticTag.Unnecessary],\r\n [new vscode.DiagnosticRelatedInformation(newLibDefinition.location, \"New definition\")]);\r\n", - "noteText": "Duplicate statement warning", - "label": "Duplicate statement warning", - "createdAt": "2026-04-16T19:11:52.130Z" - }, - { - "id": "d1c71546-1cca-41e3-b6d1-207ced15e193", - "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", - "location": { - "file": "d:\\github\\Edk2Code\\src\\index\\edkWorkspace.ts", - "startLine": 276, - "endLine": 291, - "startChar": 0, - "endChar": 5 - }, - "language": "typescript", - "snippet": "\r\n async isFileInUse(uri: vscode.Uri) {\r\n let isUndefined = true;\r\n for (const workspace of this.workspaces) {\r\n let result = await workspace.isFileInUse(uri);\r\n if(result === true){\r\n return true;\r\n }\r\n if(result === false){\r\n isUndefined = false;\r\n continue;\r\n }\r\n }\r\n if(isUndefined){return undefined;}\r\n return false;\r\n }", - "noteText": "function to check if file is used ", - "label": "function to check if file is used", - "createdAt": "2026-05-12T15:35:42.644Z" - }, - { - "id": "93cecdb2-bd0b-4edc-bf50-fa1e3540e9db", - "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", - "location": { - "file": "d:\\github\\Edk2Code\\package.json", - "startLine": 249, - "endLine": 258, - "startChar": 0, - "endChar": 6 - }, - "language": "json", - "snippet": " \"views\": {\r\n \"edk2code-sidebar\": [\r\n {\r\n \"icon\": \"assets/icon.png\",\r\n \"id\": \"workspaceView\",\r\n \"name\": \"Workspace\",\r\n \"type\": \"tree\"\r\n }\r\n ]\r\n },", - "noteText": "side bar definition", - "label": "side bar definition", - "createdAt": "2026-05-12T15:36:11.681Z" - }, - { - "id": "e07fbd5f-c49d-4365-8d87-6d5a6c932dbc", - "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", - "location": { - "file": "d:\\github\\Edk2Code\\src\\contextState\\cmds.ts", - "startLine": 241, - "endLine": 269, - "startChar": 4, - "endChar": 5 - }, - "language": "typescript", - "snippet": "export async function gotoInf(fileUri: Uri) {\r\n let locations: vscode.Location[] = [];\r\n\r\n let wps = await gEdkWorkspaces.getWorkspace(fileUri);\r\n if(wps.length){\r\n for (const wp of wps) {\r\n locations = await wp.getInfReference(fileUri);\r\n if(locations.length){\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n await edkWorkspaceTreeProvider.revealInfInTree(locations[0].uri, edkWorkspaceTreeView);\r\n }\r\n }\r\n }else{\r\n // Skip if source hasn't been indexed. Just make a search query\r\n let fileName = path.basename(fileUri.fsPath);\r\n let folderPath = path.dirname(fileUri.fsPath);\r\n\r\n let tempLocations = await rgSearch(`\\\\b${fileName}\\\\b`, [\"*.inf\"],[],true);\r\n for (const l of tempLocations) {\r\n // Check the INF file is relative to the source file\r\n if (!path.relative(path.dirname(l.uri.fsPath), folderPath).includes(\"..\")) {\r\n locations.push(l);\r\n }\r\n }\r\n await vscode.commands.executeCommand('editor.action.goToLocations', vscode.window.activeTextEditor?.document.uri, vscode.window.activeTextEditor?.selection.active, locations, \"gotoAndPeek\", \"Not found\");\r\n\r\n }\r\n\r\n }", - "noteText": "Function to go to .inf file of selected file", - "label": "Function to go to .inf file of selected file", - "createdAt": "2026-05-12T15:37:30.107Z" - }, - { - "id": "d9611b61-06b4-4165-8acd-2a6651186a7d", - "documentId": "10dbe163-5e51-4ba7-bdfc-b41aa754dd77", - "location": { - "file": "d:\\github\\Edk2Code\\src\\symbols\\infSymbols.ts", - "startLine": 285, - "endLine": 323, - "startChar": 4, - "endChar": 6 - }, - "language": "typescript", - "snippet": "onDefinition = async (parser:DocumentParser)=>{\r\n let libName = this.textLine.replace(/\\s*\\|.*/, \"\");\r\n\r\n\r\n let wps = await gEdkWorkspaces.getWorkspace(this.location.uri);\r\n if(wps.length){\r\n\r\n\r\n // Check if this is a library\r\n let isLibrary = await isFileEdkLibrary(this.location.uri);\r\n\r\n if(isLibrary){\r\n let libDeclarationLocations:vscode.Location[] = [];\r\n for (const wp of wps) {\r\n libDeclarationLocations = libDeclarationLocations.concat(await wp.getLibDefinition(libName));\r\n }\r\n return libDeclarationLocations;\r\n }else{\r\n // Modules\r\n let dscLibDeclarations:InfDsc[] = [];\r\n for (const wp of wps) {\r\n dscLibDeclarations = dscLibDeclarations.concat(await wp.getLibDeclarationModule(this.location.uri, libName));\r\n }\r\n let libDeclarationLocations:vscode.Location[] = [];\r\n for (const declaration of dscLibDeclarations) {\r\n let location = await gPathFind.findPath(declaration.path);\r\n if(location.length){\r\n libDeclarationLocations.push(location[0]);\r\n }\r\n }\r\n return libDeclarationLocations;\r\n }\r\n\r\n }else{\r\n let locations = await rgSearch(`--regexp \"LIBRARY_CLASS\\\\s*=\\\\s*\\\\b${libName}\\\\b\"`,[\"*.inf\"]);\r\n return locations;\r\n }\r\n\r\n };", - "noteText": "example of Go to library definition function", - "label": "example of Go to library definition function", - "createdAt": "2026-05-12T15:40:17.768Z" - } - ] -} \ No newline at end of file From 7d1c59714419ab3cd0fbf8d1814041ea65eafd6c Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Tue, 19 May 2026 10:05:32 -0500 Subject: [PATCH 68/73] Adding edk module build --- package.json | 85 +++ src/buildEdk2.ts | 603 ++++++++++++++++++ src/configuration.ts | 34 +- src/contextState/cmds.ts | 67 ++ src/extension.ts | 2 + src/index/edkWorkspace.ts | 5 +- src/workspaceTree/WorkspaceTreeProvider.ts | 5 +- static/buildForm.html | 677 +++++++++++++++++++++ 8 files changed, 1470 insertions(+), 8 deletions(-) create mode 100644 src/buildEdk2.ts create mode 100644 static/buildForm.html diff --git a/package.json b/package.json index 9795046..0ad1e6f 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,16 @@ "command": "edk2code.driverInfoGotoDsc", "title": "EDK2: Go to DSC declaration", "icon": "$(references)" + }, + { + "command": "edk2code.buildEdk2Workspace", + "title": "EDK2: Build workspace", + "icon": "$(tools)" + }, + { + "command": "edk2code.buildFromTree", + "title": "EDK2: Build", + "icon": "$(tools)" } ], "configuration": [ @@ -262,6 +272,58 @@ "items": { "type": "string" } + }, + "edk2code.buildToolchain": { + "order": 4, + "type": "string", + "markdownDescription": "EDK2 toolchain tag (`TOOL_CHAIN_TAG`) to use for building.", + "default": "VS2022", + "enum": [ + "GCC5", + "GCC49", + "GCC48", + "VS2022", + "VS2019", + "VS2017", + "CLANGPDB", + "CLANGDWARF", + "CLANGGCC", + "XCODE5" + ] + }, + "edk2code.buildArch": { + "order": 4, + "type": "string", + "markdownDescription": "Target architecture for EDK2 build.", + "default": "X64", + "enum": [ + "X64", + "IA32", + "AARCH64", + "ARM", + "RISCV64", + "LOONGARCH64" + ] + }, + "edk2code.buildTarget": { + "order": 4, + "type": "string", + "markdownDescription": "Build target type.", + "default": "DEBUG", + "enum": [ + "DEBUG", + "RELEASE", + "NOOPT" + ] + }, + "edk2code.buildExtraArgs": { + "order": 4, + "type": "array", + "markdownDescription": "Extra arguments appended verbatim to the `build` command.", + "default": [], + "items": { + "type": "string" + } } } } @@ -422,6 +484,14 @@ "command": "edk2code.stopMcpServer", "when": "true" }, + { + "command": "edk2code.buildEdk2Workspace", + "when": "edk2code.parseComplete" + }, + { + "command": "edk2code.buildFromTree", + "when": "false" + }, { "command": "edk2code.gotoOverwrite", "when": "false" @@ -564,6 +634,21 @@ "command": "edk2code.compileCFile", "group": "inline", "when": "view == driverInfoView && viewItem == driverInfoCompilableSource" + }, + { + "command": "edk2code.buildFromTree", + "group": "inline", + "when": "view == workspaceView && viewItem == workspaceRoot" + }, + { + "command": "edk2code.buildFromTree", + "group": "inline", + "when": "view == workspaceView && viewItem == symbolNodeBuildable" + }, + { + "command": "edk2code.copyWorkspaceNodePath", + "group": "navigation", + "when": "view == workspaceView && viewItem == symbolNodeBuildable" } ] }, diff --git a/src/buildEdk2.ts b/src/buildEdk2.ts new file mode 100644 index 0000000..db5b3fa --- /dev/null +++ b/src/buildEdk2.ts @@ -0,0 +1,603 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import { gConfigAgent, gDebugLog, gWorkspacePath } from './extension'; +import { readEdkCodeFolderFile, writeEdkCodeFolderFile, deleteEdkCodeFolderFile, getEdkCodeFolderFilePath } from './edk2CodeFolder'; + +let buildFormPanel: vscode.WebviewPanel | undefined; + +const BUILD_CONFIG_FILE = 'edk2_build_configuration.json'; +const GLOBAL_CONFIG_KEY = '__global__'; + +export interface BuildInvocation { + /** Absolute or workspace-relative path to the DSC to build. */ + dscPath?: string; + /** Absolute or workspace-relative path to an INF to build with `-m`. */ + modulePath?: string; +} + +/** + * Global build configuration stored in .edkCode/edk2_build_configuration.json. + * Shared across all module builds. Extendable for future global settings. + */ +interface GlobalBuildConfig { + /** EDK2 workspace root containing edksetup.bat/sh. Empty = use VS Code workspace path. */ + edkSetupPath: string; + /** NASM assembler path prefix. Exported as NASM_PREFIX. */ + nasmPrefix: string; + /** IASL compiler path prefix. Exported as IASL_PREFIX. */ + iaslPrefix: string; + /** Path to EDK2 BaseTools binaries. Exported as EDK_TOOLS_BIN. */ + edkToolsBin: string; +} + +const DEFAULT_GLOBAL_CONFIG: GlobalBuildConfig = { + edkSetupPath: '', + nasmPrefix: '', + iaslPrefix: '', + edkToolsBin: '' +}; + +interface DefineEntry { + name: string; + value: string; +} + +interface BuildFormState { + platform: string; + module: string; + arch: string; + target: string; + toolchain: string; + action: string; + skuid: string; + threads: string; + fdfFile: string; + romImage: string; + fvImage: string; + capsuleImage: string; + skipAutogen: boolean; + reParse: boolean; + caseInsensitive: boolean; + warningAsError: boolean; + logFile: string; + silent: boolean; + quiet: boolean; + verbose: boolean; + debug: string; + defines: DefineEntry[]; + reportFile: string; + reportType: string[]; + flag: string; + noCache: boolean; + confDir: string; + checkUsage: boolean; + ignoreSources: boolean; + pcds: string[]; + cmdLen: string; + hash: boolean; + binaryDestination: string; + binarySource: string; + genfdsMultiThread: boolean; + noGenfdsMultiThread: boolean; + disableIncludePathCheck: boolean; + extraArgs: string[]; + packagePaths: string[]; + /** DSC choices to populate the dropdown (not persisted). */ + dscPaths: string[]; +} + +/** Key used to store/load per-module config in the JSON file. */ +function configKey(state: { platform: string; module: string }): string { + return state.module || state.platform || '__default__'; +} + +function loadAllConfig(): Record { + const raw = readEdkCodeFolderFile(BUILD_CONFIG_FILE); + if (!raw) { return {}; } + try { return JSON.parse(raw); } catch { return {}; } +} + +function saveAllConfig(all: Record): void { + writeEdkCodeFolderFile(BUILD_CONFIG_FILE, JSON.stringify(all, null, 2)); +} + +function loadGlobalConfig(): GlobalBuildConfig { + const all = loadAllConfig(); + return { ...DEFAULT_GLOBAL_CONFIG, ...(all[GLOBAL_CONFIG_KEY] ?? {}) }; +} + +function saveGlobalConfig(global: GlobalBuildConfig): void { + const all = loadAllConfig(); + all[GLOBAL_CONFIG_KEY] = global; + saveAllConfig(all); +} + +function loadSavedConfig(key: string): Partial | undefined { + const all = loadAllConfig(); + return all[key] ?? undefined; +} + +function saveConfig(key: string, state: BuildFormState): void { + const all = loadAllConfig(); + // Don't persist the dscPaths list β€” it's runtime only + const { dscPaths, ...toSave } = state; + all[key] = toSave; + saveAllConfig(all); +} + +function deleteSavedConfig(key: string): void { + const all = loadAllConfig(); + delete all[key]; + // Keep at least global config + if (Object.keys(all).length === 0 || (Object.keys(all).length === 1 && all[GLOBAL_CONFIG_KEY])) { + if (Object.keys(all).length === 0) { + deleteEdkCodeFolderFile(BUILD_CONFIG_FILE); + } else { + saveAllConfig(all); + } + } else { + saveAllConfig(all); + } +} + +/** + * Public entry point for the EDK2 build command. + * Always opens the build configuration form (webview) first, then runs + * the build in a terminal when the user clicks "Build". + */ +export async function buildEdk2Workspace(options?: BuildInvocation) { + const isWindows = process.platform === 'win32'; + + // Load global config (edkSetupPath, nasmPrefix, etc.) + const globalConfig = loadGlobalConfig(); + + // Get DSC paths + const dscPaths = gConfigAgent.getBuildDscPaths(); + if (!dscPaths || dscPaths.length === 0) { + void vscode.window.showErrorMessage('No DSC paths configured. Please configure DSC paths first.'); + return; + } + + // Determine pre-selected DSC + let selectedDsc: string | undefined = options?.dscPath; + if (!selectedDsc) { + if (dscPaths.length === 1) { + selectedDsc = dscPaths[0]; + } else { + const picked = await vscode.window.showQuickPick(dscPaths, { + placeHolder: 'Select DSC file to build', + title: 'EDK2 Build' + }); + if (!picked) { return; } + selectedDsc = picked; + } + } + + // Build defaults from settings + const defaultState: BuildFormState = { + platform: selectedDsc, + module: options?.modulePath ?? '', + arch: gConfigAgent.getBuildArch(), + target: gConfigAgent.getBuildTarget(), + toolchain: gConfigAgent.getBuildToolchain(), + action: '', + skuid: '', + threads: '', + fdfFile: '', + romImage: '', + fvImage: '', + capsuleImage: '', + skipAutogen: false, + reParse: false, + caseInsensitive: false, + warningAsError: false, + logFile: '', + silent: false, + quiet: false, + verbose: false, + debug: '', + defines: Array.from(gConfigAgent.getBuildDefines().entries()).map(([k, v]) => ({ name: k, value: v })), + reportFile: '', + reportType: [], + flag: '', + noCache: false, + confDir: '', + checkUsage: false, + ignoreSources: false, + pcds: [], + cmdLen: '', + hash: false, + binaryDestination: '', + binarySource: '', + genfdsMultiThread: false, + noGenfdsMultiThread: false, + disableIncludePathCheck: false, + extraArgs: gConfigAgent.getBuildExtraArgs().filter(a => a.trim()), + packagePaths: gConfigAgent.getBuildPackagePaths().filter(p => p.trim()), + dscPaths: dscPaths + }; + + // Load saved config if available + const key = configKey(defaultState); + const saved = loadSavedConfig(key); + const initial: BuildFormState = saved + ? { ...defaultState, ...saved, dscPaths } + : defaultState; + + // Open the form (stays open; handles build internally) + showBuildForm(initial, defaultState, globalConfig, isWindows); +} + +/** Validation error with associated field ID for highlighting in the form. */ +interface ValidationError { + field: string; + message: string; +} + +/** Resolve a config path: if relative, resolve against workspace root; if absolute, use as-is. */ +function resolveConfigPath(p: string): string { + if (!p) { return ''; } + if (path.isAbsolute(p)) { return p; } + return path.join(gWorkspacePath, p); +} + +/** Validate global config paths. Returns an array of validation errors (empty if all OK). */ +function validateGlobalConfig(config: GlobalBuildConfig): ValidationError[] { + const errors: ValidationError[] = []; + + // EDK Setup Path: must point to edksetup.bat or edksetup.sh + const rawSetup = config.edkSetupPath.trim(); + if (rawSetup) { + const setupPath = resolveConfigPath(rawSetup); + if (!fs.existsSync(setupPath)) { + errors.push({ field: 'edkSetupPath', message: `EDK2 setup script not found: ${setupPath}` }); + } else if (fs.statSync(setupPath).isDirectory()) { + errors.push({ field: 'edkSetupPath', message: `Expected a file (edksetup.bat/.sh), got a directory: ${setupPath}` }); + } + } else { + // Default: look for edksetup in workspace root + const isWindows = process.platform === 'win32'; + const defaultScript = path.join(gWorkspacePath, isWindows ? 'edksetup.bat' : 'edksetup.sh'); + if (!fs.existsSync(defaultScript)) { + errors.push({ field: 'edkSetupPath', message: `EDK2 setup script not found in workspace: ${defaultScript}` }); + } + } + + // NASM Prefix: must point to nasm executable + const rawNasm = config.nasmPrefix.trim(); + if (rawNasm) { + const nasmPath = resolveConfigPath(rawNasm); + if (!fs.existsSync(nasmPath)) { + errors.push({ field: 'nasmPrefix', message: `NASM executable not found: ${nasmPath}` }); + } else if (fs.statSync(nasmPath).isDirectory()) { + errors.push({ field: 'nasmPrefix', message: `Expected a file (nasm executable), got a directory: ${nasmPath}` }); + } + } else if (!process.env['NASM_PREFIX']) { + errors.push({ field: 'nasmPrefix', message: `NASM path is not configured and NASM_PREFIX is not set in environment` }); + } + + // IASL Prefix: must point to iasl executable + const rawIasl = config.iaslPrefix.trim(); + if (rawIasl) { + const iaslPath = resolveConfigPath(rawIasl); + if (!fs.existsSync(iaslPath)) { + errors.push({ field: 'iaslPrefix', message: `IASL executable not found: ${iaslPath}` }); + } else if (fs.statSync(iaslPath).isDirectory()) { + errors.push({ field: 'iaslPrefix', message: `Expected a file (iasl executable), got a directory: ${iaslPath}` }); + } + } + + // EDK Tools Bin: must be an existing directory + const rawToolsBin = config.edkToolsBin.trim(); + if (rawToolsBin) { + const toolsBinPath = resolveConfigPath(rawToolsBin); + if (!fs.existsSync(toolsBinPath)) { + errors.push({ field: 'edkToolsBin', message: `EDK_TOOLS_BIN directory not found: ${toolsBinPath}` }); + } else if (!fs.statSync(toolsBinPath).isDirectory()) { + errors.push({ field: 'edkToolsBin', message: `Expected a directory, got a file: ${toolsBinPath}` }); + } + } + + return errors; +} + +/** Internal: assemble the command line from form state and execute it in a terminal. */ +async function runBuild(state: BuildFormState, edkRoot: string, isWindows: boolean, globalConfig: GlobalBuildConfig) { + // Derive NASM_PREFIX: directory of the nasm executable, ending with separator + const rawNasm = (globalConfig.nasmPrefix || '').trim(); + const nasmPrefix = rawNasm ? path.dirname(resolveConfigPath(rawNasm)) + path.sep : ''; + + // Derive IASL_PREFIX: directory of the iasl executable, ending with separator + const rawIasl = (globalConfig.iaslPrefix || '').trim(); + const iaslPrefix = rawIasl ? path.dirname(resolveConfigPath(rawIasl)) + path.sep : ''; + + // EDK_TOOLS_BIN: resolve as-is (already validated as a directory) + const edkToolsBin = (globalConfig.edkToolsBin || '').trim() + ? resolveConfigPath(globalConfig.edkToolsBin.trim()) : ''; + + // Derive setup script name and edkRoot directory from edkSetupPath + const rawSetup = (globalConfig.edkSetupPath || '').trim(); + let setupScript: string; + if (rawSetup) { + const resolvedSetup = resolveConfigPath(rawSetup); + edkRoot = path.dirname(resolvedSetup); + setupScript = path.basename(resolvedSetup); + } else { + setupScript = isWindows ? 'edksetup.bat' : 'edksetup.sh'; + } + + // Package paths + const packagePaths = (state.packagePaths || []).map(s => s.trim()).filter(s => s.length > 0); + + /** + * Convert an absolute INF/DSC path into a path that EDK2 `build` can resolve. + */ + const toEdkRelative = (p: string): string => { + if (!p) { return p; } + if (!path.isAbsolute(p)) { return p; } + const roots = [edkRoot, ...packagePaths]; + for (const root of roots) { + if (!root) { continue; } + const rel = path.relative(root, p); + if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) { + return rel; + } + } + return p; + }; + + const platformArg = toEdkRelative(state.platform); + const moduleArg = toEdkRelative(state.module); + + // Compute the effective workspace root (WORKSPACE env variable / CWD for edksetup). + // EDK2 resolves relative -p paths as WORKSPACE/path. We find where the platform DSC + // actually lives and derive the workspace root from that. + let workspaceRoot = edkRoot; + if (platformArg && !path.isAbsolute(platformArg)) { + const candidates = new Set(); + candidates.add(edkRoot); + for (const p of packagePaths) { + if (path.isAbsolute(p)) { + candidates.add(p); + candidates.add(path.dirname(p)); + } + } + for (const candidate of candidates) { + if (fs.existsSync(path.join(candidate, platformArg))) { + workspaceRoot = candidate; + break; + } + } + } + + // If workspaceRoot differs from edkRoot, use full path for the setup script + const setupScriptCall = (workspaceRoot !== edkRoot) + ? path.join(edkRoot, setupScript) + : setupScript; + + // Compose build args + const args: string[] = []; + if (platformArg) { args.push(`-p ${platformArg}`); } + if (moduleArg) { args.push(`-m ${moduleArg}`); } + if (state.arch) { + // Support multiple architectures separated by space or comma (e.g. "IA32 X64") + const archs = state.arch.split(/[\s,]+/).filter(Boolean); + for (const a of archs) { args.push(`-a ${a}`); } + } + if (state.target) { args.push(`-b ${state.target}`); } + if (state.toolchain) { args.push(`-t ${state.toolchain}`); } + if (state.skuid) { args.push(`-x ${state.skuid}`); } + if (state.threads) { args.push(`-n ${state.threads}`); } + if (state.fdfFile) { args.push(`-f ${state.fdfFile}`); } + if (state.romImage) { args.push(`-r ${state.romImage}`); } + if (state.fvImage) { args.push(`-i ${state.fvImage}`); } + if (state.capsuleImage) { args.push(`-C ${state.capsuleImage}`); } + if (state.skipAutogen) { args.push(`-u`); } + if (state.reParse) { args.push(`-e`); } + if (state.caseInsensitive) { args.push(`-c`); } + if (state.warningAsError) { args.push(`-w`); } + if (state.logFile) { args.push(`-j ${state.logFile}`); } + if (state.silent) { args.push(`-s`); } + if (state.quiet) { args.push(`-q`); } + if (state.verbose) { args.push(`-v`); } + if (state.debug) { args.push(`-d ${state.debug}`); } + + // Defines: array of {name, value} + for (const def of (state.defines || [])) { + if (def.name) { + const val = def.value.includes(' ') ? `"${def.value}"` : def.value; + args.push(`-D ${def.name}=${val}`); + } + } + + if (state.reportFile) { args.push(`-y ${state.reportFile}`); } + for (const rt of state.reportType || []) { + if (rt) { args.push(`-Y ${rt}`); } + } + if (state.flag) { args.push(`-F ${state.flag}`); } + if (state.noCache) { args.push(`-N`); } + if (state.confDir) { args.push(`--conf=${state.confDir}`); } + if (state.checkUsage) { args.push(`--check-usage`); } + if (state.ignoreSources) { args.push(`--ignore-sources`); } + + // PCDs: array of strings + for (const pcd of (state.pcds || [])) { + const trimmed = pcd.trim(); + if (trimmed) { args.push(`--pcd=${trimmed}`); } + } + + if (state.cmdLen) { args.push(`-l ${state.cmdLen}`); } + if (state.hash) { args.push(`--hash`); } + if (state.binaryDestination) { args.push(`--binary-destination=${state.binaryDestination}`); } + if (state.binarySource) { args.push(`--binary-source=${state.binarySource}`); } + if (state.genfdsMultiThread) { args.push(`--genfds-multi-thread`); } + if (state.noGenfdsMultiThread) { args.push(`--no-genfds-multi-thread`); } + if (state.disableIncludePathCheck) { args.push(`--disable-include-path-check`); } + + // Extra args: array of strings + for (const a of (state.extraArgs || [])) { + const trimmed = a.trim(); + if (trimmed) { args.push(trimmed); } + } + + // Trailing positional action keyword + if (state.action) { args.push(state.action); } + + const buildArgsStr = args.join(' '); + + let cmd: string; + if (isWindows) { + // On Windows, `set "VAR=value"` already preserves spaces β€” no inner quoting needed + const pkgPath = packagePaths.join(';'); + const parts: string[] = []; + parts.push(`set "WORKSPACE=${workspaceRoot}"`); + parts.push(`set "PACKAGES_PATH=${pkgPath}"`); + if (nasmPrefix) { parts.push(`set "NASM_PREFIX=${nasmPrefix}"`); } + if (iaslPrefix) { parts.push(`set "IASL_PREFIX=${iaslPrefix}"`); } + if (edkToolsBin) { parts.push(`set "EDK_TOOLS_BIN=${edkToolsBin}"`); } + parts.push(`call ${setupScriptCall}`); + parts.push(`build ${buildArgsStr}`); + cmd = parts.join(' && '); + } else { + // On Linux, the outer double-quotes in export protect spaces + const pkgPath = packagePaths.join(':'); + const parts: string[] = []; + parts.push(`export WORKSPACE="${workspaceRoot}"`); + parts.push(`export PACKAGES_PATH="${pkgPath}"`); + if (nasmPrefix) { parts.push(`export NASM_PREFIX="${nasmPrefix}"`); } + if (iaslPrefix) { parts.push(`export IASL_PREFIX="${iaslPrefix}"`); } + if (edkToolsBin) { parts.push(`export EDK_TOOLS_BIN="${edkToolsBin}"`); } + parts.push(`. ${setupScriptCall}`); + parts.push(`build ${buildArgsStr}`); + cmd = parts.join(' && '); + } + + gDebugLog.info(`EDK2 Build command: ${cmd}`); + + // Use a VS Code Task so we get proper process lifecycle tracking + const shellExec = isWindows + ? new vscode.ShellExecution(cmd, { cwd: workspaceRoot }) + : new vscode.ShellExecution(cmd, { cwd: workspaceRoot }); + + const taskDef: vscode.TaskDefinition = { type: 'edk2build' }; + const task = new vscode.Task( + taskDef, + vscode.TaskScope.Workspace, + 'EDK2 Build', + 'edk2code', + shellExec + ); + task.presentationOptions = { + reveal: vscode.TaskRevealKind.Always, + panel: vscode.TaskPanelKind.Shared, + clear: true + }; + + const execution = await vscode.tasks.executeTask(task); + + // Wait for the task process to end + return new Promise((resolve) => { + const disposable = vscode.tasks.onDidEndTaskProcess((e) => { + if (e.execution === execution) { + disposable.dispose(); + resolve(); + } + }); + }); +} + +// ──────────────────────────────────────────────────────────────────────────── +// Build configuration form (webview) +// ──────────────────────────────────────────────────────────────────────────── + +function getFormHtml(extensionPath: string): string { + const htmlPath = path.join(extensionPath, 'static', 'buildForm.html'); + return fs.readFileSync(htmlPath, 'utf8'); +} + +function showBuildForm(initial: BuildFormState, defaults: BuildFormState, globalConfig: GlobalBuildConfig, isWindows: boolean): void { + if (buildFormPanel) { + // If already open, just reveal and re-init + buildFormPanel.reveal(); + buildFormPanel.webview.postMessage({ command: 'init', state: initial, globalConfig }); + return; + } + + // Get extension path for loading HTML + const ext = vscode.extensions.getExtension('intel-corporation.edk2code'); + const extensionPath = ext?.extensionPath ?? path.join(__dirname, '..'); + + const panel = vscode.window.createWebviewPanel( + 'edk2code.buildForm', + 'EDK2 Build Configuration', + vscode.ViewColumn.Active, + { enableScripts: true, retainContextWhenHidden: true } + ); + buildFormPanel = panel; + + panel.onDidDispose(() => { + buildFormPanel = undefined; + }); + + panel.webview.onDidReceiveMessage(async (msg: any) => { + if (!msg) { return; } + if (msg.command === 'ready') { + // Send initial state + global config to webview + panel.webview.postMessage({ command: 'init', state: initial, globalConfig }); + } else if (msg.command === 'build') { + const state = msg.state as BuildFormState; + const formGlobal = msg.globalConfig as GlobalBuildConfig; + + // Save global config + if (formGlobal) { + saveGlobalConfig(formGlobal); + } + + // Persist the submitted state + saveConfig(configKey(state), state); + + // Re-read global config + const updatedGlobal = loadGlobalConfig(); + + // Validate + const errors = validateGlobalConfig(updatedGlobal); + if (errors.length > 0) { + // Send validation errors to webview for highlighting + panel.webview.postMessage({ command: 'validationErrors', errors }); + return; + } + + // Disable form during build + panel.webview.postMessage({ command: 'buildStarted' }); + + // Derive edkRoot from setup script path (runBuild will also do this, but we need it for cwd) + const rawSetup = updatedGlobal.edkSetupPath.trim(); + let finalEdkRoot: string; + if (rawSetup) { + finalEdkRoot = path.dirname(resolveConfigPath(rawSetup)); + } else { + finalEdkRoot = process.env['WORKSPACE'] || gWorkspacePath; + } + + await runBuild(state, finalEdkRoot, isWindows, updatedGlobal); + + // Re-enable form after build command is sent + panel.webview.postMessage({ command: 'buildFinished' }); + } else if (msg.command === 'cancel') { + panel.dispose(); + } else if (msg.command === 'openConfig') { + const filePath = getEdkCodeFolderFilePath(BUILD_CONFIG_FILE); + const uri = vscode.Uri.file(filePath); + vscode.commands.executeCommand('vscode.open', uri); + } else if (msg.command === 'reset') { + // Delete saved config and re-initialize with defaults + deleteSavedConfig(configKey(initial)); + panel.webview.postMessage({ command: 'init', state: defaults, globalConfig: DEFAULT_GLOBAL_CONFIG }); + } + }); + + panel.webview.html = getFormHtml(extensionPath); +} + + diff --git a/src/configuration.ts b/src/configuration.ts index b0e0e78..c46e577 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -63,7 +63,7 @@ export class ConfigAgent { } - reloadVscodeSettings(){ + reloadVscodeSettings(event?: vscode.ConfigurationChangeEvent){ this.vscodeSettings = vscode.workspace.getConfiguration('edk2code'); } @@ -252,7 +252,8 @@ export class ConfigAgent { let toSave: string[] = []; for (const [key, value] of defines.entries()) { - toSave.push(`${key.trim()}=${value.trim()}`); + // Normalize double backslashes to single to avoid over-escaping in JSON + toSave.push(`${key.trim()}=${value.trim().replace(/\\\\/g, '\\')}`); } this.workspaceConfig.buildDefines = toSave; this.writeWorkspaceConfig(this.workspaceConfig); @@ -267,7 +268,8 @@ export class ConfigAgent { toSave.push(def); } } - toSave.push(`${name}=${value.trim()}`); + // Normalize double backslashes to single to avoid over-escaping in JSON + toSave.push(`${name}=${value.trim().replace(/\\\\/g, '\\')}`); this.workspaceConfig.buildDefines = toSave; this.writeWorkspaceConfig(this.workspaceConfig); } @@ -294,7 +296,11 @@ export class ConfigAgent { let paths = this.workspaceConfig.packagePaths; let retPaths = []; for (const p of paths) { - retPaths.push(path.join(gWorkspacePath, p)); + if (path.isAbsolute(p)) { + retPaths.push(p); + } else { + retPaths.push(path.join(gWorkspacePath, p)); + } } return retPaths; } @@ -304,6 +310,10 @@ export class ConfigAgent { if(this.workspaceConfig.packagePaths.includes(path)){ return; } + // EDK2 build system does not allow spaces in PACKAGES_PATH entries + if(path.includes(' ')){ + return; + } this.workspaceConfig.packagePaths.push(path); this.writeWorkspaceConfig(this.workspaceConfig); } @@ -355,6 +365,22 @@ export class ConfigAgent { return (this.get("cscopeOverwritePath")).trim(); } + getBuildToolchain(): string { + return this.get("buildToolchain") || (process.platform === 'win32' ? "VS2022" : "GCC5"); + } + + getBuildArch(): string { + return this.get("buildArch") || "X64"; + } + + getBuildTarget(): string { + return this.get("buildTarget") || "DEBUG"; + } + + getBuildExtraArgs(): string[] { + return this.get("buildExtraArgs") || []; + } + getIsGenGuidXrefFile() { return this.get("generateGuidXref"); } diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index 6dee285..ac0c2b1 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -15,6 +15,73 @@ import { SettingsPanel } from "../settings/settingsPanel"; import { deleteEdkCodeFolder, existsEdkCodeFolderFile } from "../edk2CodeFolder"; import { infoMissingCompileInfo } from "../ui/messages"; import { checkCppConfiguration } from "../cppProviders/cppUtils"; +import { buildEdk2Workspace as buildEdk2WorkspaceImpl } from "../buildEdk2"; +import { DocumentSymbolItem, WorkspaceRootItem, WorkspaceTreeNode } from "../workspaceTree/WorkspaceTreeProvider"; + + export async function buildEdk2Workspace() { + await buildEdk2WorkspaceImpl(); + } + + /** + * Build command launched from the workspace tree. + * - On a WorkspaceRootItem: builds the whole DSC. + * - On a DocumentSymbolItem (library/module): builds just that module via `-m`. + */ + export async function buildFromTree(node: WorkspaceTreeNode | undefined) { + if (!node) { + await buildEdk2WorkspaceImpl(); + return; + } + // Keep the node selected so the user knows which module will be built + edkWorkspaceTreeView.reveal(node, { select: true, focus: false }); + if (node instanceof WorkspaceRootItem) { + await buildEdk2WorkspaceImpl({ dscPath: node.workspace.mainDsc.fsPath }); + return; + } + if (node instanceof DocumentSymbolItem) { + // Walk up parent chain to find the owning WorkspaceRootItem (DSC). + let cur: WorkspaceRootItem | DocumentSymbolItem | undefined = node.parent; + while (cur && !(cur instanceof WorkspaceRootItem)) { + cur = (cur as DocumentSymbolItem).parent; + } + const dscPath = cur instanceof WorkspaceRootItem ? cur.workspace.mainDsc.fsPath : undefined; + + // Extract the INF path directly from the DSC text line. + // textLine already resolves parser-level defines; we also call + // gEdkWorkspaces.replaceDefines() for workspace-level variables + // like $(SOME_VARIABLE) that might still be present. + let modulePath: string | undefined; + try { + const sym: any = node.symbol; + + if (sym.type === Edk2SymbolType.dscLibraryDefinition) { + void vscode.window.showWarningMessage( + 'Libraries cannot be built standalone with `build -m`. Only components (modules) listed in [Components] can be built individually.' + ); + return; + } + + const textLine: string = sym.textLine || ''; + if (sym.type === Edk2SymbolType.dscModuleDefinition) { + // Format: "Path/To/Module.inf" possibly followed by " { ... }" + const rawPath = textLine.replace(/\s*\{.*/, '').trim(); + if (rawPath) { + modulePath = await gEdkWorkspaces.replaceDefines(node.fileUri, rawPath); + } + } + } catch { + // ignore + } + if (!modulePath) { + void vscode.window.showErrorMessage('Could not resolve INF path for selected node.'); + return; + } + await buildEdk2WorkspaceImpl({ dscPath, modulePath }); + return; + } + // Unknown node type – fall back to generic build. + await buildEdk2WorkspaceImpl(); + } let discoveredBuildFolders: string[] = []; let buildFolderScanTimer: NodeJS.Timeout | undefined; diff --git a/src/extension.ts b/src/extension.ts index 6b0412f..0d09eb6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -100,6 +100,8 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('edk2code.gotoInf',async (fileUri)=>{await cmds.gotoInf(fileUri);}), vscode.commands.registerCommand('edk2code.dscUsage', async (fileUri)=>{await cmds.gotoDscDeclaration(fileUri);}), vscode.commands.registerCommand('edk2code.dscInclusion', async (fileUri)=>{await cmds.gotoDscInclusion(fileUri);}), + vscode.commands.registerCommand('edk2code.buildEdk2Workspace', async ()=>{await cmds.buildEdk2Workspace();}), + vscode.commands.registerCommand('edk2code.buildFromTree', async (node)=>{await cmds.buildFromTree(node);}), // Internal vscode.commands.registerCommand('edk2code.searchDefinition', ()=>{}), diff --git a/src/index/edkWorkspace.ts b/src/index/edkWorkspace.ts index 4af5858..301e6e3 100644 --- a/src/index/edkWorkspace.ts +++ b/src/index/edkWorkspace.ts @@ -294,8 +294,7 @@ async getWorkspace(uri: vscode.Uri): Promise { async loadConfig() { this.workspaces = []; gDebugLog.trace("Loading Configuration"); - // TODO: enable to get more commands available - //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', false); + await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', false); await vscode.commands.executeCommand('setContext', 'edk2code.isLoading', true); // If the previous workspace processing did not complete (e.g. @@ -317,7 +316,7 @@ async getWorkspace(uri: vscode.Uri): Promise { // Workspace processing is complete – mark flag and dispose // of the temporary T-tree so PathFind reverts to findFiles. gConfigAgent.setWorkspaceProcessComplete(); - //await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', true); + await vscode.commands.executeCommand('setContext', 'edk2code.parseComplete', true); await vscode.commands.executeCommand('setContext', 'edk2code.isLoading', false); } } diff --git a/src/workspaceTree/WorkspaceTreeProvider.ts b/src/workspaceTree/WorkspaceTreeProvider.ts index ee03474..4e90c56 100644 --- a/src/workspaceTree/WorkspaceTreeProvider.ts +++ b/src/workspaceTree/WorkspaceTreeProvider.ts @@ -208,6 +208,7 @@ export class DocumentSymbolItem extends vscode.TreeItem { // Build description and context based on state const isOverwritten = !!overwrittenBy; + const isBuildable = (symbol.type === Edk2SymbolType.dscModuleDefinition); let desc = symbol.detail || ''; let ctx = 'symbolNode'; if (inactive && isOverwritten) { @@ -219,9 +220,11 @@ export class DocumentSymbolItem extends vscode.TreeItem { } else if (isOverwritten) { desc = `${this.label} (overwritten) ${desc}`.trim(); ctx = 'symbolNodeOverwritten'; + } else if (isBuildable) { + ctx = 'symbolNodeBuildable'; } - if(ctx !== 'symbolNode') { + if(ctx !== 'symbolNode' && ctx !== 'symbolNodeBuildable') { // The label is shown with strikethrough in the tree when overwritten, so we move the original label to the description and show the overwrite status in the label instead. This keeps the label text fully visible without truncation, while still indicating the symbol's name and status. this.label = ""; } diff --git a/static/buildForm.html b/static/buildForm.html new file mode 100644 index 0000000..d4700d5 --- /dev/null +++ b/static/buildForm.html @@ -0,0 +1,677 @@ + + + + + +EDK2 Build Configuration + + + + + +
+ + + + + +
+

+ EDK2 Build Configuration + + πŸ“– Documentation + +

+

Configure build arguments, then click Build. Settings for individual modules are saved automatically.

+ +
+ ⚠️ Beta Feature: Build arguments were automatically calculated from your workspace configuration but may need manual adjustments. Please review before building. +
+ + +

Target

+
+ + + + + + + + + + + + + + + + + +
+ + +

Common Options

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +

Flags

+
+ + + + + + + + + + + + + + +
+ + +

Report

+
+ + + + +
+
+ + +

Binary Cache

+
+ + + + + +
+ + +

Defines (-D)

+

Macro definitions passed as -D Name=Value.

+
+ + + +

PCDs (--pcd)

+

PCD overrides passed as --pcd=PcdName=Value.

+
+ + + +

Extra Arguments

+

Additional arguments passed verbatim to build.

+
+ + + +

Environment

+
+ +
+
+ + + +

Global Configuration

+

These settings are shared across all builds and saved globally in .edkCode/edk2_build_configuration.json.

+
+ + + + + + + + + + + +
+ +
+ + +
+
+
+

Building...

+
+
+ + + + From e257d001754b7254198eb9fb2d148965e13a91b0 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Tue, 19 May 2026 16:46:40 -0500 Subject: [PATCH 69/73] 2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ad1e6f..2977464 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "edk2code", "displayName": "Edk2code", "description": "EDK2 code support", - "version": "2.0.0", + "version": "2.0.1", "icon": "assets/icon.png", "publisher": "intel-corporation", "homepage": "https://github.com/intel/Edk2Code/wiki", From 7b28822ff2e20ee17ac93ab40cb59ccb8cc64a02 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 5 Jun 2026 12:43:45 -0500 Subject: [PATCH 70/73] Update icon --- package.json | 22 ++-------------------- src/buildEdk2.ts | 2 +- src/configuration.ts | 2 +- static/buildForm.html | 2 +- 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 2977464..4bf509a 100644 --- a/package.json +++ b/package.json @@ -179,12 +179,12 @@ { "command": "edk2code.buildEdk2Workspace", "title": "EDK2: Build workspace", - "icon": "$(tools)" + "icon": "$(build)" }, { "command": "edk2code.buildFromTree", "title": "EDK2: Build", - "icon": "$(tools)" + "icon": "$(build)" } ], "configuration": [ @@ -273,24 +273,6 @@ "type": "string" } }, - "edk2code.buildToolchain": { - "order": 4, - "type": "string", - "markdownDescription": "EDK2 toolchain tag (`TOOL_CHAIN_TAG`) to use for building.", - "default": "VS2022", - "enum": [ - "GCC5", - "GCC49", - "GCC48", - "VS2022", - "VS2019", - "VS2017", - "CLANGPDB", - "CLANGDWARF", - "CLANGGCC", - "XCODE5" - ] - }, "edk2code.buildArch": { "order": 4, "type": "string", diff --git a/src/buildEdk2.ts b/src/buildEdk2.ts index db5b3fa..519e424 100644 --- a/src/buildEdk2.ts +++ b/src/buildEdk2.ts @@ -532,7 +532,7 @@ function showBuildForm(initial: BuildFormState, defaults: BuildFormState, global 'edk2code.buildForm', 'EDK2 Build Configuration', vscode.ViewColumn.Active, - { enableScripts: true, retainContextWhenHidden: true } + { enableScripts: true, retainContextWhenHidden: true, enableFindWidget: true } ); buildFormPanel = panel; diff --git a/src/configuration.ts b/src/configuration.ts index c46e577..365436c 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -366,7 +366,7 @@ export class ConfigAgent { } getBuildToolchain(): string { - return this.get("buildToolchain") || (process.platform === 'win32' ? "VS2022" : "GCC5"); + return process.platform === 'win32' ? "VS2019" : "GCC"; } getBuildArch(): string { diff --git a/static/buildForm.html b/static/buildForm.html index d4700d5..307a7fd 100644 --- a/static/buildForm.html +++ b/static/buildForm.html @@ -473,7 +473,7 @@

Global Configuration

// Selects const archOptions = ['IA32','X64','AARCH64','ARM','RISCV64','LOONGARCH64','EBC']; const targetOptions = ['DEBUG','RELEASE','NOOPT']; - const toolchainOptions = ['GCC5','GCC49','GCC48','VS2022','VS2019','VS2017','CLANGPDB','CLANGDWARF','CLANGGCC','XCODE5']; + const toolchainOptions = ['GCC','VS2022','VS2019','VS2017','CLANGPDB','CLANGDWARF','CLANGGCC','XCODE5']; const actionOptions = ['','all','fds','genc','genmake','clean','cleanall','cleanlib','modules','libraries','run']; populateSelect('platform', s.dscPaths || [s.platform], s.platform); From 900ae937aa6171ddc12f2a1580ce3ecea737feb0 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Tue, 19 May 2026 17:25:06 -0500 Subject: [PATCH 71/73] temp --- static/buildForm.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/static/buildForm.html b/static/buildForm.html index 307a7fd..49e6865 100644 --- a/static/buildForm.html +++ b/static/buildForm.html @@ -180,9 +180,6 @@

EDK2 Build Configuration - - πŸ“– Documentation -

Configure build arguments, then click Build. Settings for individual modules are saved automatically.

@@ -473,7 +470,7 @@

Global Configuration

// Selects const archOptions = ['IA32','X64','AARCH64','ARM','RISCV64','LOONGARCH64','EBC']; const targetOptions = ['DEBUG','RELEASE','NOOPT']; - const toolchainOptions = ['GCC','VS2022','VS2019','VS2017','CLANGPDB','CLANGDWARF','CLANGGCC','XCODE5']; + const git = ['GCC','VS2022','VS2019','VS2017','CLANGPDB','CLANGDWARF','CLANGGCC','XCODE5']; const actionOptions = ['','all','fds','genc','genmake','clean','cleanall','cleanlib','modules','libraries','run']; populateSelect('platform', s.dscPaths || [s.platform], s.platform); From 77f52cd1c65aeddaba447e4125a195ce13680e5a Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Wed, 20 May 2026 09:51:30 -0500 Subject: [PATCH 72/73] Workspace config --- src/Languages/buildFolder.ts | 11 +++++++-- src/buildEdk2.ts | 48 +++++++++++++++++++++++++++--------- src/contextState/cmds.ts | 4 +++ static/buildForm.html | 11 +++++++-- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/Languages/buildFolder.ts b/src/Languages/buildFolder.ts index 5cef1ca..e0728a7 100644 --- a/src/Languages/buildFolder.ts +++ b/src/Languages/buildFolder.ts @@ -1,7 +1,7 @@ import path = require("path"); import * as vscode from 'vscode'; import * as fs from 'fs'; -import { gCscope, gDebugLog, gWorkspacePath } from "../extension"; +import { gConfigAgent, gCscope, gDebugLog, gWorkspacePath } from "../extension"; import { getRealPath, getRealPathRelative, isWorkspacePath, normalizePath, readLines, split, toPosix } from "../utils"; import glob = require("fast-glob"); import { writeEdkCodeFolderFile } from "../edk2CodeFolder"; @@ -168,7 +168,14 @@ export class BuildFolder { let filteredCscope = []; for (const value of cscopeMap.values()) { try { - filteredCscope.push(value.replace(/\n$/, "")); + let cleanValue = value.replace(/\n$/, ""); + filteredCscope.push(cleanValue); + // For .dec files, add the package directory to package paths + let unquoted = cleanValue.replace(/^"|"$/g, ""); + if(unquoted.toLowerCase().endsWith(".dec")){ + let decPackageDir = getRealPathRelative(path.dirname(unquoted)); + gConfigAgent.pushBuildPackagePaths(decPackageDir); + } } catch (error) { } diff --git a/src/buildEdk2.ts b/src/buildEdk2.ts index 519e424..cc0f5aa 100644 --- a/src/buildEdk2.ts +++ b/src/buildEdk2.ts @@ -29,13 +29,16 @@ interface GlobalBuildConfig { iaslPrefix: string; /** Path to EDK2 BaseTools binaries. Exported as EDK_TOOLS_BIN. */ edkToolsBin: string; + /** Optional WORKSPACE directory override. Empty = auto-compute from platform DSC location. */ + workspaceDir: string; } const DEFAULT_GLOBAL_CONFIG: GlobalBuildConfig = { edkSetupPath: '', nasmPrefix: '', iaslPrefix: '', - edkToolsBin: '' + edkToolsBin: '', + workspaceDir: '' }; interface DefineEntry { @@ -161,6 +164,17 @@ export async function buildEdk2Workspace(options?: BuildInvocation) { // Determine pre-selected DSC let selectedDsc: string | undefined = options?.dscPath; + if (selectedDsc) { + // Normalize: if dscPath is absolute, find the matching relative entry in dscPaths + const match = dscPaths.find(d => + d === selectedDsc || + path.resolve(gWorkspacePath, d) === path.resolve(selectedDsc!) || + path.normalize(d) === path.normalize(selectedDsc!) + ); + if (match) { + selectedDsc = match; + } + } if (!selectedDsc) { if (dscPaths.length === 1) { selectedDsc = dscPaths[0]; @@ -273,9 +287,7 @@ function validateGlobalConfig(config: GlobalBuildConfig): ValidationError[] { } else if (fs.statSync(nasmPath).isDirectory()) { errors.push({ field: 'nasmPrefix', message: `Expected a file (nasm executable), got a directory: ${nasmPath}` }); } - } else if (!process.env['NASM_PREFIX']) { - errors.push({ field: 'nasmPrefix', message: `NASM path is not configured and NASM_PREFIX is not set in environment` }); - } + } // IASL Prefix: must point to iasl executable const rawIasl = config.iaslPrefix.trim(); @@ -351,10 +363,14 @@ async function runBuild(state: BuildFormState, edkRoot: string, isWindows: boole const moduleArg = toEdkRelative(state.module); // Compute the effective workspace root (WORKSPACE env variable / CWD for edksetup). - // EDK2 resolves relative -p paths as WORKSPACE/path. We find where the platform DSC - // actually lives and derive the workspace root from that. + // If the user explicitly set workspaceDir in global config, use that. + // Otherwise, auto-compute from platform DSC location. let workspaceRoot = edkRoot; - if (platformArg && !path.isAbsolute(platformArg)) { + if (globalConfig.workspaceDir) { + workspaceRoot = path.isAbsolute(globalConfig.workspaceDir) + ? globalConfig.workspaceDir + : path.resolve(gWorkspacePath, globalConfig.workspaceDir); + } else if (platformArg && !path.isAbsolute(platformArg)) { const candidates = new Set(); candidates.add(edkRoot); for (const p of packagePaths) { @@ -376,10 +392,18 @@ async function runBuild(state: BuildFormState, edkRoot: string, isWindows: boole ? path.join(edkRoot, setupScript) : setupScript; - // Compose build args + // Compose build args β€” use absolute paths for -p and -m + // Note: platformArg/moduleArg are relative to gWorkspacePath (VS Code workspace root), + // not to workspaceRoot (EDK2 WORKSPACE), so resolve against gWorkspacePath. const args: string[] = []; - if (platformArg) { args.push(`-p ${platformArg}`); } - if (moduleArg) { args.push(`-m ${moduleArg}`); } + if (platformArg) { + const absPlat = path.isAbsolute(platformArg) ? platformArg : path.resolve(gWorkspacePath, platformArg); + args.push(`-p ${absPlat}`); + } + if (moduleArg) { + const absMod = path.isAbsolute(moduleArg) ? moduleArg : path.resolve(gWorkspacePath, moduleArg); + args.push(`-m ${absMod}`); + } if (state.arch) { // Support multiple architectures separated by space or comma (e.g. "IA32 X64") const archs = state.arch.split(/[\s,]+/).filter(Boolean); @@ -520,7 +544,7 @@ function showBuildForm(initial: BuildFormState, defaults: BuildFormState, global if (buildFormPanel) { // If already open, just reveal and re-init buildFormPanel.reveal(); - buildFormPanel.webview.postMessage({ command: 'init', state: initial, globalConfig }); + buildFormPanel.webview.postMessage({ command: 'init', state: initial, globalConfig, workspacePath: gWorkspacePath }); return; } @@ -544,7 +568,7 @@ function showBuildForm(initial: BuildFormState, defaults: BuildFormState, global if (!msg) { return; } if (msg.command === 'ready') { // Send initial state + global config to webview - panel.webview.postMessage({ command: 'init', state: initial, globalConfig }); + panel.webview.postMessage({ command: 'init', state: initial, globalConfig, workspacePath: gWorkspacePath }); } else if (msg.command === 'build') { const state = msg.state as BuildFormState; const formGlobal = msg.globalConfig as GlobalBuildConfig; diff --git a/src/contextState/cmds.ts b/src/contextState/cmds.ts index ac0c2b1..74923e4 100644 --- a/src/contextState/cmds.ts +++ b/src/contextState/cmds.ts @@ -578,12 +578,16 @@ import { DocumentSymbolItem, WorkspaceRootItem, WorkspaceTreeNode } from "../wor let document = await openTextDocument(p[0].uri); let parser = await getParserForDocument(document); if(parser){ + + // Parse source files let sources = parser.getSymbolsType(Edk2SymbolType.infSource); filesList.push(parser.document.fileName); for (const source of sources) { if(reject.isCancellationRequested){break;} filesList.push(await source.getValue()); } + + // Parse DEC files let decs = parser.getSymbolsType(Edk2SymbolType.infPackage); for (const dec of decs) { if(reject.isCancellationRequested){break;} diff --git a/static/buildForm.html b/static/buildForm.html index 49e6865..8811223 100644 --- a/static/buildForm.html +++ b/static/buildForm.html @@ -192,9 +192,11 @@

Target

+ + @@ -326,6 +328,9 @@

Global Configuration

+ + +
@@ -470,7 +475,7 @@

Global Configuration

// Selects const archOptions = ['IA32','X64','AARCH64','ARM','RISCV64','LOONGARCH64','EBC']; const targetOptions = ['DEBUG','RELEASE','NOOPT']; - const git = ['GCC','VS2022','VS2019','VS2017','CLANGPDB','CLANGDWARF','CLANGGCC','XCODE5']; + const toolchainOptions = ['GCC','VS2022','VS2019','VS2017','CLANGPDB','CLANGDWARF','CLANGGCC','XCODE5']; const actionOptions = ['','all','fds','genc','genmake','clean','cleanall','cleanlib','modules','libraries','run']; populateSelect('platform', s.dscPaths || [s.platform], s.platform); @@ -541,6 +546,7 @@

Global Configuration

setVal('nasmPrefix', g.nasmPrefix); setVal('iaslPrefix', g.iaslPrefix); setVal('edkToolsBin', g.edkToolsBin); + setVal('workspaceDir', g.workspaceDir); } // ─── Add buttons ───────────────────────────────────────────────── @@ -608,7 +614,8 @@

Global Configuration

edkSetupPath: val('edkSetupPath'), nasmPrefix: val('nasmPrefix'), iaslPrefix: val('iaslPrefix'), - edkToolsBin: val('edkToolsBin') + edkToolsBin: val('edkToolsBin'), + workspaceDir: val('workspaceDir') }; } From d618da2921460435656022c6b1abd9f2b01b6bd6 Mon Sep 17 00:00:00 2001 From: "Palomino Sosa, Guillermo A" Date: Fri, 5 Jun 2026 12:12:54 -0500 Subject: [PATCH 73/73] Update buid command --- static/buildForm.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/buildForm.html b/static/buildForm.html index 8811223..3e4cddb 100644 --- a/static/buildForm.html +++ b/static/buildForm.html @@ -192,11 +192,11 @@

Target

- + - +