Skip to content

Commit 248c585

Browse files
authored
Merge pull request microsoft#205558 from microsoft/ulugbekna/rename-widget-doesnt-appear
rename suggestions: bunch of fixes
2 parents 2cec75a + 4145304 commit 248c585

File tree

2 files changed

+88
-33
lines changed

2 files changed

+88
-33
lines changed

src/vs/editor/contrib/rename/browser/rename.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ class RenameController implements IEditorContribution {
315315
}
316316

317317
cancelRenameInput(): void {
318-
this._renameInputField.cancelInput(true);
318+
this._renameInputField.cancelInput(true, 'cancelRenameInput command');
319319
}
320320

321321
focusNextRenameSuggestion(): void {

src/vs/editor/contrib/rename/browser/renameInputField.ts

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/commo
2525
import { localize } from 'vs/nls';
2626
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
2727
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
28+
import { ILogService } from 'vs/platform/log/common/log';
2829
import { defaultListStyles } from 'vs/platform/theme/browser/defaultStyles';
2930
import {
3031
editorWidgetBackground,
@@ -72,6 +73,7 @@ export class RenameInputField implements IContentWidget {
7273
@IThemeService private readonly _themeService: IThemeService,
7374
@IKeybindingService private readonly _keybindingService: IKeybindingService,
7475
@IContextKeyService contextKeyService: IContextKeyService,
76+
@ILogService private readonly _logService: ILogService,
7577
) {
7678
this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
7779
this._focusedContextKey = CONTEXT_RENAME_INPUT_FOCUSED.bindTo(contextKeyService);
@@ -105,15 +107,15 @@ export class RenameInputField implements IContentWidget {
105107
this._input.className = 'rename-input';
106108
this._input.type = 'text';
107109
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?
109110
this._disposables.add(addDisposableListener(this._input, 'focus', () => { this._focusedContextKey.set(true); }));
110111
this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); }));
111112
this._domNode.appendChild(this._input);
112113

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+
}));
117119

118120
this._label = document.createElement('div');
119121
this._label.className = 'rename-label';
@@ -177,9 +179,10 @@ export class RenameInputField implements IContentWidget {
177179

178180
const bodyBox = getClientArea(this.getDomNode().ownerDocument.body);
179181
const editorBox = getDomNodePagePosition(this._editor.getDomNode());
180-
const cursorBox = this._editor.getScrolledVisiblePosition(this._position!);
181182

182-
this._nPxAvailableAbove = cursorBox.top + editorBox.top;
183+
const cursorBoxTop = this._getTopForPosition();
184+
185+
this._nPxAvailableAbove = cursorBoxTop + editorBox.top;
183186
this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove;
184187

185188
const lineHeight = this._editor.getOption(EditorOption.lineHeight);
@@ -199,16 +202,16 @@ export class RenameInputField implements IContentWidget {
199202
const [accept, preview] = this._acceptKeybindings;
200203
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());
201204

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
204206

205207
return null;
206208
}
207209

208210
afterRender(position: ContentWidgetPositionPreference | null): void {
211+
this._trace('invoking afterRender, position: ', position ? 'not null' : 'null');
209212
if (position === null) {
210213
// cancel rename when input widget isn't rendered anymore
211-
this.cancelInput(true);
214+
this.cancelInput(true, 'afterRender (because position is null)');
212215
return;
213216
}
214217

@@ -241,10 +244,12 @@ export class RenameInputField implements IContentWidget {
241244
private _currentCancelInput?: (focusEditor: boolean) => void;
242245

243246
acceptInput(wantsPreview: boolean): void {
247+
this._trace(`invoking acceptInput`);
244248
this._currentAcceptInput?.(wantsPreview);
245249
}
246250

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'}`);
248253
this._currentCancelInput?.(focusEditor);
249254
}
250255

@@ -280,6 +285,7 @@ export class RenameInputField implements IContentWidget {
280285
return new Promise<RenameInputFieldResult | boolean>(resolve => {
281286

282287
this._currentCancelInput = (focusEditor) => {
288+
this._trace('invoking _currentCancelInput');
283289
this._currentAcceptInput = undefined;
284290
this._currentCancelInput = undefined;
285291
this._candidatesView?.clearCandidates();
@@ -288,12 +294,14 @@ export class RenameInputField implements IContentWidget {
288294
};
289295

290296
this._currentAcceptInput = (wantsPreview) => {
297+
this._trace('invoking _currentAcceptInput');
291298
assertType(this._input !== undefined);
292299
assertType(this._candidatesView !== undefined);
293300

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)');
297305
return;
298306
}
299307

@@ -302,14 +310,14 @@ export class RenameInputField implements IContentWidget {
302310
this._candidatesView.clearCandidates();
303311

304312
resolve({
305-
newName: candidateName ?? this._input.value,
313+
newName,
306314
wantsPreview: supportPreview && wantsPreview
307315
});
308316
};
309317

310-
disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true)));
318+
disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true, 'cts.token.onCancellationRequested')));
311319
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')));
313321
}
314322

