From 94ed2e6c65159fbc627e26cd2247ed12bac3f518 Mon Sep 17 00:00:00 2001 From: Jesse Freeman Date: Sat, 22 Jul 2023 21:36:27 -0400 Subject: [PATCH] Added auto complete to code editor. --- package.json | 1 + pnpm-lock.yaml | 3 ++ src/components/Editor.tsx | 102 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 61620da..6370e99 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@babel/runtime": "^7.22.6", + "@codemirror/autocomplete": "^6.9.0", "@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-css": "^6.2.0", "@codemirror/lang-html": "^6.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d50202..b173d9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@babel/runtime': specifier: ^7.22.6 version: 7.22.6 + '@codemirror/autocomplete': + specifier: ^6.9.0 + version: 6.9.0(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.15.3)(@lezer/common@1.0.3) '@codemirror/lang-cpp': specifier: ^6.0.2 version: 6.0.2 diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 11ea37d..3aa60e5 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -4,7 +4,7 @@ import { keymap } from "@codemirror/view"; import { createEffect, createSignal, onCleanup, onMount } from "solid-js"; import { githubDark, githubLight } from "@uiw/codemirror-theme-github"; import { useDarkMode } from "../lib/darkmode"; - +import { autocompletion, startCompletion } from '@codemirror/autocomplete'; const { StateCommand, Selection } = EditorState; // The Editor component. // It accepts the following props: @@ -25,6 +25,96 @@ export const Editor = (props: { // Check if the application is running in dark mode. const isDarkMode = useDarkMode() + // // Define your own completion source + // const pv8CompletionSource = (context) => { + // // Define the PV8 API methods + // const pv8API = [ + // 'DrawSprite(spriteID, x, y, flipH, flipV, drawMode, colorOffset)', + // 'DrawSprites(ids, x, y, width, flipH, flipV, drawMode, colorOffset)', + // 'DrawTile(tileID, x, y, flipH, flipV, drawMode, colorOffset)', + // 'DrawTiles(ids, x, y, width, flipH, flipV, drawMode, colorOffset)', + // 'RedrawDisplay()', + // 'RebuildTilemap()', + // // Add more API methods as needed + // ]; + + // // Convert the API methods to the completion format + // const options = pv8API.map(method => ({ label: method })); + + // // Get the token before the cursor + // const token = context.tokenBefore(); + + // // Set the start of the completion range to the start of the token, if a token was found + // const from = token ? token.from : context.pos; + + // return { + // from, + // to: context.pos, // The end of the completion range is the current cursor position + // options, + // }; + // }; + + // This is a list of PV8's APIs for demonstration purposes + // You should replace this with the actual list of APIs + const pixelVisionAPI = [ + { label: "BackgroundColor", signature: "BackgroundColor(id)", type: "function", info: "Manages system colors and the background color used to clear the display." }, + { label: "Color", signature: "Color(id, value)", type: "function", info: "Allows you to read and update color values in the ColorChip." }, + { label: "DrawRect", signature: "DrawRect(x, y, width, height, color, drawMode)", type: "function", info: "Displays a rectangle with a fill color on the screen." }, + { label: "DrawMetaSprite", signature: "DrawMetaSprite(id, x, y, flipH, flipV, drawMode, colorOffset)", type: "function", info: "Draws a Sprite Collection to the display." }, + { label: "DrawSprite", signature: "DrawSprite(id, x, y, flipH, flipV, drawMode, colorOffset)", type: "function", info: "Draws a single sprite to the display." }, + { label: "DrawText", signature: "DrawText(text, x, y, drawMode, font, colorOffset, spacing)", type: "function", info: "Renders text to the display." }, + { label: "ReadSaveData", signature: "ReadSaveData(key, defaultValue)", type: "function", info: "Reads saved data by supplying a key." }, + { label: "WriteSaveData", signature: "WriteSaveData(key, value)", type: "function", info: "Writes saved data by supplying a key and value." }, + { label: "Button", signature: "Button(Buttons button, InputState state, int controllerID)", type: "function", info: "Gets the current state of any button by calling the Button() method and supplying a button ID." }, + { label: "Key", signature: "Key(key, state)", type: "function", info: "Tests for keyboard input by calling the Key() API." }, + { label: "MouseButton", signature: "MouseButton(int button, InputState state)", type: "function", info: "Gets the current state of the mouse's left (0) and right (1) buttons by calling MouseButton()API." }, + { label: "MousePosition", signature: "MousePosition()", type: "function", info: "Returns a Point for the mouse cursor's X and Y position." }, + { label: "Init", signature: "Init()", type: "function", info: "Called when a game first loads up." }, + { label: "Draw", signature: "Draw()", type: "function", info: "Called once per frame after the Update() has been completed." }, + { label: "Update", signature: "Update(timeDelta)", type: "function", info: "Called once per frame at the beginning of the game loop." }, + { label: "PauseSong", signature: "PauseSong()", type: "function", info: "Toggles the current playback state of the sequencer." }, + { label: "PlaySong", signature: "PlaySong(id, loop, startAt)", type: "function", info: "Activates the MusicChip's tracker to playback any of the songs stored in memory." }, + { label: "RewindSong", signature: "RewindSong(position, patternID)", type: "function", info: "Rewinds the currently playing song to a specific position and pattern ID." }, + { label: "StopSong", signature: "StopSong()", type: "function", info: "Stops the currently playing song." }, + { label: "Display", signature: "Display()", type: "function", info: "Gets the resolution of the display at run time." }, + { label: "RedrawDisplay", signature: "RedrawDisplay()", type: "function", info: "Executes both the Clear() and DrawTilemap() APIs in a single call." }, + { label: "ScrollPosition", signature: "ScrollPosition(x, y)", type: "function", info: "Scrolls the tilemap by calling the ScrollPosition() API and supplying a new scroll X and Y position." }, + { label: "PlaySound", signature: "PlaySound(id, channel)", type: "function", info: "Plays a single sound effect on a specific channel." }, + { label: "Sound", signature: "Sound(int id, string data)", type: "function", info: "Reads raw sound data from the SoundChip." }, + { label: "StopSound", signature: "StopSound(channel)", type: "function", info: "Stops any sound playing on a specific channel." }, + { label: "Sprite", signature: "Sprite(id, data)", type: "function", info: "Reads and writes pixel data directly to the SpriteChip's memory." }, + { label: "TotalSprites", signature: "TotalSprites(bool ignoreEmpty)", type: "function", info: "Returns the total number of sprites in the SpriteChip." }, + { label: "Flag", signature: "Flag(column, row, value)", type: "function", info: "Quickly accesses just the flag value of a tile." }, + { label: "Tile", signature: "Tile(column, row, spriteID, colorOffset, flag, flipH, flipV)", type: "function", info: "Gets the current sprite, color offset and flag values associated with a given tile ID." }, + { label: "TilemapSize", signature: "TilemapSize(width, height, clear)", type: "function", info: "Returns a Pointrepresenting the size of the tilemap in columns(X) and rows (Y)." }, + { label: "UpdateTiles", signature: "UpdateTiles(ids, column, row, width, colorOffset, flag)", type: "function", info: "Updates the color offset and flag values of multiple tiles at once." }, + ]; + + + const completionSource: CompletionSource = (context: CompletionContext) => { + const beforeCursor = context.state.sliceDoc(0, context.pos); + const match = /\b\w+$/.exec(beforeCursor); + + if (!match) { + return null; + } + + const wordStart = match.index; + + return { + from: wordStart, + to: context.pos, + options: pixelVisionAPI.map(api => ({ + label: api.signature, + type: api.type, + info: api.info, + })), + }; +}; + + // Then you can use this completion source when you configure autocompletion + const autocompleteExtension = autocompletion({ override: [completionSource] }); + const insertTab: StateCommand = ({state, dispatch}) => { // Get the current selection. let {from, to} = state.selection.main; @@ -51,7 +141,8 @@ export const Editor = (props: { const tabKeymap = keymap.of([ { key: "Tab", run: insertTab }, - { key: "Shift-Tab", run: deleteSpaces } // Add this line + { key: "Shift-Tab", run: deleteSpaces }, + { key: "Alt-Space", run: startCompletion} ]); @@ -73,10 +164,15 @@ export const Editor = (props: { // Add different extensions based on whether the app is in dark mode or not. isDarkMode() ? githubDark : githubLight, basicSetup, + autocompletion({ + override: [completionSource], // Use the PV8 completion source + }), + // startCompletion, // Start completion immediately handleUpdate, EditorView.lineWrapping, ...(props.extensions || []), - tabKeymap + tabKeymap, + ], }), })