From a438935519f55acea859175ce0cc89b3c6bca758 Mon Sep 17 00:00:00 2001 From: Joelant05 <64587014+Joelant05@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:37:44 +0100 Subject: [PATCH] feat: initial command signatures --- src/components/Languages/Language.ts | 9 ++ src/components/Languages/Mcfunction.ts | 119 ++++++++++++++++++ .../Languages/Mcfunction/Validator.ts | 8 +- .../Languages/Mcfunction/WithinJson.ts | 35 ++++-- 4 files changed, 158 insertions(+), 13 deletions(-) diff --git a/src/components/Languages/Language.ts b/src/components/Languages/Language.ts index dc82d9829..99210f027 100644 --- a/src/components/Languages/Language.ts +++ b/src/components/Languages/Language.ts @@ -10,6 +10,7 @@ export interface IAddLanguageOptions { tokenProvider: any completionItemProvider?: languages.CompletionItemProvider codeActionProvider?: languages.CodeActionProvider + signatureHelpProvider?: languages.SignatureHelpProvider } export abstract class Language { @@ -25,6 +26,7 @@ export abstract class Language { tokenProvider, completionItemProvider, codeActionProvider, + signatureHelpProvider, }: IAddLanguageOptions) { this.id = id @@ -55,6 +57,13 @@ export abstract class Language { this.disposables.push( languages.registerCodeActionProvider(id, codeActionProvider) ) + if (signatureHelpProvider) + this.disposables.push( + languages.registerSignatureHelpProvider( + id, + signatureHelpProvider + ) + ) }) } diff --git a/src/components/Languages/Mcfunction.ts b/src/components/Languages/Mcfunction.ts index 2a0dd36ef..09603c8ae 100644 --- a/src/components/Languages/Mcfunction.ts +++ b/src/components/Languages/Mcfunction.ts @@ -146,6 +146,124 @@ const completionItemProvider: languages.CompletionItemProvider = { }, } +const signatureHelpProvider: languages.SignatureHelpProvider = { + signatureHelpTriggerCharacters: ['\n', ' '], + signatureHelpRetriggerCharacters: ['\n', ' '], + provideSignatureHelp: async ( + model: editor.ITextModel, + position: Position, + token: CancellationToken, + context: languages.SignatureHelpContext + ) => { + // Get the commandData instance so we can access command schema data + const app = await App.getApp() + if (!(app.project instanceof BedrockProject)) return + + await app.project.commandData.fired + + const commandData = app.project.commandData + + // Generate available signatures for the command on this line and figure out where the cursor is inside of the command + let signatures: languages.SignatureInformation[] = [] + let signatureIndex = 0 + let parameterIndex = 0 + + let lineContent = '' + try { + lineContent = model.getLineContent(position.lineNumber) + } catch {} + + const { tokens } = tokenizeCommand(lineContent) + + const commandName = tokens[0].word + if (commandName) { + const definitions = await commandData.getCommandDefinitions( + commandName, + false + ) + + for (const [defIndex, def] of definitions.entries()) { + const signatureParts: { + signature: string + documentation?: string + }[] = [{ signature: `/${commandName}` }] + + for (const arg of def.arguments ?? []) { + // If there is an argument name, create the signature using this name and the type (assumed string if not specified) + // TODO - better display coordinate signatures, currently they can display as 3 args but they count as one + if (arg.argumentName) { + signatureParts.push({ + signature: arg.isOptional + ? `[${arg.argumentName}: ${ + arg.type ?? 'string' + }]` + : `<${arg.argumentName}: ${ + arg.type ?? 'string' + }>`, + documentation: arg.description, + }) + } else if ( + // Otherwise, if there is no argument name, but there is a single enum value for this argument just show the only valid value in the signature + arg.additionalData?.values && + arg.additionalData?.values.length === 1 + ) { + signatureParts.push({ + signature: arg.additionalData.values[0], + }) + } + } + signatures.push({ + label: signatureParts + .map((part) => part.signature) + .join(' '), + documentation: def.description, + parameters: signatureParts.map((part) => ({ + label: part.signature, + documentation: part.documentation, + })), + }) + + // Figure out which argument the cursor is placed on + for (const [tokenIndex, token] of tokens.entries()) { + console.log( + token, + tokenIndex, + tokenIndex + 1 === tokens.length && + position.column >= token.startColumn + ) + if ( + tokenIndex + 1 === tokens.length && + position.column >= token.startColumn + ) { + parameterIndex = tokenIndex + break + } + if ( + position.column >= token.startColumn && + position.column <= token.endColumn + ) { + parameterIndex = tokenIndex + break + } + } + + // TODO - Find out which signature to use + // We should probably update this to borrow logic from the function validator + // The validator should be updated so that parsing a command also returns which schema definition(s) passed validation + } + } + + return { + dispose: () => {}, + value: { + signatures, + activeParameter: parameterIndex, + activeSignature: signatureIndex, + }, + } + }, +} + const loadCommands = async (lang: McfunctionLanguage) => { const app = await App.getApp() await app.projectManager.fired @@ -179,6 +297,7 @@ export class McfunctionLanguage extends Language { config, tokenProvider, completionItemProvider, + signatureHelpProvider, }) let loadedProject: Project | null = null diff --git a/src/components/Languages/Mcfunction/Validator.ts b/src/components/Languages/Mcfunction/Validator.ts index 91c0282c7..1ed164cb2 100644 --- a/src/components/Languages/Mcfunction/Validator.ts +++ b/src/components/Languages/Mcfunction/Validator.ts @@ -1,9 +1,5 @@ -import { - tokenizeCommand, - tokenizeTargetSelector, - castType, -} from 'bridge-common-utils' -import { CommandData, ICommandArgument } from './Data' +import { tokenizeCommand } from 'bridge-common-utils' +import { CommandData } from './Data' import type { editor } from 'monaco-editor' import { useMonaco } from '/@/utils/libs/useMonaco' import { RefSchema } from '/@/components/JSONSchema/Schema/Ref' diff --git a/src/components/Languages/Mcfunction/WithinJson.ts b/src/components/Languages/Mcfunction/WithinJson.ts index c9b9a0e88..0c56441ce 100644 --- a/src/components/Languages/Mcfunction/WithinJson.ts +++ b/src/components/Languages/Mcfunction/WithinJson.ts @@ -1,6 +1,12 @@ import { App } from '/@/App' import { getLocation } from '/@/utils/monaco/getLocation' -import type { editor, Position, Range } from 'monaco-editor' +import type { + CancellationToken, + editor, + languages, + Position, + Range, +} from 'monaco-editor' import { getJsonWordAtPosition } from '/@/utils/monaco/getJsonWord' import { tokenizeCommand } from 'bridge-common-utils' import { BedrockProject } from '/@/components/Projects/Project/BedrockProject' @@ -25,12 +31,10 @@ export async function registerEmbeddedMcfunctionProvider() { const currentTab = app.project.tabSystem?.selectedTab if (!currentTab) return - const validCommands: Record< - string, - string[] - > = await app.dataLoader.readJSON( - `data/packages/minecraftBedrock/location/validCommand.json` - ) + const validCommands: Record = + await app.dataLoader.readJSON( + `data/packages/minecraftBedrock/location/validCommand.json` + ) const { id, meta: { commandsUseSlash } = { commandsUseSlash: false }, @@ -90,4 +94,21 @@ export async function registerEmbeddedMcfunctionProvider() { } }, }) + + // TODO + // languages.registerSignatureHelpProvider('json', { + // signatureHelpTriggerCharacters: ['\n', ' '], + // signatureHelpRetriggerCharacters: ['\n', ' '], + // provideSignatureHelp: async ( + // model: editor.ITextModel, + // position: Position, + // token: CancellationToken, + // context: languages.SignatureHelpContext + // ) => { + // return { + // dispose: () => {}, + // value: {}, + // } + // }, + // }) }