Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3 (#5587)

* Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3

* pipeline errors

* fix build
This commit is contained in:
Anthony Dresser
2019-05-23 11:16:03 -07:00
committed by GitHub
parent ca36f20c6b
commit cf8f8907ee
141 changed files with 6450 additions and 1228 deletions

View File

@@ -3,13 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
import { fuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
import { isDisposable } from 'vs/base/common/lifecycle';
import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes';
import { CompletionItem } from './suggest';
import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
import { CharCode } from 'vs/base/common/charCode';
import { compareIgnoreCase } from 'vs/base/common/strings';
type StrictCompletionItem = Required<CompletionItem>;
@@ -215,8 +216,15 @@ export class CompletionModel {
if (!match) {
continue; // NO match
}
item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0);
item.score[0] = match[0]; // use score from filterText
if (compareIgnoreCase(item.completion.filterText, item.completion.label) === 0) {
// filterText and label are actually the same -> use good highlights
item.score = match;
} else {
// re-run the scorer on the label in the hope of a result BUT use the rank
// of the filterText-match
item.score = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false) || FuzzyScore.Default;
item.score[0] = match[0]; // use score from filterText
}
} else {
// by default match `word` against the `label`

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISelectedSuggestion, SuggestWidget } from './suggestWidget';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
export class CommitCharacterController {
private _disposables: IDisposable[] = [];
private _active?: {
readonly acceptCharacters: CharacterSet;
readonly item: ISelectedSuggestion;
};
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
this._disposables.push(widget.onDidFocus(this._onItem, this));
this._disposables.push(widget.onDidHide(this.reset, this));
this._disposables.push(editor.onWillType(text => {
if (this._active) {
const ch = text.charCodeAt(text.length - 1);
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
accept(this._active.item);
}
}
}));
}
private _onItem(selected: ISelectedSuggestion | undefined): void {
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
this.reset();
return;
}
const acceptCharacters = new CharacterSet();
for (const ch of selected.item.completion.commitCharacters) {
if (ch.length > 0) {
acceptCharacters.add(ch.charCodeAt(0));
}
}
this._active = { acceptCharacters, item: selected };
}
reset(): void {
this._active = undefined;
}
dispose() {
dispose(this._disposables);
}
}

View File

@@ -31,57 +31,8 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
import { Event } from 'vs/base/common/event';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IdleValue } from 'vs/base/common/async';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
import { isObject } from 'vs/base/common/types';
class AcceptOnCharacterOracle {
private _disposables: IDisposable[] = [];
private _active?: {
readonly acceptCharacters: CharacterSet;
readonly item: ISelectedSuggestion;
};
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
this._disposables.push(widget.onDidFocus(this._onItem, this));
this._disposables.push(widget.onDidHide(this.reset, this));
this._disposables.push(editor.onWillType(text => {
if (this._active) {
const ch = text.charCodeAt(text.length - 1);
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
accept(this._active.item);
}
}
}));
}
private _onItem(selected: ISelectedSuggestion | undefined): void {
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
this.reset();
return;
}
const acceptCharacters = new CharacterSet();
for (const ch of selected.item.completion.commitCharacters) {
if (ch.length > 0) {
acceptCharacters.add(ch.charCodeAt(0));
}
}
this._active = { acceptCharacters, item: selected };
}
reset(): void {
this._active = undefined;
}
dispose() {
dispose(this._disposables);
}
}
import { CommitCharacterController } from './suggestCommitCharacters';
export class SuggestController implements IEditorContribution {
@@ -113,15 +64,15 @@ export class SuggestController implements IEditorContribution {
const widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
this._toDispose.push(widget);
this._toDispose.push(widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this));
this._toDispose.push(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this));
// Wire up logic to accept a suggestion on certain characters
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, widget, item => this._onDidSelectItem(item, false, true));
const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, false, true));
this._toDispose.push(
autoAcceptOracle,
commitCharacterController,
this._model.onDidSuggest(e => {
if (e.completionModel.items.length === 0) {
autoAcceptOracle.reset();
commitCharacterController.reset();
}
})
);
@@ -210,7 +161,7 @@ export class SuggestController implements IEditorContribution {
}
}
protected _onDidSelectItem(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
protected _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
if (!event || !event.item) {
this._alternatives.getValue().reset();
this._model.cancel();
@@ -282,7 +233,7 @@ export class SuggestController implements IEditorContribution {
if (modelVersionNow !== model.getAlternativeVersionId()) {
model.undo();
}
this._onDidSelectItem(next, false, false);
this._insertSuggestion(next, false, false);
break;
}
});
@@ -364,7 +315,7 @@ export class SuggestController implements IEditorContribution {
return;
}
this._editor.pushUndoStop();
this._onDidSelectItem({ index, item, model: completionModel }, true, false);
this._insertSuggestion({ index, item, model: completionModel }, true, false);
}, undefined, listener);
});
@@ -375,10 +326,8 @@ export class SuggestController implements IEditorContribution {
}
acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void {
if (this._widget) {
const item = this._widget.getValue().getFocusedItem();
this._onDidSelectItem(item, !!keepAlternativeSuggestions, true);
}
const item = this._widget.getValue().getFocusedItem();
this._insertSuggestion(item, !!keepAlternativeSuggestions, true);
}
acceptNextSuggestion() {
@@ -390,58 +339,40 @@ export class SuggestController implements IEditorContribution {
}
cancelSuggestWidget(): void {
if (this._widget) {
this._model.cancel();
this._widget.getValue().hideWidget();
}
this._model.cancel();
this._widget.getValue().hideWidget();
}
selectNextSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectNext();
}
this._widget.getValue().selectNext();
}
selectNextPageSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectNextPage();
}
this._widget.getValue().selectNextPage();
}
selectLastSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectLast();
}
this._widget.getValue().selectLast();
}
selectPrevSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectPrevious();
}
this._widget.getValue().selectPrevious();
}
selectPrevPageSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectPreviousPage();
}
this._widget.getValue().selectPreviousPage();
}
selectFirstSuggestion(): void {
if (this._widget) {
this._widget.getValue().selectFirst();
}
this._widget.getValue().selectFirst();
}
toggleSuggestionDetails(): void {
if (this._widget) {
this._widget.getValue().toggleDetails();
}
this._widget.getValue().toggleDetails();
}
toggleSuggestionFocus(): void {
if (this._widget) {
this._widget.getValue().toggleDetailsFocus();
}
this._widget.getValue().toggleDetailsFocus();
}
}

