parameterHintsModel.ts 8.7 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async';
7
import { onUnexpectedError } from 'vs/base/common/errors';
8
import { Emitter } from 'vs/base/common/event';
9
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
10
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
11
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
12
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
13
import * as modes from 'vs/editor/common/modes';
14
import { provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
15 16 17 18 19 20

export interface TriggerContext {
	readonly triggerKind: modes.SignatureHelpTriggerKind;
	readonly triggerCharacter?: string;
}

21 22 23 24 25 26
namespace ParameterHintState {
	export const enum Type {
		Default,
		Active,
		Pending,
	}
M
Matt Bierner 已提交
27

28
	export const Default = new class { readonly type = Type.Default; };
29 30 31 32

	export class Pending {
		readonly type = Type.Pending;
		constructor(
33
			readonly request: CancelablePromise<modes.SignatureHelpResult | undefined | null>
34 35
		) { }
	}
M
Matt Bierner 已提交
36

37 38 39 40 41 42 43
	export class Active {
		readonly type = Type.Active;
		constructor(
			readonly hints: modes.SignatureHelp
		) { }
	}

44
	export type State = typeof Default | Pending | Active;
45
}
46

47 48 49 50
export class ParameterHintsModel extends Disposable {

	private static readonly DEFAULT_DELAY = 120; // ms

51 52
	private readonly _onChangedHints = this._register(new Emitter<modes.SignatureHelp | undefined>());
	public readonly onChangedHints = this._onChangedHints.event;
53

54
	private readonly editor: ICodeEditor;
M
Matt Bierner 已提交
55
	private triggerOnType = false;
56
	private _state: ParameterHintState.State = ParameterHintState.Default;
57
	private readonly _lastSignatureHelpResult = this._register(new MutableDisposable<modes.SignatureHelpResult>());
58 59 60
	private triggerChars = new CharacterSet();
	private retriggerChars = new CharacterSet();

61
	private readonly throttledDelayer: Delayer<boolean>;
62
	private triggerId = 0;
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

	constructor(
		editor: ICodeEditor,
		delay: number = ParameterHintsModel.DEFAULT_DELAY
	) {
		super();

		this.editor = editor;

		this.throttledDelayer = new Delayer(delay);

		this._register(this.editor.onDidChangeConfiguration(() => this.onEditorConfigurationChange()));
		this._register(this.editor.onDidChangeModel(e => this.onModelChanged()));
		this._register(this.editor.onDidChangeModelLanguage(_ => this.onModelChanged()));
		this._register(this.editor.onDidChangeCursorSelection(e => this.onCursorChange(e)));
		this._register(this.editor.onDidChangeModelContent(e => this.onModelContentChange()));
		this._register(modes.SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
		this._register(this.editor.onDidType(text => this.onDidType(text)));

		this.onEditorConfigurationChange();
		this.onModelChanged();
	}

86 87 88 89 90 91 92 93
	private get state() { return this._state; }
	private set state(value: ParameterHintState.State) {
		if (this._state.type === ParameterHintState.Type.Pending) {
			this._state.request.cancel();
		}
		this._state = value;
	}

94
	cancel(silent: boolean = false): void {
95
		this.state = ParameterHintState.Default;
96 97 98

		this.throttledDelayer.cancel();

99
		if (!silent) {
100
			this._onChangedHints.fire(undefined);
101 102 103 104 105
		}
	}

	trigger(context: TriggerContext, delay?: number): void {
		const model = this.editor.getModel();
M
Matt Bierner 已提交
106
		if (!model || !modes.SignatureHelpProviderRegistry.has(model)) {
107 108 109
			return;
		}

110
		const triggerId = ++this.triggerId;
111 112 113 114
		this.throttledDelayer.trigger(
			() => this.doTrigger({
				triggerKind: context.triggerKind,
				triggerCharacter: context.triggerCharacter,
115 116
				isRetrigger: this.state.type === ParameterHintState.Type.Active || this.state.type === ParameterHintState.Type.Pending,
				activeSignatureHelp: this.state.type === ParameterHintState.Type.Active ? this.state.hints : undefined
117
			}, triggerId), delay).then(undefined, onUnexpectedError);
118 119
	}

120
	public next(): void {
121
		if (this.state.type !== ParameterHintState.Type.Active) {
122 123 124 125
			return;
		}

		const length = this.state.hints.signatures.length;
126 127
		const activeSignature = this.state.hints.activeSignature;
		const last = (activeSignature % length) === (length - 1);
128 129 130 131 132 133 134 135
		const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;

		// If there is only one signature, or we're on last signature of list
		if ((length < 2 || last) && !cycle) {
			this.cancel();
			return;
		}

136
		this.updateActiveSignature(last && cycle ? 0 : activeSignature + 1);
137 138 139
	}

	public previous(): void {
140
		if (this.state.type !== ParameterHintState.Type.Active) {
141 142 143 144
			return;
		}

		const length = this.state.hints.signatures.length;
145 146
		const activeSignature = this.state.hints.activeSignature;
		const first = activeSignature === 0;
147 148 149 150 151 152 153 154
		const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;

		// If there is only one signature, or we're on first signature of list
		if ((length < 2 || first) && !cycle) {
			this.cancel();
			return;
		}

155 156 157 158
		this.updateActiveSignature(first && cycle ? length - 1 : activeSignature - 1);
	}

	private updateActiveSignature(activeSignature: number) {
159
		if (this.state.type !== ParameterHintState.Type.Active) {
160
			return;
161 162
		}

163
		this.state = new ParameterHintState.Active({ ...this.state.hints, activeSignature });
164
		this._onChangedHints.fire(this.state.hints);
165 166
	}

167
	private doTrigger(triggerContext: modes.SignatureHelpContext, triggerId: number): Promise<boolean> {
168 169 170 171 172 173 174 175 176
		this.cancel(true);

		if (!this.editor.hasModel()) {
			return Promise.resolve(false);
		}

		const model = this.editor.getModel();
		const position = this.editor.getPosition();

177 178
		this.state = new ParameterHintState.Pending(createCancelablePromise(token =>
			provideSignatureHelp(model, position, triggerContext, token)));
179

180
		return this.state.request.then(result => {
181 182
			// Check that we are still resolving the correct signature help
			if (triggerId !== this.triggerId) {
183 184 185
				if (result) {
					result.dispose();
				}
186 187 188
				return false;
			}

189
			if (!result || !result.value.signatures || result.value.signatures.length === 0) {
190 191 192
				if (result) {
					result.dispose();
				}
193
				this._lastSignatureHelpResult.clear();
194 195
				this.cancel();
				return false;
196
			} else {
197 198
				this.state = new ParameterHintState.Active(result.value);
				this._lastSignatureHelpResult.value = result;
199
				this._onChangedHints.fire(this.state.hints);
200
				return true;
201 202
			}
		}).catch(error => {
203 204 205
			if (triggerId === this.triggerId) {
				this.state = ParameterHintState.Default;
			}
206 207 208 209 210 211
			onUnexpectedError(error);
			return false;
		});
	}

	private get isTriggered(): boolean {
212 213
		return this.state.type === ParameterHintState.Type.Active
			|| this.state.type === ParameterHintState.Type.Pending
M
Matt Bierner 已提交
214
			|| this.throttledDelayer.isTriggered();
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
	}

	private onModelChanged(): void {
		this.cancel();

		// Update trigger characters
		this.triggerChars = new CharacterSet();
		this.retriggerChars = new CharacterSet();

		const model = this.editor.getModel();
		if (!model) {
			return;
		}

		for (const support of modes.SignatureHelpProviderRegistry.ordered(model)) {
			for (const ch of support.signatureHelpTriggerCharacters || []) {
				this.triggerChars.add(ch.charCodeAt(0));

				// All trigger characters are also considered retrigger characters
				this.retriggerChars.add(ch.charCodeAt(0));
			}

			for (const ch of support.signatureHelpRetriggerCharacters || []) {
				this.retriggerChars.add(ch.charCodeAt(0));
			}
		}
	}

	private onDidType(text: string) {
M
Matt Bierner 已提交
244
		if (!this.triggerOnType) {
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
			return;
		}

		const lastCharIndex = text.length - 1;
		const triggerCharCode = text.charCodeAt(lastCharIndex);

		if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
			this.trigger({
				triggerKind: modes.SignatureHelpTriggerKind.TriggerCharacter,
				triggerCharacter: text.charAt(lastCharIndex),
			});
		}
	}

	private onCursorChange(e: ICursorSelectionChangedEvent): void {
		if (e.source === 'mouse') {
			this.cancel();
		} else if (this.isTriggered) {
			this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
		}
	}

	private onModelContentChange(): void {
		if (this.isTriggered) {
			this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
		}
	}

	private onEditorConfigurationChange(): void {
M
Matt Bierner 已提交
274
		this.triggerOnType = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
275

M
Matt Bierner 已提交
276
		if (!this.triggerOnType) {
277 278 279 280 281 282 283 284
			this.cancel();
		}
	}

	dispose(): void {
		this.cancel(true);
		super.dispose();
	}
M
Matt Bierner 已提交
285
}