Skip to content

Commit

Permalink
feat: flinksql completion
Browse files Browse the repository at this point in the history
  • Loading branch information
mortalYoung authored and wewoor committed May 30, 2023
1 parent 13fdcbb commit 1fba38b
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 3 deletions.
19 changes: 19 additions & 0 deletions src/common/baseSQLWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ export abstract class BaseSQLWorker {

async autocomplete(code: string, position: any): Promise<any> {}

async doComplete(code: string, position: any): Promise<any> {
code = code || this.getTextDocument();
if (code) {
// TODO: going to do server side search in future, but now just get all keywords for completion
const keywords: string[] =
this.parser
.createParser('')
.symbolicNames?.filter((keyword: string) => keyword?.startsWith('KW_'))
.map((k: string) => k.replace('KW_', '')) || [];
return Promise.resolve({
items: keywords.map((i) => ({
label: i,
kind: 14
}))
});
}
return Promise.resolve();
}

private getTextDocument(): string {
const model = this._ctx.getMirrorModels()[0]; // When there are multiple files open, this will be an array
return model && model.getValue();
Expand Down
272 changes: 270 additions & 2 deletions src/common/languageFeatures.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import { LanguageServiceDefaults } from './_.contribution';
import { editor, Uri, IDisposable, MarkerSeverity } from '../fillers/monaco-editor-core';
import {
editor,
Uri,
IDisposable,
MarkerSeverity,
Range,
languages,
Position,
CancellationToken,
IRange
} from '../fillers/monaco-editor-core';
import { debounce } from './utils';

export interface WorkerAccessor<T> {
(first: Uri, ...more: Uri[]): Promise<T>;
}

export interface IWorker {
export interface IWorker extends ILanguageWorkerWithCompletions {
doValidation(uri: string): Promise<any>;
valid(code: string): Promise<any>;
parserTreeToString(code: string): Promise<any>;
}

export interface ILanguageWorkerWithCompletions {
doComplete(uri: string, position: lsTypes.Position): Promise<lsTypes.CompletionList | null>;
}

export class DiagnosticsAdapter<T extends IWorker> {
private _disposables: IDisposable[] = [];
private _listener: { [uri: string]: IDisposable } = Object.create(null);
Expand Down Expand Up @@ -120,3 +134,257 @@ function toDiagnostics(resource: Uri, diag: any): editor.IMarkerData {
source: diag.source
};
}

namespace lsTypes {
export type Position = any;
export type CompletionList = {
isIncomplete: boolean;
items: any[];
};

export enum InsertTextFormat {
PlainText = 1,
Snippet = 2
}
export type Range = {
start: Position;
end: Position;
};

export type TextEdit = any;
export type InsertReplaceEdit = any;
export declare namespace CompletionItemKind {
const Text: 1;
const Method: 2;
const Function: 3;
const Constructor: 4;
const Field: 5;
const Variable: 6;
const Class: 7;
const Interface: 8;
const Module: 9;
const Property: 10;
const Unit: 11;
const Value: 12;
const Enum: 13;
const Keyword: 14;
const Snippet: 15;
const Color: 16;
const File: 17;
const Reference: 18;
const Folder: 19;
const EnumMember: 20;
const Constant: 21;
const Struct: 22;
const Event: 23;
const Operator: 24;
const TypeParameter: 25;
}
export declare type CompletionItemKind =
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22
| 23
| 24
| 25;
export type Command = any;
}

export class CompletionAdapter<T extends IWorker> implements languages.CompletionItemProvider {
constructor(private readonly _worker: WorkerAccessor<T>) {}

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(
editor.getModel(resource)?.getValue() || '',
fromPosition(position)
);
})
.then((info) => {
console.log('info:', 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,
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 as any[]
).map<languages.TextEdit>(toTextEdit);
}
if (entry.insertTextFormat === lsTypes.InsertTextFormat.Snippet) {
item.insertTextRules =
languages.CompletionItemInsertTextRule.InsertAsSnippet;
}
return item;
});

return {
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;
}

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;
}
11 changes: 10 additions & 1 deletion src/common/setupLanguageMode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { WorkerManager } from './workerManager';
import { LanguageServiceDefaults } from './_.contribution';
import * as languageFeatures from './languageFeatures';
import { Uri, IDisposable } from '../fillers/monaco-editor-core';
import { Uri, IDisposable, languages } from '../fillers/monaco-editor-core';

export function setupLanguageMode<T extends languageFeatures.IWorker>(
defaults: LanguageServiceDefaults
Expand All @@ -24,6 +24,15 @@ export function setupLanguageMode<T extends languageFeatures.IWorker>(
if (modeConfiguration.diagnostics) {
providers.push(new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults));
}

if (modeConfiguration.completionItems) {
providers.push(
languages.registerCompletionItemProvider(
languageId,
new languageFeatures.CompletionAdapter(worker)
)
);
}
}

registerProviders();
Expand Down

0 comments on commit 1fba38b

Please sign in to comment.