@@ -12,6 +12,7 @@ import { getSettings } from '../react-common/settingsReactSide';
1212import  {  CollapseButton  }  from  './collapseButton' ; 
1313import  {  VariableExplorerButtonCellFormatter  }  from  './variableExplorerButtonCellFormatter' ; 
1414import  {  CellStyle ,  VariableExplorerCellFormatter  }  from  './variableExplorerCellFormatter' ; 
15+ import  {  VariableExplorerEmptyRowsView  }  from  './variableExplorerEmptyRows' ; 
1516
1617import  *  as  AdazzleReactDataGrid  from  'react-data-grid' ; 
1718
@@ -32,21 +33,27 @@ interface IVariableExplorerState {
3233 gridHeight : number ; 
3334 height : number ; 
3435 fontSize : number ; 
36+  sortDirection : string ; 
37+  sortColumn : string  |  number ; 
3538} 
3639
3740const  defaultColumnProperties  =  { 
3841 filterable : false , 
39-  sortable : false , 
42+  sortable : true , 
4043 resizable : true 
4144} ; 
4245
46+ // Sanity check on our string comparisons 
47+ const  MaxStringCompare  =  400 ; 
48+ 
4349interface  IGridRow  { 
4450 // tslint:disable-next-line:no-any 
4551 [ name : string ] : any ; 
4652} 
4753
4854export  class  VariableExplorer  extends  React . Component < IVariableExplorerProps ,  IVariableExplorerState >  { 
4955 private  divRef : React . RefObject < HTMLDivElement > ; 
56+  private  variableFetchCount : number ; 
5057
5158 constructor ( prop : IVariableExplorerProps )  { 
5259 super ( prop ) ; 
@@ -55,16 +62,19 @@ export class VariableExplorer extends React.Component<IVariableExplorerProps, IV
5562 { key : 'type' ,  name : getLocString ( 'DataScience.variableExplorerTypeColumn' ,  'Type' ) ,  type : 'string' ,  width : 120 } , 
5663 { key : 'size' ,  name : getLocString ( 'DataScience.variableExplorerSizeColumn' ,  'Count' ) ,  type : 'string' ,  width : 120 ,  formatter : < VariableExplorerCellFormatter  cellStyle = { CellStyle . numeric }  /> } , 
5764 { key : 'value' ,  name : getLocString ( 'DataScience.variableExplorerValueColumn' ,  'Value' ) ,  type : 'string' ,  width : 300 } , 
58-  { key : 'buttons' ,  name : '' ,  type : 'boolean' ,  width : 34 ,  formatter : < VariableExplorerButtonCellFormatter  showDataExplorer = { this . props . showDataExplorer }  baseTheme = { this . props . baseTheme }  />  } 
65+  { key : 'buttons' ,  name : '' ,  type : 'boolean' ,  width : 34 ,  sortable :  false ,   resizable :  false ,   formatter : < VariableExplorerButtonCellFormatter  showDataExplorer = { this . props . showDataExplorer }  baseTheme = { this . props . baseTheme }  />  } 
5966 ] ; 
6067 this . state  =  {  open : false , 
6168 gridColumns : columns , 
6269 gridRows : [ ] , 
6370 gridHeight : 200 , 
6471 height : 0 , 
65-  fontSize : 14 } ; 
72+  fontSize : 14 , 
73+  sortColumn : 'name' , 
74+  sortDirection : 'NONE' } ; 
6675
6776 this . divRef  =  React . createRef < HTMLDivElement > ( ) ; 
77+  this . variableFetchCount  =  0 ; 
6878 } 
6979
7080 public  render ( )  { 
@@ -86,13 +96,15 @@ export class VariableExplorer extends React.Component<IVariableExplorerProps, IV
8696 < div  className = { contentClassName } > 
8797 < div  id = 'variable-explorer-data-grid' > 
8898 < AdazzleReactDataGrid 
89-  columns  =  { this . state . gridColumns . map ( c  =>  {  return  { ...c ,  ...defaultColumnProperties } ;  } ) } 
99+  columns  =  { this . state . gridColumns . map ( c  =>  {  return  { ...defaultColumnProperties ,  ...c   } ;  } ) } 
90100 rowGetter  =  { this . getRow } 
91101 rowsCount  =  { this . state . gridRows . length } 
92102 minHeight  =  { this . state . gridHeight } 
93103 headerRowHeight  =  { this . state . fontSize  +  9 } 
94104 rowHeight  =  { this . state . fontSize  +  9 } 
95105 onRowDoubleClick  =  { this . rowDoubleClick } 
106+  onGridSort  =  { this . sortRows } 
107+  emptyRowsView  =  { VariableExplorerEmptyRowsView } 
96108 /> 
97109 </ div > 
98110 </ div > 
@@ -126,10 +138,15 @@ export class VariableExplorer extends React.Component<IVariableExplorerProps, IV
126138 // Help to keep us independent of main history window state if we choose to break out the variable explorer 
127139 public  newVariablesData ( newVariables : IJupyterVariable [ ] )  { 
128140 const  newGridRows  =  newVariables . map ( newVar  =>  { 
129-  return  {  buttons : { name : newVar . name ,  supportsDataExplorer : newVar . supportsDataExplorer } ,  name : newVar . name ,  type : newVar . type ,  size : '' ,  value : getLocString ( 'DataScience.variableLoadingValue' ,  'Loading...' ) } ; 
141+  return  {  buttons : { name : newVar . name ,  supportsDataExplorer : newVar . supportsDataExplorer } , 
142+  name : newVar . name , 
143+  type : newVar . type , 
144+  size : '' , 
145+  value : getLocString ( 'DataScience.variableLoadingValue' ,  'Loading...' ) } ; 
130146 } ) ; 
131147
132148 this . setState ( {  gridRows : newGridRows } ) ; 
149+  this . variableFetchCount  =  newGridRows . length ; 
133150 } 
134151
135152 // Update the value of a single variable already in our list 
@@ -148,13 +165,22 @@ export class VariableExplorer extends React.Component<IVariableExplorerProps, IV
148165 newSize  =  newVariable . count . toString ( ) ; 
149166 } 
150167
151-  const  newGridRow  =  { ...newGridRows [ i ] ,  value : newVariable . value ,  size : newSize } ; 
168+  const  newGridRow  =  { ...newGridRows [ i ] , 
169+  value : newVariable . value , 
170+  size : newSize } ; 
152171
153172 newGridRows [ i ]  =  newGridRow ; 
154173 } 
155174 } 
156175
157-  this . setState ( {  gridRows : newGridRows  } ) ; 
176+  // Update that we have retreived a new variable 
177+  // When we hit zero we have all the vars and can sort our values 
178+  this . variableFetchCount  =  this . variableFetchCount  -  1 ; 
179+  if  ( this . variableFetchCount  ===  0 )  { 
180+  this . setState ( {  gridRows : this . internalSortRows ( newGridRows ,  this . state . sortColumn ,  this . state . sortDirection )  } ) ; 
181+  }  else  { 
182+  this . setState ( {  gridRows : newGridRows  } ) ; 
183+  } 
158184 } 
159185
160186 public  toggleInputBlock  =  ( )  =>  { 
@@ -169,6 +195,94 @@ export class VariableExplorer extends React.Component<IVariableExplorerProps, IV
169195 this . props . variableExplorerToggled ( ! this . state . open ) ; 
170196 } 
171197
198+  public  sortRows  =  ( sortColumn : string  |  number ,  sortDirection : string )  =>  { 
199+  this . setState ( { 
200+  sortColumn, 
201+  sortDirection, 
202+  gridRows : this . internalSortRows ( this . state . gridRows ,  sortColumn ,  sortDirection ) 
203+  } ) ; 
204+  } 
205+ 
206+  private  getColumnType ( key : string  |  number )  : string  |  undefined  { 
207+  let  column ; 
208+  if  ( typeof  key  ===  'string' )  { 
209+  //tslint:disable-next-line:no-any 
210+  column  =  this . state . gridColumns . find ( c  =>  c . key  ===  key )  as  any ; 
211+  }  else  { 
212+  // This is the index lookup 
213+  column  =  this . state . gridColumns [ key ] ; 
214+  } 
215+ 
216+  // Special case our size column, it's displayed as a string 
217+  // but we will sort it like a number 
218+  if  ( column  &&  column . key  ===  'size' )  { 
219+  return  'number' ; 
220+  }  else  if  ( column  &&  column . type )  { 
221+  return  column . type ; 
222+  } 
223+  } 
224+ 
225+  private  internalSortRows  =  ( gridRows : IGridRow [ ] ,  sortColumn : string  |  number ,  sortDirection : string ) : IGridRow [ ]  =>  { 
226+  // Default to the name column 
227+  if  ( sortDirection  ===  'NONE' )  { 
228+  sortColumn  =  'name' ; 
229+  sortDirection  =  'ASC' ; 
230+  } 
231+ 
232+  const  columnType  =  this . getColumnType ( sortColumn ) ; 
233+  const  isStringColumn  =  columnType  ===  'string'  ||  columnType  ===  'object' ; 
234+  const  invert  =  sortDirection  !==  'DESC' ; 
235+ 
236+  // Use a special comparer for string columns as we can't compare too much of a string 
237+  // or it will take too long 
238+  const  comparer  =  isStringColumn  ?
239+  //tslint:disable-next-line:no-any 
240+  ( a : any ,  b : any ) : number  =>  { 
241+  const  aVal  =  a [ sortColumn ]  as  string ; 
242+  const  bVal  =  b [ sortColumn ]  as  string ; 
243+  const  aStr  =  aVal  ? aVal . substring ( 0 ,  Math . min ( aVal . length ,  MaxStringCompare ) ) . toUpperCase ( )  : aVal ; 
244+  const  bStr  =  bVal  ? bVal . substring ( 0 ,  Math . min ( bVal . length ,  MaxStringCompare ) ) . toUpperCase ( )  : bVal ; 
245+  const  result  =  aStr  >  bStr  ? - 1  : 1 ; 
246+  return  invert  ? - 1  *  result  : result ; 
247+  }  :
248+  //tslint:disable-next-line:no-any 
249+  ( a : any ,  b : any ) : number  =>  { 
250+  const  aVal  =  this . getComparisonValue ( a ,  sortColumn ) ; 
251+  const  bVal  =  this . getComparisonValue ( b ,  sortColumn ) ; 
252+  const  result  =  aVal  >  bVal  ? - 1  : 1 ; 
253+  return  invert  ? - 1  *  result  : result ; 
254+  } ; 
255+ 
256+  return  gridRows . sort ( comparer ) ; 
257+  } 
258+ 
259+  // Get the numerical comparison value for a column 
260+  private  getComparisonValue ( gridRow : IGridRow ,  sortColumn : string  |  number ) : number  { 
261+  return  ( sortColumn  ===  'size' )  ? this . sizeColumnComparisonValue ( gridRow )  : gridRow [ sortColumn ] ; 
262+  } 
263+ 
264+  // The size column needs special casing 
265+  private  sizeColumnComparisonValue ( gridRow : IGridRow ) : number  { 
266+  const  sizeStr : string  =  gridRow . size  as  string ; 
267+ 
268+  if  ( ! sizeStr )  { 
269+  return  - 1 ; 
270+  } 
271+ 
272+  let  sizeNumber  =  - 1 ; 
273+  const  commaIndex  =  sizeStr . indexOf ( ',' ) ; 
274+  // First check the shape case like so (5000,1000) in this case we want the 5000 to compare with 
275+  if  ( sizeStr [ 0 ]  ===  '('  &&  commaIndex  >  0 )  { 
276+  sizeNumber  =  parseInt ( sizeStr . substring ( 1 ,  commaIndex ) ,  10 ) ; 
277+  }  else  { 
278+  // If not in the shape format, assume a to i conversion 
279+  sizeNumber  =  parseInt ( sizeStr ,  10 ) ; 
280+  } 
281+ 
282+  // If our parse fails we get NaN for any case that like return -1 
283+  return  isNaN ( sizeNumber )  ? - 1  : sizeNumber ; 
284+  } 
285+ 
172286 private  rowDoubleClick  =  ( _rowIndex : number ,  row : IGridRow )  =>  { 
173287 // On row double click, see if data explorer is supported and open it if it is 
174288 if  ( row . buttons  &&  row . buttons . supportsDataExplorer  !==  undefined 
0 commit comments