Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5

This commit is contained in:
ADS Merger
2020-02-08 04:50:58 +00:00
parent 8c61538a27
commit 2af13c18d2
752 changed files with 16458 additions and 10063 deletions

View File

@@ -13,7 +13,7 @@
transition: transform 200ms linear;
padding: 0 4px;
box-sizing: border-box;
transform: translateY(Calc(-100% - 10px)); /* shadow (10px) */
transform: translateY(calc(-100% - 10px)); /* shadow (10px) */
}
.monaco-editor .find-widget textarea {

View File

@@ -56,7 +56,7 @@ const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replac
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first {0} results are highlighted, but all find operations work on the entire text.", MATCHES_LIMIT);
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
const NLS_NO_RESULTS = nls.localize('label.noResults', "No results");
const FIND_WIDGET_INITIAL_WIDTH = 419;
const PART_WIDTH = 275;
@@ -415,11 +415,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (label === NLS_NO_RESULTS) {
return searchString === ''
? nls.localize('ariaSearchNoResultEmpty', "{0} found", label)
: nls.localize('ariaSearchNoResult', "{0} found for {1}", label, searchString);
: nls.localize('ariaSearchNoResult', "{0} found for '{1}'", label, searchString);
}
return currentMatch
? nls.localize('ariaSearchNoResultWithLineNum', "{0} found for {1} at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn)
: nls.localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for {1}", label, searchString);
if (currentMatch) {
const ariaLabel = nls.localize('ariaSearchNoResultWithLineNum', "{0} found for '{1}', at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn);
const lineContent = this._codeEditor.getModel()?.getLineContent(currentMatch.startLineNumber);
if (lineContent) {
return `${lineContent}, ${ariaLabel}`;
}
return ariaLabel;
}
return nls.localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for '{1}'", label, searchString);
}
/**

View File

@@ -885,7 +885,7 @@ for (let i = 1; i <= 7; i++) {
);
}
export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('editorSelectionBackground', "Color of the editor selection."));
export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges."));
registerThemingParticipant((theme, collector) => {
const foldBackground = theme.getColor(foldBackgroundBackground);

View File

@@ -178,10 +178,8 @@ export async function formatDocumentRangeWithProvider(
if (isCodeEditor(editorOrModel)) {
// use editor to apply edits
FormattingEdit.execute(editorOrModel, edits);
FormattingEdit.execute(editorOrModel, edits, true);
alertFormattingEdits(edits);
editorOrModel.pushUndoStop();
editorOrModel.focus();
editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate);
} else {
@@ -266,12 +264,10 @@ export async function formatDocumentWithProvider(
if (isCodeEditor(editorOrModel)) {
// use editor to apply edits
FormattingEdit.execute(editorOrModel, edits);
FormattingEdit.execute(editorOrModel, edits, mode !== FormattingMode.Silent);
if (mode !== FormattingMode.Silent) {
alertFormattingEdits(edits);
editorOrModel.pushUndoStop();
editorOrModel.focus();
editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate);
}

View File

@@ -138,7 +138,7 @@ class FormatOnType implements IEditorContribution {
}
if (isNonEmptyArray(edits)) {
FormattingEdit.execute(this._editor, edits);
FormattingEdit.execute(this._editor, edits, true);
alertFormattingEdits(edits);
}

View File

@@ -43,8 +43,10 @@ export class FormattingEdit {
return fullModelRange.equalsRange(editRange);
}
static execute(editor: ICodeEditor, _edits: TextEdit[]) {
editor.pushUndoStop();
static execute(editor: ICodeEditor, _edits: TextEdit[], addUndoStops: boolean) {
if (addUndoStops) {
editor.pushUndoStop();
}
const edits = FormattingEdit._handleEolEdits(editor, _edits);
if (edits.length === 1 && FormattingEdit._isFullModelReplaceEdit(editor, edits[0])) {
// We use replace semantics and hope that markers stay put...
@@ -52,6 +54,8 @@ export class FormattingEdit {
} else {
editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)));
}
editor.pushUndoStop();
if (addUndoStops) {
editor.pushUndoStop();
}
}
}

View File

@@ -28,6 +28,7 @@ import { Action } from 'vs/base/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { isEqual } from 'vs/base/common/resources';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
class MarkerModel {
@@ -396,7 +397,7 @@ class MarkerNavigationAction extends EditorAction {
return editorService.openCodeEditor({
resource: newMarker.resource,
options: { pinned: false, revealIfOpened: true, revealInCenterIfOutsideViewport: true, selection: newMarker }
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, selection: newMarker }
}, editor).then(editor => {
if (!editor) {
return undefined;

View File

@@ -317,6 +317,7 @@ export class MarkerNavigationWidget extends PeekViewWidget {
this._icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(this._severity))}`;
this.editor.revealPositionInCenter(position, ScrollType.Smooth);
this.editor.focus();
}
updateMarker(marker: IMarker): void {

View File

@@ -36,6 +36,8 @@ import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ScrollType, IEditorAction } from 'vs/editor/common/editorCommon';
import { assertType } from 'vs/base/common/types';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
MenuRegistry.appendMenuItem(MenuId.EditorContext, <ISubmenuItem>{
@@ -129,7 +131,7 @@ abstract class SymbolNavigationAction extends EditorAction {
private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: IActiveCodeEditor, model: ReferencesModel): Promise<void> {
const gotoLocation = this._getGoToPreference(editor);
if (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1)) {
if (!(editor instanceof EmbeddedCodeEditorWidget) && (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1))) {
this._openInPeek(editor, model);
} else {
@@ -165,7 +167,7 @@ abstract class SymbolNavigationAction extends EditorAction {
resource: reference.uri,
options: {
selection: Range.collapseToStart(range),
revealInCenterIfOutsideViewport: true
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport
}
}, editor, sideBySide);

View File

@@ -27,6 +27,10 @@ import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } fro
import { Position } from 'vs/editor/common/core/position';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { PeekContext } from 'vs/editor/contrib/peekView/peekView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export class GotoDefinitionAtPositionEditorContribution implements IEditorContribution {
@@ -334,8 +338,17 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri
private gotoDefinition(position: Position, openToSide: boolean): Promise<any> {
this.editor.setPosition(position);
const action = new DefinitionAction({ openToSide, openInPeek: false, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined });
return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor));
const definitionLinkOpensInPeek = this.editor.getOption(EditorOption.definitionLinkOpensInPeek);
return this.editor.invokeWithinContext((accessor) => {
const canPeek = definitionLinkOpensInPeek && !this.isInPeekEditor(accessor);
const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined });
return action.run(accessor, this.editor);
});
}
private isInPeekEditor(accessor: ServicesAccessor): boolean | undefined {
const contextKeyService = accessor.get(IContextKeyService);
return PeekContext.inPeekEditor.getValue(contextKeyService);
}
public dispose(): void {

View File

@@ -216,14 +216,16 @@ export abstract class ReferencesController implements IEditorContribution {
}
}
closeWidget(): void {
closeWidget(focusEditor = true): void {
this._referenceSearchVisible.reset();
this._disposables.clear();
dispose(this._widget);
dispose(this._model);
this._widget = undefined;
this._model = undefined;
this._editor.focus();
if (focusEditor) {
this._editor.focus();
}
this._requestIdPool += 1; // Cancel pending requests
}
@@ -300,7 +302,7 @@ function withController(accessor: ServicesAccessor, fn: (controller: ReferencesC
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'changePeekFocus',
id: 'togglePeekWidgetFocus',
weight: KeybindingWeight.EditorContrib,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.F2),
when: ContextKeyExpr.or(ctxReferenceSearchVisible, PeekContext.inPeekEditor),

View File

@@ -19,6 +19,7 @@ import { localize } from 'vs/nls';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isEqual } from 'vs/base/common/resources';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
export const ctxHasSymbols = new RawContextKey('hasSymbols', false);
@@ -127,7 +128,7 @@ class SymbolNavigationService implements ISymbolNavigationService {
resource: reference.uri,
options: {
selection: Range.collapseToStart(reference.range),
revealInCenterIfOutsideViewport: true
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport
}
}, source).finally(() => {
this._ignoreEditorChange = false;

View File

@@ -37,12 +37,36 @@ abstract class AbstractCopyLinesAction extends EditorAction {
}
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
if (!editor.hasModel()) {
return;
}
const selections = editor.getSelections().map((selection, index) => ({ selection, index, ignore: false }));
selections.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection));
// Remove selections that would result in copying the same line
let prev = selections[0];
for (let i = 1; i < selections.length; i++) {
const curr = selections[i];
if (prev.selection.endLineNumber === curr.selection.startLineNumber) {
// these two selections would copy the same line
if (prev.index < curr.index) {
// prev wins
curr.ignore = true;
} else {
// curr wins
prev.ignore = true;
prev = curr;
}
}
}
const commands: ICommand[] = [];
const selections = editor.getSelections() || [];
for (const selection of selections) {
commands.push(new CopyLinesCommand(selection, this.down));
if (selection.ignore) {
continue;
}
commands.push(new CopyLinesCommand(selection.selection, this.down));
}
editor.pushUndoStop();

View File

@@ -179,7 +179,7 @@ class RenameController implements IEditorContribution {
selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn;
}
const supportPreview = this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview);
// no result, only hint to focus the editor or not

View File

@@ -111,6 +111,44 @@
font-weight: bold;
}
/** Status Bar **/
.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: 0 8px 0 4px;
box-shadow: 0 -.5px 3px #ddd;
}
.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,
@@ -190,7 +228,7 @@
overflow: hidden;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .monaco-icon-label {
flex-shrink: 0;
flex-shrink: 1;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right {
overflow: hidden;
@@ -324,6 +362,7 @@
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs {
padding: 0;
white-space: initial;
min-height: calc(1rem + 8px);
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs > div,

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 > .tree {
margin-bottom: 18px;
}
.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,
.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%;
}

View File

@@ -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';

View File

@@ -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();
}
@@ -528,11 +536,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);
}
}));
@@ -549,19 +554,55 @@ KeybindingsRegistry.registerKeybindingRule({
id: 'acceptSelectedSuggestion',
when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit),
primary: KeyCode.Enter,
weight
weight,
});
// 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
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'))
});
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);
},
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")
}]
}));
// continue to support the old command
CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion');
@@ -649,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({

View File

@@ -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';
@@ -20,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';
@@ -39,8 +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 { 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;
@@ -49,8 +53,8 @@ interface ISuggestionTemplateData {
/**
* Flexbox
* < ------- left ------- > < -------- right -------- >
* <icon><label><signature> <qualifier><type><readmore>
* < ------------- left ------------ > < --- right -- >
* <icon><label><signature><qualifier> <type><readmore>
*/
left: HTMLElement;
right: HTMLElement;
@@ -301,7 +305,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 +320,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'));
@@ -340,7 +344,8 @@ class SuggestionDetails {
}
renderItem(item: CompletionItem, explainMode: boolean): void {
this.renderDisposeable = dispose(this.renderDisposeable);
dispose(this.renderDisposeable);
this.renderDisposeable = undefined;
let { documentation, detail } = item.completion;
// --- documentation
@@ -447,7 +452,8 @@ class SuggestionDetails {
dispose(): void {
this.disposables.dispose();
this.renderDisposeable = dispose(this.renderDisposeable);
dispose(this.renderDisposeable);
this.renderDisposeable = undefined;
}
}
@@ -479,12 +485,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private element: HTMLElement;
private messageElement: HTMLElement;
private listElement: HTMLElement;
private statusBarElement: HTMLElement;
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();
@@ -517,18 +525,19 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
constructor(
private readonly editor: ICodeEditor,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IKeybindingService keybindingService: IKeybindingService,
@IModeService modeService: IModeService,
@IOpenerService openerService: IOpenerService,
@IMenuService menuService: IMenuService,
@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.isAuto = false;
this.focusedItem = null;
this.storageService = storageService;
@@ -542,12 +551,55 @@ 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'));
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);
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,10 +634,16 @@ 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);
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);
@@ -661,12 +719,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,7 +764,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.firstFocusInCurrentList = !this.focusedItem;
if (item !== this.focusedItem) {
if (this.currentSuggestionDetails) {
this.currentSuggestionDetails.cancel();
this.currentSuggestionDetails = null;
@@ -759,7 +818,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 +828,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 +836,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 +844,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 +854,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;
}
@@ -836,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) {
@@ -1003,6 +1062,7 @@ 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');
@@ -1014,6 +1074,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
return;
}
this.ctxSuggestWidgetDetailsVisible.set(true);
this.updateExpandDocsSetting(true);
this.showDetails(false);
this.telemetryService.publicLog2('suggestWidget:expandDetails');
@@ -1021,7 +1082,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
showDetails(loading: boolean): void {
this.expandSideOrBelow();
if (!loading) {
// When loading, don't re-layout docs, as item is not resolved yet #88731
this.expandSideOrBelow();
}
show(this.details.element);
@@ -1058,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');
@@ -1067,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');
}
@@ -1119,6 +1183,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;
}
@@ -1172,7 +1237,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
/**
* Adds the proper classes for positioning the docs to the side or below
* Adds the proper classes for positioning the docs to the side or below depending on item
*/
private expandSideOrBelow() {
if (!canExpandCompletionItem(this.focusedItem) && this.firstFocusInCurrentList) {

View File

@@ -22,6 +22,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { CompletionProviderRegistry, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes';
import { Event } from 'vs/base/common/event';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { IMenuService, IMenu } from 'vs/platform/actions/common/actions';
suite('SuggestController', function () {
@@ -46,6 +47,13 @@ suite('SuggestController', function () {
[ISuggestMemoryService, new class extends mock<ISuggestMemoryService>() {
memorize(): void { }
select(): number { return 0; }
}],
[IMenuService, new class extends mock<IMenuService>() {
createMenu() {
return new class extends mock<IMenu>() {
onDidChange = Event.None;
};
}
}]
);