@@ -259,6 +259,18 @@ function registerCommands(context: vscode.ExtensionContext): void {
259259 }
260260 } )
261261 ) ;
262+
263+ context . subscriptions . push (
264+ vscode . commands . registerCommand ( 'vbnet.attachToProcess' , async ( ) => {
265+ try {
266+ await attachToProcess ( ) ;
267+ } catch ( error ) {
268+ const message = error instanceof Error ? error . message : String ( error ) ;
269+ outputChannel ?. appendLine ( `Failed to attach to process: ${ message } ` ) ;
270+ vscode . window . showErrorMessage ( `Failed to attach to process: ${ message } ` ) ;
271+ }
272+ } )
273+ ) ;
262274}
263275
264276interface SolutionPickItem extends vscode . QuickPickItem {
@@ -270,6 +282,16 @@ interface ProjectPickItem extends vscode.QuickPickItem {
270282 projectPath : string ;
271283}
272284
285+ interface ProcessInfo {
286+ pid : number ;
287+ name : string ;
288+ commandLine ?: string ;
289+ }
290+
291+ interface ProcessPickItem extends vscode . QuickPickItem {
292+ pid : number ;
293+ }
294+
273295async function selectWorkspaceSolution ( ) : Promise < void > {
274296 const workspaceFolders = vscode . workspace . workspaceFolders ;
275297 if ( ! workspaceFolders || workspaceFolders . length === 0 ) {
@@ -567,6 +589,122 @@ function fsPathExists(filePath: string): boolean {
567589 }
568590}
569591
592+ async function attachToProcess ( ) : Promise < void > {
593+ const workspaceFolder = vscode . workspace . workspaceFolders ?. [ 0 ] ;
594+ const processes = await listProcesses ( ) ;
595+
596+ if ( processes . length === 0 ) {
597+ vscode . window . showInformationMessage ( 'No running processes found.' ) ;
598+ return ;
599+ }
600+
601+ processes . sort ( ( a , b ) => {
602+ const nameCompare = a . name . localeCompare ( b . name ) ;
603+ if ( nameCompare !== 0 ) {
604+ return nameCompare ;
605+ }
606+ return a . pid - b . pid ;
607+ } ) ;
608+
609+ const items : ProcessPickItem [ ] = processes . map ( ( proc ) => ( {
610+ label : `${ proc . name } (${ proc . pid } )` ,
611+ description : proc . commandLine ,
612+ pid : proc . pid
613+ } ) ) ;
614+
615+ const pick = await vscode . window . showQuickPick ( items , {
616+ placeHolder : 'Select a process to attach netcoredbg' ,
617+ matchOnDescription : true
618+ } ) ;
619+
620+ if ( ! pick ) {
621+ return ;
622+ }
623+
624+ const attachConfig : vscode . DebugConfiguration = {
625+ name : `VB.NET Attach (${ pick . pid } )` ,
626+ type : 'vbnet' ,
627+ request : 'attach' ,
628+ processId : pick . pid
629+ } ;
630+
631+ const started = await vscode . debug . startDebugging ( workspaceFolder , attachConfig ) ;
632+ if ( ! started ) {
633+ vscode . window . showErrorMessage ( 'Failed to start VB.NET attach session.' ) ;
634+ }
635+ }
636+
637+ async function listProcesses ( ) : Promise < ProcessInfo [ ] > {
638+ if ( process . platform === 'win32' ) {
639+ return await listWindowsProcesses ( ) ;
640+ }
641+
642+ return await listUnixProcesses ( ) ;
643+ }
644+
645+ async function listWindowsProcesses ( ) : Promise < ProcessInfo [ ] > {
646+ const output = await execCommand ( 'tasklist' , [ '/FO' , 'CSV' , '/NH' ] ) ;
647+ const lines = output . split ( / \r ? \n / ) . map ( ( line ) => line . trim ( ) ) . filter ( ( line ) => line . length > 0 ) ;
648+ const results : ProcessInfo [ ] = [ ] ;
649+
650+ for ( const line of lines ) {
651+ const values = line . split ( '","' ) . map ( ( value ) => value . replace ( / ^ " | " $ / g, '' ) ) ;
652+ if ( values . length < 2 ) {
653+ continue ;
654+ }
655+ const pid = Number . parseInt ( values [ 1 ] , 10 ) ;
656+ if ( Number . isNaN ( pid ) ) {
657+ continue ;
658+ }
659+ results . push ( {
660+ pid,
661+ name : values [ 0 ] ,
662+ commandLine : values [ 0 ]
663+ } ) ;
664+ }
665+
666+ return results ;
667+ }
668+
669+ async function listUnixProcesses ( ) : Promise < ProcessInfo [ ] > {
670+ const output = await execCommand ( 'ps' , [ '-ax' , '-o' , 'pid=,comm=,args=' ] ) ;
671+ const lines = output . split ( / \r ? \n / ) . map ( ( line ) => line . trim ( ) ) . filter ( ( line ) => line . length > 0 ) ;
672+ const results : ProcessInfo [ ] = [ ] ;
673+
674+ for ( const line of lines ) {
675+ const match = line . match ( / ^ ( \d + ) \s + ( \S + ) \s * ( .* ) $ / ) ;
676+ if ( ! match ) {
677+ continue ;
678+ }
679+ const pid = Number . parseInt ( match [ 1 ] , 10 ) ;
680+ if ( Number . isNaN ( pid ) ) {
681+ continue ;
682+ }
683+ results . push ( {
684+ pid,
685+ name : match [ 2 ] ,
686+ commandLine : match [ 3 ]
687+ } ) ;
688+ }
689+
690+ return results ;
691+ }
692+
693+ async function execCommand ( command : string , args : string [ ] ) : Promise < string > {
694+ return await new Promise ( ( resolve , reject ) => {
695+ cp . execFile ( command , args , { encoding : 'utf8' } , ( error , stdout , stderr ) => {
696+ if ( error ) {
697+ reject ( error ) ;
698+ return ;
699+ }
700+ if ( stderr ) {
701+ outputChannel ?. appendLine ( stderr . trim ( ) ) ;
702+ }
703+ resolve ( stdout ?? '' ) ;
704+ } ) ;
705+ } ) ;
706+ }
707+
570708/**
571709 * Extension deactivation.
572710 * Called when the extension is deactivated.
0 commit comments