@@ -25,6 +25,8 @@ import { DebugProtocol } from "@vscode/debugprotocol";
2525import {
2626 IDebugServiceWorker ,
2727 IStructStepResult ,
28+ IVariable ,
29+ IVariableChild ,
2830 QscEventTarget ,
2931 StepResultId ,
3032 log ,
@@ -64,16 +66,28 @@ interface IBreakpointLocationData {
6466 breakpoint : DebugProtocol . Breakpoint ;
6567}
6668
69+ type ScopeHandle =
70+ | { kind : "scope" ; scope : "locals" ; frameId : number }
71+ | { kind : "scope" ; scope : "quantum" }
72+ | { kind : "scope" ; scope : "circuit" } ;
73+
74+ type ArrayHandle = {
75+ kind : "array" ;
76+ previewChildren : IVariableChild [ ] ;
77+ truncated : boolean ;
78+ totalLength : number | undefined ;
79+ } ;
80+
81+ type VariableHandle = ScopeHandle | ArrayHandle ;
82+
6783export class QscDebugSession extends LoggingDebugSession {
6884 private static threadID = 1 ;
6985
7086 private readonly knownPaths = new Map < string , string > ( ) ;
7187
7288 private breakpointLocations : Map < string , IBreakpointLocationData [ ] > ;
7389 private breakpoints : Map < string , DebugProtocol . Breakpoint [ ] > ;
74- private variableHandles = new Handles <
75- [ "locals" | "quantum" | "circuit" , number ]
76- > ( ) ;
90+ private variableHandles = new Handles < VariableHandle > ( ) ;
7791 private failureMessage : string ;
7892 private eventTarget : QscEventTarget ;
7993 private supportsVariableType = false ;
@@ -756,17 +770,21 @@ export class QscDebugSession extends LoggingDebugSession {
756770 scopes : [
757771 new Scope (
758772 "Locals" ,
759- this . variableHandles . create ( [ "locals" , args . frameId ] ) ,
773+ this . variableHandles . create ( {
774+ kind : "scope" ,
775+ scope : "locals" ,
776+ frameId : args . frameId ,
777+ } ) ,
760778 false ,
761779 ) ,
762780 new Scope (
763781 "Quantum State" ,
764- this . variableHandles . create ( [ "quantum ", - 1 ] ) ,
782+ this . variableHandles . create ( { kind : "scope ", scope : "quantum" } ) ,
765783 true , // expensive - keeps scope collapsed in the UI by default
766784 ) ,
767785 new Scope (
768786 "Quantum Circuit" ,
769- this . variableHandles . create ( [ "circuit ", - 1 ] ) ,
787+ this . variableHandles . create ( { kind : "scope ", scope : "circuit" } ) ,
770788 true , // expensive - keeps scope collapsed in the UI by default
771789 ) ,
772790 ] ,
@@ -786,103 +804,214 @@ export class QscDebugSession extends LoggingDebugSession {
786804 variables : [ ] ,
787805 } ;
788806
789- const [ handle , frameID ] = this . variableHandles . get ( args . variablesReference ) ;
790-
791- switch ( handle ) {
792- case "locals" :
793- {
794- const locals = await this . debugService . getLocalVariables ( frameID ) ;
795- const variables = locals . map ( ( local ) => {
796- const variable : DebugProtocol . Variable = {
797- name : local . name ,
798- value : local . value ,
799- variablesReference : 0 ,
807+ const handleData = this . variableHandles . get ( args . variablesReference ) ;
808+
809+ if ( ! handleData ) {
810+ log . warn ( `variablesRequest: unknown handle ${ args . variablesReference } ` ) ;
811+ } else if ( handleData . kind === "scope" ) {
812+ switch ( handleData . scope ) {
813+ case "locals" :
814+ {
815+ const locals = await this . debugService . getLocalVariables (
816+ handleData . frameId ,
817+ ) ;
818+ response . body = {
819+ variables : locals . map ( ( local ) =>
820+ this . createVariableFromLocal ( local ) ,
821+ ) ,
800822 } ;
801- if ( this . supportsVariableType ) {
802- variable . type = local . var_type ;
823+ }
824+ break ;
825+ case "quantum" :
826+ {
827+ const associationId = getRandomGuid ( ) ;
828+ const start = performance . now ( ) ;
829+ sendTelemetryEvent (
830+ EventType . RenderQuantumStateStart ,
831+ { associationId } ,
832+ { } ,
833+ ) ;
834+ const state = await this . debugService . captureQuantumState ( ) ;
835+
836+ // When there is no quantum state, return a single variable with a message
837+ // for the user.
838+ if ( state . length == 0 ) {
839+ response . body = {
840+ variables : [
841+ {
842+ name : "None" ,
843+ value : "No qubits allocated" ,
844+ variablesReference : 0 ,
845+ } ,
846+ ] ,
847+ } ;
848+ break ;
803849 }
804- return variable ;
805- } ) ;
806- response . body = {
807- variables : variables ,
808- } ;
809- }
810- break ;
811- case "quantum" :
812- {
813- const associationId = getRandomGuid ( ) ;
814- const start = performance . now ( ) ;
815- sendTelemetryEvent (
816- EventType . RenderQuantumStateStart ,
817- { associationId } ,
818- { } ,
819- ) ;
820- const state = await this . debugService . captureQuantumState ( ) ;
821850
822- // When there is no quantum state, return a single variable with a message
823- // for the user.
824- if ( state . length == 0 ) {
851+ const variables : DebugProtocol . Variable [ ] = state . map ( ( entry ) => {
852+ const variable : DebugProtocol . Variable = {
853+ name : entry . name ,
854+ value : entry . value ,
855+ variablesReference : 0 ,
856+ type : "Complex" ,
857+ } ;
858+ return variable ;
859+ } ) ;
860+ sendTelemetryEvent (
861+ EventType . RenderQuantumStateEnd ,
862+ { associationId } ,
863+ { timeToCompleteMs : performance . now ( ) - start } ,
864+ ) ;
865+ response . body = {
866+ variables : variables ,
867+ } ;
868+ }
869+ break ;
870+ case "circuit" :
871+ {
872+ // This will get invoked when the "Quantum Circuit" scope is expanded
873+ // in the Variables view, but instead of showing any values in the variables
874+ // view, we can pop open the circuit diagram panel.
875+ if ( ! this . config . showCircuit ) {
876+ // Keep updating the circuit for the rest of this session, even if
877+ // the Variables scope gets collapsed by the user. If we don't do this,
878+ // the diagram won't get updated with each step even though the circuit
879+ // panel is still being shown, which is misleading.
880+ this . config . showCircuit = true ;
881+ await this . updateCircuit ( ) ;
882+ }
825883 response . body = {
826884 variables : [
827885 {
828- name : "None " ,
829- value : "No qubits allocated " ,
886+ name : "Circuit " ,
887+ value : "See QDK Circuit panel " ,
830888 variablesReference : 0 ,
831889 } ,
832890 ] ,
833891 } ;
834- break ;
835- }
836-
837- const variables : DebugProtocol . Variable [ ] = state . map ( ( entry ) => {
838- const variable : DebugProtocol . Variable = {
839- name : entry . name ,
840- value : entry . value ,
841- variablesReference : 0 ,
842- type : "Complex" ,
843- } ;
844- return variable ;
845- } ) ;
846- sendTelemetryEvent (
847- EventType . RenderQuantumStateEnd ,
848- { associationId } ,
849- { timeToCompleteMs : performance . now ( ) - start } ,
850- ) ;
851- response . body = {
852- variables : variables ,
853- } ;
854- }
855- break ;
856- case "circuit" :
857- {
858- // This will get invoked when the "Quantum Circuit" scope is expanded
859- // in the Variables view, but instead of showing any values in the variables
860- // view, we can pop open the circuit diagram panel.
861- if ( ! this . config . showCircuit ) {
862- // Keep updating the circuit for the rest of this session, even if
863- // the Variables scope gets collapsed by the user. If we don't do this,
864- // the diagram won't get updated with each step even though the circuit
865- // panel is still being shown, which is misleading.
866- this . config . showCircuit = true ;
867- await this . updateCircuit ( ) ;
868892 }
869- response . body = {
870- variables : [
871- {
872- name : "Circuit" ,
873- value : "See QDK Circuit panel" ,
874- variablesReference : 0 ,
875- } ,
876- ] ,
877- } ;
878- }
879- break ;
893+ break ;
894+ }
895+ } else if ( handleData . kind === "array" ) {
896+ const allChildren = this . createArrayVariables ( handleData ) ;
897+ const totalChildren = allChildren . length ;
898+ const rawStart = args . start ?? 0 ;
899+ const start = Math . max ( 0 , Math . min ( rawStart , totalChildren ) ) ;
900+ const requestedCount = args . count ;
901+ let end = totalChildren ;
902+ if ( requestedCount !== undefined ) {
903+ end = Math . min ( start + Math . max ( requestedCount , 0 ) , totalChildren ) ;
904+ }
905+ const variables =
906+ start >= totalChildren ? [ ] : allChildren . slice ( start , end ) ;
907+ response . body = {
908+ variables,
909+ } ;
880910 }
881911
882912 log . trace ( `variablesResponse: %O` , response ) ;
883913 this . sendResponse ( response ) ;
884914 }
885915
916+ private createVariableFromLocal ( local : IVariable ) : DebugProtocol . Variable {
917+ const variable : DebugProtocol . Variable = {
918+ name : local . name ,
919+ value : local . value ,
920+ variablesReference : 0 ,
921+ } ;
922+
923+ if ( this . supportsVariableType ) {
924+ variable . type = local . varType ;
925+ }
926+
927+ if ( local . varType === "Array" ) {
928+ const previewChildren = ( local . previewChildren ?? [ ] ) . map ( ( child ) => ( {
929+ ...child ,
930+ } ) ) ;
931+ const truncated = local . truncated === true ;
932+ variable . value = this . formatArraySummary (
933+ local . length ,
934+ previewChildren . length ,
935+ truncated ,
936+ local . value ,
937+ ) ;
938+
939+ if ( this . supportsVariableType ) {
940+ const elementType = local . elementType ?? "Unknown" ;
941+ variable . type = `Array<${ elementType } >` ;
942+ }
943+
944+ if ( previewChildren . length > 0 || truncated ) {
945+ variable . variablesReference = this . variableHandles . create ( {
946+ kind : "array" ,
947+ previewChildren,
948+ truncated,
949+ totalLength : local . length ,
950+ } ) ;
951+ }
952+ }
953+
954+ return variable ;
955+ }
956+
957+ private createArrayVariables ( handle : ArrayHandle ) : DebugProtocol . Variable [ ] {
958+ const variables = handle . previewChildren . map ( ( child ) => {
959+ const variable : DebugProtocol . Variable = {
960+ name : `[${ child . index } ]` ,
961+ value : child . value ,
962+ variablesReference : 0 ,
963+ } ;
964+ if ( this . supportsVariableType ) {
965+ variable . type = child . varType ;
966+ }
967+ return variable ;
968+ } ) ;
969+
970+ if ( handle . truncated ) {
971+ const remaining =
972+ handle . totalLength !== undefined
973+ ? Math . max ( handle . totalLength - handle . previewChildren . length , 0 )
974+ : undefined ;
975+ const message =
976+ remaining !== undefined && remaining > 0
977+ ? `${ remaining } more items truncated`
978+ : "More items truncated" ;
979+ const sentinel : DebugProtocol . Variable = {
980+ name : "[...]" ,
981+ value : message ,
982+ variablesReference : 0 ,
983+ } ;
984+ if ( this . supportsVariableType ) {
985+ sentinel . type = "Truncated" ;
986+ }
987+ variables . push ( sentinel ) ;
988+ }
989+
990+ return variables ;
991+ }
992+
993+ private formatArraySummary (
994+ length : number | undefined ,
995+ previewCount : number ,
996+ truncated : boolean ,
997+ fallback : string ,
998+ ) : string {
999+ if ( length !== undefined ) {
1000+ if ( truncated ) {
1001+ return `length = ${ length } (showing first ${ previewCount } )` ;
1002+ }
1003+ return `length = ${ length } ` ;
1004+ }
1005+
1006+ if ( truncated ) {
1007+ return previewCount > 0
1008+ ? `showing first ${ previewCount } `
1009+ : "values truncated" ;
1010+ }
1011+
1012+ return fallback ;
1013+ }
1014+
8861015 private createBreakpoint (
8871016 id : number ,
8881017 location : DebugProtocol . BreakpointLocation ,
0 commit comments