33'use strict' ;
44import { inject , injectable , named } from 'inversify' ;
55
6- import { DebugAdapterTracker , Event , EventEmitter } from 'vscode' ;
6+ import { DebugAdapterTracker , Disposable , Event , EventEmitter } from 'vscode' ;
77import { DebugProtocol } from 'vscode-debugprotocol' ;
88import { IDebugService } from '../../common/application/types' ;
99import { traceError } from '../../common/logger' ;
1010import { IConfigurationService , Resource } from '../../common/types' ;
1111import { DataFrameLoading , Identifiers } from '../constants' ;
1212import {
13+ IConditionalJupyterVariables ,
1314 IJupyterDebugService ,
1415 IJupyterVariable ,
15- IJupyterVariables ,
1616 IJupyterVariablesRequest ,
1717 IJupyterVariablesResponse ,
1818 INotebook
@@ -22,11 +22,13 @@ const DataViewableTypes: Set<string> = new Set<string>(['DataFrame', 'list', 'di
2222const KnownExcludedVariables = new Set < string > ( [ 'In' , 'Out' , 'exit' , 'quit' ] ) ;
2323
2424@injectable ( )
25- export class DebuggerVariables implements IJupyterVariables , DebugAdapterTracker {
26- private imported = false ;
25+ export class DebuggerVariables implements IConditionalJupyterVariables , DebugAdapterTracker {
2726 private refreshEventEmitter = new EventEmitter < void > ( ) ;
2827 private lastKnownVariables : IJupyterVariable [ ] = [ ] ;
2928 private topMostFrameId = 0 ;
29+ private importedIntoKernel = new Set < string > ( ) ;
30+ private watchedNotebooks = new Map < string , Disposable [ ] > ( ) ;
31+ private debuggingStarted = false ;
3032 constructor (
3133 @inject ( IJupyterDebugService ) @named ( Identifiers . MULTIPLEXING_DEBUGSERVICE ) private debugService : IDebugService ,
3234 @inject ( IConfigurationService ) private configService : IConfigurationService
@@ -36,50 +38,68 @@ export class DebuggerVariables implements IJupyterVariables, DebugAdapterTracker
3638 return this . refreshEventEmitter . event ;
3739 }
3840
41+ public get active ( ) : boolean {
42+ return this . debugService . activeDebugSession !== undefined && this . debuggingStarted ;
43+ }
44+
3945 // IJupyterVariables implementation
4046 public async getVariables (
41- _notebook : INotebook ,
47+ notebook : INotebook ,
4248 request : IJupyterVariablesRequest
4349 ) : Promise < IJupyterVariablesResponse > {
50+ // Listen to notebook events if we haven't already
51+ this . watchNotebook ( notebook ) ;
52+
4453 const result : IJupyterVariablesResponse = {
4554 executionCount : request . executionCount ,
4655 pageStartIndex : 0 ,
4756 pageResponse : [ ] ,
4857 totalCount : 0
4958 } ;
5059
51- if ( this . debugService . activeDebugSession ) {
52- result . pageResponse = this . lastKnownVariables ;
60+ if ( this . active ) {
61+ const startPos = request . startIndex ? request . startIndex : 0 ;
62+ const chunkSize = request . pageSize ? request . pageSize : 100 ;
63+ result . pageStartIndex = startPos ;
64+
65+ // Do one at a time. All at once doesn't work as they all have to wait for each other anyway
66+ for ( let i = startPos ; i < startPos + chunkSize && i < this . lastKnownVariables . length ; i += 1 ) {
67+ const fullVariable = ! this . lastKnownVariables [ i ] . truncated
68+ ? this . lastKnownVariables [ i ]
69+ : await this . getFullVariable ( this . lastKnownVariables [ i ] , notebook ) ;
70+ this . lastKnownVariables [ i ] = fullVariable ;
71+ result . pageResponse . push ( fullVariable ) ;
72+ }
5373 result . totalCount = this . lastKnownVariables . length ;
5474 }
5575
5676 return result ;
5777 }
5878
59- public async getMatchingVariable ( _notebook : INotebook , name : string ) : Promise < IJupyterVariable | undefined > {
60- if ( this . debugService . activeDebugSession ) {
61- return this . lastKnownVariables . find ( ( v ) => v . name === name ) ;
79+ public async getMatchingVariableValue ( _notebook : INotebook , name : string ) : Promise < string | undefined > {
80+ if ( this . active ) {
81+ // Note, full variable results isn't necessary for this call. It only really needs the variable value.
82+ return this . lastKnownVariables . find ( ( v ) => v . name === name ) ?. value ;
6283 }
6384 }
6485
65- public async getDataFrameInfo ( targetVariable : IJupyterVariable , _notebook : INotebook ) : Promise < IJupyterVariable > {
66- if ( ! this . debugService . activeDebugSession ) {
86+ public async getDataFrameInfo ( targetVariable : IJupyterVariable , notebook : INotebook ) : Promise < IJupyterVariable > {
87+ if ( ! this . active ) {
6788 // No active server just return the unchanged target variable
6889 return targetVariable ;
6990 }
91+ // Listen to notebook events if we haven't already
92+ this . watchNotebook ( notebook ) ;
7093
7194 // See if we imported or not into the kernel our special function
72- if ( ! this . imported ) {
73- this . imported = await this . importDataFrameScripts ( ) ;
74- }
95+ await this . importDataFrameScripts ( notebook ) ;
7596
7697 // Then eval calling the main function with our target variable
77- const results = await this . debugService . activeDebugSession . customRequest ( ' evaluate' , {
78- expression : `${ DataFrameLoading . DataFrameInfoFunc } (${ targetVariable . name } )` ,
98+ const results = await this . evaluate (
99+ `${ DataFrameLoading . DataFrameInfoFunc } (${ targetVariable . name } )` ,
79100 // tslint:disable-next-line: no-any
80- frameId : ( targetVariable as any ) . frameId || this . topMostFrameId ,
81- context : 'repl'
82- } ) ;
101+ ( targetVariable as any ) . frameId
102+ ) ;
83103
84104 // Results should be the updated variable.
85105 return {
@@ -90,7 +110,7 @@ export class DebuggerVariables implements IJupyterVariables, DebugAdapterTracker
90110
91111 public async getDataFrameRows (
92112 targetVariable : IJupyterVariable ,
93- _notebook : INotebook ,
113+ notebook : INotebook ,
94114 start : number ,
95115 end : number
96116 ) : Promise < { } > {
@@ -99,58 +119,115 @@ export class DebuggerVariables implements IJupyterVariables, DebugAdapterTracker
99119 // No active server just return no rows
100120 return { } ;
101121 }
122+ // Listen to notebook events if we haven't already
123+ this . watchNotebook ( notebook ) ;
102124
103125 // See if we imported or not into the kernel our special function
104- if ( ! this . imported ) {
105- this . imported = await this . importDataFrameScripts ( ) ;
106- }
126+ await this . importDataFrameScripts ( notebook ) ;
107127
108- // Then eval calling the main function with our target variable
109- const minnedEnd = Math . min ( end , targetVariable . rowCount || 0 ) ;
110- const results = await this . debugService . activeDebugSession . customRequest ( 'evaluate' , {
111- expression : `${ DataFrameLoading . DataFrameRowFunc } (${ targetVariable . name } , ${ start } , ${ minnedEnd } )` ,
112- // tslint:disable-next-line: no-any
113- frameId : ( targetVariable as any ) . frameId || this . topMostFrameId ,
114- context : 'repl'
115- } ) ;
128+ // Since the debugger splits up long requests, split this based on the number of items.
116129
117- // Results should be the row.
118- return JSON . parse ( results . result . slice ( 1 , - 1 ) ) ;
130+ // Maximum 100 cells at a time or one row
131+ // tslint:disable-next-line: no-any
132+ let output : any ;
133+ const minnedEnd = Math . min ( targetVariable . rowCount || 0 , end ) ;
134+ const totalRowCount = end - start ;
135+ const cellsPerRow = targetVariable . columns ! . length ;
136+ const chunkSize = Math . floor ( Math . max ( 1 , Math . min ( 100 / cellsPerRow , totalRowCount / cellsPerRow ) ) ) ;
137+ for ( let pos = start ; pos < end ; pos += chunkSize ) {
138+ const chunkEnd = Math . min ( pos + chunkSize , minnedEnd ) ;
139+ const results = await this . evaluate (
140+ `${ DataFrameLoading . DataFrameRowFunc } (${ targetVariable . name } , ${ pos } , ${ chunkEnd } )` ,
141+ // tslint:disable-next-line: no-any
142+ ( targetVariable as any ) . frameId
143+ ) ;
144+ const chunkResults = JSON . parse ( results . result . slice ( 1 , - 1 ) ) ;
145+ if ( output && output . data ) {
146+ output = {
147+ ...output ,
148+ data : output . data . concat ( chunkResults . data )
149+ } ;
150+ } else {
151+ output = chunkResults ;
152+ }
153+ }
154+
155+ // Results should be the rows.
156+ return output ;
119157 }
120158
121- public onDidSendMessage ( message : DebugProtocol . Response ) {
159+ // tslint:disable-next-line: no-any
160+ public onDidSendMessage ( message : any ) {
122161 // If using the interactive debugger, update our variables.
123- if ( message . type === 'response' && message . command === 'variables' ) {
162+ if ( message . type === 'response' && message . command === 'initialize' ) {
163+ this . debuggingStarted = true ;
164+ } else if ( message . type === 'response' && message . command === 'variables' ) {
124165 // tslint:disable-next-line: no-suspicious-comment
125166 // TODO: Figure out what resource to use
126167 this . updateVariables ( undefined , message as DebugProtocol . VariablesResponse ) ;
127168 } else if ( message . type === 'response' && message . command === 'stackTrace' ) {
128169 // This should be the top frame. We need to use this to compute the value of a variable
129170 this . updateStackFrame ( message as DebugProtocol . StackTraceResponse ) ;
171+ } else if ( message . type === 'event' && message . event === 'terminated' ) {
172+ // When the debugger exits, make sure the variables are cleared
173+ this . lastKnownVariables = [ ] ;
174+ this . topMostFrameId = 0 ;
175+ this . debuggingStarted = false ;
176+ this . refreshEventEmitter . fire ( ) ;
130177 }
131178 }
132179
180+ private watchNotebook ( notebook : INotebook ) {
181+ const key = notebook . identity . toString ( ) ;
182+ if ( ! this . watchedNotebooks . has ( key ) ) {
183+ const disposables : Disposable [ ] = [ ] ;
184+ disposables . push ( notebook . onKernelChanged ( this . resetImport . bind ( this , key ) ) ) ;
185+ disposables . push ( notebook . onKernelRestarted ( this . resetImport . bind ( this , key ) ) ) ;
186+ disposables . push (
187+ notebook . onDisposed ( ( ) => {
188+ this . resetImport ( key ) ;
189+ disposables . forEach ( ( d ) => d . dispose ( ) ) ;
190+ this . watchedNotebooks . delete ( key ) ;
191+ } )
192+ ) ;
193+ this . watchedNotebooks . set ( key , disposables ) ;
194+ }
195+ }
196+
197+ private resetImport ( key : string ) {
198+ this . importedIntoKernel . delete ( key ) ;
199+ }
200+
133201 // tslint:disable-next-line: no-any
134- private async evalute ( code : string ) : Promise < any > {
202+ private async evaluate ( code : string , frameId ?: number ) : Promise < any > {
135203 if ( this . debugService . activeDebugSession ) {
136- return this . debugService . activeDebugSession . customRequest ( 'evaluate' , {
204+ const results = await this . debugService . activeDebugSession . customRequest ( 'evaluate' , {
137205 expression : code ,
138- frameId : this . topMostFrameId ,
206+ frameId : this . topMostFrameId || frameId ,
139207 context : 'repl'
140208 } ) ;
209+ if ( results && results . result !== 'None' ) {
210+ return results ;
211+ } else {
212+ traceError ( `Cannot evaluate ${ code } ` ) ;
213+ return undefined ;
214+ }
141215 }
142216 throw Error ( 'Debugger is not active, cannot evaluate.' ) ;
143217 }
144218
145- private async importDataFrameScripts ( ) : Promise < boolean > {
219+ private async importDataFrameScripts ( notebook : INotebook ) : Promise < void > {
146220 try {
147- await this . evalute ( DataFrameLoading . DataFrameSysImport ) ;
148- await this . evalute ( DataFrameLoading . DataFrameInfoImport ) ;
149- await this . evalute ( DataFrameLoading . DataFrameRowImport ) ;
150- return true ;
221+ const key = notebook . identity . toString ( ) ;
222+ if ( ! this . importedIntoKernel . has ( key ) ) {
223+ await this . evaluate ( DataFrameLoading . DataFrameSysImport ) ;
224+ await this . evaluate ( DataFrameLoading . DataFrameInfoImport ) ;
225+ await this . evaluate ( DataFrameLoading . DataFrameRowImport ) ;
226+ await this . evaluate ( DataFrameLoading . VariableInfoImport ) ;
227+ this . importedIntoKernel . add ( key ) ;
228+ }
151229 } catch ( exc ) {
152230 traceError ( 'Error attempting to import in debugger' , exc ) ;
153- return false ;
154231 }
155232 }
156233
@@ -160,6 +237,29 @@ export class DebuggerVariables implements IJupyterVariables, DebugAdapterTracker
160237 }
161238 }
162239
240+ private async getFullVariable ( variable : IJupyterVariable , notebook : INotebook ) : Promise < IJupyterVariable > {
241+ // See if we imported or not into the kernel our special function
242+ await this . importDataFrameScripts ( notebook ) ;
243+
244+ // Then eval calling the variable info function with our target variable
245+ const results = await this . evaluate (
246+ `${ DataFrameLoading . VariableInfoFunc } (${ variable . name } )` ,
247+ // tslint:disable-next-line: no-any
248+ ( variable as any ) . frameId
249+ ) ;
250+ if ( results ) {
251+ // Results should be the updated variable.
252+ return {
253+ ...variable ,
254+ truncated : false ,
255+ ...JSON . parse ( results . result . slice ( 1 , - 1 ) )
256+ } ;
257+ } else {
258+ // If no results, just return current value. Better than nothing.
259+ return variable ;
260+ }
261+ }
262+
163263 private updateVariables ( resource : Resource , variablesResponse : DebugProtocol . VariablesResponse ) {
164264 const exclusionList = this . configService . getSettings ( resource ) . datascience . variableExplorerExclude
165265 ? this . configService . getSettings ( ) . datascience . variableExplorerExclude ?. split ( ';' )
@@ -193,7 +293,7 @@ export class DebuggerVariables implements IJupyterVariables, DebugAdapterTracker
193293 size : 0 ,
194294 supportsDataExplorer : DataViewableTypes . has ( v . type || '' ) ,
195295 value : v . value ,
196- truncated : false ,
296+ truncated : true ,
197297 frameId : v . variablesReference
198298 } ;
199299 } ) ;
0 commit comments