mirror of
https://github.com/Microsoft/vscode
synced 2024-10-02 17:32:41 +00:00
rename: show list of rename candidate names, allow tabbing through them and selecting one by pressing 'enter'
This commit is contained in:
parent
14770d1197
commit
bd1536407a
|
@ -208,6 +208,13 @@ class RenameController implements IEditorContribution {
|
|||
// part 2 - do rename at location
|
||||
const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token);
|
||||
|
||||
const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled
|
||||
const newNameCandidates = Promise.all(
|
||||
this._languageFeaturesService.newSymbolNamesProvider
|
||||
.all(model)
|
||||
.map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able
|
||||
).then((candidates) => candidates.filter((c): c is string[] => !!c).flat());
|
||||
|
||||
const selection = this.editor.getSelection();
|
||||
let selectionStart = 0;
|
||||
let selectionEnd = loc.text.length;
|
||||
|
@ -218,7 +225,7 @@ class RenameController implements IEditorContribution {
|
|||
}
|
||||
|
||||
const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
|
||||
const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, cts2.token);
|
||||
const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newNameCandidates, cts2.token);
|
||||
|
||||
// no result, only hint to focus the editor or not
|
||||
if (typeof inputFieldResult === 'boolean') {
|
||||
|
|
|
@ -23,6 +23,19 @@
|
|||
opacity: .8;
|
||||
}
|
||||
|
||||
.monaco-editor .rename-box .new-name-candidates-container {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.monaco-editor .rename-box .new-name-candidate {
|
||||
/* FIXME@ulugbekna: adapt colors to be nice */
|
||||
background-color: rgb(2, 96, 190);
|
||||
color: white;
|
||||
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.monaco-editor .rename-box.preview .rename-label {
|
||||
display: inherit;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./renameInputField';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
@ -30,6 +31,7 @@ export class RenameInputField implements IContentWidget {
|
|||
private _position?: Position;
|
||||
private _domNode?: HTMLElement;
|
||||
private _input?: HTMLInputElement;
|
||||
private _newNameCandidates?: NewSymbolNameCandidates;
|
||||
private _label?: HTMLDivElement;
|
||||
private _visible?: boolean;
|
||||
private readonly _visibleContextKey: IContextKey<boolean>;
|
||||
|
@ -77,6 +79,11 @@ export class RenameInputField implements IContentWidget {
|
|||
this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit."));
|
||||
this._domNode.appendChild(this._input);
|
||||
|
||||
// TODO@ulugbekna: add keyboard support for cycling through the candidates
|
||||
this._newNameCandidates = new NewSymbolNameCandidates();
|
||||
this._domNode.appendChild(this._newNameCandidates!.domNode);
|
||||
this._disposables.add(this._newNameCandidates);
|
||||
|
||||
this._label = document.createElement('div');
|
||||
this._label.className = 'rename-label';
|
||||
this._domNode.appendChild(this._label);
|
||||
|
@ -108,7 +115,7 @@ export class RenameInputField implements IContentWidget {
|
|||
}
|
||||
|
||||
private _updateFont(): void {
|
||||
if (!this._input || !this._label) {
|
||||
if (!this._input || !this._label || !this._newNameCandidates) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -117,6 +124,10 @@ export class RenameInputField implements IContentWidget {
|
|||
this._input.style.fontWeight = fontInfo.fontWeight;
|
||||
this._input.style.fontSize = `${fontInfo.fontSize}px`;
|
||||
|
||||
this._newNameCandidates.domNode.style.fontFamily = fontInfo.fontFamily;
|
||||
this._newNameCandidates.domNode.style.fontWeight = fontInfo.fontWeight;
|
||||
this._newNameCandidates.domNode.style.fontSize = `${fontInfo.fontSize}px`;
|
||||
|
||||
this._label.style.fontSize = `${fontInfo.fontSize * 0.8}px`;
|
||||
}
|
||||
|
||||
|
@ -155,7 +166,7 @@ export class RenameInputField implements IContentWidget {
|
|||
this._currentCancelInput?.(focusEditor);
|
||||
}
|
||||
|
||||
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise<RenameInputFieldResult | boolean> {
|
||||
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, newNameCandidates: Promise<string[]>, token: CancellationToken): Promise<RenameInputFieldResult | boolean> {
|
||||
|
||||
this._domNode!.classList.toggle('preview', supportPreview);
|
||||
|
||||
|
@ -167,26 +178,39 @@ export class RenameInputField implements IContentWidget {
|
|||
|
||||
const disposeOnDone = new DisposableStore();
|
||||
|
||||
newNameCandidates.then(candidates => {
|
||||
this._newNameCandidates!.setCandidates(candidates);
|
||||
});
|
||||
|
||||
return new Promise<RenameInputFieldResult | boolean>(resolve => {
|
||||
|
||||
this._currentCancelInput = (focusEditor) => {
|
||||
this._currentAcceptInput = undefined;
|
||||
this._currentCancelInput = undefined;
|
||||
this._newNameCandidates?.clearCandidates();
|
||||
resolve(focusEditor);
|
||||
return true;
|
||||
};
|
||||
|
||||
this._currentAcceptInput = (wantsPreview) => {
|
||||
if (this._input!.value.trim().length === 0 || this._input!.value === value) {
|
||||
if (this._input!.value.trim().length === 0) {
|
||||
// empty or whitespace only or not changed
|
||||
this.cancelInput(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCandidate = this._newNameCandidates?.selectedCandidate;
|
||||
if ((selectedCandidate === undefined && this._input!.value === value) || selectedCandidate === value) {
|
||||
this.cancelInput(true);
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentAcceptInput = undefined;
|
||||
this._currentCancelInput = undefined;
|
||||
this._newNameCandidates?.clearCandidates();
|
||||
|
||||
resolve({
|
||||
newName: this._input!.value,
|
||||
newName: selectedCandidate ?? this._input!.value,
|
||||
wantsPreview: supportPreview && wantsPreview
|
||||
});
|
||||
};
|
||||
|
@ -222,3 +246,45 @@ export class RenameInputField implements IContentWidget {
|
|||
this._editor.layoutContentWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
class NewSymbolNameCandidates implements IDisposable {
|
||||
|
||||
public readonly domNode: HTMLDivElement;
|
||||
|
||||
private _candidates: HTMLSpanElement[] = [];
|
||||
private _disposables = new DisposableStore();
|
||||
|
||||
constructor() {
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.className = 'new-name-candidates-container';
|
||||
this.domNode.tabIndex = -1; // Make the div unfocusable
|
||||
}
|
||||
|
||||
get selectedCandidate(): string | undefined {
|
||||
const activeDocument = dom.getActiveDocument();
|
||||
const activeElement = activeDocument.activeElement;
|
||||
const index = this._candidates.indexOf(activeElement as HTMLSpanElement);
|
||||
return index !== -1 ? this._candidates[index].innerText : undefined;
|
||||
}
|
||||
|
||||
setCandidates(candidates: string[]): void {
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
const candidate = candidates[i];
|
||||
const candidateElt = document.createElement('span');
|
||||
candidateElt.className = 'new-name-candidate';
|
||||
candidateElt.innerText = candidate;
|
||||
candidateElt.tabIndex = 0;
|
||||
this.domNode.appendChild(candidateElt);
|
||||
this._candidates.push(candidateElt);
|
||||
}
|
||||
}
|
||||
|
||||
clearCandidates(): void {
|
||||
this.domNode.innerText = ''; // TODO@ulugbekna: make sure this is the right way to clean up children
|
||||
this._candidates = [];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue