瀏覽代碼

Extract a common `CompletionAdapter`

Alex Dima 3 年之前
父節點
當前提交
1c2358bdf9
共有 7 個文件被更改,包括 282 次插入656 次删除
  1. 246 5
      src/common/lspLanguageFeatures.ts
  2. 1 1
      src/css/cssMode.ts
  3. 11 188
      src/css/languageFeatures.ts
  4. 2 2
      src/html/htmlMode.ts
  5. 10 228
      src/html/languageFeatures.ts
  6. 1 1
      src/json/jsonMode.ts
  7. 11 231
      src/json/languageFeatures.ts

+ 246 - 5
src/common/lspLanguageFeatures.ts

@@ -29,12 +29,12 @@ export interface ILanguageWorkerWithDiagnostics {
 }
 
 export class DiagnosticsAdapter<T extends ILanguageWorkerWithDiagnostics> {
-	protected _disposables: IDisposable[] = [];
-	private _listener: { [uri: string]: IDisposable } = Object.create(null);
+	protected readonly _disposables: IDisposable[] = [];
+	private readonly _listener: { [uri: string]: IDisposable } = Object.create(null);
 
 	constructor(
-		private _languageId: string,
-		protected _worker: WorkerAccessor<T>,
+		private readonly _languageId: string,
+		protected readonly _worker: WorkerAccessor<T>,
 		configChangeEvent: IEvent<any>
 	) {
 		const onModelAdd = (model: editor.IModel): void => {
@@ -97,7 +97,7 @@ export class DiagnosticsAdapter<T extends ILanguageWorkerWithDiagnostics> {
 
 	public dispose(): void {
 		this._disposables.forEach((d) => d && d.dispose());
-		this._disposables = [];
+		this._disposables.length = 0;
 	}
 
 	private _doValidate(resource: Uri, languageId: string): void {
@@ -149,3 +149,244 @@ function toDiagnostics(resource: Uri, diag: lsTypes.Diagnostic): editor.IMarkerD
 }
 
 //#endregion
+
+//#region CompletionAdapter
+
+export interface ILanguageWorkerWithCompletions {
+	doComplete(uri: string, position: lsTypes.Position): Promise<lsTypes.CompletionList>;
+}
+
+export class CompletionAdapter<T extends ILanguageWorkerWithCompletions>
+	implements languages.CompletionItemProvider
+{
+	constructor(
+		private readonly _worker: WorkerAccessor<T>,
+		private readonly _triggerCharacters: string[]
+	) {}
+
+	public get triggerCharacters(): string[] {
+		return this._triggerCharacters;
+	}
+
+	provideCompletionItems(
+		model: editor.IReadOnlyModel,
+		position: Position,
+		context: languages.CompletionContext,
+		token: CancellationToken
+	): Promise<languages.CompletionList | undefined> {
+		const resource = model.uri;
+
+		return this._worker(resource)
+			.then((worker) => {
+				return worker.doComplete(resource.toString(), fromPosition(position));
+			})
+			.then((info) => {
+				if (!info) {
+					return;
+				}
+				const wordInfo = model.getWordUntilPosition(position);
+				const wordRange = new Range(
+					position.lineNumber,
+					wordInfo.startColumn,
+					position.lineNumber,
+					wordInfo.endColumn
+				);
+
+				const items: languages.CompletionItem[] = info.items.map((entry) => {
+					const item: languages.CompletionItem = {
+						label: entry.label,
+						insertText: entry.insertText || entry.label,
+						sortText: entry.sortText,
+						filterText: entry.filterText,
+						documentation: entry.documentation,
+						detail: entry.detail,
+						command: toCommand(entry.command),
+						range: wordRange,
+						kind: toCompletionItemKind(entry.kind)
+					};
+					if (entry.textEdit) {
+						if (isInsertReplaceEdit(entry.textEdit)) {
+							item.range = {
+								insert: toRange(entry.textEdit.insert),
+								replace: toRange(entry.textEdit.replace)
+							};
+						} else {
+							item.range = toRange(entry.textEdit.range);
+						}
+						item.insertText = entry.textEdit.newText;
+					}
+					if (entry.additionalTextEdits) {
+						item.additionalTextEdits =
+							entry.additionalTextEdits.map<languages.TextEdit>(toTextEdit);
+					}
+					if (entry.insertTextFormat === lsTypes.InsertTextFormat.Snippet) {
+						item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
+					}
+					return item;
+				});
+
+				return {
+					isIncomplete: info.isIncomplete,
+					suggestions: items
+				};
+			});
+	}
+}
+
+export function fromPosition(position: Position): lsTypes.Position;
+export function fromPosition(position: undefined): undefined;
+export function fromPosition(position: Position | undefined): lsTypes.Position | undefined;
+export function fromPosition(position: Position | undefined): lsTypes.Position | undefined {
+	if (!position) {
+		return void 0;
+	}
+	return { character: position.column - 1, line: position.lineNumber - 1 };
+}
+
+export function fromRange(range: IRange): lsTypes.Range;
+export function fromRange(range: undefined): undefined;
+export function fromRange(range: IRange | undefined): lsTypes.Range | undefined;
+export function fromRange(range: IRange | undefined): lsTypes.Range | undefined {
+	if (!range) {
+		return void 0;
+	}
+	return {
+		start: {
+			line: range.startLineNumber - 1,
+			character: range.startColumn - 1
+		},
+		end: { line: range.endLineNumber - 1, character: range.endColumn - 1 }
+	};
+}
+export function toRange(range: lsTypes.Range): Range;
+export function toRange(range: undefined): undefined;
+export function toRange(range: lsTypes.Range | undefined): Range | undefined;
+export function toRange(range: lsTypes.Range | undefined): Range | undefined {
+	if (!range) {
+		return void 0;
+	}
+	return new Range(
+		range.start.line + 1,
+		range.start.character + 1,
+		range.end.line + 1,
+		range.end.character + 1
+	);
+}
+
+function isInsertReplaceEdit(
+	edit: lsTypes.TextEdit | lsTypes.InsertReplaceEdit
+): edit is lsTypes.InsertReplaceEdit {
+	return (
+		typeof (<lsTypes.InsertReplaceEdit>edit).insert !== 'undefined' &&
+		typeof (<lsTypes.InsertReplaceEdit>edit).replace !== 'undefined'
+	);
+}
+
+function toCompletionItemKind(kind: number | undefined): languages.CompletionItemKind {
+	const mItemKind = languages.CompletionItemKind;
+
+	switch (kind) {
+		case lsTypes.CompletionItemKind.Text:
+			return mItemKind.Text;
+		case lsTypes.CompletionItemKind.Method:
+			return mItemKind.Method;
+		case lsTypes.CompletionItemKind.Function:
+			return mItemKind.Function;
+		case lsTypes.CompletionItemKind.Constructor:
+			return mItemKind.Constructor;
+		case lsTypes.CompletionItemKind.Field:
+			return mItemKind.Field;
+		case lsTypes.CompletionItemKind.Variable:
+			return mItemKind.Variable;
+		case lsTypes.CompletionItemKind.Class:
+			return mItemKind.Class;
+		case lsTypes.CompletionItemKind.Interface:
+			return mItemKind.Interface;
+		case lsTypes.CompletionItemKind.Module:
+			return mItemKind.Module;
+		case lsTypes.CompletionItemKind.Property:
+			return mItemKind.Property;
+		case lsTypes.CompletionItemKind.Unit:
+			return mItemKind.Unit;
+		case lsTypes.CompletionItemKind.Value:
+			return mItemKind.Value;
+		case lsTypes.CompletionItemKind.Enum:
+			return mItemKind.Enum;
+		case lsTypes.CompletionItemKind.Keyword:
+			return mItemKind.Keyword;
+		case lsTypes.CompletionItemKind.Snippet:
+			return mItemKind.Snippet;
+		case lsTypes.CompletionItemKind.Color:
+			return mItemKind.Color;
+		case lsTypes.CompletionItemKind.File:
+			return mItemKind.File;
+		case lsTypes.CompletionItemKind.Reference:
+			return mItemKind.Reference;
+	}
+	return mItemKind.Property;
+}
+
+function fromCompletionItemKind(kind: languages.CompletionItemKind): lsTypes.CompletionItemKind {
+	const mItemKind = languages.CompletionItemKind;
+
+	switch (kind) {
+		case mItemKind.Text:
+			return lsTypes.CompletionItemKind.Text;
+		case mItemKind.Method:
+			return lsTypes.CompletionItemKind.Method;
+		case mItemKind.Function:
+			return lsTypes.CompletionItemKind.Function;
+		case mItemKind.Constructor:
+			return lsTypes.CompletionItemKind.Constructor;
+		case mItemKind.Field:
+			return lsTypes.CompletionItemKind.Field;
+		case mItemKind.Variable:
+			return lsTypes.CompletionItemKind.Variable;
+		case mItemKind.Class:
+			return lsTypes.CompletionItemKind.Class;
+		case mItemKind.Interface:
+			return lsTypes.CompletionItemKind.Interface;
+		case mItemKind.Module:
+			return lsTypes.CompletionItemKind.Module;
+		case mItemKind.Property:
+			return lsTypes.CompletionItemKind.Property;
+		case mItemKind.Unit:
+			return lsTypes.CompletionItemKind.Unit;
+		case mItemKind.Value:
+			return lsTypes.CompletionItemKind.Value;
+		case mItemKind.Enum:
+			return lsTypes.CompletionItemKind.Enum;
+		case mItemKind.Keyword:
+			return lsTypes.CompletionItemKind.Keyword;
+		case mItemKind.Snippet:
+			return lsTypes.CompletionItemKind.Snippet;
+		case mItemKind.Color:
+			return lsTypes.CompletionItemKind.Color;
+		case mItemKind.File:
+			return lsTypes.CompletionItemKind.File;
+		case mItemKind.Reference:
+			return lsTypes.CompletionItemKind.Reference;
+	}
+	return lsTypes.CompletionItemKind.Property;
+}
+
+export function toTextEdit(textEdit: lsTypes.TextEdit): languages.TextEdit;
+export function toTextEdit(textEdit: undefined): undefined;
+export function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined;
+export function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined {
+	if (!textEdit) {
+		return void 0;
+	}
+	return {
+		range: toRange(textEdit.range),
+		text: textEdit.newText
+	};
+}
+
+function toCommand(c: lsTypes.Command | undefined): languages.Command | undefined {
+	return c && c.command === 'editor.action.triggerSuggest'
+		? { id: c.command, title: c.title, arguments: c.arguments }
+		: undefined;
+}
+
+//#endregion

+ 1 - 1
src/css/cssMode.ts

@@ -29,7 +29,7 @@ export function setupMode(defaults: LanguageServiceDefaults): IDisposable {
 			providers.push(
 				languages.registerCompletionItemProvider(
 					languageId,
-					new languageFeatures.CompletionAdapter(worker)
+					new languageFeatures.CSSCompletionAdapter(worker)
 				)
 			);
 		}

+ 11 - 188
src/css/languageFeatures.ts

@@ -12,207 +12,30 @@ import {
 	IMarkdownString,
 	Uri,
 	Position,
-	IRange,
-	Range,
 	CancellationToken
 } from '../fillers/monaco-editor-core';
-import { DiagnosticsAdapter } from '../common/lspLanguageFeatures';
+import {
+	DiagnosticsAdapter,
+	fromPosition,
+	toRange,
+	toTextEdit,
+	fromRange,
+	CompletionAdapter
+} from '../common/lspLanguageFeatures';
 
 export interface WorkerAccessor {
 	(first: Uri, ...more: Uri[]): Promise<CSSWorker>;
 }
 
-// --- diagnostics --- ---
-
 export class CSSDiagnosticsAdapter extends DiagnosticsAdapter<CSSWorker> {
 	constructor(languageId: string, worker: WorkerAccessor, defaults: LanguageServiceDefaults) {
 		super(languageId, worker, defaults.onDidChange);
 	}
 }
 
-// --- completion ------
-
-function fromPosition(position: Position): lsTypes.Position;
-function fromPosition(position: undefined): undefined;
-function fromPosition(position: Position | undefined): lsTypes.Position | undefined;
-function fromPosition(position: Position | undefined): lsTypes.Position | undefined {
-	if (!position) {
-		return void 0;
-	}
-	return { character: position.column - 1, line: position.lineNumber - 1 };
-}
-
-function fromRange(range: IRange): lsTypes.Range;
-function fromRange(range: undefined): undefined;
-function fromRange(range: IRange | undefined): lsTypes.Range | undefined;
-function fromRange(range: IRange | undefined): lsTypes.Range | undefined {
-	if (!range) {
-		return void 0;
-	}
-	return {
-		start: {
-			line: range.startLineNumber - 1,
-			character: range.startColumn - 1
-		},
-		end: { line: range.endLineNumber - 1, character: range.endColumn - 1 }
-	};
-}
-function toRange(range: lsTypes.Range): Range;
-function toRange(range: undefined): undefined;
-function toRange(range: lsTypes.Range | undefined): Range | undefined;
-function toRange(range: lsTypes.Range | undefined): Range | undefined {
-	if (!range) {
-		return void 0;
-	}
-	return new Range(
-		range.start.line + 1,
-		range.start.character + 1,
-		range.end.line + 1,
-		range.end.character + 1
-	);
-}
-
-function isInsertReplaceEdit(
-	edit: lsTypes.TextEdit | lsTypes.InsertReplaceEdit
-): edit is lsTypes.InsertReplaceEdit {
-	return (
-		typeof (<lsTypes.InsertReplaceEdit>edit).insert !== 'undefined' &&
-		typeof (<lsTypes.InsertReplaceEdit>edit).replace !== 'undefined'
-	);
-}
-
-function toCompletionItemKind(kind: number | undefined): languages.CompletionItemKind {
-	const mItemKind = languages.CompletionItemKind;
-
-	switch (kind) {
-		case lsTypes.CompletionItemKind.Text:
-			return mItemKind.Text;
-		case lsTypes.CompletionItemKind.Method:
-			return mItemKind.Method;
-		case lsTypes.CompletionItemKind.Function:
-			return mItemKind.Function;
-		case lsTypes.CompletionItemKind.Constructor:
-			return mItemKind.Constructor;
-		case lsTypes.CompletionItemKind.Field:
-			return mItemKind.Field;
-		case lsTypes.CompletionItemKind.Variable:
-			return mItemKind.Variable;
-		case lsTypes.CompletionItemKind.Class:
-			return mItemKind.Class;
-		case lsTypes.CompletionItemKind.Interface:
-			return mItemKind.Interface;
-		case lsTypes.CompletionItemKind.Module:
-			return mItemKind.Module;
-		case lsTypes.CompletionItemKind.Property:
-			return mItemKind.Property;
-		case lsTypes.CompletionItemKind.Unit:
-			return mItemKind.Unit;
-		case lsTypes.CompletionItemKind.Value:
-			return mItemKind.Value;
-		case lsTypes.CompletionItemKind.Enum:
-			return mItemKind.Enum;
-		case lsTypes.CompletionItemKind.Keyword:
-			return mItemKind.Keyword;
-		case lsTypes.CompletionItemKind.Snippet:
-			return mItemKind.Snippet;
-		case lsTypes.CompletionItemKind.Color:
-			return mItemKind.Color;
-		case lsTypes.CompletionItemKind.File:
-			return mItemKind.File;
-		case lsTypes.CompletionItemKind.Reference:
-			return mItemKind.Reference;
-	}
-	return mItemKind.Property;
-}
-
-function toTextEdit(textEdit: lsTypes.TextEdit): languages.TextEdit;
-function toTextEdit(textEdit: undefined): undefined;
-function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined;
-function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined {
-	if (!textEdit) {
-		return void 0;
-	}
-	return {
-		range: toRange(textEdit.range),
-		text: textEdit.newText
-	};
-}
-
-function toCommand(c: lsTypes.Command | undefined): languages.Command | undefined {
-	return c && c.command === 'editor.action.triggerSuggest'
-		? { id: c.command, title: c.title, arguments: c.arguments }
-		: undefined;
-}
-
-export class CompletionAdapter implements languages.CompletionItemProvider {
-	constructor(private _worker: WorkerAccessor) {}
-
-	public get triggerCharacters(): string[] {
-		return ['/', '-', ':'];
-	}
-
-	provideCompletionItems(
-		model: editor.IReadOnlyModel,
-		position: Position,
-		context: languages.CompletionContext,
-		token: CancellationToken
-	): Promise<languages.CompletionList | undefined> {
-		const resource = model.uri;
-
-		return this._worker(resource)
-			.then((worker) => {
-				return worker.doComplete(resource.toString(), fromPosition(position));
-			})
-			.then((info) => {
-				if (!info) {
-					return;
-				}
-				const wordInfo = model.getWordUntilPosition(position);
-				const wordRange = new Range(
-					position.lineNumber,
-					wordInfo.startColumn,
-					position.lineNumber,
-					wordInfo.endColumn
-				);
-
-				const items: languages.CompletionItem[] = info.items.map((entry) => {
-					const item: languages.CompletionItem = {
-						label: entry.label,
-						insertText: entry.insertText || entry.label,
-						sortText: entry.sortText,
-						filterText: entry.filterText,
-						documentation: entry.documentation,
-						detail: entry.detail,
-						command: toCommand(entry.command),
-						range: wordRange,
-						kind: toCompletionItemKind(entry.kind)
-					};
-					if (entry.textEdit) {
-						if (isInsertReplaceEdit(entry.textEdit)) {
-							item.range = {
-								insert: toRange(entry.textEdit.insert),
-								replace: toRange(entry.textEdit.replace)
-							};
-						} else {
-							item.range = toRange(entry.textEdit.range);
-						}
-						item.insertText = entry.textEdit.newText;
-					}
-					if (entry.additionalTextEdits) {
-						item.additionalTextEdits =
-							entry.additionalTextEdits.map<languages.TextEdit>(toTextEdit);
-					}
-					if (entry.insertTextFormat === lsTypes.InsertTextFormat.Snippet) {
-						item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
-					}
-					return item;
-				});
-
-				return {
-					isIncomplete: info.isIncomplete,
-					suggestions: items
-				};
-			});
+export class CSSCompletionAdapter extends CompletionAdapter<CSSWorker> {
+	constructor(worker: WorkerAccessor) {
+		super(worker, ['/', '-', ':']);
 	}
 }
 

+ 2 - 2
src/html/htmlMode.ts

@@ -21,7 +21,7 @@ export function setupMode1(defaults: LanguageServiceDefaults): void {
 	// all modes
 	languages.registerCompletionItemProvider(
 		languageId,
-		new languageFeatures.CompletionAdapter(worker)
+		new languageFeatures.HTMLCompletionAdapter(worker)
 	);
 	languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker));
 
@@ -77,7 +77,7 @@ export function setupMode(defaults: LanguageServiceDefaults): IDisposable {
 			providers.push(
 				languages.registerCompletionItemProvider(
 					languageId,
-					new languageFeatures.CompletionAdapter(worker)
+					new languageFeatures.HTMLCompletionAdapter(worker)
 				)
 			);
 		}

+ 10 - 228
src/html/languageFeatures.ts

@@ -11,242 +11,24 @@ import {
 	IMarkdownString,
 	Uri,
 	Position,
-	IRange,
 	Range,
 	CancellationToken
 } from '../fillers/monaco-editor-core';
+import {
+	fromPosition,
+	toRange,
+	toTextEdit,
+	fromRange,
+	CompletionAdapter
+} from '../common/lspLanguageFeatures';
 
 export interface WorkerAccessor {
 	(...more: Uri[]): Promise<HTMLWorker>;
 }
 
-// --- completion ------
-
-function fromPosition(position: Position): lsTypes.Position;
-function fromPosition(position: undefined): undefined;
-function fromPosition(position: Position | undefined): lsTypes.Position | undefined;
-function fromPosition(position: Position | undefined): lsTypes.Position | undefined {
-	if (!position) {
-		return void 0;
-	}
-	return { character: position.column - 1, line: position.lineNumber - 1 };
-}
-
-function fromRange(range: IRange): lsTypes.Range;
-function fromRange(range: undefined): undefined;
-function fromRange(range: IRange | undefined): lsTypes.Range | undefined;
-function fromRange(range: IRange | undefined): lsTypes.Range | undefined {
-	if (!range) {
-		return void 0;
-	}
-	return {
-		start: {
-			line: range.startLineNumber - 1,
-			character: range.startColumn - 1
-		},
-		end: { line: range.endLineNumber - 1, character: range.endColumn - 1 }
-	};
-}
-function toRange(range: lsTypes.Range): Range;
-function toRange(range: undefined): undefined;
-function toRange(range: lsTypes.Range | undefined): Range | undefined;
-function toRange(range: lsTypes.Range | undefined): Range | undefined {
-	if (!range) {
-		return void 0;
-	}
-	return new Range(
-		range.start.line + 1,
-		range.start.character + 1,
-		range.end.line + 1,
-		range.end.character + 1
-	);
-}
-
-function isInsertReplaceEdit(
-	edit: lsTypes.TextEdit | lsTypes.InsertReplaceEdit
-): edit is lsTypes.InsertReplaceEdit {
-	return (
-		typeof (<lsTypes.InsertReplaceEdit>edit).insert !== 'undefined' &&
-		typeof (<lsTypes.InsertReplaceEdit>edit).replace !== 'undefined'
-	);
-}
-
-function toCompletionItemKind(kind: number | undefined): languages.CompletionItemKind {
-	const mItemKind = languages.CompletionItemKind;
-
-	switch (kind) {
-		case lsTypes.CompletionItemKind.Text:
-			return mItemKind.Text;
-		case lsTypes.CompletionItemKind.Method:
-			return mItemKind.Method;
-		case lsTypes.CompletionItemKind.Function:
-			return mItemKind.Function;
-		case lsTypes.CompletionItemKind.Constructor:
-			return mItemKind.Constructor;
-		case lsTypes.CompletionItemKind.Field:
-			return mItemKind.Field;
-		case lsTypes.CompletionItemKind.Variable:
-			return mItemKind.Variable;
-		case lsTypes.CompletionItemKind.Class:
-			return mItemKind.Class;
-		case lsTypes.CompletionItemKind.Interface:
-			return mItemKind.Interface;
-		case lsTypes.CompletionItemKind.Module:
-			return mItemKind.Module;
-		case lsTypes.CompletionItemKind.Property:
-			return mItemKind.Property;
-		case lsTypes.CompletionItemKind.Unit:
-			return mItemKind.Unit;
-		case lsTypes.CompletionItemKind.Value:
-			return mItemKind.Value;
-		case lsTypes.CompletionItemKind.Enum:
-			return mItemKind.Enum;
-		case lsTypes.CompletionItemKind.Keyword:
-			return mItemKind.Keyword;
-		case lsTypes.CompletionItemKind.Snippet:
-			return mItemKind.Snippet;
-		case lsTypes.CompletionItemKind.Color:
-			return mItemKind.Color;
-		case lsTypes.CompletionItemKind.File:
-			return mItemKind.File;
-		case lsTypes.CompletionItemKind.Reference:
-			return mItemKind.Reference;
-	}
-	return mItemKind.Property;
-}
-
-function fromCompletionItemKind(kind: languages.CompletionItemKind): lsTypes.CompletionItemKind {
-	const mItemKind = languages.CompletionItemKind;
-
-	switch (kind) {
-		case mItemKind.Text:
-			return lsTypes.CompletionItemKind.Text;
-		case mItemKind.Method:
-			return lsTypes.CompletionItemKind.Method;
-		case mItemKind.Function:
-			return lsTypes.CompletionItemKind.Function;
-		case mItemKind.Constructor:
-			return lsTypes.CompletionItemKind.Constructor;
-		case mItemKind.Field:
-			return lsTypes.CompletionItemKind.Field;
-		case mItemKind.Variable:
-			return lsTypes.CompletionItemKind.Variable;
-		case mItemKind.Class:
-			return lsTypes.CompletionItemKind.Class;
-		case mItemKind.Interface:
-			return lsTypes.CompletionItemKind.Interface;
-		case mItemKind.Module:
-			return lsTypes.CompletionItemKind.Module;
-		case mItemKind.Property:
-			return lsTypes.CompletionItemKind.Property;
-		case mItemKind.Unit:
-			return lsTypes.CompletionItemKind.Unit;
-		case mItemKind.Value:
-			return lsTypes.CompletionItemKind.Value;
-		case mItemKind.Enum:
-			return lsTypes.CompletionItemKind.Enum;
-		case mItemKind.Keyword:
-			return lsTypes.CompletionItemKind.Keyword;
-		case mItemKind.Snippet:
-			return lsTypes.CompletionItemKind.Snippet;
-		case mItemKind.Color:
-			return lsTypes.CompletionItemKind.Color;
-		case mItemKind.File:
-			return lsTypes.CompletionItemKind.File;
-		case mItemKind.Reference:
-			return lsTypes.CompletionItemKind.Reference;
-	}
-	return lsTypes.CompletionItemKind.Property;
-}
-
-function toTextEdit(textEdit: lsTypes.TextEdit): languages.TextEdit;
-function toTextEdit(textEdit: undefined): undefined;
-function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined;
-function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined {
-	if (!textEdit) {
-		return void 0;
-	}
-	return {
-		range: toRange(textEdit.range),
-		text: textEdit.newText
-	};
-}
-
-function toCommand(c: lsTypes.Command | undefined): languages.Command | undefined {
-	return c && c.command === 'editor.action.triggerSuggest'
-		? { id: c.command, title: c.title, arguments: c.arguments }
-		: undefined;
-}
-
-export class CompletionAdapter implements languages.CompletionItemProvider {
-	constructor(private _worker: WorkerAccessor) {}
-
-	public get triggerCharacters(): string[] {
-		return ['.', ':', '<', '"', '=', '/'];
-	}
-
-	provideCompletionItems(
-		model: editor.IReadOnlyModel,
-		position: Position,
-		context: languages.CompletionContext,
-		token: CancellationToken
-	): Promise<languages.CompletionList | undefined> {
-		const resource = model.uri;
-
-		return this._worker(resource)
-			.then((worker) => {
-				return worker.doComplete(resource.toString(), fromPosition(position));
-			})
-			.then((info) => {
-				if (!info) {
-					return;
-				}
-				const wordInfo = model.getWordUntilPosition(position);
-				const wordRange = new Range(
-					position.lineNumber,
-					wordInfo.startColumn,
-					position.lineNumber,
-					wordInfo.endColumn
-				);
-
-				const items: languages.CompletionItem[] = info.items.map((entry) => {
-					const item: languages.CompletionItem = {
-						label: entry.label,
-						insertText: entry.insertText || entry.label,
-						sortText: entry.sortText,
-						filterText: entry.filterText,
-						documentation: entry.documentation,
-						detail: entry.detail,
-						command: toCommand(entry.command),
-						range: wordRange,
-						kind: toCompletionItemKind(entry.kind)
-					};
-					if (entry.textEdit) {
-						if (isInsertReplaceEdit(entry.textEdit)) {
-							item.range = {
-								insert: toRange(entry.textEdit.insert),
-								replace: toRange(entry.textEdit.replace)
-							};
-						} else {
-							item.range = toRange(entry.textEdit.range);
-						}
-						item.insertText = entry.textEdit.newText;
-					}
-					if (entry.additionalTextEdits) {
-						item.additionalTextEdits =
-							entry.additionalTextEdits.map<languages.TextEdit>(toTextEdit);
-					}
-					if (entry.insertTextFormat === lsTypes.InsertTextFormat.Snippet) {
-						item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
-					}
-					return item;
-				});
-
-				return {
-					isIncomplete: info.isIncomplete,
-					suggestions: items
-				};
-			});
+export class HTMLCompletionAdapter extends CompletionAdapter<HTMLWorker> {
+	constructor(worker: WorkerAccessor) {
+		super(worker, ['.', ':', '<', '"', '=', '/']);
 	}
 }
 

+ 1 - 1
src/json/jsonMode.ts

@@ -46,7 +46,7 @@ export function setupMode(defaults: LanguageServiceDefaults): IDisposable {
 			providers.push(
 				languages.registerCompletionItemProvider(
 					languageId,
-					new languageFeatures.CompletionAdapter(worker)
+					new languageFeatures.JSONCompletionAdapter(worker)
 				)
 			);
 		}

+ 11 - 231
src/json/languageFeatures.ts

@@ -12,18 +12,22 @@ import {
 	IMarkdownString,
 	Uri,
 	Position,
-	IRange,
 	Range,
 	CancellationToken
 } from '../fillers/monaco-editor-core';
-import { DiagnosticsAdapter } from '../common/lspLanguageFeatures';
+import {
+	DiagnosticsAdapter,
+	fromPosition,
+	toRange,
+	toTextEdit,
+	fromRange,
+	CompletionAdapter
+} from '../common/lspLanguageFeatures';
 
 export interface WorkerAccessor {
 	(...more: Uri[]): Promise<JSONWorker>;
 }
 
-// --- diagnostics --- ---
-
 export class JSONDiagnosticsAdapter extends DiagnosticsAdapter<JSONWorker> {
 	constructor(languageId: string, worker: WorkerAccessor, defaults: LanguageServiceDefaults) {
 		super(languageId, worker, defaults.onDidChange);
@@ -47,233 +51,9 @@ export class JSONDiagnosticsAdapter extends DiagnosticsAdapter<JSONWorker> {
 	}
 }
 
-// --- completion ------
-
-function fromPosition(position: Position): lsTypes.Position;
-function fromPosition(position: undefined): undefined;
-function fromPosition(position: Position | undefined): lsTypes.Position | undefined;
-function fromPosition(position: Position | undefined): lsTypes.Position | undefined {
-	if (!position) {
-		return void 0;
-	}
-	return { character: position.column - 1, line: position.lineNumber - 1 };
-}
-
-function fromRange(range: IRange): lsTypes.Range;
-function fromRange(range: undefined): undefined;
-function fromRange(range: IRange | undefined): lsTypes.Range | undefined;
-function fromRange(range: IRange | undefined): lsTypes.Range | undefined {
-	if (!range) {
-		return void 0;
-	}
-	return {
-		start: {
-			line: range.startLineNumber - 1,
-			character: range.startColumn - 1
-		},
-		end: { line: range.endLineNumber - 1, character: range.endColumn - 1 }
-	};
-}
-function toRange(range: lsTypes.Range): Range;
-function toRange(range: undefined): undefined;
-function toRange(range: lsTypes.Range | undefined): Range | undefined;
-function toRange(range: lsTypes.Range | undefined): Range | undefined {
-	if (!range) {
-		return void 0;
-	}
-	return new Range(
-		range.start.line + 1,
-		range.start.character + 1,
-		range.end.line + 1,
-		range.end.character + 1
-	);
-}
-
-function isInsertReplaceEdit(
-	edit: lsTypes.TextEdit | lsTypes.InsertReplaceEdit
-): edit is lsTypes.InsertReplaceEdit {
-	return (
-		typeof (<lsTypes.InsertReplaceEdit>edit).insert !== 'undefined' &&
-		typeof (<lsTypes.InsertReplaceEdit>edit).replace !== 'undefined'
-	);
-}
-
-function toCompletionItemKind(kind: number | undefined): languages.CompletionItemKind {
-	const mItemKind = languages.CompletionItemKind;
-
-	switch (kind) {
-		case lsTypes.CompletionItemKind.Text:
-			return mItemKind.Text;
-		case lsTypes.CompletionItemKind.Method:
-			return mItemKind.Method;
-		case lsTypes.CompletionItemKind.Function:
-			return mItemKind.Function;
-		case lsTypes.CompletionItemKind.Constructor:
-			return mItemKind.Constructor;
-		case lsTypes.CompletionItemKind.Field:
-			return mItemKind.Field;
-		case lsTypes.CompletionItemKind.Variable:
-			return mItemKind.Variable;
-		case lsTypes.CompletionItemKind.Class:
-			return mItemKind.Class;
-		case lsTypes.CompletionItemKind.Interface:
-			return mItemKind.Interface;
-		case lsTypes.CompletionItemKind.Module:
-			return mItemKind.Module;
-		case lsTypes.CompletionItemKind.Property:
-			return mItemKind.Property;
-		case lsTypes.CompletionItemKind.Unit:
-			return mItemKind.Unit;
-		case lsTypes.CompletionItemKind.Value:
-			return mItemKind.Value;
-		case lsTypes.CompletionItemKind.Enum:
-			return mItemKind.Enum;
-		case lsTypes.CompletionItemKind.Keyword:
-			return mItemKind.Keyword;
-		case lsTypes.CompletionItemKind.Snippet:
-			return mItemKind.Snippet;
-		case lsTypes.CompletionItemKind.Color:
-			return mItemKind.Color;
-		case lsTypes.CompletionItemKind.File:
-			return mItemKind.File;
-		case lsTypes.CompletionItemKind.Reference:
-			return mItemKind.Reference;
-	}
-	return mItemKind.Property;
-}
-
-function fromCompletionItemKind(kind: languages.CompletionItemKind): lsTypes.CompletionItemKind {
-	const mItemKind = languages.CompletionItemKind;
-
-	switch (kind) {
-		case mItemKind.Text:
-			return lsTypes.CompletionItemKind.Text;
-		case mItemKind.Method:
-			return lsTypes.CompletionItemKind.Method;
-		case mItemKind.Function:
-			return lsTypes.CompletionItemKind.Function;
-		case mItemKind.Constructor:
-			return lsTypes.CompletionItemKind.Constructor;
-		case mItemKind.Field:
-			return lsTypes.CompletionItemKind.Field;
-		case mItemKind.Variable:
-			return lsTypes.CompletionItemKind.Variable;
-		case mItemKind.Class:
-			return lsTypes.CompletionItemKind.Class;
-		case mItemKind.Interface:
-			return lsTypes.CompletionItemKind.Interface;
-		case mItemKind.Module:
-			return lsTypes.CompletionItemKind.Module;
-		case mItemKind.Property:
-			return lsTypes.CompletionItemKind.Property;
-		case mItemKind.Unit:
-			return lsTypes.CompletionItemKind.Unit;
-		case mItemKind.Value:
-			return lsTypes.CompletionItemKind.Value;
-		case mItemKind.Enum:
-			return lsTypes.CompletionItemKind.Enum;
-		case mItemKind.Keyword:
-			return lsTypes.CompletionItemKind.Keyword;
-		case mItemKind.Snippet:
-			return lsTypes.CompletionItemKind.Snippet;
-		case mItemKind.Color:
-			return lsTypes.CompletionItemKind.Color;
-		case mItemKind.File:
-			return lsTypes.CompletionItemKind.File;
-		case mItemKind.Reference:
-			return lsTypes.CompletionItemKind.Reference;
-	}
-	return lsTypes.CompletionItemKind.Property;
-}
-
-function toTextEdit(textEdit: lsTypes.TextEdit): languages.TextEdit;
-function toTextEdit(textEdit: undefined): undefined;
-function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined;
-function toTextEdit(textEdit: lsTypes.TextEdit | undefined): languages.TextEdit | undefined {
-	if (!textEdit) {
-		return void 0;
-	}
-	return {
-		range: toRange(textEdit.range),
-		text: textEdit.newText
-	};
-}
-
-function toCommand(c: lsTypes.Command | undefined): languages.Command | undefined {
-	return c && c.command === 'editor.action.triggerSuggest'
-		? { id: c.command, title: c.title, arguments: c.arguments }
-		: undefined;
-}
-
-export class CompletionAdapter implements languages.CompletionItemProvider {
-	constructor(private _worker: WorkerAccessor) {}
-
-	public get triggerCharacters(): string[] {
-		return [' ', ':', '"'];
-	}
-
-	provideCompletionItems(
-		model: editor.IReadOnlyModel,
-		position: Position,
-		context: languages.CompletionContext,
-		token: CancellationToken
-	): Promise<languages.CompletionList | undefined> {
-		const resource = model.uri;
-
-		return this._worker(resource)
-			.then((worker) => {
-				return worker.doComplete(resource.toString(), fromPosition(position));
-			})
-			.then((info) => {
-				if (!info) {
-					return;
-				}
-				const wordInfo = model.getWordUntilPosition(position);
-				const wordRange = new Range(
-					position.lineNumber,
-					wordInfo.startColumn,
-					position.lineNumber,
-					wordInfo.endColumn
-				);
-
-				const items: languages.CompletionItem[] = info.items.map((entry) => {
-					const item: languages.CompletionItem = {
-						label: entry.label,
-						insertText: entry.insertText || entry.label,
-						sortText: entry.sortText,
-						filterText: entry.filterText,
-						documentation: entry.documentation,
-						detail: entry.detail,
-						command: toCommand(entry.command),
-						range: wordRange,
-						kind: toCompletionItemKind(entry.kind)
-					};
-					if (entry.textEdit) {
-						if (isInsertReplaceEdit(entry.textEdit)) {
-							item.range = {
-								insert: toRange(entry.textEdit.insert),
-								replace: toRange(entry.textEdit.replace)
-							};
-						} else {
-							item.range = toRange(entry.textEdit.range);
-						}
-						item.insertText = entry.textEdit.newText;
-					}
-					if (entry.additionalTextEdits) {
-						item.additionalTextEdits =
-							entry.additionalTextEdits.map<languages.TextEdit>(toTextEdit);
-					}
-					if (entry.insertTextFormat === lsTypes.InsertTextFormat.Snippet) {
-						item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
-					}
-					return item;
-				});
-
-				return {
-					isIncomplete: info.isIncomplete,
-					suggestions: items
-				};
-			});
+export class JSONCompletionAdapter extends CompletionAdapter<JSONWorker> {
+	constructor(worker: WorkerAccessor) {
+		super(worker, [' ', ':', '"']);
 	}
 }