Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -2,32 +2,36 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { onUnexpectedError } from 'vs/base/common/errors';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { ISuggestSupport } from 'vs/editor/common/modes';
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
import { IEditorContribution, ScrollType, Handler } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CompletionItemProvider, CompletionItemInsertTextRule } from 'vs/editor/common/modes';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { Context as SuggestContext } from './suggest';
import { SuggestModel, State } from './suggestModel';
import { ICompletionItem } from './completionModel';
import { SuggestWidget, ISelectedSuggestion } from './suggestWidget';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
import { SuggestMemories } from 'vs/editor/contrib/suggest/suggestMemory';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ICompletionItem } from './completionModel';
import { Context as SuggestContext, ISuggestionItem } from './suggest';
import { SuggestAlternatives } from './suggestAlternatives';
import { State, SuggestModel } from './suggestModel';
import { ISelectedSuggestion, SuggestWidget } from './suggestWidget';
import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
import { once, anyEvent } from 'vs/base/common/event';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IdleValue } from 'vs/base/common/async';
class AcceptOnCharacterOracle {
@@ -85,40 +89,62 @@ export class SuggestController implements IEditorContribution {
private _model: SuggestModel;
private _widget: SuggestWidget;
private _memory: SuggestMemories;
private readonly _memory: IdleValue<SuggestMemories>;
private readonly _alternatives: IdleValue<SuggestAlternatives>;
private _toDispose: IDisposable[] = [];
private readonly _sticky = false; // for development purposes only
constructor(
private _editor: ICodeEditor,
@IEditorWorkerService editorWorker: IEditorWorkerService,
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
this._model = new SuggestModel(this._editor);
this._memory = _instantiationService.createInstance(SuggestMemories, this._editor.getConfiguration().contribInfo.suggestSelection);
this._model = new SuggestModel(this._editor, editorWorker);
this._memory = new IdleValue(() => {
let res = _instantiationService.createInstance(SuggestMemories, this._editor);
this._toDispose.push(res);
return res;
});
this._alternatives = new IdleValue(() => {
let res = new SuggestAlternatives(this._editor, this._contextKeyService);
this._toDispose.push(res);
return res;
});
this._toDispose.push(_instantiationService.createInstance(WordContextKey, _editor));
this._toDispose.push(this._model.onDidTrigger(e => {
if (!this._widget) {
this._createSuggestWidget();
}
this._widget.showTriggered(e.auto);
this._widget.showTriggered(e.auto, e.shy ? 250 : 50);
}));
this._toDispose.push(this._model.onDidSuggest(e => {
let index = this._memory.select(this._editor.getModel(), this._editor.getPosition(), e.completionModel.items);
this._widget.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
if (!e.shy) {
let index = this._memory.getValue().select(this._editor.getModel(), this._editor.getPosition(), e.completionModel.items);
this._widget.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
}
}));
this._toDispose.push(this._model.onDidCancel(e => {
if (this._widget && !e.retrigger) {
this._widget.hideWidget();
}
}));
this._toDispose.push(this._editor.onDidBlurEditorText(() => {
if (!this._sticky) {
this._model.cancel();
}
}));
// Manage the acceptSuggestionsOnEnter context key
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
let updateFromConfig = () => {
const { acceptSuggestionOnEnter, suggestSelection } = this._editor.getConfiguration().contribInfo;
const { acceptSuggestionOnEnter } = this._editor.getConfiguration().contribInfo;
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
this._memory.setMode(suggestSelection);
};
this._toDispose.push(this._editor.onDidChangeConfiguration((e) => updateFromConfig()));
updateFromConfig();
@@ -127,10 +153,10 @@ export class SuggestController implements IEditorContribution {
private _createSuggestWidget(): void {
this._widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
this._toDispose.push(this._widget.onDidSelect(this._onDidSelectItem, this));
this._toDispose.push(this._widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this));
// Wire up logic to accept a suggestion on certain characters
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, this._widget, item => this._onDidSelectItem(item));
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, this._widget, item => this._onDidSelectItem(item, false, true));
this._toDispose.push(
autoAcceptOracle,
this._model.onDidSuggest(e => {
@@ -144,7 +170,7 @@ export class SuggestController implements IEditorContribution {
this._toDispose.push(this._widget.onDidFocus(({ item }) => {
const position = this._editor.getPosition();
const startColumn = item.position.column - item.suggestion.overwriteBefore;
const startColumn = item.suggestion.range.startColumn;
const endColumn = position.column;
let value = true;
if (
@@ -152,7 +178,7 @@ export class SuggestController implements IEditorContribution {
&& this._model.state === State.Auto
&& !item.suggestion.command
&& !item.suggestion.additionalTextEdits
&& item.suggestion.snippetType !== 'textmate'
&& !(item.suggestion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet)
&& endColumn - startColumn === item.suggestion.insertText.length
) {
const oldText = this._editor.getModel().getValueInRange({
@@ -186,40 +212,51 @@ export class SuggestController implements IEditorContribution {
}
}
protected _onDidSelectItem(event: ISelectedSuggestion): void {
protected _onDidSelectItem(event: ISelectedSuggestion, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
if (!event || !event.item) {
this._alternatives.getValue().reset();
this._model.cancel();
return;
}
const model = this._editor.getModel();
const modelVersionNow = model.getAlternativeVersionId();
const { suggestion, position } = event.item;
const editorColumn = this._editor.getPosition().column;
const columnDelta = editorColumn - position.column;
// pushing undo stops *before* additional text edits and
// *after* the main edit
this._editor.pushUndoStop();
if (undoStops) {
this._editor.pushUndoStop();
}
if (Array.isArray(suggestion.additionalTextEdits)) {
this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
}
// keep item in memory
this._memory.memorize(this._editor.getModel(), this._editor.getPosition(), event.item);
this._memory.getValue().memorize(model, this._editor.getPosition(), event.item);
let { insertText } = suggestion;
if (suggestion.snippetType !== 'textmate') {
if (!(suggestion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet)) {
insertText = SnippetParser.escape(insertText);
}
const overwriteBefore = position.column - suggestion.range.startColumn;
const overwriteAfter = suggestion.range.endColumn - position.column;
SnippetController2.get(this._editor).insert(
insertText,
suggestion.overwriteBefore + columnDelta,
suggestion.overwriteAfter,
false, false
overwriteBefore + columnDelta,
overwriteAfter,
false, false,
!(suggestion.insertTextRules & CompletionItemInsertTextRule.KeepWhitespace)
);
this._editor.pushUndoStop();
if (undoStops) {
this._editor.pushUndoStop();
}
if (!suggestion.command) {
// done
@@ -231,10 +268,25 @@ export class SuggestController implements IEditorContribution {
} else {
// exec command, done
this._commandService.executeCommand(suggestion.command.id, ...suggestion.command.arguments).done(undefined, onUnexpectedError);
this._commandService.executeCommand(suggestion.command.id, ...suggestion.command.arguments).then(undefined, onUnexpectedError);
this._model.cancel();
}
if (keepAlternativeSuggestions) {
this._alternatives.getValue().set(event, next => {
// this is not so pretty. when inserting the 'next'
// suggestion we undo until we are at the state at
// which we were before inserting the previous suggestion...
while (model.canUndo()) {
if (modelVersionNow !== model.getAlternativeVersionId()) {
model.undo();
}
this._onDidSelectItem(next, false, false);
break;
}
});
}
this._alertCompletionItem(event.item);
}
@@ -243,19 +295,92 @@ export class SuggestController implements IEditorContribution {
alert(msg);
}
triggerSuggest(onlyFrom?: ISuggestSupport[]): void {
triggerSuggest(onlyFrom?: CompletionItemProvider[]): void {
this._model.trigger({ auto: false }, false, onlyFrom);
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
this._editor.focus();
}
acceptSelectedSuggestion(): void {
triggerSuggestAndAcceptBest(defaultTypeText: string): void {
const positionNow = this._editor.getPosition();
const fallback = () => {
if (positionNow.equals(this._editor.getPosition())) {
this._editor.trigger('suggest', Handler.Type, { text: defaultTypeText });
}
};
const makesTextEdit = (item: ISuggestionItem): boolean => {
if (item.suggestion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet || item.suggestion.additionalTextEdits) {
// snippet, other editor -> makes edit
return true;
}
const position = this._editor.getPosition();
const startColumn = item.suggestion.range.startColumn;
const endColumn = position.column;
if (endColumn - startColumn !== item.suggestion.insertText.length) {
// unequal lengths -> makes edit
return true;
}
const textNow = this._editor.getModel().getValueInRange({
startLineNumber: position.lineNumber,
startColumn,
endLineNumber: position.lineNumber,
endColumn
});
// unequal text -> makes edit
return textNow !== item.suggestion.insertText;
};
once(this._model.onDidTrigger)(_ => {
// wait for trigger because only then the cancel-event is trustworthy
let listener: IDisposable[] = [];
anyEvent<any>(this._model.onDidTrigger, this._model.onDidCancel)(() => {
// retrigger or cancel -> try to type default text
dispose(listener);
fallback();
}, undefined, listener);
this._model.onDidSuggest(({ completionModel }) => {
dispose(listener);
if (completionModel.items.length === 0) {
fallback();
return;
}
const index = this._memory.getValue().select(this._editor.getModel(), this._editor.getPosition(), completionModel.items);
const item = completionModel.items[index];
if (!makesTextEdit(item)) {
fallback();
return;
}
this._editor.pushUndoStop();
this._onDidSelectItem({ index, item, model: completionModel }, true, false);
}, undefined, listener);
});
this._model.trigger({ auto: false, shy: true });
this._editor.revealLine(positionNow.lineNumber, ScrollType.Smooth);
this._editor.focus();
}
acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void {
if (this._widget) {
const item = this._widget.getFocusedItem();
this._onDidSelectItem(item);
this._onDidSelectItem(item, keepAlternativeSuggestions, true);
}
}
acceptNextSuggestion() {
this._alternatives.getValue().next();
}
acceptPrevSuggestion() {
this._alternatives.getValue().prev();
}
cancelSuggestWidget(): void {
if (this._widget) {
this._model.cancel();
@@ -353,7 +478,7 @@ const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(Sugge
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestion',
precondition: SuggestContext.Visible,
handler: x => x.acceptSelectedSuggestion(),
handler: x => x.acceptSelectedSuggestion(true),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
@@ -364,7 +489,7 @@ registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestionOnEnter',
precondition: SuggestContext.Visible,
handler: x => x.acceptSelectedSuggestion(),
handler: x => x.acceptSelectedSuggestion(false),
kbOpts: {
weight: weight,
kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit),
@@ -469,3 +594,53 @@ registerEditorCommand(new SuggestCommand({
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Space }
}
}));
//#region tab completions
registerEditorCommand(new SuggestCommand({
id: 'insertBestCompletion',
precondition: ContextKeyExpr.and(
ContextKeyExpr.equals('config.editor.tabCompletion', 'on'),
WordContextKey.AtEnd,
SuggestContext.Visible.toNegated(),
SuggestAlternatives.OtherSuggestions.toNegated(),
SnippetController2.InSnippetMode.toNegated()
),
handler: x => x.triggerSuggestAndAcceptBest('\t'),//todo@joh fallback/default configurable?
kbOpts: {
weight,
primary: KeyCode.Tab
}
}));
registerEditorCommand(new SuggestCommand({
id: 'insertNextSuggestion',
precondition: ContextKeyExpr.and(
ContextKeyExpr.equals('config.editor.tabCompletion', 'on'),
SuggestAlternatives.OtherSuggestions,
SuggestContext.Visible.toNegated(),
SnippetController2.InSnippetMode.toNegated()
),
handler: x => x.acceptNextSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyCode.Tab
}
}));
registerEditorCommand(new SuggestCommand({
id: 'insertPrevSuggestion',
precondition: ContextKeyExpr.and(
ContextKeyExpr.equals('config.editor.tabCompletion', 'on'),
SuggestAlternatives.OtherSuggestions,
SuggestContext.Visible.toNegated(),
SnippetController2.InSnippetMode.toNegated()
),
handler: x => x.acceptPrevSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.Shift | KeyCode.Tab
}
}));