From b8a5e5b67ca7f188b258d01706d8df622d2d9df0 Mon Sep 17 00:00:00 2001 From: SOUMITRO-SAHA Date: Sun, 29 Sep 2024 20:49:59 +0530 Subject: [PATCH 01/10] Fix: Add Syntax Highlighting to Sidebar Code Snippets --- extensions/void/package-lock.json | 306 +++++++++++++++++- extensions/void/package.json | 4 +- .../void/src/sidebar/MarkdownRender.tsx | 129 +++++--- 3 files changed, 396 insertions(+), 43 deletions(-) diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index 1073079a..47203796 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.1", "dependencies": { "@anthropic-ai/sdk": "^0.27.1", - "openai": "^4.57.0" + "openai": "^4.57.0", + "react-syntax-highlighter": "^15.5.0" }, "devDependencies": { "@eslint/js": "^9.9.1", @@ -19,6 +20,7 @@ "@types/node": "^22.5.1", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/react-syntax-highlighter": "^15.5.13", "@types/vscode": "1.92.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", @@ -184,6 +186,18 @@ "node": ">=4" } }, + "node_modules/@babel/runtime": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -685,6 +699,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -2644,6 +2668,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2815,6 +2852,14 @@ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/formdata-node": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", @@ -3106,6 +3151,16 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", @@ -3146,6 +3201,71 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/hastscript/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -3155,6 +3275,15 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3937,8 +4066,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -4094,7 +4222,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4102,6 +4229,20 @@ "loose-envify": "cli.js" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -5767,6 +5908,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -5842,7 +5992,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -5895,6 +6044,22 @@ "react": ">=18" } }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5952,6 +6117,128 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -7472,6 +7759,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/extensions/void/package.json b/extensions/void/package.json index 43b34951..9553b286 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -111,6 +111,7 @@ "@types/node": "^22.5.1", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/react-syntax-highlighter": "^15.5.13", "@types/vscode": "1.92.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", @@ -136,6 +137,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.27.1", - "openai": "^4.57.0" + "openai": "^4.57.0", + "react-syntax-highlighter": "^15.5.0" } } diff --git a/extensions/void/src/sidebar/MarkdownRender.tsx b/extensions/void/src/sidebar/MarkdownRender.tsx index e9cc2b96..4df719c3 100644 --- a/extensions/void/src/sidebar/MarkdownRender.tsx +++ b/extensions/void/src/sidebar/MarkdownRender.tsx @@ -1,34 +1,69 @@ -import React, { JSX, useState } from 'react'; -import { MarkedToken, Token, TokensList } from 'marked'; -import { awaitVSCodeResponse, getVSCodeAPI } from './getVscodeApi'; +import React, { JSX, useState } from "react"; +import { MarkedToken, Token, TokensList } from "marked"; +import { awaitVSCodeResponse, getVSCodeAPI } from "./getVscodeApi"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs"; // code block with Apply button at top -export const BlockCode = ({ text, disableApplyButton = false }: { text: string, disableApplyButton?: boolean }) => { - return
- {disableApplyButton ? null :
- -
} -
-
-				{text}
-			
+export const BlockCode = ({ + text, + language, + disableApplyButton = false, +}: { + text: string; + language?: string; + disableApplyButton?: boolean; +}) => { + const customStyle = { + ...atomOneDarkReasonable, + 'code[class*="language-"]': { + ...atomOneDarkReasonable['code[class*="language-"]'], + background: "none", + }, + }; + + return ( +
+ {disableApplyButton ? null : ( +
+ +
+ )} +
+ + {text} + +
-
-} + ); +}; const Render = ({ token }: { token: Token }) => { - // deal with built-in tokens first (assume marked token) - const t = token as MarkedToken + const t = token as MarkedToken; if (t.type === "space") { return {t.raw}; } if (t.type === "code") { - return + return ; } if (t.type === "heading") { @@ -42,7 +77,7 @@ const Render = ({ token }: { token: Token }) => { {t.header.map((cell: any, index: number) => ( - + {cell.raw} ))} @@ -52,7 +87,10 @@ const Render = ({ token }: { token: Token }) => { {t.rows.map((row: any[], rowIndex: number) => ( {row.map((cell: any, cellIndex: number) => ( - + {cell.raw} ))} @@ -72,11 +110,11 @@ const Render = ({ token }: { token: Token }) => { } if (t.type === "list") { - - const ListTag = t.ordered ? 'ol' : 'ul'; + const ListTag = t.ordered ? "ol" : "ul"; return ( - {t.items.map((item, index) => (
  • @@ -91,15 +129,23 @@ const Render = ({ token }: { token: Token }) => { } if (t.type === "paragraph") { - return

    - {t.tokens.map((token, index) => ( - - ))} -

    ; + return ( +

    + {t.tokens.map((token, index) => ( + + ))} +

    + ); } if (t.type === "html") { - return
    {``}{t.raw}{``}
    ; + return ( +
    +				{``}
    +				{t.raw}
    +				{``}
    +			
    + ); } if (t.type === "text" || t.type === "escape") { @@ -111,7 +157,11 @@ const Render = ({ token }: { token: Token }) => { } if (t.type === "link") { - return {t.text}; + return ( + + {t.text} + + ); } if (t.type === "image") { @@ -128,7 +178,11 @@ const Render = ({ token }: { token: Token }) => { // inline code if (t.type === "codespan") { - return {t.text}; + return ( + + {t.text} + + ); } if (t.type === "br") { @@ -139,12 +193,13 @@ const Render = ({ token }: { token: Token }) => { return {t.text}; } - // default - return
    - Unknown type: - {t.raw} -
    ; + return ( +
    + Unknown type: + {t.raw} +
    + ); }; const MarkdownRender = ({ tokens }: { tokens: TokensList }) => { From cd9bca045199b0c2a6961897c538e4dfcf773499 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 15 Oct 2024 20:51:46 -0700 Subject: [PATCH 02/10] split up message direction and Sidebar tabs --- extensions/void/package-lock.json | 27 +- extensions/void/package.json | 10 +- extensions/void/src/extension.ts | 20 +- extensions/void/src/shared_types.ts | 44 +-- extensions/void/src/sidebar/Sidebar.tsx | 295 +----------------- extensions/void/src/sidebar/SidebarChat.tsx | 258 +++++++++++++++ .../src/sidebar/SidebarThreadSelector.tsx | 40 +++ extensions/void/src/sidebar/getVscodeApi.ts | 63 +++- 8 files changed, 407 insertions(+), 350 deletions(-) create mode 100644 extensions/void/src/sidebar/SidebarChat.tsx create mode 100644 extensions/void/src/sidebar/SidebarThreadSelector.tsx diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index 6ebc6476..a23620fe 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -9,8 +9,8 @@ "version": "0.0.1", "dependencies": { "@anthropic-ai/sdk": "^0.27.1", - "diff-match-patch": "^1.0.5", "diff": "^7.0.0", + "diff-match-patch": "^1.0.5", "ollama": "^0.5.9", "openai": "^4.57.0" }, @@ -23,6 +23,7 @@ "@types/node": "^22.5.1", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/uuid": "^10.0.0", "@types/vscode": "1.89.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", @@ -42,7 +43,8 @@ "rimraf": "^6.0.1", "tailwindcss": "^3.4.10", "typescript": "5.5.4", - "typescript-eslint": "^8.3.0" + "typescript-eslint": "^8.3.0", + "uuid": "^10.0.0" }, "engines": { "vscode": "^1.89.0" @@ -768,6 +770,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/vscode": { "version": "1.89.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.89.0.tgz", @@ -7720,6 +7729,20 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", diff --git a/extensions/void/package.json b/extensions/void/package.json index 17c44e1f..72bfb32a 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -363,6 +363,7 @@ "@types/node": "^22.5.1", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/uuid": "^10.0.0", "@types/vscode": "1.89.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", @@ -382,13 +383,14 @@ "rimraf": "^6.0.1", "tailwindcss": "^3.4.10", "typescript": "5.5.4", - "typescript-eslint": "^8.3.0" + "typescript-eslint": "^8.3.0", + "uuid": "^10.0.0" }, "dependencies": { "@anthropic-ai/sdk": "^0.27.1", + "diff": "^7.0.0", "diff-match-patch": "^1.0.5", "ollama": "^0.5.9", - "openai": "^4.57.0", - "diff": "^7.0.0" + "openai": "^4.57.0" } -} \ No newline at end of file +} diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 0c0541be..55c53350 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { DisplayChangesProvider } from './DisplayChangesProvider'; -import { BaseDiffArea, ChatThreads, WebviewMessage } from './shared_types'; +import { BaseDiffArea, ChatThreads, MessageFromSidebar, MessageToSidebar } from './shared_types'; import { SidebarWebviewProvider } from './SidebarWebviewProvider'; import { ApiConfig } from './common/sendLLMMessage'; @@ -79,7 +79,7 @@ export function activate(context: vscode.ExtensionContext) { const filePath = editor.document.uri; // send message to the webview (Sidebar.tsx) - webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies WebviewMessage)); + webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar)); }) ); @@ -105,23 +105,23 @@ export function activate(context: vscode.ExtensionContext) { // top navigation bar commands context.subscriptions.push(vscode.commands.registerCommand('void.startNewThread', async () => { - webview.postMessage({ type: 'startNewThread' } satisfies WebviewMessage) + webview.postMessage({ type: 'startNewThread' } satisfies MessageToSidebar) })) context.subscriptions.push(vscode.commands.registerCommand('void.toggleThreadSelector', async () => { - webview.postMessage({ type: 'toggleThreadSelector' } satisfies WebviewMessage) + webview.postMessage({ type: 'toggleThreadSelector' } satisfies MessageToSidebar) })) // when config changes, send it to the sidebar vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('void')) { const apiConfig = getApiConfig() - webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar) } }) // Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`) - webview.onDidReceiveMessage(async (m: WebviewMessage) => { + webview.onDidReceiveMessage(async (m: MessageFromSidebar) => { if (m.type === 'requestFiles') { @@ -131,7 +131,7 @@ export function activate(context: vscode.ExtensionContext) { ) // send contents to webview - webview.postMessage({ type: 'files', files, } satisfies WebviewMessage) + webview.postMessage({ type: 'files', files, } satisfies MessageToSidebar) } else if (m.type === 'applyChanges') { @@ -168,11 +168,11 @@ export function activate(context: vscode.ExtensionContext) { } else if (m.type === 'getApiConfig') { const apiConfig = getApiConfig() - webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar) } else if (m.type === 'getAllThreads') { const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {} - webview.postMessage({ type: 'allThreads', threads } satisfies WebviewMessage) + webview.postMessage({ type: 'allThreads', threads } satisfies MessageToSidebar) } else if (m.type === 'persistThread') { const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {} @@ -180,7 +180,7 @@ export function activate(context: vscode.ExtensionContext) { context.workspaceState.update('allThreads', updatedThreads) } else { - console.error('unrecognized command', m.type, m) + console.error('unrecognized command', m) } }) } diff --git a/extensions/void/src/shared_types.ts b/extensions/void/src/shared_types.ts index ad2025f1..0fcbef42 100644 --- a/extensions/void/src/shared_types.ts +++ b/extensions/void/src/shared_types.ts @@ -38,45 +38,25 @@ type Diff = { lenses: vscode.CodeLens[], } & BaseDiff -type WebviewMessage = ( - - // editor -> sidebar +// editor -> sidebar +type MessageToSidebar = ( | { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor - - // sidebar -> editor - | { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar - - // sidebar -> editor - | { type: 'requestFiles', filepaths: vscode.Uri[] } - - // editor -> sidebar | { type: 'files', files: { filepath: vscode.Uri, content: string }[] } - - // sidebar -> editor - | { type: 'getApiConfig' } - - // editor -> sidebar | { type: 'apiConfig', apiConfig: ApiConfig } - - // sidebar -> editor - | { type: 'getAllThreads' } - - // editor -> sidebar | { type: 'allThreads', threads: ChatThreads } - - // sidebar -> editor - | { type: 'persistThread', thread: ChatThreads[string] } - - // editor -> sidebar | { type: 'startNewThread' } - - // editor -> sidebar | { type: 'toggleThreadSelector' } - ) +// sidebar -> editor +type MessageFromSidebar = ( + | { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar + | { type: 'requestFiles', filepaths: vscode.Uri[] } + | { type: 'getApiConfig' } + | { type: 'getAllThreads' } + | { type: 'persistThread', thread: ChatThreads[string] } +) -type Command = WebviewMessage['type'] type ChatThreads = { [id: string]: { @@ -105,8 +85,8 @@ export { Diff, DiffArea, CodeSelection, File, - WebviewMessage, - Command, + MessageFromSidebar, + MessageToSidebar, ChatThreads, ChatMessage, } diff --git a/extensions/void/src/sidebar/Sidebar.tsx b/extensions/void/src/sidebar/Sidebar.tsx index 8521d83d..e41e87e4 100644 --- a/extensions/void/src/sidebar/Sidebar.tsx +++ b/extensions/void/src/sidebar/Sidebar.tsx @@ -1,317 +1,42 @@ import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react" import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage" -import { File, CodeSelection, WebviewMessage, ChatMessage } from "../shared_types" -import { awaitVSCodeResponse, getVSCodeAPI, resolveAwaitingVSCodeResponse } from "./getVscodeApi" +import { CodeSelection, ChatMessage, MessageToSidebar } from "../shared_types" +import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode } from "./getVscodeApi" -import { marked } from 'marked'; -import MarkdownRender from "./markdown/MarkdownRender"; -import BlockCode from "./markdown/BlockCode"; - -import * as vscode from 'vscode' -import { SelectedFiles } from "./components/SelectedFiles"; +import { SidebarThreadSelector } from "./SidebarThreadSelector"; import { useThreads } from "./threadsContext"; - - -const filesStr = (fullFiles: File[]) => { - return fullFiles.map(({ filepath, content }) => - ` -${filepath.fsPath} -\`\`\` -${content} -\`\`\``).join('\n') -} - -const userInstructionsStr = (instructions: string, files: File[], selection: CodeSelection | null) => { - return ` -${filesStr(files)} - -${!selection ? '' : ` -I am currently selecting this code: -\`\`\`${selection.selectionStr}\`\`\` -`} - -Please edit the code following these instructions (or, if appropriate, answer my question instead): -${instructions} - -If you make a change, rewrite the entire file. -`; // TODO don't rewrite the whole file on prompt, instead rewrite it when click Apply -} - - -const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { - - const role = chatMessage.role - const children = chatMessage.displayContent - - if (!children) - return null - - let chatbubbleContents: React.ReactNode - - if (role === 'user') { - chatbubbleContents = <> - - {chatMessage.selection?.selectionStr && } - {children} - - } - else if (role === 'assistant') { - - chatbubbleContents = // sectionsHTML - } - - - return
    -
    - {chatbubbleContents} -
    -
    -} - -const ThreadSelector = ({ onClose }: { onClose: () => void }) => { - const { allThreads, currentThread, switchToThread } = useThreads() - return ( -
    -
    - -
    - {/* iterate through all past threads */} - {Object.keys(allThreads ?? {}).map((threadId) => { - const pastThread = (allThreads ?? {})[threadId]; - return ( - - ) - })} -
    - ) -} +import { SidebarChat } from "./SidebarChat"; const Sidebar = () => { - const { allThreads, currentThread, addMessageToHistory, startNewThread, } = useThreads() - - // state of current message - const [selection, setSelection] = useState(null) // the code the user is selecting - const [files, setFiles] = useState([]) // the names of the files in the chat - const [instructions, setInstructions] = useState('') // the user's instructions - - // state of chat - const [messageStream, setMessageStream] = useState('') - const [isLoading, setIsLoading] = useState(false) const [isThreadSelectorOpen, setIsThreadSelectorOpen] = useState(false) - const abortFnRef = useRef<(() => void) | null>(null) - - const [apiConfig, setApiConfig] = useState(null) - // get Api Config on mount useEffect(() => { getVSCodeAPI().postMessage({ type: 'getApiConfig' }) }, []) - // Receive messages from the extension + // Receive messages from the VSCode extension useEffect(() => { const listener = (event: MessageEvent) => { - - const m = event.data as WebviewMessage; - // resolve any awaiting promises - // eg. it will resolve the promise below for `await VSCodeResponse('files')` - resolveAwaitingVSCodeResponse(m) - - // if user pressed ctrl+l, add their selection to the sidebar - if (m.type === 'ctrl+l') { - setSelection(m.selection) - const filepath = m.selection.filePath - - // add current file to the context if it's not already in the files array - if (!files.find(f => f.fsPath === filepath.fsPath)) - setFiles(files => [...files, filepath]) - - } - // when get apiConfig, set - else if (m.type === 'apiConfig') { - setApiConfig(m.apiConfig) - } - - // if they pressed the + to add a new chat - else if (m.type === 'startNewThread') { - setIsThreadSelectorOpen(false) - if (currentThread?.messages.length !== 0) - startNewThread() - } - - // if they opened thread selector - else if (m.type === 'toggleThreadSelector') { - setIsThreadSelectorOpen(v => !v) - } - + const m = event.data as MessageToSidebar; + onMessageFromVSCode(m) } window.addEventListener('message', listener); return () => { window.removeEventListener('message', listener) } - }, [files, selection, startNewThread, currentThread]) + }, []) - const formRef = useRef(null) - const onSubmit = async (e: FormEvent) => { - - e.preventDefault() - if (isLoading) return - - setIsLoading(true) - setInstructions(''); - formRef.current?.reset(); // reset the form's text - setSelection(null) - setFiles([]) - - // request file content from vscode and await response - getVSCodeAPI().postMessage({ type: 'requestFiles', filepaths: files }) - const relevantFiles = await awaitVSCodeResponse('files') - - // add message to chat history - const content = userInstructionsStr(instructions, relevantFiles.files, selection) - // console.log('prompt:\n', content) - const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files } - addMessageToHistory(newHistoryElt) - - // send message to LLM - let { abort } = sendLLMMessage({ - messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })), { role: 'user', content }], - onText: (newText, fullText) => setMessageStream(fullText), - onFinalMessage: (content) => { - // add assistant's message to chat history, and clear selection - const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, } - addMessageToHistory(newHistoryElt) - - // clear selection - setMessageStream('') - setIsLoading(false) - }, - apiConfig: apiConfig - }) - abortFnRef.current = abort - - } - - const onStop = useCallback(() => { - // abort claude - abortFnRef.current?.() - - // if messageStream was not empty, add it to the history - const llmContent = messageStream || '(canceled)' - const newHistoryElt: ChatMessage = { role: 'assistant', displayContent: messageStream, content: llmContent } - addMessageToHistory(newHistoryElt) - - setMessageStream('') - setIsLoading(false) - - }, [addMessageToHistory, messageStream]) - - //Clear code selection - const clearSelection = () => { - setSelection(null); - }; - return <>
    {isThreadSelectorOpen && (
    - setIsThreadSelectorOpen(false)} /> + setIsThreadSelectorOpen(false)} />
    )} -
    - {/* previous messages */} - {currentThread !== null && currentThread.messages.map((message, i) => - - )} - {/* message stream */} - -
    - {/* chatbar */} -
    - {/* selection */} -
    -
    -
    - {/* selection */} - {(files.length || selection?.selectionStr) &&
    - {/* selected files */} - - {/* selected code */} - {!!selection?.selectionStr && ( - - Remove - - )} /> - )} -
    } -
    { if (e.key === 'Enter' && !e.shiftKey) onSubmit(e) }} - - onSubmit={(e) => { - console.log('submit!') - e.preventDefault(); - onSubmit(e) - }}> - {/* input */} - -