View File

@@ -95,7 +95,6 @@ export class SuggestModel implements IDisposable {
private _quickSuggestDelay: number;
private _triggerCharacterListener: IDisposable;
private readonly _triggerQuickSuggest = new TimeoutTimer();
private readonly _triggerRefilter = new TimeoutTimer();
private _state: State = State.Idle;
private _requestToken?: CancellationTokenSource;
@@ -161,7 +160,7 @@ export class SuggestModel implements IDisposable {
}
dispose(): void {
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest, this._triggerRefilter]);
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest]);
this._toDispose = dispose(this._toDispose);
dispose(this._completionModel);
this.cancel();
@@ -221,7 +220,6 @@ export class SuggestModel implements IDisposable {
cancel(retrigger: boolean = false): void {
if (this._state !== State.Idle) {
this._triggerRefilter.cancel();
this._triggerQuickSuggest.cancel();
if (this._requestToken) {
this._requestToken.cancel();
@@ -328,14 +326,15 @@ export class SuggestModel implements IDisposable {
}
private _refilterCompletionItems(): void {
if (this._state === State.Idle) {
return;
}
if (!this._editor.hasModel()) {
return;
}
// refine active suggestion
this._triggerRefilter.cancelAndSet(() => {
// Re-filter suggestions. This MUST run async because filtering/scoring
// uses the model content AND the cursor position. The latter is NOT
// updated when the document has changed (the event which drives this method)
// and therefore a little pause (next mirco task) is needed. See:
// https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context#25933985
Promise.resolve().then(() => {
if (this._state === State.Idle) {
return;
}
if (!this._editor.hasModel()) {
return;
}
@@ -343,7 +342,7 @@ export class SuggestModel implements IDisposable {
const position = this._editor.getPosition();
const ctx = new LineContext(model, position, this._state === State.Auto, false);
this._onNewContext(ctx);
}, 0);
});
}
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: Set<CompletionItemProvider>, existingItems?: CompletionItem[]): void {

View File

@@ -30,7 +30,6 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CompletionItemKind, completionKindToCssClass } from 'vs/editor/common/modes';
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
@@ -545,10 +544,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
return;
}
item.resolve(CancellationToken.None).then(() => {
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
this.editor.focus();
});
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
this.editor.focus();
}
private _getSuggestionAriaAlertLabel(item: CompletionItem): string {

View File

@@ -671,8 +671,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return withOracle(async (sugget, editor) => {
class TestCtrl extends SuggestController {
_onDidSelectItem(item: ISelectedSuggestion) {
super._onDidSelectItem(item, false, true);
_insertSuggestion(item: ISelectedSuggestion) {
super._insertSuggestion(item, false, true);
}
}
const ctrl = <TestCtrl>editor.registerAndInstantiateContribution(TestCtrl);
@@ -687,7 +687,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
const [first] = event.completionModel.items;
assert.equal(first.completion.label, 'bar');
ctrl._onDidSelectItem({ item: first, index: 0, model: event.completionModel });
ctrl._insertSuggestion({ item: first, index: 0, model: event.completionModel });
});
assert.equal(