From 8a9b191fda3b0a5ae2ca0b2db1154f7dfd3bc671 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 7 Dec 2015 14:49:35 +0100 Subject: [PATCH] suggest: cleanup --- src/vs/editor/common/modes/modesFilters.ts | 8 +- .../editor/contrib/suggest/browser/suggest.ts | 87 +++++++------- .../contrib/suggest/browser/suggestModel.ts | 107 ++++++++---------- .../contrib/suggest/browser/suggestWidget.ts | 45 +++----- 4 files changed, 105 insertions(+), 142 deletions(-) diff --git a/src/vs/editor/common/modes/modesFilters.ts b/src/vs/editor/common/modes/modesFilters.ts index d9276237c18..040afe57232 100644 --- a/src/vs/editor/common/modes/modesFilters.ts +++ b/src/vs/editor/common/modes/modesFilters.ts @@ -25,15 +25,11 @@ export var ContiguousSubString: ISuggestionFilter = wrapBaseFilter(Filters.match // Combined Filters export function or(first: ISuggestionFilter, second: ISuggestionFilter): ISuggestionFilter { - return (word: string, suggestion: ISuggestion): Filters.IMatch[] => { - return first(word, suggestion) || second(word, suggestion); - }; + return (word, suggestion) => first(word, suggestion) || second(word, suggestion); } export function and(first: ISuggestionFilter, second: ISuggestionFilter): ISuggestionFilter { - return (word: string, suggestion: ISuggestion): Filters.IMatch[] => { - return first(word, suggestion) && second(word, suggestion); - }; + return (word, suggestion) => first(word, suggestion) && second(word, suggestion); } export var DefaultFilter = or(or(Prefix, CamelCase), ContiguousSubString); \ No newline at end of file diff --git a/src/vs/editor/contrib/suggest/browser/suggest.ts b/src/vs/editor/contrib/suggest/browser/suggest.ts index ce5b963b4fb..cc3e286dc20 100644 --- a/src/vs/editor/contrib/suggest/browser/suggest.ts +++ b/src/vs/editor/contrib/suggest/browser/suggest.ts @@ -7,22 +7,22 @@ import nls = require('vs/nls'); import Lifecycle = require('vs/base/common/lifecycle'); import Snippet = require('vs/editor/contrib/snippet/common/snippet'); -import SuggestWidget = require('./suggestWidget'); -import SuggestModel = require('./suggestModel'); +import { SuggestWidget } from './suggestWidget'; +import { SuggestModel } from './suggestModel'; import Errors = require('vs/base/common/errors'); -import {TPromise} from 'vs/base/common/winjs.base'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; -import {CommonEditorRegistry, ContextKey, EditorActionDescriptor} from 'vs/editor/common/editorCommonExtensions'; -import {EditorAction, Behaviour} from 'vs/editor/common/editorAction'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { EditorBrowserRegistry } from 'vs/editor/browser/editorBrowserExtensions'; +import { CommonEditorRegistry, ContextKey, EditorActionDescriptor } from 'vs/editor/common/editorCommonExtensions'; +import { EditorAction, Behaviour } from 'vs/editor/common/editorAction'; import EditorBrowser = require('vs/editor/browser/editorBrowser'); import EditorCommon = require('vs/editor/common/editorCommon'); import Modes = require('vs/editor/common/modes'); import EventEmitter = require('vs/base/common/eventEmitter'); -import {IKeybindingService, IKeybindingContextKey} from 'vs/platform/keybinding/common/keybindingService'; -import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import {SuggestRegistry, ACCEPT_SELECTED_SUGGESTION_CMD, CONTEXT_SUGGEST_WIDGET_VISIBLE} from 'vs/editor/contrib/suggest/common/suggest'; -import {INullService} from 'vs/platform/instantiation/common/instantiation'; -import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; +import { IKeybindingService, IKeybindingContextKey } from 'vs/platform/keybinding/common/keybindingService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { SuggestRegistry, ACCEPT_SELECTED_SUGGESTION_CMD, CONTEXT_SUGGEST_WIDGET_VISIBLE } from 'vs/editor/contrib/suggest/common/suggest'; +import { IInstantiationService, INullService } from 'vs/platform/instantiation/common/instantiation'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; export class SuggestController implements EditorCommon.IEditorContribution { static ID = 'editor.contrib.suggestController'; @@ -31,39 +31,25 @@ export class SuggestController implements EditorCommon.IEditorContribution { return editor.getContribution(SuggestController.ID); } - private editor:EditorBrowser.ICodeEditor; - private model:SuggestModel.SuggestModel; - private suggestWidget: SuggestWidget.SuggestWidget; - private toDispose: Lifecycle.IDisposable[]; - + private model: SuggestModel; + private widget: SuggestWidget; private triggerCharacterListeners: Function[]; private suggestWidgetVisible: IKeybindingContextKey; + private toDispose: Lifecycle.IDisposable[]; constructor( - editor:EditorBrowser.ICodeEditor, + private editor:EditorBrowser.ICodeEditor, @IKeybindingService keybindingService: IKeybindingService, - @ITelemetryService telemetryService: ITelemetryService + @IInstantiationService instantiationService: IInstantiationService ) { - this.editor = editor; this.suggestWidgetVisible = keybindingService.createKey(CONTEXT_SUGGEST_WIDGET_VISIBLE, false); - - this.model = new SuggestModel.SuggestModel( - this.editor, - (snippet:Snippet.CodeSnippet, overwriteBefore:number, overwriteAfter:number) => Snippet.get(this.editor).run(snippet, overwriteBefore, overwriteAfter) - ); - - this.suggestWidget = new SuggestWidget.SuggestWidget( - this.editor, - this.model, - telemetryService, - keybindingService, - () => this.suggestWidgetVisible.set(true), - () => this.suggestWidgetVisible.reset() - ); + this.model = new SuggestModel(this.editor); + this.widget = instantiationService.createInstance(SuggestWidget, this.editor, this.model); this.triggerCharacterListeners = []; this.toDispose = []; + this.toDispose.push(this.widget.onDidVisibilityChange(visible => visible ? this.suggestWidgetVisible.set(true) : this.suggestWidgetVisible.reset())); this.toDispose.push(editor.addListener2(EditorCommon.EventType.ConfigurationChanged, () => this.update())); this.toDispose.push(editor.addListener2(EditorCommon.EventType.ModelChanged, () => this.update())); this.toDispose.push(editor.addListener2(EditorCommon.EventType.ModelModeChanged, () => this.update())); @@ -74,6 +60,9 @@ export class SuggestController implements EditorCommon.IEditorContribution { })); this.toDispose.push(SuggestRegistry.onDidChange(this.update, this)); + // TODO@Joao: what is this? + this.toDispose.push(this.model.onDidAccept(e => Snippet.get(this.editor).run(e.snippet, e.overwriteBefore, e.overwriteAfter))); + this.update(); } @@ -85,12 +74,12 @@ export class SuggestController implements EditorCommon.IEditorContribution { this.toDispose = Lifecycle.disposeAll(this.toDispose); this.triggerCharacterListeners = Lifecycle.cAll(this.triggerCharacterListeners); - if (this.suggestWidget) { - this.suggestWidget.destroy(); - this.suggestWidget = null; + if (this.widget) { + this.widget.dispose(); + this.widget = null; } if (this.model) { - this.model.destroy(); + this.model.dispose(); this.model = null; } } @@ -167,38 +156,38 @@ export class SuggestController implements EditorCommon.IEditorContribution { } public acceptSelectedSuggestion(): void { - if (this.suggestWidget) { - this.suggestWidget.acceptSelectedSuggestion(); + if (this.widget) { + this.widget.acceptSelectedSuggestion(); } } public hideSuggestWidget(): void { - if (this.suggestWidget) { - this.suggestWidget.cancel(); + if (this.widget) { + this.widget.cancel(); } } public selectNextSuggestion(): void { - if (this.suggestWidget) { - this.suggestWidget.selectNext(); + if (this.widget) { + this.widget.selectNext(); } } public selectNextPageSuggestion(): void { - if (this.suggestWidget) { - this.suggestWidget.selectNextPage(); + if (this.widget) { + this.widget.selectNextPage(); } } public selectPrevSuggestion(): void { - if (this.suggestWidget) { - this.suggestWidget.selectPrevious(); + if (this.widget) { + this.widget.selectPrevious(); } } public selectPrevPageSuggestion(): void { - if (this.suggestWidget) { - this.suggestWidget.selectPreviousPage(); + if (this.widget) { + this.widget.selectPreviousPage(); } } } diff --git a/src/vs/editor/contrib/suggest/browser/suggestModel.ts b/src/vs/editor/contrib/suggest/browser/suggestModel.ts index c633843918e..463274596fe 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestModel.ts @@ -21,10 +21,10 @@ import { CodeSnippet } from 'vs/editor/contrib/snippet/common/snippet'; import { IDisposable, disposeAll } from 'vs/base/common/lifecycle'; import { SuggestRegistry, ISuggestResult2, suggest } from '../common/suggest'; -enum SuggestState { - NOT_ACTIVE = 0, - MANUAL_TRIGGER = 1, - AUTO_TRIGGER = 2 +enum State { + Idle = 0, + Manual = 1, + Auto = 2 } export interface SuggestDataEvent { @@ -261,17 +261,21 @@ export interface ISuggestEvent { auto: boolean; } -export class SuggestModel { +export interface IAcceptEvent { + snippet: CodeSnippet; + overwriteBefore: number; + overwriteAfter: number; +} + + +export class SuggestModel implements IDisposable { - private editor: EditorCommon.ICommonCodeEditor; - private onAccept:(snippet: CodeSnippet, overwriteBefore:number, overwriteAfter:number)=>void; private toDispose: IDisposable[]; private autoSuggestDelay:number; private triggerAutoSuggestPromise:TPromise; - private state:SuggestState; + private state:State; - // Members which make sense when state is active private requestPromise:TPromise; private requestContext:SuggestionContext; private raw:RawModel; @@ -285,41 +289,26 @@ export class SuggestModel { private _onDidSuggest: Emitter = new Emitter(); public get onDidSuggest(): Event { return this._onDidSuggest.event; } - constructor(editor:EditorCommon.ICommonCodeEditor, onAccept:(snippet: CodeSnippet, overwriteBefore:number, overwriteAfter:number)=>void) { - this.editor = editor; - this.onAccept = onAccept; - this.toDispose = []; - - this.toDispose.push(this.editor.addListener2(EditorCommon.EventType.ConfigurationChanged, () => this.onEditorConfigurationChange())); - this.onEditorConfigurationChange(); + // TODO@joao: remove + private _onDidAccept: Emitter = new Emitter(); + public get onDidAccept(): Event { return this._onDidAccept.event; } + constructor(private editor: EditorCommon.ICommonCodeEditor) { + this.state = State.Idle; this.triggerAutoSuggestPromise = null; - this.state = SuggestState.NOT_ACTIVE; - this.requestPromise = null; this.raw = null; this.requestContext = null; - this.toDispose.push(this.editor.addListener2(EditorCommon.EventType.CursorSelectionChanged, (e: EditorCommon.ICursorSelectionChangedEvent) => { - if (!e.selection.isEmpty()) { - this.cancel(); - return; - } - - if (e.source !== 'keyboard' || e.reason !== '') { - this.cancel(); - return; - } - - this.onCursorChange(); - })); - this.toDispose.push(this.editor.addListener2(EditorCommon.EventType.ModelChanged, (e) => { - this.cancel(); - })); + this.toDispose = []; + this.toDispose.push(this.editor.addListener2(EditorCommon.EventType.ConfigurationChanged, () => this.onEditorConfigurationChange())); + this.toDispose.push(this.editor.addListener2(EditorCommon.EventType.CursorSelectionChanged, e => this.onCursorChange(e))); + this.toDispose.push(this.editor.addListener2(EditorCommon.EventType.ModelChanged, () => this.cancel())); + this.onEditorConfigurationChange(); } public cancel(silent:boolean = false, retrigger:boolean = false):boolean { - var actuallyCanceled = (this.state !== SuggestState.NOT_ACTIVE); + var actuallyCanceled = this.state !== State.Idle; if (this.triggerAutoSuggestPromise) { this.triggerAutoSuggestPromise.cancel(); @@ -331,7 +320,7 @@ export class SuggestModel { this.requestPromise = null; } - this.state = SuggestState.NOT_ACTIVE; + this.state = State.Idle; this.raw = null; this.requestContext = null; @@ -346,6 +335,7 @@ export class SuggestModel { if(!this.requestContext) { return null; } + return { lineNumber: this.requestContext.lineNumber, column: this.requestContext.column @@ -353,15 +343,25 @@ export class SuggestModel { } private isAutoSuggest():boolean { - return this.state === SuggestState.AUTO_TRIGGER; + return this.state === State.Auto; } - private onCursorChange():void { + private onCursorChange(e: EditorCommon.ICursorSelectionChangedEvent):void { + if (!e.selection.isEmpty()) { + this.cancel(); + return; + } + + if (e.source !== 'keyboard' || e.reason !== '') { + this.cancel(); + return; + } + if (!SuggestRegistry.has(this.editor.getModel())) { return; } - var isInactive = this.state === SuggestState.NOT_ACTIVE; + var isInactive = this.state === State.Idle; if (isInactive && !this.editor.getConfiguration().quickSuggestions) { return; @@ -382,7 +382,7 @@ export class SuggestModel { } } else if (this.raw && this.raw.incomplete) { - this.trigger(this.state === SuggestState.AUTO_TRIGGER, undefined, true); + this.trigger(this.state === State.Auto, undefined, true); } else { this.onNewContext(ctx); } @@ -402,35 +402,27 @@ export class SuggestModel { return; } - var $tTrigger = timer.start(timer.Topic.EDITOR, 'suggest/TRIGGER'); - // Cancel previous requests, change state & update UI this.cancel(false, retrigger); - this.state = (auto || characterTriggered) ? SuggestState.AUTO_TRIGGER : SuggestState.MANUAL_TRIGGER; + this.state = (auto || characterTriggered) ? State.Auto : State.Manual; this._onDidTrigger.fire({ auto: this.isAutoSuggest(), characterTriggered, retrigger }); // Capture context when request was sent this.requestContext = ctx; - // Send mode request - var $tRequest = timer.start(timer.Topic.EDITOR, 'suggest/REQUEST'); var position = this.editor.getPosition(); - let raw = new RawModel(); let rank = 0; + this.requestPromise = suggest(model, position, triggerCharacter, groups).then(all => { for (let suggestions of all) { if (raw.insertSuggestions(rank, suggestions)) { rank++; } } - }); - - this.requestPromise.then(() => { - $tRequest.stop(); this.requestPromise = null; - if (this.state === SuggestState.NOT_ACTIVE) { + if (this.state === State.Idle) { return; } @@ -447,15 +439,13 @@ export class SuggestModel { } else { this._onDidSuggest.fire({ suggestions: null, auto: this.isAutoSuggest() }); } - }, () => { - $tRequest.stop(); - }).done(() => $tTrigger.stop()); + }).then(null, onUnexpectedError); } private onNewContext(ctx:SuggestionContext):void { if (this.requestContext && !this.requestContext.isValidForNewContext(ctx)) { if (this.requestContext.isValidForRetrigger(ctx)) { - this.trigger(this.state === SuggestState.AUTO_TRIGGER, undefined, true); + this.trigger(this.state === State.Auto, undefined, true); } else { this.cancel(); } @@ -491,7 +481,10 @@ export class SuggestModel { : Math.max(0, parentSuggestions.overwriteAfter); this.cancel(); - this.onAccept(new CodeSnippet(item.suggestion.codeSnippet), overwriteBefore, overwriteAfter); + this._onDidAccept.fire({ + snippet: new CodeSnippet(item.suggestion.codeSnippet), + overwriteBefore, overwriteAfter + }); return true; } @@ -504,7 +497,7 @@ export class SuggestModel { } } - public destroy():void { + public dispose():void { this.cancel(true); this.toDispose = disposeAll(this.toDispose); } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 2d423131536..f4d5460ebc0 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -10,6 +10,7 @@ import nls = require('vs/nls'); import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, disposeAll } from 'vs/base/common/lifecycle'; import Errors = require('vs/base/common/errors'); +import Event, { Emitter } from 'vs/base/common/event'; import dom = require('vs/base/browser/dom'); import Tree = require('vs/base/parts/tree/common/tree'); import TreeImpl = require('vs/base/parts/tree/browser/treeImpl'); @@ -138,12 +139,6 @@ interface ISuggestionTemplateData { class Renderer implements Tree.IRenderer { - private editor: EditorBrowser.ICodeEditor; - - constructor(editor: EditorBrowser.ICodeEditor) { - this.editor = editor; - } - public getHeight(tree:Tree.ITree, element:any):number { // var suggestion = element; @@ -245,14 +240,13 @@ interface ITelemetryData { wasAutomaticallyTriggered?: boolean; } -export class SuggestWidget implements EditorBrowser.IContentWidget { +export class SuggestWidget implements EditorBrowser.IContentWidget, IDisposable { static ID = 'editor.widget.suggestWidget'; static WIDTH = 438; static LOADING_MESSAGE = new MessageRoot(nls.localize('suggestWidget.loading', "Loading...")); static NO_SUGGESTIONS_MESSAGE = new MessageRoot(nls.localize('suggestWidget.noSuggestions', "No suggestions.")); - private editor: EditorBrowser.ICodeEditor; private shouldShowEmptySuggestionList: boolean; private isActive: boolean; private isLoading: boolean; @@ -273,21 +267,15 @@ export class SuggestWidget implements EditorBrowser.IContentWidget { // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; - private _onShown: () => void; - private _onHidden: () => void; + private _onDidVisibilityChange: Emitter = new Emitter(); + public get onDidVisibilityChange(): Event { return this._onDidVisibilityChange.event; } constructor( - editor: EditorBrowser.ICodeEditor, + private editor: EditorBrowser.ICodeEditor, private model: SuggestModel, - telemetryService:ITelemetryService, - keybindingService:IKeybindingService, - onShown: () => void, - onHidden: () => void + @IKeybindingService keybindingService: IKeybindingService, + @ITelemetryService telemetryService: ITelemetryService ) { - this.editor = editor; - this._onShown = onShown; - this._onHidden = onHidden; - this.isActive = false; this.isLoading = false; this.isAuto = false; @@ -324,14 +312,12 @@ export class SuggestWidget implements EditorBrowser.IContentWidget { dom.addClass(this.element, 'no-icons'); } - var dataSource = new DataSource(); - this.renderer = new Renderer(this.editor); + this.renderer = new Renderer(); + const dataSource = new DataSource(); + const controller = new Controller(); + const configuration = { renderer: this.renderer, dataSource, controller }; - this.tree = new TreeImpl.Tree(this.element, { - dataSource: dataSource, - renderer: this.renderer, - controller: new Controller() - }, { + this.tree = new TreeImpl.Tree(this.element, configuration, { twistiePixels: 0, alwaysFocused: true, verticalScrollMode: 'visible', @@ -612,7 +598,7 @@ export class SuggestWidget implements EditorBrowser.IContentWidget { } public show(): void { - this._onShown(); + this._onDidVisibilityChange.fire(true); this.isActive = true; this.tree.layout(); this.editor.layoutContentWidget(this); @@ -626,8 +612,7 @@ export class SuggestWidget implements EditorBrowser.IContentWidget { } public hide(): void { - this._onHidden(); - + this._onDidVisibilityChange.fire(false); this.isActive = false; dom.removeClass(this.element, 'visible'); this.editor.layoutContentWidget(this); @@ -679,7 +664,7 @@ export class SuggestWidget implements EditorBrowser.IContentWidget { this.editor.layoutContentWidget(this); } - public destroy() : void { + public dispose() : void { this.modelListenersToRemove = disposeAll(this.modelListenersToRemove); this.model = null; this.tree.dispose();