提交 9a839aa9 编写于 作者: J Johannes Rieken

suggest - populate suggest status bar with toolbars from menu, use special action rendering

上级 b8cace23
......@@ -136,13 +136,11 @@
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 1px 8px 1px 4px;
padding: 0 8px 0 4px;
box-shadow: 0 -.5px 3px #ddd;
}
.monaco-editor .suggest-widget > .suggest-status-bar span {
opacity: 0.7;
}
.monaco-editor .suggest-widget.list-right.docs-side > .suggest-status-bar {
left: auto;
right: 0;
......
......@@ -10,8 +10,19 @@
margin-bottom: 18px;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar span {
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-label {
min-height: 18px;
opacity: 0.5;
color: inherit;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label {
margin-right: 0;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label::after {
content: ', ';
margin-right: 0.3em;
}
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row > .contents > .main > .right > .readMore,
......
......@@ -17,14 +17,19 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Range } from 'vs/editor/common/core/range';
import { FuzzyScore } from 'vs/base/common/filters';
import { isDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { MenuId } from 'vs/platform/actions/common/actions';
export const Context = {
Visible: new RawContextKey<boolean>('suggestWidgetVisible', false),
DetailsVisible: new RawContextKey<boolean>('suggestWidgetDetailsVisible', false),
MultipleSuggestions: new RawContextKey<boolean>('suggestWidgetMultipleSuggestions', false),
MakesTextEdit: new RawContextKey('suggestionMakesTextEdit', true),
AcceptSuggestionsOnEnter: new RawContextKey<boolean>('acceptSuggestionOnEnter', true)
AcceptSuggestionsOnEnter: new RawContextKey<boolean>('acceptSuggestionOnEnter', true),
HasInsertAndReplaceRange: new RawContextKey('suggestionHasInsertAndReplaceRange', false),
};
export const suggestWidgetStatusbarMenu = new MenuId('suggestWidgetStatusBar');
export class CompletionItem {
_brand!: 'ISuggestionItem';
......
......@@ -23,7 +23,7 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Context as SuggestContext, CompletionItem } from './suggest';
import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } from './suggest';
import { SuggestAlternatives } from './suggestAlternatives';
import { State, SuggestModel } from './suggestModel';
import { ISelectedSuggestion, SuggestWidget } from './suggestWidget';
......@@ -33,17 +33,16 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ
import { IdleValue } from 'vs/base/common/async';
import { isObject, assertType } from 'vs/base/common/types';
import { CommitCharacterController } from './suggestCommitCharacters';
import { IPosition } from 'vs/editor/common/core/position';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import * as platform from 'vs/base/common/platform';
import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
/**
* Stop suggest widget from disappearing when clicking into other areas
* For development purpose only
*/
const _sticky = false;
// sticky suggest widget which doesn't disappear on focus out and such
let _sticky = false;
// _sticky = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
class LineSuffix {
......@@ -138,9 +137,17 @@ export class SuggestController implements IEditorContribution {
}));
// Wire up makes text edit context key
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
const ctxMakesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
const ctxHasInsertAndReplace = SuggestContext.HasInsertAndReplaceRange.bindTo(this._contextKeyService);
this._toDispose.add(toDisposable(() => {
ctxMakesTextEdit.reset();
ctxHasInsertAndReplace.reset();
}));
this._toDispose.add(widget.onDidFocus(({ item }) => {
// (ctx: makesTextEdit)
const position = this.editor.getPosition()!;
const startColumn = item.editStart.column;
const endColumn = position.column;
......@@ -161,9 +168,11 @@ export class SuggestController implements IEditorContribution {
});
value = oldText !== item.completion.insertText;
}
makesTextEdit.set(value);
ctxMakesTextEdit.set(value);
// (ctx: hasInsertAndReplaceRange)
ctxHasInsertAndReplace.set(!Position.equals(item.editInsertEnd, item.editReplaceEnd));
}));
this._toDispose.add(toDisposable(() => makesTextEdit.reset()));
this._toDispose.add(widget.onDetailsKeyDown(e => {
// cmd + c on macOS, ctrl + c on Win / Linux
......@@ -435,7 +444,6 @@ export class SuggestController implements IEditorContribution {
}
this._insertSuggestion(item, flags);
}
acceptNextSuggestion() {
this._alternatives.getValue().next();
}
......@@ -546,12 +554,28 @@ KeybindingsRegistry.registerKeybindingRule({
id: 'acceptSelectedSuggestion',
when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit),
primary: KeyCode.Enter,
weight
weight,
});
MenuRegistry.appendMenuItem(suggestWidgetStatusbarMenu, {
command: { id: 'acceptSelectedSuggestion', title: nls.localize({ key: 'accept.accept', comment: ['{0} will be a keybinding, e.g "Enter to insert"'] }, "{0} to insert") },
group: 'left',
order: 1,
when: SuggestContext.HasInsertAndReplaceRange.toNegated()
});
MenuRegistry.appendMenuItem(suggestWidgetStatusbarMenu, {
command: { id: 'acceptSelectedSuggestion', title: nls.localize({ key: 'accept.insert', comment: ['{0} will be a keybinding, e.g "Enter to insert"'] }, "{0} to insert") },
group: 'left',
order: 1,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'insert'))
});
MenuRegistry.appendMenuItem(suggestWidgetStatusbarMenu, {
command: { id: 'acceptSelectedSuggestion', title: nls.localize({ key: 'accept.replace', comment: ['{0} will be a keybinding, e.g "Enter to replace"'] }, "{0} to replace") },
group: 'left',
order: 1,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'replace'))
});
// todo@joh control enablement via context key
// shift+enter and shift+tab use the alternative-flag so that the suggest controller
// is doing the opposite of the editor.suggest.overwriteOnAccept-configuration
registerEditorCommand(new SuggestCommand({
id: 'acceptAlternativeSelectedSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus),
......@@ -564,6 +588,19 @@ registerEditorCommand(new SuggestCommand({
handler(x) {
x.acceptSelectedSuggestion(false, true);
},
menuOpts: [{
menuId: suggestWidgetStatusbarMenu,
group: 'left',
order: 2,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'insert')),
title: nls.localize({ key: 'accept.replace', comment: ['{0} will be a keybinding, e.g "Enter to replace"'] }, "{0} to replace")
}, {
menuId: suggestWidgetStatusbarMenu,
group: 'left',
order: 2,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'replace')),
title: nls.localize({ key: 'accept.insert', comment: ['{0} will be a keybinding, e.g "Enter to insert"'] }, "{0} to insert")
}]
}));
......@@ -653,7 +690,20 @@ registerEditorCommand(new SuggestCommand({
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.Space,
mac: { primary: KeyMod.WinCtrl | KeyCode.Space }
}
},
menuOpts: [{
menuId: suggestWidgetStatusbarMenu,
group: 'right',
order: 1,
when: SuggestContext.DetailsVisible,
title: nls.localize('detail.more', "show less")
}, {
menuId: suggestWidgetStatusbarMenu,
group: 'right',
order: 1,
when: SuggestContext.DetailsVisible.toNegated(),
title: nls.localize('detail.less', "show more")
}]
}));
registerEditorCommand(new SuggestCommand({
......
......@@ -21,7 +21,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
import { Context as SuggestContext, CompletionItem } from './suggest';
import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } from './suggest';
import { CompletionModel } from './completionModel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { attachListStyler } from 'vs/platform/theme/common/styler';
......@@ -40,9 +40,11 @@ import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileKind } from 'vs/platform/files/common/files';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { flatten } from 'vs/base/common/arrays';
import { flatten, isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Position } from 'vs/editor/common/core/position';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction } from 'vs/base/common/actions';
const expandSuggestionDocsByDefault = false;
......@@ -472,9 +474,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
readonly allowEditorOverflow = true;
readonly suppressMouseDown = false;
private readonly msgDetailMore: string;
private readonly msgDetailsLess: string;
private state: State | null = null;
private isAuto: boolean = false;
private loadingTimeout: IDisposable = Disposable.None;
......@@ -487,14 +486,13 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private messageElement: HTMLElement;
private listElement: HTMLElement;
private statusBarElement: HTMLElement;
private statusBarLeftSpan: HTMLSpanElement;
private statusBarRightSpan: HTMLSpanElement;
private details: SuggestionDetails;
private list: List<CompletionItem>;
private listHeight?: number;
private readonly suggestWidgetVisible: IContextKey<boolean>;
private readonly suggestWidgetMultipleSuggestions: IContextKey<boolean>;
private readonly ctxSuggestWidgetVisible: IContextKey<boolean>;
private readonly ctxSuggestWidgetDetailsVisible: IContextKey<boolean>;
private readonly ctxSuggestWidgetMultipleSuggestions: IContextKey<boolean>;
private readonly showTimeout = new TimeoutTimer();
private readonly toDispose = new DisposableStore();
......@@ -527,19 +525,18 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
constructor(
private readonly editor: ICodeEditor,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IModeService modeService: IModeService,
@IOpenerService openerService: IOpenerService,
@IMenuService menuService: IMenuService,
@IInstantiationService instantiationService: IInstantiationService,
) {
const markdownRenderer = this.toDispose.add(new MarkdownRenderer(editor, modeService, openerService));
const kbToggleDetails = keybindingService.lookupKeybinding('toggleSuggestionDetails')?.getLabel() ?? '';
this.msgDetailsLess = nls.localize('detail.less', "{0} for less...", kbToggleDetails);
this.msgDetailMore = nls.localize('detail.more', "{0} for more...", kbToggleDetails);
this.isAuto = false;
this.focusedItem = null;
......@@ -559,11 +556,43 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
applyStatusBarStyle();
this.statusBarElement = append(this.element, $('.suggest-status-bar'));
this.statusBarLeftSpan = append(this.statusBarElement, $('span'));
this.statusBarRightSpan = append(this.statusBarElement, $('span'));
this.setStatusBarLeftText('');
this.setStatusBarRightText('');
const actionViewItemProvider = <IActionViewItemProvider>(action => {
const kb = keybindingService.lookupKeybindings(action.id);
return new class extends ActionViewItem {
constructor() {
super(undefined, action, { label: true, icon: false });
}
updateLabel() {
if (isFalsyOrEmpty(kb) || !this.label) {
return super.updateLabel();
}
const { label } = this.getAction();
this.label.textContent = /{\d}/.test(label)
? strings.format(this.getAction().label, kb[0].getLabel())
: `${this.getAction().label} (${kb[0].getLabel()})`;
}
};
});
const leftActions = new ActionBar(this.statusBarElement, { actionViewItemProvider });
const rightActions = new ActionBar(this.statusBarElement, { actionViewItemProvider });
const menu = menuService.createMenu(suggestWidgetStatusbarMenu, contextKeyService);
const renderMenu = () => {
const left: IAction[] = [];
const right: IAction[] = [];
for (let [group, actions] of menu.getActions()) {
if (group === 'left') {
left.push(...actions);
} else {
right.push(...actions);
}
}
leftActions.clear();
leftActions.push(left);
rightActions.clear();
rightActions.push(right);
};
this.toDispose.add(menu.onDidChange(() => renderMenu()));
this.toDispose.add(menu);
this.details = instantiationService.createInstance(SuggestionDetails, this.element, this, this.editor, markdownRenderer, kbToggleDetails);
......@@ -612,8 +641,9 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
}));
this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService);
this.suggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService);
this.ctxSuggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService);
this.ctxSuggestWidgetDetailsVisible = SuggestContext.DetailsVisible.bindTo(contextKeyService);
this.ctxSuggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService);
this.editor.addContentWidget(this);
this.setState(State.Hidden);
......@@ -734,23 +764,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.firstFocusInCurrentList = !this.focusedItem;
if (item !== this.focusedItem) {
// update statusbar
// todo@joh,pine -> this should a toolbar with actions so that these things become
// mouse clickable and fit for accessibility...
const wantsInsert = this.editor.getOption(EditorOption.suggest).insertMode === 'insert';
const kbAccept = this.keybindingService.lookupKeybinding('acceptSelectedSuggestion')?.getLabel();
const kbAcceptAlt = this.keybindingService.lookupKeybinding('acceptAlternativeSelectedSuggestion')?.getLabel();
if (!Position.equals(item.editInsertEnd, item.editReplaceEnd)) {
// insert AND replace
if (wantsInsert) {
this.setStatusBarLeftText(nls.localize('insert', "{0} to insert, {1} to replace", kbAccept, kbAcceptAlt));
} else {
this.setStatusBarLeftText(nls.localize('replace', "{0} to replace, {1} to insert", kbAccept, kbAcceptAlt));
}
} else {
this.setStatusBarLeftText(nls.localize('accept', "{0} to accept", kbAccept));
}
if (this.currentSuggestionDetails) {
this.currentSuggestionDetails.cancel();
this.currentSuggestionDetails = null;
......@@ -785,16 +798,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
removeClass(this.element, 'docs-side');
}
if (canExpandCompletionItem(this.focusedItem)) {
if (this.expandDocsSettingFromStorage()) {
this.setStatusBarRightText(this.msgDetailsLess);
} else {
this.setStatusBarRightText(this.msgDetailMore);
}
} else {
this.statusBarRightSpan.innerText = '';
}
this.editor.setAriaOptions({ activeDescendant: getAriaId(index) });
}).catch(onUnexpectedError);
}
......@@ -892,7 +895,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
let visibleCount = this.completionModel.items.length;
const isEmpty = visibleCount === 0;
this.suggestWidgetMultipleSuggestions.set(visibleCount > 1);
this.ctxSuggestWidgetMultipleSuggestions.set(visibleCount > 1);
if (isEmpty) {
if (isAuto) {
......@@ -1059,21 +1062,21 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
if (this.expandDocsSettingFromStorage()) {
this.ctxSuggestWidgetDetailsVisible.set(false);
this.updateExpandDocsSetting(false);
hide(this.details.element);
removeClass(this.element, 'docs-side');
removeClass(this.element, 'docs-below');
this.editor.layoutContentWidget(this);
this.setStatusBarRightText(this.msgDetailMore);
this.telemetryService.publicLog2('suggestWidget:collapseDetails');
} else {
if (this.state !== State.Open && this.state !== State.Details && this.state !== State.Frozen) {
return;
}
this.ctxSuggestWidgetDetailsVisible.set(true);
this.updateExpandDocsSetting(true);
this.showDetails(false);
this.setStatusBarRightText(this.msgDetailsLess);
this.telemetryService.publicLog2('suggestWidget:expandDetails');
}
}
......@@ -1119,7 +1122,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.listHeight = newHeight;
}
this.suggestWidgetVisible.set(true);
this.ctxSuggestWidgetVisible.set(true);
this.showTimeout.cancelAndSet(() => {
addClass(this.element, 'visible');
......@@ -1128,8 +1131,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
private hide(): void {
this.suggestWidgetVisible.reset();
this.suggestWidgetMultipleSuggestions.reset();
this.ctxSuggestWidgetVisible.reset();
this.ctxSuggestWidgetMultipleSuggestions.reset();
removeClass(this.element, 'visible');
}
......@@ -1282,14 +1285,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL);
}
private setStatusBarLeftText(s: string) {
this.statusBarLeftSpan.innerText = s;
}
private setStatusBarRightText(s: string) {
this.statusBarRightSpan.innerText = s;
}
dispose(): void {
this.details.dispose();
this.list.dispose();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册