diff --git a/.env.example b/.env.example index b33d84d..9215454 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ NEXTAUTH_URL=https://example.com GITHUB_SECRET="" -GITHUB_ID="" \ No newline at end of file +GITHUB_ID="" +NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build" +NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c" \ No newline at end of file diff --git a/components/HooksEditor.tsx b/components/HooksEditor.tsx index 79680be..863fa49 100644 --- a/components/HooksEditor.tsx +++ b/components/HooksEditor.tsx @@ -1,6 +1,6 @@ import React, { useRef } from "react"; import { useSnapshot, ref } from "valtio"; -import Editor from "@monaco-editor/react"; +import Editor, { loader } from "@monaco-editor/react"; import type monaco from "monaco-editor"; import { ArrowBendLeftUp } from "phosphor-react"; import { useTheme } from "next-themes"; @@ -14,6 +14,15 @@ import { saveFile, state } from "../state"; import EditorNavigation from "./EditorNavigation"; import Text from "./Text"; +import { MonacoServices } from "@codingame/monaco-languageclient"; +import { createLanguageClient, createWebSocket } from "../utils/languageClient"; +import { listen } from "@codingame/monaco-jsonrpc"; + +loader.config({ + paths: { + vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs", + }, +}); const HooksEditor = () => { const editorRef = useRef(); @@ -35,12 +44,44 @@ const HooksEditor = () => { {snap.files.length > 0 && router.isReady ? ( { + // @ts-expect-error + window.monaco = monaco; + monaco.languages.register({ + id: "c", + extensions: [".c", ".h"], + aliases: ["C", "c", "H", "h"], + mimetypes: ["text/plain"], + }); + MonacoServices.install(monaco); + // create the web socket + const webSocket = createWebSocket( + process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || "" + ); + // listen when the web socket is opened + listen({ + webSocket, + onConnection: (connection) => { + // create and start the language client + const languageClient = createLanguageClient(connection); + const disposable = languageClient.start(); + connection.onClose(() => disposable.dispose()); + connection.onError((error) => console.log(error)); + }, + }); + // // hook editor to global state + // editor.updateOptions({ + // minimap: { + // enabled: false, + // }, + // ...snap.editorSettings, + // }); if (!state.editorCtx) { state.editorCtx = ref(monaco.editor); // @ts-expect-error @@ -51,15 +92,14 @@ const HooksEditor = () => { }} onMount={(editor, monaco) => { editorRef.current = editor; - // hook editor to global state editor.updateOptions({ - minimap: { - enabled: false, + glyphMargin: true, + lightbulb: { + enabled: true, }, - ...snap.editorSettings, }); editor.addCommand( - monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, + monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { saveFile(editor.getValue()); } diff --git a/next.config.js b/next.config.js index 6ddb801..d54a3fb 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,12 @@ module.exports = { reactStrictMode: true, images: { - domains: ['avatars.githubusercontent.com'], + domains: ["avatars.githubusercontent.com"], }, -} + webpack(config) { + config.resolve.alias["vscode"] = require.resolve( + "@codingame/monaco-languageclient/lib/vscode-compatibility" + ); + return config; + }, +}; diff --git a/package.json b/package.json index 77ccc12..0462103 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint": "next lint" }, "dependencies": { + "@codingame/monaco-jsonrpc": "^0.3.1", + "@codingame/monaco-languageclient": "^0.17.0", "@monaco-editor/react": "^4.3.1", "@octokit/core": "^3.5.1", "@radix-ui/colors": "^0.1.7", @@ -17,10 +19,11 @@ "@radix-ui/react-dropdown-menu": "^0.1.1", "@radix-ui/react-id": "^0.1.1", "@stitches/react": "^1.2.6-0", - "monaco-editor": "^0.29.1", + "monaco-editor": "^0.30.1", "next": "^12.0.4", "next-auth": "^4.0.0-beta.5", "next-themes": "^0.0.15", + "normalize-url": "^7.0.2", "octokit": "^1.7.0", "phosphor-react": "^1.3.1", "re-resizable": "^6.9.1", @@ -28,7 +31,10 @@ "react-dom": "17.0.2", "react-hot-toast": "^2.1.1", "react-new-window": "^0.2.1", - "valtio": "^1.2.5" + "reconnecting-websocket": "^4.4.0", + "valtio": "^1.2.5", + "vscode-languageserver": "^7.0.0", + "vscode-uri": "^3.0.2" }, "devDependencies": { "@types/react": "17.0.31", diff --git a/utils/languageClient.ts b/utils/languageClient.ts new file mode 100644 index 0000000..693bcea --- /dev/null +++ b/utils/languageClient.ts @@ -0,0 +1,42 @@ +import { MessageConnection } from "@codingame/monaco-jsonrpc"; +import { MonacoLanguageClient, ErrorAction, CloseAction, createConnection } from "@codingame/monaco-languageclient"; +import normalizeUrl from "normalize-url"; +import ReconnectingWebSocket from "reconnecting-websocket"; + +export function createLanguageClient(connection: MessageConnection): MonacoLanguageClient { + return new MonacoLanguageClient({ + name: "Sample Language Client", + clientOptions: { + // use a language id as a document selector + documentSelector: ['c', 'h'], + // disable the default error handler + errorHandler: { + error: () => ErrorAction.Continue, + closed: () => CloseAction.Restart + } + }, + // create a language client connection from the JSON RPC connection on demand + connectionProvider: { + get: (errorHandler, closeHandler) => { + return Promise.resolve(createConnection(connection, errorHandler, closeHandler)) + } + } + }); +} + +export function createUrl(path: string): string { + const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; + return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`); +} + +export function createWebSocket(url: string): any { + const socketOptions = { + maxReconnectionDelay: 10000, + minReconnectionDelay: 1000, + reconnectionDelayGrowFactor: 1.3, + connectionTimeout: 10000, + maxRetries: Infinity, + debug: false + }; + return new ReconnectingWebSocket(url, [], socketOptions); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b2c7883..9c1a8c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -58,6 +58,24 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" +"@codingame/monaco-jsonrpc@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@codingame/monaco-jsonrpc/-/monaco-jsonrpc-0.3.1.tgz#f28dc2e27fbfe9276d2faaf40c9a572272cf3ffd" + integrity sha512-Zxilei5fGV89uGJcFKDQFfZfUbWdP8/NGympXQUX/XRle4CYUabfvEOdY0Diq0NruttlRH3RdWVZ7Nw6f4TClQ== + dependencies: + vscode-jsonrpc "^6.0.0" + +"@codingame/monaco-languageclient@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@codingame/monaco-languageclient/-/monaco-languageclient-0.17.0.tgz#ef1c42ed70392118ce4f563f6779d7bd2566b3d4" + integrity sha512-8DZjV02STdOEy6MY24q0hQGQtoTapW7iakUbznLglCPTPyvZxUGwu8DQ5YlaBZLnrT3hPhxCmxNdoGWOnsFtqQ== + dependencies: + glob-to-regexp "^0.4.1" + vscode-jsonrpc "6.0.0" + vscode-languageclient "7.0.0" + vscode-languageserver-textdocument "^1.0.1" + vscode-uri "^3.0.2" + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -2671,10 +2689,10 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -monaco-editor@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.29.1.tgz#6ee93d8a5320704d48fd7058204deed72429c020" - integrity sha512-rguaEG/zrPQSaKzQB7IfX/PpNa0qxF1FY8ZXRkN4WIl8qZdTQRSRJCtRto7IMcSgrU6H53RXI+fTcywOBC4aVw== +monaco-editor@^0.30.1: + version "0.30.1" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.30.1.tgz#47f8d18a0aa2264fc5654581741ab8d7bec01689" + integrity sha512-B/y4+b2O5G2gjuxIFtCE2EkM17R2NM7/3F8x0qcPsqy4V83bitJTIO4TIeZpYlzu/xy6INiY/+84BEm6+7Cmzg== ms@2.0.0: version "2.0.0" @@ -2816,6 +2834,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-7.0.2.tgz#3f250c964484715e6bfbcdfa2524ad76c57e1297" + integrity sha512-HC9c6eHqxmiR6sL9DKt9ttLkiLaI1jytdkJMGAEvkLAAdlOi99kR7UMWWWRrwjucuFabaau4ZuvP1Zv+6PpDjA== + oauth@^0.9.15: version "0.9.15" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" @@ -3301,6 +3324,11 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +reconnecting-websocket@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" + integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng== + regenerator-runtime@0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz#e96bf612a3362d12bb69f7e8f74ffeab25c7ac91" @@ -3410,7 +3438,7 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.5: +semver@^7.2.1, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -3848,6 +3876,50 @@ vm-browserify@1.1.2: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vscode-jsonrpc@6.0.0, vscode-jsonrpc@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" + integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== + +vscode-languageclient@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" + integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== + dependencies: + minimatch "^3.0.4" + semver "^7.3.4" + vscode-languageserver-protocol "3.16.0" + +vscode-languageserver-protocol@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" + integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== + dependencies: + vscode-jsonrpc "6.0.0" + vscode-languageserver-types "3.16.0" + +vscode-languageserver-textdocument@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz#879f2649bfa5a6e07bc8b392c23ede2dfbf43eff" + integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A== + +vscode-languageserver-types@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" + integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== + +vscode-languageserver@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" + integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== + dependencies: + vscode-languageserver-protocol "3.16.0" + +vscode-uri@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.2.tgz#ecfd1d066cb8ef4c3a208decdbab9a8c23d055d0" + integrity sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA== + watchpack@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"