Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -6,8 +6,9 @@
'use strict';
import { fuzzyScore } from 'vs/base/common/filters';
import { ISuggestSupport } from 'vs/editor/common/modes';
import { ISuggestSupport, ISuggestResult } from 'vs/editor/common/modes';
import { ISuggestionItem, SnippetConfig } from './suggest';
import { isDisposable } from 'vs/base/common/lifecycle';
export interface ICompletionItem extends ISuggestionItem {
matches?: number[];
@@ -15,6 +16,15 @@ export interface ICompletionItem extends ISuggestionItem {
idx?: number;
}
/* __GDPR__FRAGMENT__
"ICompletionStats" : {
"suggestionCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"snippetCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"textCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
// __GDPR__TODO__: This is a dynamically extensible structure which can not be declared statically.
export interface ICompletionStats {
suggestionCount: number;
snippetCount: number;
@@ -50,6 +60,18 @@ export class CompletionModel {
}
}
dispose(): void {
const seen = new Set<ISuggestResult>();
for (const { container } of this._items) {
if (!seen.has(container)) {
seen.add(container);
if (isDisposable(container)) {
container.dispose();
}
}
}
}
get lineContext(): LineContext {
return this._lineContext;
}

View File

@@ -213,7 +213,6 @@
box-sizing: border-box;
height: 100%;
width: 100%;
white-space: pre-wrap;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .type {
@@ -229,12 +228,26 @@
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs {
margin: 0;
padding: 4px 5px;
white-space: pre-wrap;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs {
white-space: initial;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs .code {
white-space: pre-wrap;
word-wrap: break-word;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > p:empty {
display: none;
}
.monaco-editor .suggest-widget .details code {
border-radius: 3px;
padding: 0 0.4em;
}
/* High Contrast and Dark Theming */

View File

@@ -12,7 +12,7 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModel, IEditorContribution, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { ISuggestResult, ISuggestSupport, ISuggestion, SuggestRegistry } from 'vs/editor/common/modes';
import { ISuggestResult, ISuggestSupport, ISuggestion, SuggestRegistry, SuggestContext, SuggestTriggerKind } from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -42,7 +42,7 @@ export function setSnippetSuggestSupport(support: ISuggestSupport): ISuggestSupp
return old;
}
export function provideSuggestionItems(model: IModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[]): TPromise<ISuggestionItem[]> {
export function provideSuggestionItems(model: IModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[], context?: SuggestContext): TPromise<ISuggestionItem[]> {
const allSuggestions: ISuggestionItem[] = [];
const acceptSuggestion = createSuggesionFilter(snippetConfig);
@@ -57,6 +57,8 @@ export function provideSuggestionItems(model: IModel, position: Position, snippe
supports.unshift([_snippetSuggestSupport]);
}
const suggestConext = context || { triggerKind: SuggestTriggerKind.Invoke };
// add suggestions from contributed providers - providers are ordered in groups of
// equal score and once a group produces a result the process stops
let hasResult = false;
@@ -73,7 +75,7 @@ export function provideSuggestionItems(model: IModel, position: Position, snippe
return undefined;
}
return asWinJsPromise(token => support.provideCompletionItems(model, position, token)).then(container => {
return asWinJsPromise(token => support.provideCompletionItems(model, position, suggestConext, token)).then(container => {
const len = allSuggestions.length;

View File

@@ -104,10 +104,7 @@ export class SuggestController implements IEditorContribution {
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
let updateFromConfig = () => {
const { acceptSuggestionOnEnter } = this._editor.getConfiguration().contribInfo;
acceptSuggestionsOnEnter.set(
acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart'
|| (<any /*migrate from old world*/>acceptSuggestionOnEnter) === true
);
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
};
this._toDispose.push(this._editor.onDidChangeConfiguration((e) => updateFromConfig()));
updateFromConfig();
@@ -173,37 +170,55 @@ export class SuggestController implements IEditorContribution {
}
private _onDidSelectItem(item: ICompletionItem): void {
if (item) {
const { suggestion, position } = item;
const columnDelta = this._editor.getPosition().column - position.column;
if (Array.isArray(suggestion.additionalTextEdits)) {
this._editor.pushUndoStop();
this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
this._editor.pushUndoStop();
}
let { insertText } = suggestion;
if (suggestion.snippetType !== 'textmate') {
insertText = SnippetParser.escape(insertText);
}
SnippetController2.get(this._editor).insert(
insertText,
suggestion.overwriteBefore + columnDelta,
suggestion.overwriteAfter
);
if (suggestion.command) {
this._commandService.executeCommand(suggestion.command.id, ...suggestion.command.arguments).done(undefined, onUnexpectedError);
}
this._alertCompletionItem(item);
this._telemetryService.publicLog('suggestSnippetInsert', { ...this._editor.getTelemetryData(), suggestionType: suggestion.type });
if (!item) {
this._model.cancel();
return;
}
this._model.cancel();
const { suggestion, position } = item;
const columnDelta = this._editor.getPosition().column - position.column;
if (Array.isArray(suggestion.additionalTextEdits)) {
this._editor.pushUndoStop();
this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
this._editor.pushUndoStop();
}
let { insertText } = suggestion;
if (suggestion.snippetType !== 'textmate') {
insertText = SnippetParser.escape(insertText);
}
SnippetController2.get(this._editor).insert(
insertText,
suggestion.overwriteBefore + columnDelta,
suggestion.overwriteAfter
);
if (!suggestion.command) {
// done
this._model.cancel();
} else if (suggestion.command.id === TriggerSuggestAction.id) {
// retigger
this._model.trigger({ auto: this._model.state === State.Auto }, true);
} else {
// exec command, done
this._commandService.executeCommand(suggestion.command.id, ...suggestion.command.arguments).done(undefined, onUnexpectedError);
this._model.cancel();
}
this._alertCompletionItem(item);
/* __GDPR__
"suggestSnippetInsert" : {
"suggestionType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${EditorTelemetryData}"
]
}
*/
this._telemetryService.publicLog('suggestSnippetInsert', { ...this._editor.getTelemetryData(), suggestionType: suggestion.type });
}
private _alertCompletionItem({ suggestion }: ICompletionItem): void {
@@ -212,7 +227,7 @@ export class SuggestController implements IEditorContribution {
}
triggerSuggest(onlyFrom?: ISuggestSupport[]): void {
this._model.trigger(false, false, onlyFrom);
this._model.trigger({ auto: false }, false, onlyFrom);
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
this._editor.focus();
}
@@ -283,9 +298,11 @@ export class SuggestController implements IEditorContribution {
@editorAction
export class TriggerSuggestAction extends EditorAction {
static readonly id = 'editor.action.triggerSuggest';
constructor() {
super({
id: 'editor.action.triggerSuggest',
id: TriggerSuggestAction.id,
label: nls.localize('suggest.trigger.label', "Trigger Suggest"),
alias: 'Trigger Suggest',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCompletionItemProvider),

View File

@@ -11,7 +11,7 @@ import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICommonCodeEditor, IModel, IWordAtPosition } from 'vs/editor/common/editorCommon';
import { ISuggestSupport, SuggestRegistry, StandardTokenType } from 'vs/editor/common/modes';
import { ISuggestSupport, SuggestRegistry, StandardTokenType, SuggestTriggerKind } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
import { provideSuggestionItems, getSuggestionComparator, ISuggestionItem } from './suggest';
import { CompletionModel } from './completionModel';
@@ -31,6 +31,11 @@ export interface ISuggestEvent {
auto: boolean;
}
export interface SuggestTriggerContext {
auto: boolean;
triggerCharacter?: string;
}
export class LineContext {
static shouldAutoTrigger(editor: ICommonCodeEditor): boolean {
@@ -88,90 +93,94 @@ export const enum State {
export class SuggestModel implements IDisposable {
private toDispose: IDisposable[] = [];
private quickSuggestDelay: number;
private triggerCharacterListener: IDisposable;
private triggerAutoSuggestPromise: TPromise<void>;
private triggerRefilter = new TimeoutTimer();
private _editor: ICommonCodeEditor;
private _toDispose: IDisposable[] = [];
private _quickSuggestDelay: number;
private _triggerCharacterListener: IDisposable;
private _triggerAutoSuggestPromise: TPromise<void>;
private _triggerRefilter = new TimeoutTimer();
private _state: State;
private requestPromise: TPromise<void>;
private context: LineContext;
private currentPosition: Position;
private completionModel: CompletionModel;
private _requestPromise: TPromise<void>;
private _context: LineContext;
private _currentPosition: Position;
private _completionModel: CompletionModel;
private _onDidCancel: Emitter<ICancelEvent> = new Emitter<ICancelEvent>();
get onDidCancel(): Event<ICancelEvent> { return this._onDidCancel.event; }
private _onDidTrigger: Emitter<ITriggerEvent> = new Emitter<ITriggerEvent>();
get onDidTrigger(): Event<ITriggerEvent> { return this._onDidTrigger.event; }
private _onDidSuggest: Emitter<ISuggestEvent> = new Emitter<ISuggestEvent>();
get onDidSuggest(): Event<ISuggestEvent> { return this._onDidSuggest.event; }
constructor(private editor: ICommonCodeEditor) {
readonly onDidCancel: Event<ICancelEvent> = this._onDidCancel.event;
readonly onDidTrigger: Event<ITriggerEvent> = this._onDidTrigger.event;
readonly onDidSuggest: Event<ISuggestEvent> = this._onDidSuggest.event;
constructor(editor: ICommonCodeEditor) {
this._editor = editor;
this._state = State.Idle;
this.triggerAutoSuggestPromise = null;
this.requestPromise = null;
this.completionModel = null;
this.context = null;
this.currentPosition = editor.getPosition() || new Position(1, 1);
this._triggerAutoSuggestPromise = null;
this._requestPromise = null;
this._completionModel = null;
this._context = null;
this._currentPosition = this._editor.getPosition() || new Position(1, 1);
// wire up various listeners
this.toDispose.push(this.editor.onDidChangeModel(() => {
this.updateTriggerCharacters();
this._toDispose.push(this._editor.onDidChangeModel(() => {
this._updateTriggerCharacters();
this.cancel();
}));
this.toDispose.push(editor.onDidChangeModelLanguage(() => {
this.updateTriggerCharacters();
this._toDispose.push(this._editor.onDidChangeModelLanguage(() => {
this._updateTriggerCharacters();
this.cancel();
}));
this.toDispose.push(this.editor.onDidChangeConfiguration(() => {
this.updateTriggerCharacters();
this.updateQuickSuggest();
this._toDispose.push(this._editor.onDidChangeConfiguration(() => {
this._updateTriggerCharacters();
this._updateQuickSuggest();
}));
this.toDispose.push(SuggestRegistry.onDidChange(() => {
this.updateTriggerCharacters();
this.updateActiveSuggestSession();
this._toDispose.push(SuggestRegistry.onDidChange(() => {
this._updateTriggerCharacters();
this._updateActiveSuggestSession();
}));
this.toDispose.push(this.editor.onDidChangeCursorSelection(e => {
this.onCursorChange(e);
this._toDispose.push(this._editor.onDidChangeCursorSelection(e => {
this._onCursorChange(e);
}));
this._toDispose.push(this._editor.onDidChangeModelContent(e => {
this._refilterCompletionItems();
}));
this.updateTriggerCharacters();
this.updateQuickSuggest();
this._updateTriggerCharacters();
this._updateQuickSuggest();
}
dispose(): void {
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this.triggerCharacterListener, this.triggerRefilter]);
this.toDispose = dispose(this.toDispose);
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerRefilter]);
this._toDispose = dispose(this._toDispose);
dispose(this._completionModel);
this.cancel();
}
// --- handle configuration & precondition changes
private updateQuickSuggest(): void {
this.quickSuggestDelay = this.editor.getConfiguration().contribInfo.quickSuggestionsDelay;
private _updateQuickSuggest(): void {
this._quickSuggestDelay = this._editor.getConfiguration().contribInfo.quickSuggestionsDelay;
if (isNaN(this.quickSuggestDelay) || (!this.quickSuggestDelay && this.quickSuggestDelay !== 0) || this.quickSuggestDelay < 0) {
this.quickSuggestDelay = 10;
if (isNaN(this._quickSuggestDelay) || (!this._quickSuggestDelay && this._quickSuggestDelay !== 0) || this._quickSuggestDelay < 0) {
this._quickSuggestDelay = 10;
}
}
private updateTriggerCharacters(): void {
private _updateTriggerCharacters(): void {
dispose(this.triggerCharacterListener);
dispose(this._triggerCharacterListener);
if (this.editor.getConfiguration().readOnly
|| !this.editor.getModel()
|| !this.editor.getConfiguration().contribInfo.suggestOnTriggerCharacters) {
if (this._editor.getConfiguration().readOnly
|| !this._editor.getModel()
|| !this._editor.getConfiguration().contribInfo.suggestOnTriggerCharacters) {
return;
}
const supportsByTriggerCharacter: { [ch: string]: ISuggestSupport[] } = Object.create(null);
for (const support of SuggestRegistry.all(this.editor.getModel())) {
for (const support of SuggestRegistry.all(this._editor.getModel())) {
if (isFalsyOrEmpty(support.triggerCharacters)) {
continue;
}
@@ -185,7 +194,7 @@ export class SuggestModel implements IDisposable {
}
}
this.triggerCharacterListener = this.editor.onDidType(text => {
this._triggerCharacterListener = this._editor.onDidType(text => {
const lastChar = text.charAt(text.length - 1);
const supports = supportsByTriggerCharacter[lastChar];
@@ -193,14 +202,14 @@ export class SuggestModel implements IDisposable {
// keep existing items that where not computed by the
// supports/providers that want to trigger now
const items: ISuggestionItem[] = [];
if (this.completionModel) {
for (const item of this.completionModel.items) {
if (this._completionModel) {
for (const item of this._completionModel.items) {
if (supports.indexOf(item.support) < 0) {
items.push(item);
}
}
}
this.trigger(true, Boolean(this.completionModel), supports, items);
this.trigger({ auto: true, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items);
}
});
}
@@ -213,41 +222,43 @@ export class SuggestModel implements IDisposable {
cancel(retrigger: boolean = false): void {
if (this.triggerAutoSuggestPromise) {
this.triggerAutoSuggestPromise.cancel();
this.triggerAutoSuggestPromise = null;
if (this._triggerAutoSuggestPromise) {
this._triggerAutoSuggestPromise.cancel();
this._triggerAutoSuggestPromise = null;
}
if (this.requestPromise) {
this.requestPromise.cancel();
this.requestPromise = null;
if (this._requestPromise) {
this._requestPromise.cancel();
this._requestPromise = null;
}
this._state = State.Idle;
this.completionModel = null;
this.context = null;
dispose(this._completionModel);
this._completionModel = null;
this._context = null;
this._onDidCancel.fire({ retrigger });
}
private updateActiveSuggestSession(): void {
private _updateActiveSuggestSession(): void {
if (this._state !== State.Idle) {
if (!SuggestRegistry.has(this.editor.getModel())) {
if (!SuggestRegistry.has(this._editor.getModel())) {
this.cancel();
} else {
this.trigger(this._state === State.Auto, true);
this.trigger({ auto: this._state === State.Auto }, true);
}
}
}
private onCursorChange(e: ICursorSelectionChangedEvent): void {
private _onCursorChange(e: ICursorSelectionChangedEvent): void {
const prevPosition = this.currentPosition;
this.currentPosition = this.editor.getPosition();
const prevPosition = this._currentPosition;
this._currentPosition = this._editor.getPosition();
if (!e.selection.isEmpty()
|| e.source !== 'keyboard'
|| e.reason !== CursorChangeReason.NotSet) {
|| e.reason !== CursorChangeReason.NotSet
) {
if (this._state === State.Idle) {
// Early exit if nothing needs to be done!
@@ -259,11 +270,11 @@ export class SuggestModel implements IDisposable {
return;
}
if (!SuggestRegistry.has(this.editor.getModel())) {
if (!SuggestRegistry.has(this._editor.getModel())) {
return;
}
const model = this.editor.getModel();
const model = this._editor.getModel();
if (!model) {
return;
}
@@ -272,33 +283,33 @@ export class SuggestModel implements IDisposable {
// trigger 24x7 IntelliSense when idle, enabled, when cursor
// moved RIGHT, and when at a good position
if (this.editor.getConfiguration().contribInfo.quickSuggestions !== false
&& prevPosition.isBefore(this.currentPosition)
if (this._editor.getConfiguration().contribInfo.quickSuggestions !== false
&& prevPosition.isBefore(this._currentPosition)
) {
this.cancel();
if (LineContext.shouldAutoTrigger(this.editor)) {
this.triggerAutoSuggestPromise = TPromise.timeout(this.quickSuggestDelay);
this.triggerAutoSuggestPromise.then(() => {
const model = this.editor.getModel();
const pos = this.editor.getPosition();
if (LineContext.shouldAutoTrigger(this._editor)) {
this._triggerAutoSuggestPromise = TPromise.timeout(this._quickSuggestDelay);
this._triggerAutoSuggestPromise.then(() => {
const model = this._editor.getModel();
const pos = this._editor.getPosition();
if (!model) {
return;
}
// validate enabled now
const { quickSuggestions } = this.editor.getConfiguration().contribInfo;
const { quickSuggestions } = this._editor.getConfiguration().contribInfo;
if (quickSuggestions === false) {
return;
} else if (quickSuggestions === true) {
// all good
} else {
// Check the type of the token that triggered this
model.tokenizeIfCheap(pos.lineNumber);
const { tokenType } = model
.getLineTokens(pos.lineNumber)
.findTokenAtOffset(pos.column - 1);
.findTokenAtOffset(Math.max(pos.column - 1 - 1, 0));
const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other
|| quickSuggestions.comments && tokenType === StandardTokenType.Comment
|| quickSuggestions.strings && tokenType === StandardTokenType.String;
@@ -308,33 +319,40 @@ export class SuggestModel implements IDisposable {
}
}
this.triggerAutoSuggestPromise = null;
this.trigger(true);
this._triggerAutoSuggestPromise = null;
this.trigger({ auto: true });
});
}
}
}
}
} else {
private _refilterCompletionItems(): void {
if (this._state === State.Idle) {
return;
}
const model = this._editor.getModel();
if (model) {
// refine active suggestion
this.triggerRefilter.cancelAndSet(() => {
const position = this.editor.getPosition();
this._triggerRefilter.cancelAndSet(() => {
const position = this._editor.getPosition();
const ctx = new LineContext(model, position, this._state === State.Auto);
this.onNewContext(ctx);
this._onNewContext(ctx);
}, 25);
}
}
public trigger(auto: boolean, retrigger: boolean = false, onlyFrom?: ISuggestSupport[], existingItems?: ISuggestionItem[]): void {
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: ISuggestSupport[], existingItems?: ISuggestionItem[]): void {
const model = this.editor.getModel();
const model = this._editor.getModel();
if (!model) {
return;
}
const auto = context.auto;
const ctx = new LineContext(model, this._editor.getPosition(), auto);
const ctx = new LineContext(model, this.editor.getPosition(), auto);
if (!LineContext.isInEditableRange(this.editor)) {
if (!LineContext.isInEditableRange(this._editor)) {
return;
}
@@ -344,92 +362,97 @@ export class SuggestModel implements IDisposable {
this._onDidTrigger.fire({ auto });
// Capture context when request was sent
this.context = ctx;
this._context = ctx;
this.requestPromise = provideSuggestionItems(model, this.editor.getPosition(),
this.editor.getConfiguration().contribInfo.snippetSuggestions,
onlyFrom
this._requestPromise = provideSuggestionItems(model, this._editor.getPosition(),
this._editor.getConfiguration().contribInfo.snippetSuggestions,
onlyFrom,
{
triggerCharacter: context.triggerCharacter,
triggerKind: context.triggerCharacter ? SuggestTriggerKind.TriggerCharacter : SuggestTriggerKind.Invoke
}
).then(items => {
this.requestPromise = null;
this._requestPromise = null;
if (this._state === State.Idle) {
return;
}
const model = this.editor.getModel();
const model = this._editor.getModel();
if (!model) {
return;
}
if (!isFalsyOrEmpty(existingItems)) {
const cmpFn = getSuggestionComparator(this.editor.getConfiguration().contribInfo.snippetSuggestions);
const cmpFn = getSuggestionComparator(this._editor.getConfiguration().contribInfo.snippetSuggestions);
items = items.concat(existingItems).sort(cmpFn);
}
const ctx = new LineContext(model, this.editor.getPosition(), auto);
this.completionModel = new CompletionModel(items, this.context.column, {
const ctx = new LineContext(model, this._editor.getPosition(), auto);
dispose(this._completionModel);
this._completionModel = new CompletionModel(items, this._context.column, {
leadingLineContent: ctx.leadingLineContent,
characterCountDelta: this.context ? ctx.column - this.context.column : 0
}, this.editor.getConfiguration().contribInfo.snippetSuggestions);
this.onNewContext(ctx);
characterCountDelta: this._context ? ctx.column - this._context.column : 0
}, this._editor.getConfiguration().contribInfo.snippetSuggestions);
this._onNewContext(ctx);
}).then(null, onUnexpectedError);
}
private onNewContext(ctx: LineContext): void {
private _onNewContext(ctx: LineContext): void {
if (!this.context) {
if (!this._context) {
// happens when 24x7 IntelliSense is enabled and still in its delay
return;
}
if (ctx.lineNumber !== this.context.lineNumber) {
if (ctx.lineNumber !== this._context.lineNumber) {
// e.g. happens when pressing Enter while IntelliSense is computed
this.cancel();
return;
}
if (ctx.column < this.context.column) {
if (ctx.column < this._context.column) {
// typed -> moved cursor LEFT -> retrigger if still on a word
if (ctx.leadingWord.word) {
this.trigger(this.context.auto, true);
this.trigger({ auto: this._context.auto }, true);
} else {
this.cancel();
}
return;
}
if (!this.completionModel) {
if (!this._completionModel) {
// happens when IntelliSense is not yet computed
return;
}
if (ctx.column > this.context.column && this.completionModel.incomplete && ctx.leadingWord.word.length !== 0) {
if (ctx.column > this._context.column && this._completionModel.incomplete && ctx.leadingWord.word.length !== 0) {
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
const { complete, incomplete } = this.completionModel.resolveIncompleteInfo();
this.trigger(this._state === State.Auto, true, incomplete, complete);
const { complete, incomplete } = this._completionModel.resolveIncompleteInfo();
this.trigger({ auto: this._state === State.Auto }, true, incomplete, complete);
} else {
// typed -> moved cursor RIGHT -> update UI
let oldLineContext = this.completionModel.lineContext;
let oldLineContext = this._completionModel.lineContext;
let isFrozen = false;
this.completionModel.lineContext = {
this._completionModel.lineContext = {
leadingLineContent: ctx.leadingLineContent,
characterCountDelta: ctx.column - this.context.column
characterCountDelta: ctx.column - this._context.column
};
if (this.completionModel.items.length === 0) {
if (this._completionModel.items.length === 0) {
if (LineContext.shouldAutoTrigger(this.editor) && this.context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
if (LineContext.shouldAutoTrigger(this._editor) && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
// retrigger when heading into a new word
this.trigger(this.context.auto, true);
this.trigger({ auto: this._context.auto }, true);
return;
}
if (!this.context.auto) {
if (!this._context.auto) {
// freeze when IntelliSense was manually requested
this.completionModel.lineContext = oldLineContext;
isFrozen = this.completionModel.items.length > 0;
this._completionModel.lineContext = oldLineContext;
isFrozen = this._completionModel.items.length > 0;
if (isFrozen && ctx.leadingWord.word.length === 0) {
// there were results before but now there aren't
@@ -446,8 +469,8 @@ export class SuggestModel implements IDisposable {
}
this._onDidSuggest.fire({
completionModel: this.completionModel,
auto: this.context.auto,
completionModel: this._completionModel,
auto: this._context.auto,
isFrozen,
});
}

View File

@@ -28,10 +28,13 @@ import { alert } from 'vs/base/browser/ui/aria/aria';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
const sticky = false; // for development purposes
const sticky = true; // for development purposes
const expandSuggestionDocsByDefault = false;
const maxSuggestionsToShow = 12;
@@ -142,7 +145,7 @@ class Renderer implements IRenderer<ICompletionItem, ISuggestionTemplateData> {
data.colorspan.style.backgroundColor = '';
if (suggestion.type === 'color') {
let color = matchesColor(suggestion.label) || matchesColor(suggestion.documentation);
let color = matchesColor(suggestion.label) || typeof suggestion.documentation === 'string' && matchesColor(suggestion.documentation);
if (color) {
data.icon.className = 'icon customcolor';
data.colorspan.style.backgroundColor = color;
@@ -203,6 +206,7 @@ class SuggestionDetails {
container: HTMLElement,
private widget: SuggestWidget,
private editor: ICodeEditor,
private markdownRenderer: MarkdownRenderer,
private triggerKeybindingLabel: string
) {
this.disposables = [];
@@ -244,7 +248,14 @@ class SuggestionDetails {
return;
}
removeClass(this.el, 'no-docs');
this.docs.textContent = item.suggestion.documentation;
if (typeof item.suggestion.documentation === 'string') {
removeClass(this.docs, 'markdown-docs');
this.docs.textContent = item.suggestion.documentation;
} else {
addClass(this.docs, 'markdown-docs');
this.docs.innerHTML = '';
this.docs.appendChild(this.markdownRenderer.render(item.suggestion.documentation));
}
if (item.suggestion.detail) {
this.type.innerText = item.suggestion.detail;
@@ -382,10 +393,13 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IKeybindingService keybindingService: IKeybindingService
@IKeybindingService keybindingService: IKeybindingService,
@IModeService modeService: IModeService,
@IOpenerService openerService: IOpenerService
) {
const kb = keybindingService.lookupKeybinding('editor.action.triggerSuggest');
const triggerKeybindingLabel = !kb ? '' : ` (${kb.getLabel()})`;
const markdownRenderer = new MarkdownRenderer(editor, modeService, openerService);
this.isAuto = false;
this.focusedItem = null;
@@ -405,7 +419,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.messageElement = append(this.element, $('.message'));
this.listElement = append(this.element, $('.tree'));
this.details = new SuggestionDetails(this.element, this, this.editor, triggerKeybindingLabel);
this.details = new SuggestionDetails(this.element, this, this.editor, markdownRenderer, triggerKeybindingLabel);
let renderer = new Renderer(this, this.editor, triggerKeybindingLabel);
@@ -715,6 +729,15 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
} else {
const { stats } = this.completionModel;
stats['wasAutomaticallyTriggered'] = !!isAuto;
/* __GDPR__
"suggestWidget" : {
"wasAutomaticallyTriggered" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${ICompletionStats}",
"${EditorTelemetryData}"
]
}
*/
this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() });
this.focusedItem = null;
@@ -842,6 +865,13 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.details.element.style.borderColor = this.detailsFocusBorderColor;
}
}
/* __GDPR__
"suggestWidget:toggleDetailsFocus" : {
"${include}": [
"${EditorTelemetryData}"
]
}
*/
this.telemetryService.publicLog('suggestWidget:toggleDetailsFocus', this.editor.getTelemetryData());
}
@@ -856,6 +886,13 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
removeClass(this.element, 'docs-side');
removeClass(this.element, 'docs-below');
this.editor.layoutContentWidget(this);
/* __GDPR__
"suggestWidget:collapseDetails" : {
"${include}": [
"${EditorTelemetryData}"
]
}
*/
this.telemetryService.publicLog('suggestWidget:collapseDetails', this.editor.getTelemetryData());
} else {
if (this.state !== State.Open && this.state !== State.Details) {
@@ -864,6 +901,13 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.updateExpandDocsSetting(true);
this.showDetails();
/* __GDPR__
"suggestWidget:expandDetails" : {
"${include}": [
"${EditorTelemetryData}"
]
}
*/
this.telemetryService.publicLog('suggestWidget:expandDetails', this.editor.getTelemetryData());
}
@@ -1062,10 +1106,20 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
registerThemingParticipant((theme, collector) => {
let matchHighlight = theme.getColor(editorSuggestWidgetHighlightForeground);
if (matchHighlight) {
collector.addRule(`.monaco-editor .suggest-widget:not(.frozen) .monaco-highlighted-label .highlight { color: ${matchHighlight}; }`);
collector.addRule(`.monaco-editor .suggest-widget:not(.frozen) .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${matchHighlight}; }`);
}
let foreground = theme.getColor(editorSuggestWidgetForeground);
if (foreground) {
collector.addRule(`.monaco-editor .suggest-widget { color: ${foreground}; }`);
}
const link = theme.getColor(textLinkForeground);
if (link) {
collector.addRule(`.monaco-editor .suggest-widget a { color: ${link}; }`);
}
let codeBackground = theme.getColor(textCodeBlockBackground);
if (codeBackground) {
collector.addRule(`.monaco-editor .suggest-widget code { background-color: ${codeBackground}; }`);
}
});

View File

@@ -11,7 +11,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { Model } from 'vs/editor/common/model/model';
import { ICommonCodeEditor, Handler } from 'vs/editor/common/editorCommon';
import { ISuggestSupport, ISuggestResult, SuggestRegistry } from 'vs/editor/common/modes';
import { ISuggestSupport, ISuggestResult, SuggestRegistry, SuggestTriggerKind } from 'vs/editor/common/modes';
import { SuggestModel, LineContext } from 'vs/editor/contrib/suggest/browser/suggestModel';
import { MockCodeEditor, MockScopeLocation } from 'vs/editor/test/common/mocks/mockCodeEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@@ -20,6 +20,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
function createMockEditor(model: Model): MockCodeEditor {
const contextKeyService = new MockContextKeyService();
@@ -149,25 +151,25 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
// cancel on trigger
assertEvent(model.onDidCancel, function () {
model.trigger(false);
model.trigger({ auto: false });
}, function (event) {
assert.equal(event.retrigger, false);
}),
assertEvent(model.onDidCancel, function () {
model.trigger(false, true);
model.trigger({ auto: false }, true);
}, function (event) {
assert.equal(event.retrigger, true);
}),
assertEvent(model.onDidTrigger, function () {
model.trigger(true);
model.trigger({ auto: true });
}, function (event) {
assert.equal(event.auto, true);
}),
assertEvent(model.onDidTrigger, function () {
model.trigger(false);
model.trigger({ auto: false });
}, function (event) {
assert.equal(event.auto, false);
})
@@ -183,12 +185,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return withOracle(model => {
return TPromise.join([
assertEvent(model.onDidCancel, function () {
model.trigger(true);
model.trigger({ auto: true });
}, function (event) {
assert.equal(event.retrigger, false);
}),
assertEvent(model.onDidSuggest, function () {
model.trigger(false);
model.trigger({ auto: false });
}, function (event) {
assert.equal(event.auto, false);
assert.equal(event.isFrozen, false);
@@ -239,7 +241,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return assertEvent(model.onDidSuggest, () => {
// make sure completionModel starts here!
model.trigger(true);
model.trigger({ auto: true });
}, event => {
return assertEvent(model.onDidSuggest, () => {
@@ -338,7 +340,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 3 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.isFrozen, false);
@@ -363,7 +365,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 3 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.isFrozen, false);
@@ -400,7 +402,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 4 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.completionModel.incomplete, true);
@@ -437,7 +439,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 4 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.completionModel.incomplete, true);
@@ -457,4 +459,82 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
});
});
});
test('Trigger character is provided in suggest context', function () {
let triggerCharacter = '';
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
triggerCharacters: ['.'],
provideCompletionItems(doc, pos, context) {
assert.equal(context.triggerKind, SuggestTriggerKind.TriggerCharacter);
triggerCharacter = context.triggerCharacter;
return <ISuggestResult>{
currentWord: '',
incomplete: false,
suggestions: [
{
label: 'foo.bar',
type: 'property',
insertText: 'foo.bar',
overwriteBefore: pos.column - 1
}
]
};
}
}));
model.setValue('');
return withOracle((model, editor) => {
return assertEvent(model.onDidSuggest, () => {
editor.setPosition({ lineNumber: 1, column: 1 });
editor.trigger('keyboard', Handler.Type, { text: 'foo.' });
}, event => {
assert.equal(triggerCharacter, '.');
});
});
});
test('Mac press and hold accent character insertion does not update suggestions, #35269', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
incomplete: true,
suggestions: [{
label: 'abc',
type: 'property',
insertText: 'abc',
overwriteBefore: pos.column - 1
}, {
label: 'äbc',
type: 'property',
insertText: 'äbc',
overwriteBefore: pos.column - 1
}]
};
}
}));
model.setValue('');
return withOracle((model, editor) => {
return assertEvent(model.onDidSuggest, () => {
editor.setPosition({ lineNumber: 1, column: 1 });
editor.trigger('keyboard', Handler.Type, { text: 'a' });
}, event => {
assert.equal(event.completionModel.items.length, 1);
assert.equal(event.completionModel.items[0].suggestion.label, 'abc');
return assertEvent(model.onDidSuggest, () => {
editor.executeEdits('test', [EditOperation.replace(new Range(1, 1, 1, 2), 'ä')]);
}, event => {
// suggest model changed to äbc
assert.equal(event.completionModel.items.length, 1);
assert.equal(event.completionModel.items[0].suggestion.label, 'äbc');
});
});
});
});
});