44 *--------------------------------------------------------------------------------------------*/
55
66import 'vs/css!./media/processExplorer' ;
7- import { listProcesses } from 'vs/base/node/ps' ;
87import { webFrame , ipcRenderer , clipboard } from 'electron' ;
98import { repeat } from 'vs/base/common/strings' ;
109import { totalmem } from 'os' ;
@@ -16,31 +15,45 @@ import * as platform from 'vs/base/common/platform';
1615import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu' ;
1716import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu' ;
1817import { ProcessItem } from 'vs/base/common/processes' ;
18+ import { addDisposableListener } from 'vs/base/browser/dom' ;
19+ import { IDisposable } from 'vs/base/common/lifecycle' ;
20+
1921
20- let processList : any [ ] ;
2122let mapPidToWindowTitle = new Map < number , string > ( ) ;
2223
2324const DEBUG_FLAGS_PATTERN = / \s - - ( i n s p e c t | d e b u g ) ( - b r k | p o r t ) ? = ( \d + ) ? / ;
2425const DEBUG_PORT_PATTERN = / \s - - ( i n s p e c t | d e b u g ) - p o r t = ( \d + ) / ;
26+ const listeners : IDisposable [ ] = [ ] ;
27+ const collapsedStateCache : Map < string , boolean > = new Map < string , boolean > ( ) ;
28+ let lastRequestTime : number ;
29+
30+ interface FormattedProcessItem {
31+ cpu : string ;
32+ memory : string ;
33+ pid : string ;
34+ name : string ;
35+ formattedName : string ;
36+ cmd : string ;
37+ }
2538
26- function getProcessList ( rootProcess : ProcessItem ) {
27- const processes : any [ ] = [ ] ;
39+ function getProcessList ( rootProcess : ProcessItem , isLocal : boolean ) : FormattedProcessItem [ ] {
40+ const processes : FormattedProcessItem [ ] = [ ] ;
2841
2942 if ( rootProcess ) {
30- getProcessItem ( processes , rootProcess , 0 ) ;
43+ getProcessItem ( processes , rootProcess , 0 , isLocal ) ;
3144 }
3245
3346 return processes ;
3447}
3548
36- function getProcessItem ( processes : any [ ] , item : ProcessItem , indent : number ) : void {
49+ function getProcessItem ( processes : FormattedProcessItem [ ] , item : ProcessItem , indent : number , isLocal : boolean ) : void {
3750 const isRoot = ( indent === 0 ) ;
3851
3952 const MB = 1024 * 1024 ;
4053
4154 let name = item . name ;
4255 if ( isRoot ) {
43- name = `${ product . applicationName } main` ;
56+ name = isLocal ? `${ product . applicationName } main` : 'remote agent' ;
4457 }
4558
4659 if ( name === 'window' ) {
@@ -52,17 +65,17 @@ function getProcessItem(processes: any[], item: ProcessItem, indent: number): vo
5265 const formattedName = isRoot ? name : `${ repeat ( ' ' , indent ) } ${ name } ` ;
5366 const memory = process . platform === 'win32' ? item . mem : ( totalmem ( ) * ( item . mem / 100 ) ) ;
5467 processes . push ( {
55- cpu : Number ( item . load . toFixed ( 0 ) ) ,
56- memory : Number ( ( memory / MB ) . toFixed ( 0 ) ) ,
57- pid : Number ( ( item . pid ) . toFixed ( 0 ) ) ,
68+ cpu : item . load . toFixed ( 0 ) ,
69+ memory : ( memory / MB ) . toFixed ( 0 ) ,
70+ pid : item . pid . toFixed ( 0 ) ,
5871 name,
5972 formattedName,
6073 cmd : item . cmd
6174 } ) ;
6275
6376 // Recurse into children if any
6477 if ( Array . isArray ( item . children ) ) {
65- item . children . forEach ( child => getProcessItem ( processes , child , indent + 1 ) ) ;
78+ item . children . forEach ( child => getProcessItem ( processes , child , indent + 1 , isLocal ) ) ;
6679 }
6780}
6881
@@ -71,7 +84,7 @@ function isDebuggable(cmd: string): boolean {
7184 return ( matches && matches . length >= 2 ) || cmd . indexOf ( 'node ' ) >= 0 || cmd . indexOf ( 'node.exe' ) >= 0 ;
7285}
7386
74- function attachTo ( item : ProcessItem ) {
87+ function attachTo ( item : FormattedProcessItem ) {
7588 const config : any = {
7689 type : 'node' ,
7790 request : 'attach' ,
@@ -113,35 +126,57 @@ function getProcessIdWithHighestProperty(processList: any[], propertyName: strin
113126 return maxProcessId ;
114127}
115128
116- function updateProcessInfo ( processList : any [ ] ) : void {
129+ function updateSectionCollapsedState ( shouldExpand : boolean , body : HTMLElement , twistie : HTMLImageElement , sectionName : string ) {
130+ if ( shouldExpand ) {
131+ body . classList . remove ( 'hidden' ) ;
132+ collapsedStateCache . set ( sectionName , false ) ;
133+ twistie . src = './media/expanded.svg' ;
134+ } else {
135+ body . classList . add ( 'hidden' ) ;
136+ collapsedStateCache . set ( sectionName , true ) ;
137+ twistie . src = './media/collapsed.svg' ;
138+ }
139+ }
140+
141+ function renderTableSection ( sectionName : string , processList : FormattedProcessItem [ ] , renderManySections : boolean , sectionIsLocal : boolean ) : void {
117142 const container = document . getElementById ( 'process-list' ) ;
118143 if ( ! container ) {
119144 return ;
120145 }
121146
122- container . innerHTML = '' ;
123147 const highestCPUProcess = getProcessIdWithHighestProperty ( processList , 'cpu' ) ;
124148 const highestMemoryProcess = getProcessIdWithHighestProperty ( processList , 'memory' ) ;
125149
126- const tableHead = document . createElement ( 'thead' ) ;
127- tableHead . innerHTML = `<tr>
128- <th scope="col" class="cpu">${ localize ( 'cpu' , "CPU %" ) } </th>
129- <th scope="col" class="memory">${ localize ( 'memory' , "Memory (MB)" ) } </th>
130- <th scope="col" class="pid">${ localize ( 'pid' , "pid" ) } </th>
131- <th scope="col" class="nameLabel">${ localize ( 'name' , "Name" ) } </th>
132- </tr>` ;
150+ const body = document . createElement ( 'tbody' ) ;
151+
152+ if ( renderManySections ) {
153+ const headerRow = document . createElement ( 'tr' ) ;
154+ const data = document . createElement ( 'td' ) ;
155+ data . textContent = sectionName ;
156+ data . colSpan = 4 ;
157+ headerRow . appendChild ( data ) ;
158+
159+ const twistie = document . createElement ( 'img' ) ;
160+ updateSectionCollapsedState ( ! collapsedStateCache . get ( sectionName ) , body , twistie , sectionName ) ;
161+ data . prepend ( twistie ) ;
133162
134- const tableBody = document . createElement ( 'tbody' ) ;
163+ listeners . push ( addDisposableListener ( data , 'click' , ( e ) => {
164+ const isHidden = body . classList . contains ( 'hidden' ) ;
165+ updateSectionCollapsedState ( isHidden , body , twistie , sectionName ) ;
166+ } ) ) ;
167+
168+ container . appendChild ( headerRow ) ;
169+ }
135170
136171 processList . forEach ( p => {
137172 const row = document . createElement ( 'tr' ) ;
138- row . id = p . pid ;
173+ row . id = p . pid . toString ( ) ;
139174
140175 const cpu = document . createElement ( 'td' ) ;
141176 p . pid === highestCPUProcess
142177 ? cpu . classList . add ( 'centered' , 'highest' )
143178 : cpu . classList . add ( 'centered' ) ;
144- cpu . textContent = p . cpu ;
179+ cpu . textContent = p . cpu . toString ( ) ;
145180
146181 const memory = document . createElement ( 'td' ) ;
147182 p . pid === highestMemoryProcess
@@ -160,22 +195,53 @@ function updateProcessInfo(processList: any[]): void {
160195 name . textContent = p . formattedName ;
161196
162197 row . append ( cpu , memory , pid , name ) ;
163- tableBody . appendChild ( row ) ;
198+
199+ listeners . push ( addDisposableListener ( row , 'contextmenu' , ( e ) => {
200+ showContextMenu ( e , p , sectionIsLocal ) ;
201+ } ) ) ;
202+
203+ body . appendChild ( row ) ;
164204 } ) ;
165205
166- container . append ( tableHead , tableBody ) ;
206+ container . appendChild ( body ) ;
207+ }
208+
209+ function updateProcessInfo ( processLists : { [ key : string ] : ProcessItem } ) : void {
210+ const container = document . getElementById ( 'process-list' ) ;
211+ if ( ! container ) {
212+ return ;
213+ }
214+
215+ container . innerHTML = '' ;
216+ listeners . forEach ( l => l . dispose ( ) ) ;
217+
218+ const tableHead = document . createElement ( 'thead' ) ;
219+ tableHead . innerHTML = `<tr>
220+ <th scope="col" class="cpu">${ localize ( 'cpu' , "CPU %" ) } </th>
221+ <th scope="col" class="memory">${ localize ( 'memory' , "Memory (MB)" ) } </th>
222+ <th scope="col" class="pid">${ localize ( 'pid' , "pid" ) } </th>
223+ <th scope="col" class="nameLabel">${ localize ( 'name' , "Name" ) } </th>
224+ </tr>` ;
225+
226+ container . append ( tableHead ) ;
227+
228+ const hasMultipleMachines = Object . keys ( processLists ) . length > 1 ;
229+ Object . keys ( processLists ) . forEach ( ( key , i ) => {
230+ const isLocal = i === 0 ;
231+ renderTableSection ( key , getProcessList ( processLists [ key ] , isLocal ) , hasMultipleMachines , isLocal ) ;
232+ } ) ;
167233}
168234
169235function applyStyles ( styles : ProcessExplorerStyles ) : void {
170236 const styleTag = document . createElement ( 'style' ) ;
171237 const content : string [ ] = [ ] ;
172238
173239 if ( styles . hoverBackground ) {
174- content . push ( `tbody > tr:hover { background-color: ${ styles . hoverBackground } ; }` ) ;
240+ content . push ( `tbody > tr:hover, table > tr:hover { background-color: ${ styles . hoverBackground } ; }` ) ;
175241 }
176242
177243 if ( styles . hoverForeground ) {
178- content . push ( `tbody > tr:hover{ color: ${ styles . hoverForeground } ; }` ) ;
244+ content . push ( `tbody > tr:hover, table > tr:hover { color: ${ styles . hoverForeground } ; }` ) ;
179245 }
180246
181247 if ( styles . highlightForeground ) {
@@ -200,13 +266,13 @@ function applyZoom(zoomLevel: number): void {
200266 browser . setZoomLevel ( webFrame . getZoomLevel ( ) , /*isTrusted*/ false ) ;
201267}
202268
203- function showContextMenu ( e : MouseEvent ) {
269+ function showContextMenu ( e : MouseEvent , item : FormattedProcessItem , isLocal : boolean ) {
204270 e . preventDefault ( ) ;
205271
206272 const items : IContextMenuItem [ ] = [ ] ;
273+ const pid = Number ( item . pid ) ;
207274
208- const pid = parseInt ( ( e . currentTarget as HTMLElement ) . id ) ;
209- if ( pid && typeof pid === 'number' ) {
275+ if ( isLocal ) {
210276 items . push ( {
211277 label : localize ( 'killProcess' , "Kill Process" ) ,
212278 click ( ) {
@@ -224,55 +290,60 @@ function showContextMenu(e: MouseEvent) {
224290 items . push ( {
225291 type : 'separator'
226292 } ) ;
293+ }
227294
228- items . push ( {
229- label : localize ( 'copy' , "Copy" ) ,
230- click ( ) {
231- const row = document . getElementById ( pid . toString ( ) ) ;
232- if ( row ) {
233- clipboard . writeText ( row . innerText ) ;
234- }
295+ items . push ( {
296+ label : localize ( 'copy' , "Copy" ) ,
297+ click ( ) {
298+ const row = document . getElementById ( pid . toString ( ) ) ;
299+ if ( row ) {
300+ clipboard . writeText ( row . innerText ) ;
235301 }
236- } ) ;
302+ }
303+ } ) ;
237304
238- items . push ( {
239- label : localize ( 'copyAll' , "Copy All" ) ,
240- click ( ) {
241- const processList = document . getElementById ( 'process-list' ) ;
242- if ( processList ) {
243- clipboard . writeText ( processList . innerText ) ;
244- }
305+ items . push ( {
306+ label : localize ( 'copyAll' , "Copy All" ) ,
307+ click ( ) {
308+ const processList = document . getElementById ( 'process-list' ) ;
309+ if ( processList ) {
310+ clipboard . writeText ( processList . innerText ) ;
245311 }
312+ }
313+ } ) ;
314+
315+ if ( item && isLocal && isDebuggable ( item . cmd ) ) {
316+ items . push ( {
317+ type : 'separator'
246318 } ) ;
247319
248- const item = processList . filter ( process => process . pid === pid ) [ 0 ] ;
249- if ( item && isDebuggable ( item . cmd ) ) {
250- items . push ( {
251- type : 'separator'
252- } ) ;
253-
254- items . push ( {
255- label : localize ( 'debug' , "Debug" ) ,
256- click ( ) {
257- attachTo ( item ) ;
258- }
259- } ) ;
260- }
261- } else {
262320 items . push ( {
263- label : localize ( 'copyAll ' , "Copy All " ) ,
321+ label : localize ( 'debug ' , "Debug " ) ,
264322 click ( ) {
265- const processList = document . getElementById ( 'process-list' ) ;
266- if ( processList ) {
267- clipboard . writeText ( processList . innerText ) ;
268- }
323+ attachTo ( item ) ;
269324 }
270325 } ) ;
271326 }
272327
273328 popup ( items ) ;
274329}
275330
331+ function requestProcessList ( totalWaitTime : number ) : void {
332+ setTimeout ( ( ) => {
333+ const nextRequestTime = Date . now ( ) ;
334+ const waited = totalWaitTime + nextRequestTime - lastRequestTime ;
335+ lastRequestTime = nextRequestTime ;
336+
337+ // Wait at least a second between requests.
338+ if ( waited > 1000 ) {
339+ ipcRenderer . send ( 'windowsInfoRequest' ) ;
340+ ipcRenderer . send ( 'vscode:listProcesses' ) ;
341+ } else {
342+ requestProcessList ( waited ) ;
343+ }
344+ } , 200 ) ;
345+ }
346+
276347export function startup ( data : ProcessExplorerData ) : void {
277348 applyStyles ( data . styles ) ;
278349 applyZoom ( data . zoomLevel ) ;
@@ -283,23 +354,14 @@ export function startup(data: ProcessExplorerData): void {
283354 windows . forEach ( window => mapPidToWindowTitle . set ( window . pid , window . title ) ) ;
284355 } ) ;
285356
286- setInterval ( ( ) => {
287- ipcRenderer . send ( 'windowsInfoRequest' ) ;
288-
289- listProcesses ( data . pid ) . then ( processes => {
290- processList = getProcessList ( processes ) ;
291- updateProcessInfo ( processList ) ;
292-
293- const tableRows = document . getElementsByTagName ( 'tr' ) ;
294- for ( let i = 0 ; i < tableRows . length ; i ++ ) {
295- const tableRow = tableRows [ i ] ;
296- tableRow . addEventListener ( 'contextmenu' , ( e ) => {
297- showContextMenu ( e ) ;
298- } ) ;
299- }
300- } ) ;
301- } , 1200 ) ;
357+ ipcRenderer . on ( 'vscode:listProcessesResponse' , ( _event : Event , processRoots : { [ key : string ] : ProcessItem } ) => {
358+ updateProcessInfo ( processRoots ) ;
359+ requestProcessList ( 0 ) ;
360+ } ) ;
302361
362+ lastRequestTime = Date . now ( ) ;
363+ ipcRenderer . send ( 'windowsInfoRequest' ) ;
364+ ipcRenderer . send ( 'vscode:listProcesses' ) ;
303365
304366 document . onkeydown = ( e : KeyboardEvent ) => {
305367 const cmdOrCtrlKey = platform . isMacintosh ? e . metaKey : e . ctrlKey ;
0 commit comments