315323
this._show();
@@ -321,6 +329,7 @@ export class RenameInputField implements IContentWidget {
321329
}
322330

323331
private _show(): void {
332+
this._trace('invoking _show');
324333
this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth);
325334
this._visible = true;
326335
this._visibleContextKey.set(true);
@@ -335,9 +344,13 @@ export class RenameInputField implements IContentWidget {
335344
}
336345

337346
private async _updateRenameCandidates(candidates: ProviderResult<NewSymbolName[]>[], currentName: string, token: CancellationToken) {
347+
const trace = (...args: any[]) => this._trace('_updateRenameCandidates', ...args);
348+
349+
trace('start');
338350
const namesListResults = await raceCancellation(Promise.allSettled(candidates), token);
339351

340352
if (namesListResults === undefined) {
353+
trace('returning early - received updateRenameCandidates results - undefined');
341354
return;
342355
}
343356

@@ -346,39 +359,67 @@ export class RenameInputField implements IContentWidget {
346359
? namesListResult.value
347360
: []
348361
);
362+
trace(`received updateRenameCandidates results - total (unfiltered) ${newNames.length} candidates.`);
349363

350364
// deduplicate and filter out the current value
351365
const distinctNames = arrays.distinct(newNames, v => v.newSymbolName);
366+
trace(`distinct candidates - ${distinctNames.length} candidates.`);
367+
352368
const validDistinctNames = distinctNames.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName);
369+
trace(`valid distinct candidates - ${newNames.length} candidates.`);
353370

354371
if (validDistinctNames.length < 1) {
372+
trace('returning early - no valid distinct candidates');
355373
return;
356374
}
357375

358376
// show the candidates
377+
trace('setting candidates');
359378
this._candidatesView!.setCandidates(validDistinctNames);
360379

361380
// 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');
362382
this._editor.layoutContentWidget(this);
363383
}
364384

365385
private _hide(): void {
386+
this._trace('invoked _hide');
366387
this._visible = false;
367388
this._visibleContextKey.reset();
368389
this._editor.layoutContentWidget(this);
369390
}
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+
}
370407
}
371408

372-
export class CandidatesView {
409+
class CandidatesView {
373410

374411
private readonly _listWidget: List<NewSymbolName>;
375412
private readonly _listContainer: HTMLDivElement;
376413

377414
private _lineHeight: number;
378415
private _availableHeight: number;
379416

417+
private _disposables: DisposableStore;
418+
380419
constructor(parent: HTMLElement, opts: { fontInfo: FontInfo; onSelectionChange: () => void }) {
381420

421+
this._disposables = new DisposableStore();
422+
382423
this._availableHeight = 0;
383424

384425
this._lineHeight = opts.fontInfo.lineHeight;
@@ -397,7 +438,7 @@ export class CandidatesView {
397438
}
398439

399440
getHeight(element: NewSymbolName): number {
400-
return that.candidateViewHeight;
441+
return that._candidateViewHeight;
401442
}
402443
};
403444

@@ -429,18 +470,22 @@ export class CandidatesView {
429470
}
430471
);
431472

432-
this._listWidget.onDidChangeSelection(e => {
473+
this._disposables.add(this._listWidget.onDidChangeSelection(e => {
433474
if (e.elements.length > 0) {
434475
opts.onSelectionChange();
435476
}
436-
});
477+
}));
478+
479+
this._disposables.add(this._listWidget.onDidBlur(e => {
480+
this._listWidget.setFocus([]);
481+
}));
437482

438483
this._listWidget.style(defaultListStyles);
439484
}
440485

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();
444489
}
445490

446491
// height - max height allowed by parent element
@@ -451,18 +496,12 @@ export class CandidatesView {
451496
}
452497
}
453498

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-
460499
public setCandidates(candidates: NewSymbolName[]): void {
461500
const height = this._pickListHeight(candidates.length);
462501

463502
this._listWidget.splice(0, 0, candidates);
464503

465-
this._listWidget.layout(height);
504+
this._listWidget.layout(height, this._pickListWidth(candidates));
466505

467506
this._listContainer.style.height = `${height}px`;
468507
}
@@ -525,9 +564,25 @@ export class CandidatesView {
525564
}
526565
return focusedIx > 0;
527566
}
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+
528583
}
529584

530-
export class CandidateView { // TODO@ulugbekna: remove export
585+
class CandidateView {
531586

532587
// TODO@ulugbekna: accessibility
533588

0 commit comments

Comments
 (0)