From 0d8e9acd8d017ad37aa9bed93c158653ef47f1cf Mon Sep 17 00:00:00 2001 From: Joelant05 <64587014+Joelant05@users.noreply.github.com> Date: Mon, 12 Sep 2022 19:19:12 +0100 Subject: [PATCH 1/5] feat: basic molang auto-completions --- src/components/Languages/MoLang.ts | 98 ++++++------ src/components/Languages/Molang/Data.ts | 139 ++++++++++++++++++ .../Languages/Molang/TokenProvider.ts | 49 ++++++ src/components/Languages/Molang/WithinJson.ts | 0 .../Projects/Project/BedrockProject.ts | 3 + 5 files changed, 240 insertions(+), 49 deletions(-) create mode 100644 src/components/Languages/Molang/Data.ts create mode 100644 src/components/Languages/Molang/TokenProvider.ts create mode 100644 src/components/Languages/Molang/WithinJson.ts diff --git a/src/components/Languages/MoLang.ts b/src/components/Languages/MoLang.ts index 5de42480d..dd6d2ad35 100644 --- a/src/components/Languages/MoLang.ts +++ b/src/components/Languages/MoLang.ts @@ -1,7 +1,15 @@ -import type { editor, languages } from 'monaco-editor' +import type { + CancellationToken, + editor, + languages, + Position, +} from 'monaco-editor' import { Language } from './Language' import { CustomMoLang } from 'molang' -import { useMonaco } from '../../utils/libs/useMonaco' +import { useMonaco } from '/@/utils/libs/useMonaco' +import { tokenProvider } from './Molang/TokenProvider' +import { App } from '/@/App' +import { BedrockProject } from '/@/components/Projects/Project/BedrockProject' export const config: languages.LanguageConfiguration = { comments: { @@ -32,53 +40,44 @@ export const config: languages.LanguageConfiguration = { ], } -export const tokenProvider = { - ignoreCase: true, - brackets: [ - ['(', ')', 'delimiter.parenthesis'], - ['[', ']', 'delimiter.square'], - ['{', '}', 'delimiter.curly'], - ], - keywords: [ - 'return', - 'loop', - 'for_each', - 'break', - 'continue', - 'this', - 'function', - ], - identifiers: [ - 'v', - 't', - 'c', - 'q', - 'f', - 'a', - 'arg', - 'variable', - 'temp', - 'context', - 'query', - ], - tokenizer: { - root: [ - [/#.*/, 'comment'], - [/'[^']'/, 'string'], - [/[0-9]+(\.[0-9]+)?/, 'number'], - [/true|false/, 'number'], - [/\=|\,|\!|%=|\*=|\+=|-=|\/=|<|=|>|<>/, 'definition'], - [ - /[a-z_$][\w$]*/, - { - cases: { - '@keywords': 'keyword', - '@identifiers': 'type.identifier', - '@default': 'identifier', - }, - }, - ], - ], +// TODO - dynamic completions for custom molang functions +const completionItemProvider: languages.CompletionItemProvider = { + triggerCharacters: ['.', '(', ',', "'"], + provideCompletionItems: async ( + model: editor.ITextModel, + position: Position, + context: languages.CompletionContext, + token: CancellationToken + ) => { + const project = await App.getApp().then((app) => app.project) + if (!(project instanceof BedrockProject)) return + + const molangData = project.molangData + await molangData.fired + + // Get the entire line + const line = model.getLineContent(position.lineNumber) + // Attempt to look behind the cursor to get context on what to propose + const lineUntilCursor = line.slice(0, position.column - 1) + + // Auto-completions for global instances + const { Range } = await useMonaco() + + return { + suggestions: (await molangData.getGlobalSuggestions()).map( + (suggestion) => { + return { + ...suggestion, + range: new Range( + position.lineNumber, + position.column, + position.lineNumber, + position.column + ), + } + } + ), + } }, } @@ -90,6 +89,7 @@ export class MoLangLanguage extends Language { extensions: ['molang'], config, tokenProvider, + completionItemProvider, }) } diff --git a/src/components/Languages/Molang/Data.ts b/src/components/Languages/Molang/Data.ts new file mode 100644 index 000000000..7ad58d047 --- /dev/null +++ b/src/components/Languages/Molang/Data.ts @@ -0,0 +1,139 @@ +import { markRaw } from 'vue' +import { Signal } from '/@/components/Common/Event/Signal' +import { App } from '/@/App' +import { + IRequirements, + RequiresMatcher, +} from '/@/components/Data/RequiresMatcher/RequiresMatcher' +import type { languages } from 'monaco-editor' +import { useMonaco } from '/@/utils/libs/useMonaco' + +export interface MolangValueDefinition { + type: 'math' | 'query' | 'variable' | 'context' + valueName: string + description?: string + isProperty?: boolean + arguments?: MolangFunctionArgument[] + isDeprecated?: boolean + deprecationMessage: string +} + +export interface MolangDefinition { + requires?: IRequirements + values: MolangValueDefinition[] +} + +export interface MolangFunctionArgument { + argumentName: string + type: 'string' | 'number' + additionalData?: { + values?: string[] + schemaReference?: string + } +} + +export interface MolangContextDefinition { + fileType: string + data: MolangDefinition[] +} + +export class MolangData extends Signal { + protected _baseData?: any + protected _contextData?: any + + async loadCommandData(packageName: string) { + const app = await App.getApp() + + this._baseData = markRaw( + await app.dataLoader.readJSON( + `data/packages/${packageName}/language/molang/main.json` + ) + ) + this._contextData = markRaw( + await app.dataLoader.readJSON( + `data/packages/${packageName}/language/molang/context.json` + ) + ) + + this.dispatch() + } + + async getSchemaForFileType(fileType?: string) { + if (!this._baseData) + throw new Error(`Acessing molangData before it was loaded.`) + + let validEntries: MolangValueDefinition[] = [] + const requiresMatcher = new RequiresMatcher() + await requiresMatcher.setup() + + const contextData = ( + this._contextData?.contexts as MolangContextDefinition[] + ).find((entry) => entry.fileType === fileType)?.data + + const allDefinitions: MolangDefinition[] = ( + this._baseData?.vanilla as MolangDefinition[] + ).concat(contextData ?? []) + + for (const entry of allDefinitions) { + if (!entry.requires || requiresMatcher.isValid(entry.requires)) + validEntries = validEntries.concat(entry.values) + } + + return validEntries + } + + /** + * Suggestions that should be made available anywhere in a Molang context + */ + async getGlobalSuggestions(): Promise[]> { + const { languages } = await useMonaco() + + return [ + { + label: 'math', + kind: languages.CompletionItemKind.Function, + insertText: 'math', + }, + { + label: 'variable', + kind: languages.CompletionItemKind.Variable, + insertText: 'variable', + }, + { + label: 'v', + kind: languages.CompletionItemKind.Variable, + insertText: 'v', + }, + { + label: 'context', + kind: languages.CompletionItemKind.Variable, + insertText: 'context', + }, + { + label: 'c', + kind: languages.CompletionItemKind.Variable, + insertText: 'c', + }, + { + label: 'query', + kind: languages.CompletionItemKind.Function, + insertText: 'query', + }, + { + label: 'q', + kind: languages.CompletionItemKind.Function, + insertText: 'q', + }, + { + label: 'temp', + kind: languages.CompletionItemKind.Variable, + insertText: 'temp', + }, + { + label: 't', + kind: languages.CompletionItemKind.Variable, + insertText: 't', + }, + ] + } +} diff --git a/src/components/Languages/Molang/TokenProvider.ts b/src/components/Languages/Molang/TokenProvider.ts new file mode 100644 index 000000000..6e6bdf80d --- /dev/null +++ b/src/components/Languages/Molang/TokenProvider.ts @@ -0,0 +1,49 @@ +export const tokenProvider = { + ignoreCase: true, + brackets: [ + ['(', ')', 'delimiter.parenthesis'], + ['[', ']', 'delimiter.square'], + ['{', '}', 'delimiter.curly'], + ], + keywords: [ + 'return', + 'loop', + 'for_each', + 'break', + 'continue', + 'this', + 'function', + ], + identifiers: [ + 'v', + 't', + 'c', + 'q', + 'f', + 'a', + 'arg', + 'variable', + 'temp', + 'context', + 'query', + ], + tokenizer: { + root: [ + [/#.*/, 'comment'], + [/'[^']'/, 'string'], + [/[0-9]+(\.[0-9]+)?/, 'number'], + [/true|false/, 'number'], + [/\=|\,|\!|%=|\*=|\+=|-=|\/=|<|=|>|<>/, 'definition'], + [ + /[a-z_$][\w$]*/, + { + cases: { + '@keywords': 'keyword', + '@identifiers': 'type.identifier', + '@default': 'identifier', + }, + }, + ], + ], + }, +} diff --git a/src/components/Languages/Molang/WithinJson.ts b/src/components/Languages/Molang/WithinJson.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/Projects/Project/BedrockProject.ts b/src/components/Projects/Project/BedrockProject.ts index 5a2e6ceee..4d6cc9668 100644 --- a/src/components/Projects/Project/BedrockProject.ts +++ b/src/components/Projects/Project/BedrockProject.ts @@ -11,6 +11,7 @@ import { CommandData } from '/@/components/Languages/Mcfunction/Data' import { FileTab } from '../../TabSystem/FileTab' import { HTMLPreviewTab } from '../../Editors/HTMLPreview/HTMLPreview' import { LangData } from '/@/components/Languages/Lang/Data' +import { MolangData } from '../../Languages/Molang/Data' const bedrockPreviews: ITabPreviewConfig[] = [ { @@ -56,6 +57,7 @@ const bedrockPreviews: ITabPreviewConfig[] = [ export class BedrockProject extends Project { commandData = new CommandData() langData = new LangData() + molangData = new MolangData() onCreate() { bedrockPreviews.forEach((tabPreview) => @@ -87,6 +89,7 @@ export class BedrockProject extends Project { this.commandData.loadCommandData('minecraftBedrock') this.langData.loadLangData('minecraftBedrock') + this.molangData.loadCommandData('minecraftBedrock') } getCurrentDataPackage() { From 8a36881973cd27dce576bb17936c87405ac60cdd Mon Sep 17 00:00:00 2001 From: Joelant05 <64587014+Joelant05@users.noreply.github.com> Date: Wed, 14 Sep 2022 19:33:04 +0100 Subject: [PATCH 2/5] upd: progress on molang schema loading --- src/components/Languages/MoLang.ts | 41 ++++++++++----- src/components/Languages/Molang/Data.ts | 52 +++++++++++++++---- .../Languages/Molang/TokenProvider.ts | 3 +- 3 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/components/Languages/MoLang.ts b/src/components/Languages/MoLang.ts index dd6d2ad35..ed394021d 100644 --- a/src/components/Languages/MoLang.ts +++ b/src/components/Languages/MoLang.ts @@ -64,23 +64,29 @@ const completionItemProvider: languages.CompletionItemProvider = { const { Range } = await useMonaco() return { - suggestions: (await molangData.getGlobalSuggestions()).map( - (suggestion) => { - return { - ...suggestion, - range: new Range( - position.lineNumber, - position.column, - position.lineNumber, - position.column - ), - } - } - ), + suggestions: [], } }, } +const loadValues = async (lang: MoLangLanguage) => { + const app = await App.getApp() + await app.projectManager.fired + + const project = app.project + if (!(project instanceof BedrockProject)) return + + await project.molangData.fired + + const values = await project.molangData.allValues() + tokenProvider.root = values + .map((value) => [value, 'variable']) + .concat(tokenProvider.tokenizer.root) + + // TODO - not working? + lang.updateTokenProvider(tokenProvider) +} + export class MoLangLanguage extends Language { protected molang = new CustomMoLang({}) constructor() { @@ -93,6 +99,15 @@ export class MoLangLanguage extends Language { }) } + onModelAdded(model: editor.ITextModel) { + const isLangFor = super.onModelAdded(model) + if (!isLangFor) return false + + loadValues(this) + + return true + } + async validate(model: editor.IModel) { const { editor, MarkerSeverity } = await useMonaco() diff --git a/src/components/Languages/Molang/Data.ts b/src/components/Languages/Molang/Data.ts index 7ad58d047..55120bbcd 100644 --- a/src/components/Languages/Molang/Data.ts +++ b/src/components/Languages/Molang/Data.ts @@ -58,34 +58,47 @@ export class MolangData extends Signal { this.dispatch() } - async getSchemaForFileType(fileType?: string) { + async getSchema(fileType?: string) { if (!this._baseData) - throw new Error(`Acessing molangData before it was loaded.`) + throw new Error(`Acessing base molangData before it was loaded.`) - let validEntries: MolangValueDefinition[] = [] + let validEntries: MolangDefinition[] = [] const requiresMatcher = new RequiresMatcher() await requiresMatcher.setup() - const contextData = ( - this._contextData?.contexts as MolangContextDefinition[] - ).find((entry) => entry.fileType === fileType)?.data + // Get file type specific schemas if needed + let contextData + if (fileType) contextData = this.getContextSchema(fileType) + // Combine the context schemas with the base schemas const allDefinitions: MolangDefinition[] = ( this._baseData?.vanilla as MolangDefinition[] ).concat(contextData ?? []) + // Validate each schema by "requires" property and return valid entries for (const entry of allDefinitions) { if (!entry.requires || requiresMatcher.isValid(entry.requires)) - validEntries = validEntries.concat(entry.values) + validEntries = validEntries.concat(entry) } return validEntries } - /** - * Suggestions that should be made available anywhere in a Molang context - */ - async getGlobalSuggestions(): Promise[]> { + getContextSchema(fileType?: string) { + if (!this._contextData) + throw new Error(`Acessing context molangData before it was loaded.`) + + // Find context schema for the specified file type, or return all context schemas if no file type is defined + const contextData = ( + this._contextData?.contexts as MolangContextDefinition[] + ).find((entry) => (fileType ? entry.fileType === fileType : true)) + + return contextData?.data ?? [] + } + + async getNamespaceSuggestions(): Promise< + Partial[] + > { const { languages } = await useMonaco() return [ @@ -136,4 +149,21 @@ export class MolangData extends Signal { }, ] } + + /** + * Returns a list of all keywords from the schema value names + */ + async allValues() { + return this.getSchema().then((schema) => + [ + ...new Set( + schema + .concat(this.getContextSchema()) + .map((def) => def.values) + ), + ] + .map((def) => def.map((def) => def.valueName)) + .flat() + ) + } } diff --git a/src/components/Languages/Molang/TokenProvider.ts b/src/components/Languages/Molang/TokenProvider.ts index 6e6bdf80d..445dded67 100644 --- a/src/components/Languages/Molang/TokenProvider.ts +++ b/src/components/Languages/Molang/TokenProvider.ts @@ -1,4 +1,4 @@ -export const tokenProvider = { +export const tokenProvider: any = { ignoreCase: true, brackets: [ ['(', ')', 'delimiter.parenthesis'], @@ -26,6 +26,7 @@ export const tokenProvider = { 'temp', 'context', 'query', + 'math', ], tokenizer: { root: [ From b0450d045780d496d2f7db2a420d769cf9ff0d7f Mon Sep 17 00:00:00 2001 From: Joelant05 <64587014+Joelant05@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:38:42 +0100 Subject: [PATCH 3/5] upd: convert molang schemas values to completion items --- src/components/Languages/MoLang.ts | 48 +++++++++++++++++++++++-- src/components/Languages/Molang/Data.ts | 27 ++++++++++---- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/components/Languages/MoLang.ts b/src/components/Languages/MoLang.ts index ed394021d..6792d6bd5 100644 --- a/src/components/Languages/MoLang.ts +++ b/src/components/Languages/MoLang.ts @@ -49,22 +49,64 @@ const completionItemProvider: languages.CompletionItemProvider = { context: languages.CompletionContext, token: CancellationToken ) => { + const { Range } = await useMonaco() + const project = await App.getApp().then((app) => app.project) if (!(project instanceof BedrockProject)) return const molangData = project.molangData await molangData.fired + let completionItems: languages.CompletionItem[] = [] + // Get the entire line const line = model.getLineContent(position.lineNumber) // Attempt to look behind the cursor to get context on what to propose const lineUntilCursor = line.slice(0, position.column - 1) - // Auto-completions for global instances - const { Range } = await useMonaco() + // Namespace property auto-compeltions + if (lineUntilCursor.endsWith('.')) { + const strippedLine = lineUntilCursor.slice(0, -1) + const schema = await molangData.getSchema() + for (const entry of schema) { + if ( + entry.namespace.some((namespace) => + strippedLine.endsWith(namespace) + ) + ) + completionItems = completionItems.concat( + ( + await molangData.getCompletionItemFromValues( + entry.values + ) + ).map((suggestion) => ({ + ...suggestion, + range: new Range( + position.lineNumber, + position.column, + position.lineNumber, + position.column + ), + })) + ) + } + } + + // Global instance namespace auto-completions + completionItems = completionItems.concat( + (await molangData.getNamespaceSuggestions()).map((suggestion) => ({ + ...suggestion, + range: new Range( + position.lineNumber, + position.column, + position.lineNumber, + position.column + ), + })) + ) return { - suggestions: [], + suggestions: completionItems, } }, } diff --git a/src/components/Languages/Molang/Data.ts b/src/components/Languages/Molang/Data.ts index 55120bbcd..4e7a7b123 100644 --- a/src/components/Languages/Molang/Data.ts +++ b/src/components/Languages/Molang/Data.ts @@ -9,7 +9,6 @@ import type { languages } from 'monaco-editor' import { useMonaco } from '/@/utils/libs/useMonaco' export interface MolangValueDefinition { - type: 'math' | 'query' | 'variable' | 'context' valueName: string description?: string isProperty?: boolean @@ -20,6 +19,7 @@ export interface MolangValueDefinition { export interface MolangDefinition { requires?: IRequirements + namespace: string[] values: MolangValueDefinition[] } @@ -96,15 +96,28 @@ export class MolangData extends Signal { return contextData?.data ?? [] } - async getNamespaceSuggestions(): Promise< - Partial[] - > { + async getCompletionItemFromValues(values: MolangValueDefinition[]) { const { languages } = await useMonaco() + return values + .filter((value) => !value.isDeprecated) + .map((value) => { + return { + insertText: value.valueName, + kind: value.isProperty + ? languages.CompletionItemKind.Variable + : languages.CompletionItemKind.Function, + label: value.valueName, + documentation: value.description, + } + }) + } + async getNamespaceSuggestions() { + const { languages } = await useMonaco() return [ { label: 'math', - kind: languages.CompletionItemKind.Function, + kind: languages.CompletionItemKind.Variable, insertText: 'math', }, { @@ -129,12 +142,12 @@ export class MolangData extends Signal { }, { label: 'query', - kind: languages.CompletionItemKind.Function, + kind: languages.CompletionItemKind.Variable, insertText: 'query', }, { label: 'q', - kind: languages.CompletionItemKind.Function, + kind: languages.CompletionItemKind.Variable, insertText: 'q', }, { From 449846ee8f6fdb49178259384aeaa36fe4cfb22d Mon Sep 17 00:00:00 2001 From: Joelant05 <64587014+Joelant05@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:52:51 +0100 Subject: [PATCH 4/5] upd: insert brackets after function --- src/components/Languages/Molang/Data.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Languages/Molang/Data.ts b/src/components/Languages/Molang/Data.ts index 4e7a7b123..fc0dc7f66 100644 --- a/src/components/Languages/Molang/Data.ts +++ b/src/components/Languages/Molang/Data.ts @@ -102,7 +102,9 @@ export class MolangData extends Signal { .filter((value) => !value.isDeprecated) .map((value) => { return { - insertText: value.valueName, + insertText: `${value.valueName}${ + value.isProperty ? '' : '()' + }`, kind: value.isProperty ? languages.CompletionItemKind.Variable : languages.CompletionItemKind.Function, From ae7a63821cb3ca08bc54d45e1fda6a50e33cde97 Mon Sep 17 00:00:00 2001 From: Joelant05 <64587014+Joelant05@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:53:16 +0100 Subject: [PATCH 5/5] fix: don't show global suggestions when accessing namespace --- src/components/Languages/MoLang.ts | 34 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/Languages/MoLang.ts b/src/components/Languages/MoLang.ts index 6792d6bd5..7157961a7 100644 --- a/src/components/Languages/MoLang.ts +++ b/src/components/Languages/MoLang.ts @@ -64,7 +64,12 @@ const completionItemProvider: languages.CompletionItemProvider = { // Attempt to look behind the cursor to get context on what to propose const lineUntilCursor = line.slice(0, position.column - 1) + // Function argument auto-completions + // const withinBrackets + // Namespace property auto-compeltions + // TODO - place cursor in brackets after inserting text + let isAccessingNamespace = false if (lineUntilCursor.endsWith('.')) { const strippedLine = lineUntilCursor.slice(0, -1) const schema = await molangData.getSchema() @@ -73,7 +78,8 @@ const completionItemProvider: languages.CompletionItemProvider = { entry.namespace.some((namespace) => strippedLine.endsWith(namespace) ) - ) + ) { + isAccessingNamespace = true completionItems = completionItems.concat( ( await molangData.getCompletionItemFromValues( @@ -89,21 +95,25 @@ const completionItemProvider: languages.CompletionItemProvider = { ), })) ) + } } } // Global instance namespace auto-completions - completionItems = completionItems.concat( - (await molangData.getNamespaceSuggestions()).map((suggestion) => ({ - ...suggestion, - range: new Range( - position.lineNumber, - position.column, - position.lineNumber, - position.column - ), - })) - ) + if (!isAccessingNamespace) + completionItems = completionItems.concat( + (await molangData.getNamespaceSuggestions()).map( + (suggestion) => ({ + ...suggestion, + range: new Range( + position.lineNumber, + position.column, + position.lineNumber, + position.column + ), + }) + ) + ) return { suggestions: completionItems,