@@ -25,6 +25,7 @@ import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/commo
25
25
import { localize } from 'vs/nls' ;
26
26
import { IContextKey , IContextKeyService , RawContextKey } from 'vs/platform/contextkey/common/contextkey' ;
27
27
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding' ;
28
+ import { ILogService } from 'vs/platform/log/common/log' ;
28
29
import { defaultListStyles } from 'vs/platform/theme/browser/defaultStyles' ;
29
30
import {
30
31
editorWidgetBackground ,
@@ -72,6 +73,7 @@ export class RenameInputField implements IContentWidget {
72
73
@IThemeService private readonly _themeService : IThemeService ,
73
74
@IKeybindingService private readonly _keybindingService : IKeybindingService ,
74
75
@IContextKeyService contextKeyService : IContextKeyService ,
76
+ @ILogService private readonly _logService : ILogService ,
75
77
) {
76
78
this . _visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE . bindTo ( contextKeyService ) ;
77
79
this . _focusedContextKey = CONTEXT_RENAME_INPUT_FOCUSED . bindTo ( contextKeyService ) ;
@@ -105,15 +107,15 @@ export class RenameInputField implements IContentWidget {
105
107
this . _input . className = 'rename-input' ;
106
108
this . _input . type = 'text' ;
107
109
this . _input . setAttribute ( 'aria-label' , localize ( 'renameAriaLabel' , "Rename input. Type new name and press Enter to commit." ) ) ;
108
- // TODO@ulugbekna : is using addDisposableListener's right way to do it?
109
110
this . _disposables . add ( addDisposableListener ( this . _input , 'focus' , ( ) => { this . _focusedContextKey . set ( true ) ; } ) ) ;
110
111
this . _disposables . add ( addDisposableListener ( this . _input , 'blur' , ( ) => { this . _focusedContextKey . reset ( ) ; } ) ) ;
111
112
this . _domNode . appendChild ( this . _input ) ;
112
113
113
- this . _candidatesView = new CandidatesView ( this . _domNode , {
114
- fontInfo : this . _editor . getOption ( EditorOption . fontInfo ) ,
115
- onSelectionChange : ( ) => this . acceptInput ( false ) // we don't allow preview with mouse click for now
116
- } ) ;
114
+ this . _candidatesView = this . _disposables . add (
115
+ new CandidatesView ( this . _domNode , {
116
+ fontInfo : this . _editor . getOption ( EditorOption . fontInfo ) ,
117
+ onSelectionChange : ( ) => this . acceptInput ( false ) // we don't allow preview with mouse click for now
118
+ } ) ) ;
117
119
118
120
this . _label = document . createElement ( 'div' ) ;
119
121
this . _label . className = 'rename-label' ;
@@ -177,9 +179,10 @@ export class RenameInputField implements IContentWidget {
177
179
178
180
const bodyBox = getClientArea ( this . getDomNode ( ) . ownerDocument . body ) ;
179
181
const editorBox = getDomNodePagePosition ( this . _editor . getDomNode ( ) ) ;
180
- const cursorBox = this . _editor . getScrolledVisiblePosition ( this . _position ! ) ;
181
182
182
- this . _nPxAvailableAbove = cursorBox . top + editorBox . top ;
183
+ const cursorBoxTop = this . _getTopForPosition ( ) ;
184
+
185
+ this . _nPxAvailableAbove = cursorBoxTop + editorBox . top ;
183
186
this . _nPxAvailableBelow = bodyBox . height - this . _nPxAvailableAbove ;
184
187
185
188
const lineHeight = this . _editor . getOption ( EditorOption . lineHeight ) ;
@@ -199,16 +202,16 @@ export class RenameInputField implements IContentWidget {
199
202
const [ accept , preview ] = this . _acceptKeybindings ;
200
203
this . _label ! . innerText = localize ( { key : 'label' , comment : [ 'placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"' ] } , "{0} to Rename, {1} to Preview" , this . _keybindingService . lookupKeybinding ( accept ) ?. getLabel ( ) , this . _keybindingService . lookupKeybinding ( preview ) ?. getLabel ( ) ) ;
201
204
202
- this . _domNode ! . style . minWidth = `250px` ; // to prevent from widening when candidates come in
203
- this . _domNode ! . style . maxWidth = `400px` ; // TODO@ulugbekna : what if we have a very long name?
205
+ this . _domNode ! . style . minWidth = `200px` ; // to prevent from widening when candidates come in
204
206
205
207
return null ;
206
208
}
207
209
208
210
afterRender ( position : ContentWidgetPositionPreference | null ) : void {
211
+ this . _trace ( 'invoking afterRender, position: ' , position ? 'not null' : 'null' ) ;
209
212
if ( position === null ) {
210
213
// cancel rename when input widget isn't rendered anymore
211
- this . cancelInput ( true ) ;
214
+ this . cancelInput ( true , 'afterRender (because position is null)' ) ;
212
215
return ;
213
216
}
214
217
@@ -241,10 +244,12 @@ export class RenameInputField implements IContentWidget {
241
244
private _currentCancelInput ?: ( focusEditor : boolean ) => void ;
242
245
243
246
acceptInput ( wantsPreview : boolean ) : void {
247
+ this . _trace ( `invoking acceptInput` ) ;
244
248
this . _currentAcceptInput ?.( wantsPreview ) ;
245
249
}
246
250
247
- cancelInput ( focusEditor : boolean ) : void {
251
+ cancelInput ( focusEditor : boolean , caller : string ) : void {
252
+ this . _trace ( `invoking cancelInput, caller: ${ caller } , _currentCancelInput: ${ this . _currentAcceptInput ? 'not undefined' : 'undefined' } ` ) ;
248
253
this . _currentCancelInput ?.( focusEditor ) ;
249
254
}
250
255
@@ -280,6 +285,7 @@ export class RenameInputField implements IContentWidget {
280
285
return new Promise < RenameInputFieldResult | boolean > ( resolve => {
281
286
282
287
this . _currentCancelInput = ( focusEditor ) => {
288
+ this . _trace ( 'invoking _currentCancelInput' ) ;
283
289
this . _currentAcceptInput = undefined ;
284
290
this . _currentCancelInput = undefined ;
285
291
this . _candidatesView ?. clearCandidates ( ) ;
@@ -288,12 +294,14 @@ export class RenameInputField implements IContentWidget {
288
294
} ;
289
295
290
296
this . _currentAcceptInput = ( wantsPreview ) => {
297
+ this . _trace ( 'invoking _currentAcceptInput' ) ;
291
298
assertType ( this . _input !== undefined ) ;
292
299
assertType ( this . _candidatesView !== undefined ) ;
293
300
294
- const candidateName = this . _candidatesView . focusedCandidate ;
295
- if ( ( candidateName === undefined && this . _input . value === value ) || this . _input . value . trim ( ) . length === 0 ) {
296
- this . cancelInput ( true ) ;
301
+ const newName = this . _candidatesView . focusedCandidate ?? this . _input . value ;
302
+
303
+ if ( newName === value || newName . trim ( ) . length === 0 /* is just whitespace */ ) {
304
+ this . cancelInput ( true , '_currentAcceptInput (because newName === value || newName.trim().length === 0)' ) ;
297
305
return ;
298
306
}
299
307
@@ -302,14 +310,14 @@ export class RenameInputField implements IContentWidget {
302
310
this . _candidatesView . clearCandidates ( ) ;
303
311
304
312
resolve ( {
305
- newName : candidateName ?? this . _input . value ,
313
+ newName,
306
314
wantsPreview : supportPreview && wantsPreview
307
315
} ) ;
308
316
} ;
309
317
310
- disposeOnDone . add ( cts . token . onCancellationRequested ( ( ) => this . cancelInput ( true ) ) ) ;
318
+ disposeOnDone . add ( cts . token . onCancellationRequested ( ( ) => this . cancelInput ( true , 'cts.token.onCancellationRequested' ) ) ) ;
311
319
if ( ! _sticky ) {
312
- disposeOnDone . add ( this . _editor . onDidBlurEditorWidget ( ( ) => this . cancelInput ( ! this . _domNode ?. ownerDocument . hasFocus ( ) ) ) ) ;
320
+ disposeOnDone . add ( this . _editor . onDidBlurEditorWidget ( ( ) => this . cancelInput ( ! this . _domNode ?. ownerDocument . hasFocus ( ) , 'editor.onDidBlurEditorWidget' ) ) ) ;
313
321
}
314
322
315
323
this . _show ( ) ;
@@ -321,6 +329,7 @@ export class RenameInputField implements IContentWidget {
321
329
}
322
330
323
331
private _show ( ) : void {
332
+ this . _trace ( 'invoking _show' ) ;
324
333
this . _editor . revealLineInCenterIfOutsideViewport ( this . _position ! . lineNumber , ScrollType . Smooth ) ;
325
334
this . _visible = true ;
326
335
this . _visibleContextKey . set ( true ) ;
@@ -335,9 +344,13 @@ export class RenameInputField implements IContentWidget {
335
344
}
336
345
337
346
private async _updateRenameCandidates ( candidates : ProviderResult < NewSymbolName [ ] > [ ] , currentName : string , token : CancellationToken ) {
347
+ const trace = ( ...args : any [ ] ) => this . _trace ( '_updateRenameCandidates' , ...args ) ;
348
+
349
+ trace ( 'start' ) ;
338
350
const namesListResults = await raceCancellation ( Promise . allSettled ( candidates ) , token ) ;
339
351
340
352
if ( namesListResults === undefined ) {
353
+ trace ( 'returning early - received updateRenameCandidates results - undefined' ) ;
341
354
return ;
342
355
}
343
356
@@ -346,39 +359,67 @@ export class RenameInputField implements IContentWidget {
346
359
? namesListResult . value
347
360
: [ ]
348
361
) ;
362
+ trace ( `received updateRenameCandidates results - total (unfiltered) ${ newNames . length } candidates.` ) ;
349
363
350
364
// deduplicate and filter out the current value
351
365
const distinctNames = arrays . distinct ( newNames , v => v . newSymbolName ) ;
366
+ trace ( `distinct candidates - ${ distinctNames . length } candidates.` ) ;
367
+
352
368
const validDistinctNames = distinctNames . filter ( ( { newSymbolName } ) => newSymbolName . trim ( ) . length > 0 && newSymbolName !== this . _input ?. value && newSymbolName !== currentName ) ;
369
+ trace ( `valid distinct candidates - ${ newNames . length } candidates.` ) ;
353
370
354
371
if ( validDistinctNames . length < 1 ) {
372
+ trace ( 'returning early - no valid distinct candidates' ) ;
355
373
return ;
356
374
}
357
375
358
376
// show the candidates
377
+ trace ( 'setting candidates' ) ;
359
378
this . _candidatesView ! . setCandidates ( validDistinctNames ) ;
360
379
361
380
// ask editor to re-layout given that the widget is now of a different size after rendering rename candidates
381
+ trace ( 'asking editor to re-layout' ) ;
362
382
this . _editor . layoutContentWidget ( this ) ;
363
383
}
364
384
365
385
private _hide ( ) : void {
386
+ this . _trace ( 'invoked _hide' ) ;
366
387
this . _visible = false ;
367
388
this . _visibleContextKey . reset ( ) ;
368
389
this . _editor . layoutContentWidget ( this ) ;
369
390
}
391
+
392
+ private _getTopForPosition ( ) : number {
393
+ const visibleRanges = this . _editor . getVisibleRanges ( ) ;
394
+ let firstLineInViewport : number ;
395
+ if ( visibleRanges . length > 0 ) {
396
+ firstLineInViewport = visibleRanges [ 0 ] . startLineNumber ;
397
+ } else {
398
+ this . _logService . warn ( 'RenameInputField#_getTopForPosition: this should not happen - visibleRanges is empty' ) ;
399
+ firstLineInViewport = Math . max ( 1 , this . _position ! . lineNumber - 5 ) ; // @ulugbekna : fallback to current line minus 5
400
+ }
401
+ return this . _editor . getTopForLineNumber ( this . _position ! . lineNumber ) - this . _editor . getTopForLineNumber ( firstLineInViewport ) ;
402
+ }
403
+
404
+ private _trace ( ...args : any [ ] ) {
405
+ this . _logService . trace ( 'RenameInputField' , ...args ) ;
406
+ }
370
407
}
371
408
372
- export class CandidatesView {
409
+ class CandidatesView {
373
410
374
411
private readonly _listWidget : List < NewSymbolName > ;
375
412
private readonly _listContainer : HTMLDivElement ;
376
413
377
414
private _lineHeight : number ;
378
415
private _availableHeight : number ;
379
416
417
+ private _disposables : DisposableStore ;
418
+
380
419
constructor ( parent : HTMLElement , opts : { fontInfo : FontInfo ; onSelectionChange : ( ) => void } ) {
381
420
421
+ this . _disposables = new DisposableStore ( ) ;
422
+
382
423
this . _availableHeight = 0 ;
383
424
384
425
this . _lineHeight = opts . fontInfo . lineHeight ;
@@ -397,7 +438,7 @@ export class CandidatesView {
397
438
}
398
439
399
440
getHeight ( element : NewSymbolName ) : number {
400
- return that . candidateViewHeight ;
441
+ return that . _candidateViewHeight ;
401
442
}
402
443
} ;
403
444
@@ -429,18 +470,22 @@ export class CandidatesView {
429
470
}
430
471
) ;
431
472
432
- this . _listWidget . onDidChangeSelection ( e => {
473
+ this . _disposables . add ( this . _listWidget . onDidChangeSelection ( e => {
433
474
if ( e . elements . length > 0 ) {
434
475
opts . onSelectionChange ( ) ;
435
476
}
436
- } ) ;
477
+ } ) ) ;
478
+
479
+ this . _disposables . add ( this . _listWidget . onDidBlur ( e => {
480
+ this . _listWidget . setFocus ( [ ] ) ;
481
+ } ) ) ;
437
482
438
483
this . _listWidget . style ( defaultListStyles ) ;
439
484
}
440
485
441
- public get candidateViewHeight ( ) : number {
442
- const { totalHeight } = CandidateView . getLayoutInfo ( { lineHeight : this . _lineHeight } ) ;
443
- return totalHeight ;
486
+ dispose ( ) {
487
+ this . _listWidget . dispose ( ) ;
488
+ this . _disposables . dispose ( ) ;
444
489
}
445
490
446
491
// height - max height allowed by parent element
@@ -451,18 +496,12 @@ export class CandidatesView {
451
496
}
452
497
}
453
498
454
- private _pickListHeight ( nCandidates : number ) {
455
- const heightToFitAllCandidates = this . candidateViewHeight * nCandidates ;
456
- const height = Math . min ( heightToFitAllCandidates , this . _availableHeight , this . candidateViewHeight * 7 /* max # of candidates we want to show at once */ ) ;
457
- return height ;
458
- }
459
-
460
499
public setCandidates ( candidates : NewSymbolName [ ] ) : void {
461
500
const height = this . _pickListHeight ( candidates . length ) ;
462
501
463
502
this . _listWidget . splice ( 0 , 0 , candidates ) ;
464
503
465
- this . _listWidget . layout ( height ) ;
504
+ this . _listWidget . layout ( height , this . _pickListWidth ( candidates ) ) ;
466
505
467
506
this . _listContainer . style . height = `${ height } px` ;
468
507
}
@@ -525,9 +564,25 @@ export class CandidatesView {
525
564
}
526
565
return focusedIx > 0 ;
527
566
}
567
+
568
+ private get _candidateViewHeight ( ) : number {
569
+ const { totalHeight } = CandidateView . getLayoutInfo ( { lineHeight : this . _lineHeight } ) ;
570
+ return totalHeight ;
571
+ }
572
+
573
+ private _pickListHeight ( nCandidates : number ) {
574
+ const heightToFitAllCandidates = this . _candidateViewHeight * nCandidates ;
575
+ const height = Math . min ( heightToFitAllCandidates , this . _availableHeight , this . _candidateViewHeight * 7 /* max # of candidates we want to show at once */ ) ;
576
+ return height ;
577
+ }
578
+
579
+ private _pickListWidth ( candidates : NewSymbolName [ ] ) : number {
580
+ return Math . max ( ...candidates . map ( c => c . newSymbolName . length ) ) * 7 /* approximate # of pixes taken by a single character */ ;
581
+ }
582
+
528
583
}
529
584
530
- export class CandidateView { // TODO @ulugbekna : remove export
585
+ class CandidateView {
531
586
532
587
// TODO@ulugbekna : accessibility
533
588
0 commit comments