Skip to content

Commit

Permalink
feat: support syntax completions
Browse files Browse the repository at this point in the history
  • Loading branch information
HaydenOrz authored and wewoor committed Jun 12, 2023
1 parent c0d5ecf commit 7e00463
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 24 deletions.
36 changes: 34 additions & 2 deletions src/_.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { languages, Emitter, IEvent, editor } from './fillers/monaco-editor-core';
import { languages, Emitter, IEvent, editor, Position } from './fillers/monaco-editor-core';
import { Suggestions } from 'dt-sql-parser';

interface ILang extends languages.ILanguageExtensionPoint {
loader: () => Promise<ILangImpl>;
Expand Down Expand Up @@ -144,11 +145,35 @@ export interface DiagnosticsOptions {
readonly validate?: boolean;
}

/**
* A completion item, it will be convert to monaco.languages.CompletionItem.
*/
export interface ICompletionItem extends Partial<languages.CompletionItem> {
label: string | languages.CompletionItemLabel;
kind: languages.CompletionItemKind;
detail: string;
}

/**
* A service to build completions.
* @param model monaco TextModel which triggers completion
* @param position location that triggers completion
* @param completionContext context of completion
* @param suggestions suggestions for completion
*/
export type CompletionService = (
model: editor.IReadOnlyModel,
position: Position,
completionContext: languages.CompletionContext,
suggestions: Suggestions | null
) => Promise<ICompletionItem[]>;

export interface LanguageServiceDefaults {
readonly languageId: string;
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly diagnosticsOptions: DiagnosticsOptions;
readonly modeConfiguration: ModeConfiguration;
readonly completionService?: CompletionService;
setDiagnosticsOptions(options: DiagnosticsOptions): void;
setModeConfiguration(modeConfiguration: ModeConfiguration): void;
}
Expand All @@ -158,15 +183,18 @@ export class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
private _diagnosticsOptions!: DiagnosticsOptions;
private _modeConfiguration!: ModeConfiguration;
private _languageId: string;
private _completionService?: CompletionService;

constructor(
languageId: string,
diagnosticsOptions: DiagnosticsOptions,
modeConfiguration: ModeConfiguration
modeConfiguration: ModeConfiguration,
completionService?: CompletionService
) {
this._languageId = languageId;
this.setDiagnosticsOptions(diagnosticsOptions);
this.setModeConfiguration(modeConfiguration);
this._completionService = completionService;
}

get onDidChange(): IEvent<LanguageServiceDefaults> {
Expand All @@ -185,6 +213,10 @@ export class LanguageServiceDefaultsImpl implements LanguageServiceDefaults {
return this._diagnosticsOptions;
}

get completionService(): CompletionService | undefined {
return this._completionService;
}

setDiagnosticsOptions(options: DiagnosticsOptions): void {
this._diagnosticsOptions = options || Object.create(null);
this._onDidChange.fire(this);
Expand Down
68 changes: 48 additions & 20 deletions src/languageFeatures.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { LanguageServiceDefaults } from './_.contribution';
import {
editor,
Uri,
Expand All @@ -11,6 +10,7 @@ import {
} from './fillers/monaco-editor-core';
import { debounce } from './common/utils';
import type { Suggestions, ParserError } from 'dt-sql-parser';
import type { LanguageServiceDefaults, CompletionService, ICompletionItem } from './_.contribution';

export interface WorkerAccessor<T> {
(first: Uri, ...more: Uri[]): Promise<T>;
Expand Down Expand Up @@ -140,10 +140,14 @@ function toDiagnostics(resource: Uri, diag: any): editor.IMarkerData {
}

export class CompletionAdapter<T extends IWorker> implements languages.CompletionItemProvider {
constructor(private readonly _worker: WorkerAccessor<T>) {}
constructor(private readonly _worker: WorkerAccessor<T>, defaults: LanguageServiceDefaults) {
this._defaults = defaults;
}

private _defaults: LanguageServiceDefaults;

get triggerCharacters() {
return ['.'];
public get triggerCharacters(): string[] {
return ['.', ' '];
}

provideCompletionItems(
Expand All @@ -153,18 +157,16 @@ export class CompletionAdapter<T extends IWorker> implements languages.Completio
token: CancellationToken
): Promise<languages.CompletionList | undefined> {
const resource = model.uri;

return this._worker(resource)
.then((worker) => {
return worker.doComplete(editor.getModel(resource)?.getValue() || '', position);
})
.then((suggestions) => {
if (!suggestions) {
return;
}
const offset = model.getOffsetAt(position);
const { syntax, keywords } = suggestions;

const completionService =
this._defaults.completionService ?? defaultCompletionService;
return completionService(model, position, context, suggestions);
})
.then((completions) => {
const wordInfo = model.getWordUntilPosition(position);
const wordRange = new Range(
position.lineNumber,
Expand All @@ -173,19 +175,45 @@ export class CompletionAdapter<T extends IWorker> implements languages.Completio
wordInfo.endColumn
);

const keywordsCompletionItems: languages.CompletionItem[] = keywords.map((kw) => ({
label: kw,
kind: languages.CompletionItemKind.Keyword,
insertText: kw,
range: wordRange,
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
offset,
detail: '关键字'
const completionItems: languages.CompletionItem[] = completions.map((item) => ({
...item,
insertText:
item.insertText ??
(typeof item.label === 'string' ? item.label : item.label.label),
range: item.range ?? wordRange,
insertTextRules:
item.insertTextRules ??
languages.CompletionItemInsertTextRule.InsertAsSnippet
}));

return {
suggestions: keywordsCompletionItems
suggestions: completionItems
};
});
}
}

/**
* A built-in completion service.
* It will invoke when there is no external completionService.
* It will only build completion items of keywords.
*/
const defaultCompletionService: CompletionService = function (
_model,
_position,
_context,
suggestions
) {
if (!suggestions) {
return Promise.resolve([]);
}
const { keywords } = suggestions;

const keywordsCompletionItems: ICompletionItem[] = keywords.map((kw) => ({
label: kw,
kind: languages.CompletionItemKind.Keyword,
detail: '关键字'
}));

return Promise.resolve(keywordsCompletionItems);
};
11 changes: 10 additions & 1 deletion src/monaco.contribution.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import './sql/sql.contribution';
import './flinksql/flinksql.contribution';
import './hivesql/hivesql.contribution';
import './mysql/mysql.contribution';
import './plsql/plsql.contribution';
import './pgsql/pgsql.contribution';
import './sparksql/sparksql.contribution';

export { registerFlinkSQLLanguage } from './flinksql/flinksql.contribution';

export * from './_.contribution';
export * from './languageService';
export * from './languageFeatures';
export * from './setupLanguageMode';
export * from './workerManager';
export * from './common/utils';

export {
SyntaxContextType,
WordRange,
SyntaxSuggestion,
Suggestions,
TextSlice
} from 'dt-sql-parser';
2 changes: 1 addition & 1 deletion src/setupLanguageMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function setupLanguageMode<T extends languageFeatures.IWorker>(
providers.push(
languages.registerCompletionItemProvider(
languageId,
new languageFeatures.CompletionAdapter(worker)
new languageFeatures.CompletionAdapter(worker, defaults)
)
);
}
Expand Down

0 comments on commit 7e00463

Please sign in to comment.