提交 005725b9 编写于 作者: P Pine Wu

Suggest Widget status bar for #39441

Squashed commit of the following:

commit 70b0382e
Author: Johannes Rieken <johannes.rieken@gmail.com>
Date:   Fri Jan 24 16:41:06 2020 +0100

    update statusbar with "real" keybindings

commit d413a30a
Author: Pine Wu <octref@gmail.com>
Date:   Fri Jan 24 15:31:55 2020 +0100

    Polish

commit a1bd86cc
Author: Pine Wu <octref@gmail.com>
Date:   Fri Jan 24 12:25:32 2020 +0100

    Simple StatusBar for suggest
上级 7ec3c528
......@@ -2668,6 +2668,10 @@ export interface ISuggestOptions {
* Show snippet-suggestions.
*/
showSnippets?: boolean;
/**
* Controls the visibility of the status bar at the bottom of the suggest widget.
*/
hideStatusBar?: boolean;
}
export type InternalSuggestOptions = Readonly<Required<ISuggestOptions>>;
......@@ -2709,6 +2713,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
showFolders: true,
showTypeParameters: true,
showSnippets: true,
hideStatusBar: true
};
super(
EditorOption.suggest, 'suggest', defaults,
......@@ -2893,6 +2898,11 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
type: 'boolean',
default: true,
markdownDescription: nls.localize('editor.suggest.showSnippets', "When enabled IntelliSense shows `snippet`-suggestions.")
},
'editor.suggest.hideStatusBar': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('editor.suggest.hideStatusBar', "Controls the visibility of the status bar at the bottom of the suggest widget.")
}
}
);
......@@ -2937,6 +2947,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
showFolders: EditorBooleanOption.boolean(input.showFolders, this.defaultValue.showFolders),
showTypeParameters: EditorBooleanOption.boolean(input.showTypeParameters, this.defaultValue.showTypeParameters),
showSnippets: EditorBooleanOption.boolean(input.showSnippets, this.defaultValue.showSnippets),
hideStatusBar: EditorBooleanOption.boolean(input.hideStatusBar, this.defaultValue.hideStatusBar),
};
}
}
......
......@@ -111,6 +111,49 @@
font-weight: bold;
}
/** Status Bar **/
.monaco-editor .suggest-widget > .tree {
margin-bottom: 18px;
}
.monaco-editor .suggest-widget > .suggest-status-bar {
visibility: hidden;
position: absolute;
left: 0;
box-sizing: border-box;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
width: 100%;
font-size: 80%;
border-left-width: 1px;
border-left-style: solid;
border-right-width: 1px;
border-right-style: solid;
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 1px 8px 1px 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;
}
.monaco-editor .suggest-widget.docs-side > .suggest-status-bar {
width: 50%;
}
/** ReadMore Icon styles **/
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .codicon-close,
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar {
visibility: visible;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar span {
min-height: 18px;
}
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row > .contents > .main > .right > .readMore,
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused > .contents > .main > .right:not(.always-show-details) > .readMore {
display: none;
}
.monaco-editor .suggest-widget.with-status-bar:not(.docs-side) .monaco-list .monaco-list-row:hover > .contents > .main > .right.can-expand-details > .details-label {
width: 100%;
}
......@@ -43,7 +43,7 @@ import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeH
* Stop suggest widget from disappearing when clicking into other areas
* For development purpose only
*/
const _sticky = false;
const _sticky = true;
class LineSuffix {
......@@ -528,11 +528,8 @@ const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(Sugge
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestion',
precondition: SuggestContext.Visible,
handler(x, args) {
const alternative: boolean = typeof args === 'object' && typeof args.alternative === 'boolean'
? args.alternative
: false;
x.acceptSelectedSuggestion(true, alternative);
handler(x) {
x.acceptSelectedSuggestion(true, false);
}
}));
......@@ -552,16 +549,23 @@ KeybindingsRegistry.registerKeybindingRule({
weight
});
// 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
KeybindingsRegistry.registerKeybindingRule({
id: 'acceptSelectedSuggestion',
when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus),
primary: KeyMod.Shift | KeyCode.Tab,
secondary: [KeyMod.Shift | KeyCode.Enter],
args: { alternative: true },
weight
});
registerEditorCommand(new SuggestCommand({
id: 'acceptAlternativeSelectedSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.Enter,
secondary: [KeyMod.Shift | KeyCode.Tab],
},
handler(x) {
x.acceptSelectedSuggestion(false, true);
},
}));
// continue to support the old command
CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion');
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/suggest';
import 'vs/css!./media/suggestStatusBar';
import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded
import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded
import * as nls from 'vs/nls';
......@@ -41,6 +42,7 @@ import { FileKind } from 'vs/platform/files/common/files';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { flatten } from 'vs/base/common/arrays';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Position } from 'vs/editor/common/core/position';
const expandSuggestionDocsByDefault = false;
......@@ -301,7 +303,7 @@ class SuggestionDetails {
private readonly widget: SuggestWidget,
private readonly editor: ICodeEditor,
private readonly markdownRenderer: MarkdownRenderer,
private readonly triggerKeybindingLabel: string,
private readonly kbToggleDetails: string,
) {
this.disposables = new DisposableStore();
......@@ -316,7 +318,7 @@ class SuggestionDetails {
this.header = append(this.body, $('.header'));
this.close = append(this.header, $('span.codicon.codicon-close'));
this.close.title = nls.localize('readLess', "Read less...{0}", this.triggerKeybindingLabel);
this.close.title = nls.localize('readLess', "Read less...{0}", this.kbToggleDetails);
this.type = append(this.header, $('p.type'));
this.docs = append(this.body, $('p.docs'));
......@@ -468,6 +470,9 @@ 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;
......@@ -479,6 +484,9 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private element: HTMLElement;
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;
......@@ -517,18 +525,20 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
constructor(
private readonly editor: ICodeEditor,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IKeybindingService keybindingService: IKeybindingService,
@IModeService modeService: IModeService,
@IOpenerService openerService: IOpenerService,
@IInstantiationService instantiationService: IInstantiationService,
) {
const kb = keybindingService.lookupKeybinding('editor.action.triggerSuggest');
const triggerKeybindingLabel = !kb ? '' : ` (${kb.getLabel()})`;
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;
this.storageService = storageService;
......@@ -542,12 +552,23 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.messageElement = append(this.element, $('.message'));
this.listElement = append(this.element, $('.tree'));
this.details = instantiationService.createInstance(SuggestionDetails, this.element, this, this.editor, markdownRenderer, triggerKeybindingLabel);
const applyStatusBarStyle = () => toggleClass(this.element, 'with-status-bar', !this.editor.getOption(EditorOption.suggest).hideStatusBar);
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('');
this.details = instantiationService.createInstance(SuggestionDetails, this.element, this, this.editor, markdownRenderer, kbToggleDetails);
const applyIconStyle = () => toggleClass(this.element, 'no-icons', !this.editor.getOption(EditorOption.suggest).showIcons);
applyIconStyle();
let renderer = instantiationService.createInstance(ItemRenderer, this, this.editor, triggerKeybindingLabel);
let renderer = instantiationService.createInstance(ItemRenderer, this, this.editor, kbToggleDetails);
this.list = new List('SuggestWidget', this.listElement, this, [renderer], {
useShadows: false,
......@@ -582,7 +603,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.toDispose.add(this.list.onSelectionChange(e => this.onListSelection(e)));
this.toDispose.add(this.list.onFocusChange(e => this.onListFocus(e)));
this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged()));
this.toDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.suggest)) { applyIconStyle(); } }));
this.toDispose.add(this.editor.onDidChangeConfiguration(e => {
if (e.hasChanged(EditorOption.suggest)) {
applyStatusBarStyle();
applyIconStyle();
}
}));
this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService);
this.suggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService);
......@@ -661,12 +687,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
const backgroundColor = theme.getColor(editorSuggestWidgetBackground);
if (backgroundColor) {
this.listElement.style.backgroundColor = backgroundColor.toString();
this.statusBarElement.style.backgroundColor = backgroundColor.toString();
this.details.element.style.backgroundColor = backgroundColor.toString();
this.messageElement.style.backgroundColor = backgroundColor.toString();
}
const borderColor = theme.getColor(editorSuggestWidgetBorder);
if (borderColor) {
this.listElement.style.borderColor = borderColor.toString();
this.statusBarElement.style.borderColor = borderColor.toString();
this.details.element.style.borderColor = borderColor.toString();
this.messageElement.style.borderColor = borderColor.toString();
this.detailsBorderColor = borderColor.toString();
......@@ -704,6 +732,22 @@ 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();
......@@ -739,6 +783,16 @@ 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);
}
......@@ -759,7 +813,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
switch (state) {
case State.Hidden:
hide(this.messageElement, this.details.element, this.listElement);
hide(this.messageElement, this.details.element, this.listElement, this.statusBarElement);
this.hide();
this.listHeight = 0;
if (stateChanged) {
......@@ -769,7 +823,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
break;
case State.Loading:
this.messageElement.textContent = SuggestWidget.LOADING_MESSAGE;
hide(this.listElement, this.details.element);
hide(this.listElement, this.details.element, this.statusBarElement);
show(this.messageElement);
removeClass(this.element, 'docs-side');
this.show();
......@@ -777,7 +831,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
break;
case State.Empty:
this.messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE;
hide(this.listElement, this.details.element);
hide(this.listElement, this.details.element, this.statusBarElement);
show(this.messageElement);
removeClass(this.element, 'docs-side');
this.show();
......@@ -785,7 +839,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
break;
case State.Open:
hide(this.messageElement);
show(this.listElement);
show(this.listElement, this.statusBarElement);
this.show();
break;
case State.Frozen:
......@@ -795,7 +849,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
break;
case State.Details:
hide(this.messageElement);
show(this.details.element, this.listElement);
show(this.details.element, this.listElement, this.statusBarElement);
this.show();
break;
}
......@@ -1008,6 +1062,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
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) {
......@@ -1016,6 +1071,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.updateExpandDocsSetting(true);
this.showDetails(false);
this.setStatusBarRightText(this.msgDetailsLess);
this.telemetryService.publicLog2('suggestWidget:expandDetails');
}
}
......@@ -1119,6 +1175,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.element.style.lineHeight = `${this.unfocusedHeight}px`;
this.listElement.style.height = `${height}px`;
this.statusBarElement.style.top = `${height}px`;
this.list.layout(height);
return height;
}
......@@ -1220,6 +1277,14 @@ 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();
......
......@@ -3657,6 +3657,10 @@ declare namespace monaco.editor {
* Show snippet-suggestions.
*/
showSnippets?: boolean;
/**
* Controls the visibility of the status bar at the bottom of the suggest widget.
*/
hideStatusBar?: boolean;
}
export type InternalSuggestOptions = Readonly<Required<ISuggestOptions>>;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册