11import vscode = require( "vscode" ) ;
2- import {
3- currentFile ,
4- currentFileFromContent ,
5- getFileText ,
6- methodOffsetToLine ,
7- stripClassMemberNameQuotes ,
8- } from "../utils" ;
2+ import { currentFile , getFileText , methodOffsetToLine , stripClassMemberNameQuotes } from "../utils" ;
93import {
104 InitializedEvent ,
115 LoggingDebugSession ,
@@ -27,6 +21,7 @@ import { lsExtensionId, schemas } from "../extension";
2721import { DocumentContentProvider } from "../providers/DocumentContentProvider" ;
2822import { formatPropertyValue } from "./utils" ;
2923import { isfsConfig } from "../utils/FileProviderUtil" ;
24+ import { getDocumentForUri } from "../utils/documentIndex" ;
3025
3126interface LaunchRequestArguments extends DebugProtocol . LaunchRequestArguments {
3227 /** An absolute path to the "program" to debug. */
@@ -45,15 +40,16 @@ interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
4540}
4641
4742/** converts a uri from VS Code to a server-side XDebug file URI with respect to source root settings */
48- async function convertClientPathToDebugger ( uri : vscode . Uri , namespace : string ) : Promise < string > {
43+ function convertClientPathToDebugger ( uri : vscode . Uri , namespace : string ) : string {
4944 const { scheme, path } = uri ;
5045 let fileName : string ;
5146 if ( scheme && schemas . includes ( scheme ) ) {
5247 const { ns } = isfsConfig ( uri ) ;
5348 if ( ns ) namespace = ns ;
5449 fileName = path . slice ( 1 ) . replace ( / \/ / g, "." ) ;
5550 } else {
56- fileName = currentFileFromContent ( uri , await getFileText ( uri ) ) ?. name ;
51+ fileName = getDocumentForUri ( uri ) ;
52+ if ( ! fileName ) return ;
5753 }
5854
5955 namespace = encodeURIComponent ( namespace ) ;
@@ -115,6 +111,20 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
115111 /** If this is a `launch` session */
116112 private _isLaunch = false ;
117113
114+ /** A cache of documents we have fetched the text of in this session */
115+ private _docCache : Map < string , string > = new Map ( ) ;
116+
117+ /** Get the text of file `uri`, using our cache. */
118+ private async _getFileText ( uri : vscode . Uri ) : Promise < string > {
119+ const uriString = uri . toString ( ) ;
120+ let content = this . _docCache . get ( uriString ) ;
121+ if ( content == undefined ) {
122+ content = await getFileText ( uri ) . catch ( ( ) => "" ) ;
123+ this . _docCache . set ( uriString , content ) ;
124+ }
125+ return content ;
126+ }
127+
118128 public constructor ( ) {
119129 super ( ) ;
120130
@@ -147,6 +157,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
147157 const file = currentFile ( ) ;
148158 this . _workspace = file ?. workspaceFolder ;
149159 this . _api = new AtelierAPI ( file ?. uri ) ;
160+ if ( file ?. uri ) this . _workspaceFolderUri = vscode . workspace . getWorkspaceFolder ( file . uri ) ?. uri ;
150161 }
151162 return ;
152163 }
@@ -350,11 +361,37 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
350361 try {
351362 await this . _waitForDebugTarget ( ) ;
352363
353- const filePath = args . source . path ;
354- const scheme = filePath . split ( ":" ) [ 0 ] ;
355- const uri = schemas . includes ( scheme ) ? vscode . Uri . parse ( filePath ) : vscode . Uri . file ( filePath ) ;
356- const fileUri = await convertClientPathToDebugger ( uri , this . _namespace ) ;
357- const [ , fileName ] = fileUri . match ( / \| ( [ ^ | ] + ) $ / ) ;
364+ const uri = vscode . Uri . parse ( args . source . path ) ;
365+ const wsFolder = vscode . workspace . getWorkspaceFolder ( uri ) ;
366+ if ( ! wsFolder || ( this . _workspaceFolderUri && wsFolder . uri . toString ( ) != this . _workspaceFolderUri . toString ( ) ) ) {
367+ response . body = {
368+ breakpoints : args . breakpoints . map ( ( ) => {
369+ return {
370+ verified : false ,
371+ message : "This file is not from the same workspace folder as the debug target" ,
372+ reason : "failed" ,
373+ } ;
374+ } ) ,
375+ } ;
376+ this . sendResponse ( response ) ;
377+ return ;
378+ }
379+ const xdebugUri = convertClientPathToDebugger ( uri , this . _namespace ) ;
380+ if ( ! xdebugUri ) {
381+ response . body = {
382+ breakpoints : args . breakpoints . map ( ( ) => {
383+ return {
384+ verified : false ,
385+ message : "Failed to determine the class or routine name of this file" ,
386+ reason : "failed" ,
387+ } ;
388+ } ) ,
389+ } ;
390+ this . sendResponse ( response ) ;
391+ return ;
392+ }
393+ const [ , fileName ] = xdebugUri . match ( / \| ( [ ^ | ] + ) $ / ) ;
394+ const fileExt = fileName . split ( "." ) . pop ( ) . toLowerCase ( ) ;
358395 const languageServer : boolean = vscode . extensions . getExtension ( lsExtensionId ) ?. isActive ?? false ;
359396
360397 const currentList = await this . _connection . sendBreakpointListCommand ( ) ;
@@ -371,7 +408,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
371408
372409 let xdebugBreakpoints : ( xdebug . ConditionalBreakpoint | xdebug . ClassLineBreakpoint | xdebug . LineBreakpoint ) [ ] = [ ] ;
373410 let symbols : vscode . DocumentSymbol [ ] ;
374- if ( fileName . endsWith ( "cls" ) ) {
411+ if ( fileExt == "cls" ) {
375412 // Compute DocumentSymbols for this class
376413 symbols = (
377414 await vscode . commands . executeCommand < vscode . DocumentSymbol [ ] > ( "vscode.executeDocumentSymbolProvider" , uri )
@@ -380,7 +417,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
380417 xdebugBreakpoints = await Promise . all (
381418 args . breakpoints . map ( async ( breakpoint ) => {
382419 const line = breakpoint . line ;
383- if ( fileName . endsWith ( "cls" ) ) {
420+ if ( fileExt == "cls" ) {
384421 // Find the class member that this breakpoint is in
385422 let currentSymbol : vscode . DocumentSymbol ;
386423 for ( const symbol of symbols ) {
@@ -395,7 +432,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
395432 currentSymbol . detail . toLowerCase ( ) !== "query"
396433 ) {
397434 // This breakpoint is in a method
398- const currentdoc = ( await getFileText ( uri ) ) . split ( / \r ? \n / ) ;
435+ const currentdoc = ( await this . _getFileText ( uri ) ) . split ( / \r ? \n / ) ;
399436 const methodName = stripClassMemberNameQuotes ( currentSymbol . name ) ;
400437 if ( languageServer ) {
401438 // selectionRange.start.line is the method definition line
@@ -411,15 +448,15 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
411448 if ( breakpoint . condition ) {
412449 return new xdebug . ClassConditionalBreakpoint (
413450 breakpoint . condition ,
414- fileUri ,
451+ xdebugUri ,
415452 line ,
416453 methodName ,
417454 line - methodlinenum - 1 ,
418455 breakpoint . hitCondition
419456 ) ;
420457 } else {
421458 return new xdebug . ClassLineBreakpoint (
422- fileUri ,
459+ xdebugUri ,
423460 line ,
424461 methodName ,
425462 line - methodlinenum - 1 ,
@@ -433,15 +470,15 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
433470 if ( breakpoint . condition ) {
434471 return new xdebug . ClassConditionalBreakpoint (
435472 breakpoint . condition ,
436- fileUri ,
473+ xdebugUri ,
437474 line ,
438475 methodName ,
439476 line - currentSymbol . selectionRange . start . line ,
440477 breakpoint . hitCondition
441478 ) ;
442479 } else {
443480 return new xdebug . ClassLineBreakpoint (
444- fileUri ,
481+ xdebugUri ,
445482 line ,
446483 methodName ,
447484 line - currentSymbol . selectionRange . start . line ,
@@ -450,24 +487,24 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
450487 }
451488 }
452489 }
453- } else if ( filePath . endsWith ( "mac" ) || filePath . endsWith ( "int" ) ) {
490+ } else if ( [ "mac" , "int" ] . includes ( fileExt ) ) {
454491 if ( breakpoint . condition ) {
455492 return new xdebug . RoutineConditionalBreakpoint (
456493 breakpoint . condition ,
457- fileUri ,
494+ xdebugUri ,
458495 line ,
459496 "" ,
460497 line - 1 ,
461498 breakpoint . hitCondition
462499 ) ;
463500 } else {
464- return new xdebug . RoutineLineBreakpoint ( fileUri , line , "" , line - 1 , breakpoint . hitCondition ) ;
501+ return new xdebug . RoutineLineBreakpoint ( xdebugUri , line , "" , line - 1 , breakpoint . hitCondition ) ;
465502 }
466503 } else {
467504 if ( breakpoint . condition ) {
468- return new xdebug . ConditionalBreakpoint ( breakpoint . condition , fileUri , line , breakpoint . hitCondition ) ;
505+ return new xdebug . ConditionalBreakpoint ( breakpoint . condition , xdebugUri , line , breakpoint . hitCondition ) ;
469506 } else {
470- return new xdebug . LineBreakpoint ( fileUri , line , breakpoint . hitCondition ) ;
507+ return new xdebug . LineBreakpoint ( xdebugUri , line , breakpoint . hitCondition ) ;
471508 }
472509 }
473510 } )
@@ -483,6 +520,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
483520 verified : false ,
484521 line : breakpoint . line ,
485522 message : "Hit Count must be a positive integer" ,
523+ reason : "failed" ,
486524 } ;
487525 } else {
488526 await this . _connection . sendBreakpointSetCommand ( breakpoint ) ;
@@ -493,6 +531,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
493531 verified : false ,
494532 line : breakpoint . line ,
495533 message : error . message ,
534+ reason : "failed" ,
496535 } ;
497536 }
498537 } )
@@ -573,6 +612,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
573612 verified : false ,
574613 instructionReference : breakpoint . variable ,
575614 message : error . message ,
615+ reason : "failed" ,
576616 } ;
577617 }
578618 } )
@@ -604,38 +644,54 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
604644 ) : Promise < void > {
605645 const stack = await this . _connection . sendStackGetCommand ( ) ;
606646
607- // Is set to true if we're at the CSP or unit test ending watchpoint.
608- // We need to do this so VS Code doesn't try to open the source of
609- // a stack frame before the debug session terminates. That should
610- // only happen if the server has source code for %SYS.cspServer.mac/int
611- // or %Api.Atelier.v<X>.cls/.int where X >= 8.
647+ /** Is set to true if we're at the CSP or unit test ending watchpoint.
648+ * We need to do this so VS Code doesn't try to open the source of
649+ * a stack frame before the debug session terminates. */
612650 let noStack = false ;
613651 const stackFrames = await Promise . all (
614652 stack . stack . map ( async ( stackFrame : xdebug . StackFrame , index ) : Promise < StackFrame > => {
615- const [ , namespace , name ] = decodeURI ( stackFrame . fileUri ) . match ( / ^ d b g p : \/ \/ \| ( [ ^ | ] + ) \| ( . * ) $ / ) ;
616- const routine = name ;
653+ if ( noStack ) return ; // Stack frames won't be sent
654+ const [ , namespace , docName ] = decodeURI ( stackFrame . fileUri ) . match ( / ^ d b g p : \/ \/ \| ( [ ^ | ] + ) \| ( . * ) $ / ) ;
617655 const fileUri = DocumentContentProvider . getUri (
618- routine ,
656+ docName ,
619657 this . _workspace ,
620658 namespace ,
621659 undefined ,
622660 this . _workspaceFolderUri
623661 ) ;
624- const source = new Source ( routine , fileUri . toString ( ) ) ;
662+ const source = new Source ( docName , fileUri . toString ( ) ) ;
625663 let line = stackFrame . line + 1 ;
626664 const place = `${ stackFrame . method } +${ stackFrame . methodOffset } ` ;
627665 const stackFrameId = this . _stackFrameIdCounter ++ ;
628- const fileText : string | undefined = await getFileText ( fileUri ) . catch ( ( ) => undefined ) ;
666+ if ( index == 0 && this . _break ) {
667+ const csp = this . _isCsp && [ "%SYS.cspServer.mac" , "%SYS.cspServer.int" ] . includes ( source . name ) ;
668+ const unitTest = this . _isUnitTest && source . name . startsWith ( "%Api.Atelier.v" ) ;
669+ if ( csp || unitTest ) {
670+ // Check if we're at our special watchpoint
671+ const { result } = await this . _connection . sendEvalCommand (
672+ csp ? this . _cspWatchpointCondition : this . _unitTestWatchpointCondition
673+ ) ;
674+ if ( result . type == "int" && result . value == "1" ) {
675+ // Stop the debugging session
676+ const xdebugResponse = await this . _connection . sendDetachCommand ( ) ;
677+ await this . _checkStatus ( xdebugResponse ) ;
678+ noStack = true ;
679+ return ;
680+ }
681+ }
682+ }
683+ const fileText = await this . _getFileText ( fileUri ) ;
629684 const hasCmdLoc = typeof stackFrame . cmdBeginLine == "number" ;
630- if ( fileText == undefined ) {
685+ if ( ! fileText . length ) {
631686 // Can't get the source for the document
632687 this . _stackFrames . set ( stackFrameId , stackFrame ) ;
633688 return {
634689 id : stackFrameId ,
635690 name : place ,
691+ // Don't provide a source path so VS Code doesn't attempt
692+ // to open this file or provide an option to "create" it
636693 source : {
637- name : routine ,
638- path : fileUri . toString ( ) ,
694+ name : docName ,
639695 presentationHint : "deemphasize" ,
640696 } ,
641697 line,
@@ -655,33 +711,8 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
655711 const newLine = methodOffsetToLine ( symbols , fileText , stackFrame . method , stackFrame . methodOffset ) ;
656712 if ( newLine != undefined ) line = newLine ;
657713 }
658- if (
659- this . _isCsp &&
660- this . _break &&
661- [ "%SYS.cspServer.mac" , "%SYS.cspServer.int" ] . includes ( source . name ) &&
662- index == 0
663- ) {
664- // Check if we're at our special watchpoint
665- const { result } = await this . _connection . sendEvalCommand ( this . _cspWatchpointCondition ) ;
666- if ( result . type == "int" && result . value == "1" ) {
667- // Stop the debugging session
668- const xdebugResponse = await this . _connection . sendDetachCommand ( ) ;
669- await this . _checkStatus ( xdebugResponse ) ;
670- noStack = true ;
671- }
672- }
673- if ( this . _isUnitTest && this . _break && source . name . startsWith ( "%Api.Atelier.v" ) && index == 0 ) {
674- // Check if we're at our special watchpoint
675- const { result } = await this . _connection . sendEvalCommand ( this . _unitTestWatchpointCondition ) ;
676- if ( result . type == "int" && result . value == "1" ) {
677- // Stop the debugging session
678- const xdebugResponse = await this . _connection . sendDetachCommand ( ) ;
679- await this . _checkStatus ( xdebugResponse ) ;
680- noStack = true ;
681- }
682- }
683714 this . _stackFrames . set ( stackFrameId , stackFrame ) ;
684- } catch ( ex ) {
715+ } catch {
685716 noSource = true ;
686717 }
687718 const lineDiff = line - stackFrame . line ;
0 commit comments