SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,455 @@
/*---------------------------------------------------------------------------------------------
* 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 { onUnexpectedError } from 'vs/base/common/errors';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { TimeoutTimer } from 'vs/base/common/async';
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 { Position } from 'vs/editor/common/core/position';
import { provideSuggestionItems, getSuggestionComparator, ISuggestionItem } from './suggest';
import { CompletionModel } from './completionModel';
import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
export interface ICancelEvent {
retrigger: boolean;
}
export interface ITriggerEvent {
auto: boolean;
}
export interface ISuggestEvent {
completionModel: CompletionModel;
isFrozen: boolean;
auto: boolean;
}
export class LineContext {
static shouldAutoTrigger(editor: ICommonCodeEditor): boolean {
const model = editor.getModel();
if (!model) {
return false;
}
const pos = editor.getPosition();
model.tokenizeIfCheap(pos.lineNumber);
const word = model.getWordAtPosition(pos);
if (!word) {
return false;
}
if (word.endColumn !== pos.column) {
return false;
}
if (!isNaN(Number(word.word))) {
return false;
}
return true;
}
static isInEditableRange(editor: ICommonCodeEditor): boolean {
const model = editor.getModel();
const position = editor.getPosition();
if (model.hasEditableRange()) {
const editableRange = model.getEditableRange();
if (!editableRange.containsPosition(position)) {
return false;
}
}
return true;
}
readonly lineNumber: number;
readonly column: number;
readonly leadingLineContent: string;
readonly leadingWord: IWordAtPosition;
readonly auto: boolean;
constructor(model: IModel, position: Position, auto: boolean) {
this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
this.leadingWord = model.getWordUntilPosition(position);
this.lineNumber = position.lineNumber;
this.column = position.column;
this.auto = auto;
}
}
export const enum State {
Idle = 0,
Manual = 1,
Auto = 2
}
export class SuggestModel implements IDisposable {
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 _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) {
this._state = State.Idle;
this.triggerAutoSuggestPromise = null;
this.requestPromise = null;
this.completionModel = null;
this.context = null;
this.currentPosition = editor.getPosition() || new Position(1, 1);
// wire up various listeners
this.toDispose.push(this.editor.onDidChangeModel(() => {
this.updateTriggerCharacters();
this.cancel();
}));
this.toDispose.push(editor.onDidChangeModelLanguage(() => {
this.updateTriggerCharacters();
this.cancel();
}));
this.toDispose.push(this.editor.onDidChangeConfiguration(() => {
this.updateTriggerCharacters();
this.updateQuickSuggest();
}));
this.toDispose.push(SuggestRegistry.onDidChange(() => {
this.updateTriggerCharacters();
this.updateActiveSuggestSession();
}));
this.toDispose.push(this.editor.onDidChangeCursorSelection(e => {
this.onCursorChange(e);
}));
this.updateTriggerCharacters();
this.updateQuickSuggest();
}
dispose(): void {
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this.triggerCharacterListener, this.triggerRefilter]);
this.toDispose = dispose(this.toDispose);
this.cancel();
}
// --- handle configuration & precondition changes
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;
}
}
private updateTriggerCharacters(): void {
dispose(this.triggerCharacterListener);
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())) {
if (isFalsyOrEmpty(support.triggerCharacters)) {
continue;
}
for (const ch of support.triggerCharacters) {
const array = supportsByTriggerCharacter[ch];
if (!array) {
supportsByTriggerCharacter[ch] = [support];
} else {
array.push(support);
}
}
}
this.triggerCharacterListener = this.editor.onDidType(text => {
const lastChar = text.charAt(text.length - 1);
const supports = supportsByTriggerCharacter[lastChar];
if (supports) {
// 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 (supports.indexOf(item.support) < 0) {
items.push(item);
}
}
}
this.trigger(true, Boolean(this.completionModel), supports, items);
}
});
}
// --- trigger/retrigger/cancel suggest
get state(): State {
return this._state;
}
cancel(retrigger: boolean = false): void {
if (this.triggerAutoSuggestPromise) {
this.triggerAutoSuggestPromise.cancel();
this.triggerAutoSuggestPromise = null;
}
if (this.requestPromise) {
this.requestPromise.cancel();
this.requestPromise = null;
}
this._state = State.Idle;
this.completionModel = null;
this.context = null;
this._onDidCancel.fire({ retrigger });
}
private updateActiveSuggestSession(): void {
if (this._state !== State.Idle) {
if (!SuggestRegistry.has(this.editor.getModel())) {
this.cancel();
} else {
this.trigger(this._state === State.Auto, true);
}
}
}
private onCursorChange(e: ICursorSelectionChangedEvent): void {
const prevPosition = this.currentPosition;
this.currentPosition = this.editor.getPosition();
if (!e.selection.isEmpty()
|| e.source !== 'keyboard'
|| e.reason !== CursorChangeReason.NotSet) {
if (this._state === State.Idle) {
// Early exit if nothing needs to be done!
// Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)
return;
}
this.cancel();
return;
}
if (!SuggestRegistry.has(this.editor.getModel())) {
return;
}
const model = this.editor.getModel();
if (!model) {
return;
}
if (this._state === State.Idle) {
// 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)
) {
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 (!model) {
return;
}
// validate enabled now
const { quickSuggestions } = this.editor.getConfiguration().contribInfo;
if (quickSuggestions === false) {
return;
} else if (quickSuggestions === true) {
// all good
} else {
model.tokenizeIfCheap(pos.lineNumber);
const { tokenType } = model
.getLineTokens(pos.lineNumber)
.findTokenAtOffset(pos.column - 1);
const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other
|| quickSuggestions.comments && tokenType === StandardTokenType.Comment
|| quickSuggestions.strings && tokenType === StandardTokenType.String;
if (!inValidScope) {
return;
}
}
this.triggerAutoSuggestPromise = null;
this.trigger(true);
});
}
}
} else {
// refine active suggestion
this.triggerRefilter.cancelAndSet(() => {
const position = this.editor.getPosition();
const ctx = new LineContext(model, position, this._state === State.Auto);
this.onNewContext(ctx);
}, 25);
}
}
public trigger(auto: boolean, retrigger: boolean = false, onlyFrom?: ISuggestSupport[], existingItems?: ISuggestionItem[]): void {
const model = this.editor.getModel();
if (!model) {
return;
}
const ctx = new LineContext(model, this.editor.getPosition(), auto);
if (!LineContext.isInEditableRange(this.editor)) {
return;
}
// Cancel previous requests, change state & update UI
this.cancel(retrigger);
this._state = auto ? State.Auto : State.Manual;
this._onDidTrigger.fire({ auto });
// Capture context when request was sent
this.context = ctx;
this.requestPromise = provideSuggestionItems(model, this.editor.getPosition(),
this.editor.getConfiguration().contribInfo.snippetSuggestions,
onlyFrom
).then(items => {
this.requestPromise = null;
if (this._state === State.Idle) {
return;
}
const model = this.editor.getModel();
if (!model) {
return;
}
if (!isFalsyOrEmpty(existingItems)) {
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, {
leadingLineContent: ctx.leadingLineContent,
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 {
if (!this.context) {
// happens when 24x7 IntelliSense is enabled and still in its delay
return;
}
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) {
// typed -> moved cursor LEFT -> retrigger if still on a word
if (ctx.leadingWord.word) {
this.trigger(this.context.auto, true);
} else {
this.cancel();
}
return;
}
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) {
// 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);
} else {
// typed -> moved cursor RIGHT -> update UI
let oldLineContext = this.completionModel.lineContext;
let isFrozen = false;
this.completionModel.lineContext = {
leadingLineContent: ctx.leadingLineContent,
characterCountDelta: ctx.column - this.context.column
};
if (this.completionModel.items.length === 0) {
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);
return;
}
if (!this.context.auto) {
// freeze when IntelliSense was manually requested
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
// and also we are not on a word anymore -> cancel
this.cancel();
return;
}
} else {
// nothing left
this.cancel();
return;
}
}
this._onDidSuggest.fire({
completionModel: this.completionModel,
auto: this.context.auto,
isFrozen,
});
}
}
}