diff options
| author | Nate Sesti <sestinj@gmail.com> | 2023-07-24 01:00:42 -0700 | 
|---|---|---|
| committer | Nate Sesti <sestinj@gmail.com> | 2023-07-24 01:00:42 -0700 | 
| commit | 2e376b7e61f68a48ccb2a5be7ab6ab55ab30c670 (patch) | |
| tree | 6c1077859938fadce3a6f17f267127a66fd11df4 /extension | |
| parent | 59c549a468abd40207f8dc46eacdc0e02bbe21ad (diff) | |
| parent | 82b03aeb0882cb884c398104b7934d63c6ceed00 (diff) | |
| download | sncontinue-2e376b7e61f68a48ccb2a5be7ab6ab55ab30c670.tar.gz sncontinue-2e376b7e61f68a48ccb2a5be7ab6ab55ab30c670.tar.bz2 sncontinue-2e376b7e61f68a48ccb2a5be7ab6ab55ab30c670.zip  | |
Merge branch 'main' into show-react-immediately
Diffstat (limited to 'extension')
37 files changed, 2500 insertions, 662 deletions
diff --git a/extension/DEV_README.md b/extension/DEV_README.md index 87ed9334..72ea5c6a 100644 --- a/extension/DEV_README.md +++ b/extension/DEV_README.md @@ -6,8 +6,17 @@ This is the Continue VS Code Extension. Its primary jobs are  2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openContinueGUI` command, as defined in `src/commands.ts`.  3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `server/` (unless extension settings define a server URL other than localhost:65432, in which case the extension will just connect to that). -4. Open Continue +## Setting up for development -# Notes +1. Clone this repo +2. `cd extension` +3. `npm run full-package` + +   > If NPM is not installed, you can use `brew install node` on Mac, or see the [installation page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) for other platforms, or more detailed instructions. + +4. Open a VS Code window with `/extension` as the workspace root (_this is important, development mode will not work otherwise_) +5. Open any `.ts` file in the workspace, then press F5 and select "VS Code Extension Development" to begin debugging. + +## Notes  - We require vscode engine `^1.67.0` and use `@types/vscode` version `1.67.0` because this is the earliest version that doesn't break any of the APIs we are using. If you go back to `1.66.0`, then it will break `vscode.window.tabGroups`. diff --git a/extension/README.md b/extension/README.md index 2d449b92..2944325b 100644 --- a/extension/README.md +++ b/extension/README.md @@ -25,6 +25,16 @@ Let Continue build the scaffolding of Python scripts, React components, and more  - “/edit make an IAM policy that creates a user with read-only access to S3”  - “/edit use this schema to write me a SQL query that gets recently churned users” +## Install + +Continue requires that you have Python 3.8 or greater. If you do not, please [install](https://python.org) it + +If your Continue server is not setting up, please check the console logs: +1. `cmd+shift+p` (MacOS) / `ctrl+shift+p` (Windows) +2. Search for and then select "Developer: Toggle Developer Tools" +3. Select `Console` +4. Read the console logs +  ## OpenAI API Key  New users can try out Continue with GPT-4 using a proxy server that securely makes calls to OpenAI using our API key. Continue should just work the first time you install the extension in VS Code. diff --git a/extension/package-lock.json b/extension/package-lock.json index a235aa81..6e049c1e 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@  {    "name": "continue", -  "version": "0.0.158", +  "version": "0.0.190",    "lockfileVersion": 2,    "requires": true,    "packages": {      "": {        "name": "continue", -      "version": "0.0.158", +      "version": "0.0.190",        "license": "Apache-2.0",        "dependencies": {          "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 6bb6b720..884b518c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@    "displayName": "Continue",    "pricing": "Free",    "description": "The open-source coding autopilot", -  "version": "0.0.158", +  "version": "0.0.190",    "publisher": "Continue",    "engines": {      "vscode": "^1.67.0" @@ -106,13 +106,18 @@          "command": "continue.quickTextEntry",          "category": "Continue",          "title": "Quick Text Entry" +      }, +      { +        "command": "continue.quickFix", +        "category": "Continue", +        "title": "Quick Fix"        }      ],      "keybindings": [        {          "command": "continue.focusContinueInput", -        "mac": "cmd+k", -        "key": "ctrl+k" +        "mac": "cmd+m", +        "key": "ctrl+m"        },        {          "command": "continue.suggestionDown", @@ -222,7 +227,7 @@      "test": "node ./out/test/runTest.js",      "jest": "jest --config ./jest.config.js",      "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", -    "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.2-py3-none-any.whl ../extension/server/continuedev-0.1.2-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package", +    "full-package": "cd ../continuedev && poetry install && poetry build && cp ./dist/continuedev-0.1.2-py3-none-any.whl ../extension/server/continuedev-0.1.2-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package",      "install-extension": "code --install-extension ./build/continue-0.0.8.vsix",      "uninstall": "code --uninstall-extension .continue",      "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index 7316581d..13e02e86 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -11,12 +11,12 @@          "@styled-icons/heroicons-outline": "^10.47.0",          "@styled-icons/heroicons-solid": "^10.47.0",          "@types/vscode-webview": "^1.57.1", +        "@uiw/react-markdown-preview": "^4.1.13",          "downshift": "^7.6.0",          "posthog-js": "^1.58.0",          "prismjs": "^1.29.0",          "react": "^18.2.0",          "react-dom": "^18.2.0", -        "react-markdown": "^8.0.5",          "react-redux": "^8.0.5",          "react-switch": "^7.0.0",          "react-syntax-highlighter": "^15.5.0", @@ -963,6 +963,16 @@        "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",        "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="      }, +    "node_modules/@types/parse5": { +      "version": "6.0.3", +      "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", +      "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" +    }, +    "node_modules/@types/prismjs": { +      "version": "1.26.0", +      "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz", +      "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==" +    },      "node_modules/@types/prop-types": {        "version": "15.7.5",        "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1027,6 +1037,34 @@        "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz",        "integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ=="      }, +    "node_modules/@uiw/copy-to-clipboard": { +      "version": "1.0.15", +      "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.15.tgz", +      "integrity": "sha512-1bbGZ3T+SGmA07BoVPK4UCUDcowDN/moctviJGQexfOc9qL8TMLDQPr7mTPvDKhgJkgnlKkAQNFU8PiarIi9sQ==" +    }, +    "node_modules/@uiw/react-markdown-preview": { +      "version": "4.1.13", +      "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-4.1.13.tgz", +      "integrity": "sha512-fmIGvBpK6HJyDFf7EokjZSIS0713Bq5KwhOsZ8IkbCMYDcDThFlmMkTTqyzGjL3phrkP9ED5O63WSILzefqe6A==", +      "dependencies": { +        "@babel/runtime": "^7.17.2", +        "@uiw/copy-to-clipboard": "~1.0.12", +        "react-markdown": "~8.0.0", +        "rehype-attr": "~2.1.0", +        "rehype-autolink-headings": "~6.1.1", +        "rehype-ignore": "^1.0.1", +        "rehype-prism-plus": "~1.5.0", +        "rehype-raw": "^6.1.1", +        "rehype-rewrite": "~3.0.6", +        "rehype-slug": "~5.1.0", +        "remark-gfm": "~3.0.1", +        "unist-util-visit": "^4.1.0" +      }, +      "peerDependencies": { +        "react": ">=16.8.0", +        "react-dom": ">=16.8.0" +      } +    },      "node_modules/@vitejs/plugin-react-swc": {        "version": "3.2.0",        "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", @@ -1163,6 +1201,15 @@          "url": "https://github.com/sponsors/wooorm"        }      }, +    "node_modules/bcp-47-match": { +      "version": "2.0.3", +      "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", +      "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/binary-extensions": {        "version": "2.2.0",        "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1172,6 +1219,11 @@          "node": ">=8"        }      }, +    "node_modules/boolbase": { +      "version": "1.0.0", +      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", +      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" +    },      "node_modules/braces": {        "version": "3.0.2",        "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -1245,6 +1297,15 @@          }        ]      }, +    "node_modules/ccount": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", +      "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/chalk": {        "version": "2.4.2",        "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1370,6 +1431,11 @@          "node": ">=4"        }      }, +    "node_modules/css-selector-parser": { +      "version": "1.4.1", +      "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz", +      "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==" +    },      "node_modules/css-to-react-native": {        "version": "3.2.0",        "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -1473,6 +1539,18 @@          "node": ">=0.3.1"        }      }, +    "node_modules/direction": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", +      "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", +      "bin": { +        "direction": "cli.js" +      }, +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/dlv": {        "version": "1.1.3",        "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -1684,6 +1762,11 @@        "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",        "dev": true      }, +    "node_modules/github-slugger": { +      "version": "2.0.0", +      "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", +      "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" +    },      "node_modules/glob-parent": {        "version": "6.0.2",        "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1729,6 +1812,86 @@          "node": ">=4"        }      }, +    "node_modules/hast-util-from-parse5": { +      "version": "7.1.2", +      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", +      "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "@types/unist": "^2.0.0", +        "hastscript": "^7.0.0", +        "property-information": "^6.0.0", +        "vfile": "^5.0.0", +        "vfile-location": "^4.0.0", +        "web-namespaces": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { +      "version": "3.1.1", +      "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", +      "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", +      "dependencies": { +        "@types/hast": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-from-parse5/node_modules/hastscript": { +      "version": "7.2.0", +      "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", +      "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "comma-separated-tokens": "^2.0.0", +        "hast-util-parse-selector": "^3.0.0", +        "property-information": "^6.0.0", +        "space-separated-tokens": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-has-property": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz", +      "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==", +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-heading-rank": { +      "version": "2.1.1", +      "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz", +      "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==", +      "dependencies": { +        "@types/hast": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-is-element": { +      "version": "2.1.3", +      "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", +      "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "@types/unist": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "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", @@ -1738,6 +1901,83 @@          "url": "https://opencollective.com/unified"        }      }, +    "node_modules/hast-util-raw": { +      "version": "7.2.3", +      "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", +      "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "@types/parse5": "^6.0.0", +        "hast-util-from-parse5": "^7.0.0", +        "hast-util-to-parse5": "^7.0.0", +        "html-void-elements": "^2.0.0", +        "parse5": "^6.0.0", +        "unist-util-position": "^4.0.0", +        "unist-util-visit": "^4.0.0", +        "vfile": "^5.0.0", +        "web-namespaces": "^2.0.0", +        "zwitch": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-select": { +      "version": "5.0.5", +      "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-5.0.5.tgz", +      "integrity": "sha512-QQhWMhgTFRhCaQdgTKzZ5g31GLQ9qRb1hZtDPMqQaOhpLBziWcshUS0uCR5IJ0U1jrK/mxg35fmcq+Dp/Cy2Aw==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "@types/unist": "^2.0.0", +        "bcp-47-match": "^2.0.0", +        "comma-separated-tokens": "^2.0.0", +        "css-selector-parser": "^1.0.0", +        "direction": "^2.0.0", +        "hast-util-has-property": "^2.0.0", +        "hast-util-to-string": "^2.0.0", +        "hast-util-whitespace": "^2.0.0", +        "not": "^0.1.0", +        "nth-check": "^2.0.0", +        "property-information": "^6.0.0", +        "space-separated-tokens": "^2.0.0", +        "unist-util-visit": "^4.0.0", +        "zwitch": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-to-parse5": { +      "version": "7.1.0", +      "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", +      "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "comma-separated-tokens": "^2.0.0", +        "property-information": "^6.0.0", +        "space-separated-tokens": "^2.0.0", +        "web-namespaces": "^2.0.0", +        "zwitch": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/hast-util-to-string": { +      "version": "2.0.0", +      "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", +      "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", +      "dependencies": { +        "@types/hast": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "node_modules/hast-util-whitespace": {        "version": "2.0.1",        "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", @@ -1814,6 +2054,15 @@        "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",        "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="      }, +    "node_modules/html-void-elements": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", +      "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/inline-style-parser": {        "version": "0.1.1",        "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", @@ -1995,6 +2244,15 @@        "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",        "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="      }, +    "node_modules/longest-streak": { +      "version": "3.1.0", +      "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", +      "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/loose-envify": {        "version": "1.4.0",        "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2019,6 +2277,15 @@          "url": "https://github.com/sponsors/wooorm"        }      }, +    "node_modules/markdown-table": { +      "version": "3.0.3", +      "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", +      "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/mdast-util-definitions": {        "version": "5.1.2",        "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", @@ -2033,6 +2300,32 @@          "url": "https://opencollective.com/unified"        }      }, +    "node_modules/mdast-util-find-and-replace": { +      "version": "2.2.2", +      "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", +      "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "escape-string-regexp": "^5.0.0", +        "unist-util-is": "^5.0.0", +        "unist-util-visit-parents": "^5.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { +      "version": "5.0.0", +      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", +      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", +      "engines": { +        "node": ">=12" +      }, +      "funding": { +        "url": "https://github.com/sponsors/sindresorhus" +      } +    },      "node_modules/mdast-util-from-markdown": {        "version": "1.3.0",        "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz", @@ -2056,6 +2349,107 @@          "url": "https://opencollective.com/unified"        }      }, +    "node_modules/mdast-util-gfm": { +      "version": "2.0.2", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", +      "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", +      "dependencies": { +        "mdast-util-from-markdown": "^1.0.0", +        "mdast-util-gfm-autolink-literal": "^1.0.0", +        "mdast-util-gfm-footnote": "^1.0.0", +        "mdast-util-gfm-strikethrough": "^1.0.0", +        "mdast-util-gfm-table": "^1.0.0", +        "mdast-util-gfm-task-list-item": "^1.0.0", +        "mdast-util-to-markdown": "^1.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-gfm-autolink-literal": { +      "version": "1.0.3", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", +      "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "ccount": "^2.0.0", +        "mdast-util-find-and-replace": "^2.0.0", +        "micromark-util-character": "^1.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-gfm-footnote": { +      "version": "1.0.2", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", +      "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "mdast-util-to-markdown": "^1.3.0", +        "micromark-util-normalize-identifier": "^1.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-gfm-strikethrough": { +      "version": "1.0.3", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", +      "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "mdast-util-to-markdown": "^1.3.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-gfm-table": { +      "version": "1.0.7", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", +      "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "markdown-table": "^3.0.0", +        "mdast-util-from-markdown": "^1.0.0", +        "mdast-util-to-markdown": "^1.3.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-gfm-task-list-item": { +      "version": "1.0.2", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", +      "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "mdast-util-to-markdown": "^1.3.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/mdast-util-phrasing": { +      "version": "3.0.1", +      "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", +      "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "unist-util-is": "^5.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "node_modules/mdast-util-to-hast": {        "version": "12.3.0",        "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", @@ -2075,6 +2469,25 @@          "url": "https://opencollective.com/unified"        }      }, +    "node_modules/mdast-util-to-markdown": { +      "version": "1.5.0", +      "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", +      "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "@types/unist": "^2.0.0", +        "longest-streak": "^3.0.0", +        "mdast-util-phrasing": "^3.0.0", +        "mdast-util-to-string": "^3.0.0", +        "micromark-util-decode-string": "^1.0.0", +        "unist-util-visit": "^4.0.0", +        "zwitch": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "node_modules/mdast-util-to-string": {        "version": "3.1.1",        "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz", @@ -2163,6 +2576,120 @@          "uvu": "^0.5.0"        }      }, +    "node_modules/micromark-extension-gfm": { +      "version": "2.0.3", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", +      "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", +      "dependencies": { +        "micromark-extension-gfm-autolink-literal": "^1.0.0", +        "micromark-extension-gfm-footnote": "^1.0.0", +        "micromark-extension-gfm-strikethrough": "^1.0.0", +        "micromark-extension-gfm-table": "^1.0.0", +        "micromark-extension-gfm-tagfilter": "^1.0.0", +        "micromark-extension-gfm-task-list-item": "^1.0.0", +        "micromark-util-combine-extensions": "^1.0.0", +        "micromark-util-types": "^1.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/micromark-extension-gfm-autolink-literal": { +      "version": "1.0.5", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", +      "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", +      "dependencies": { +        "micromark-util-character": "^1.0.0", +        "micromark-util-sanitize-uri": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/micromark-extension-gfm-footnote": { +      "version": "1.1.2", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", +      "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", +      "dependencies": { +        "micromark-core-commonmark": "^1.0.0", +        "micromark-factory-space": "^1.0.0", +        "micromark-util-character": "^1.0.0", +        "micromark-util-normalize-identifier": "^1.0.0", +        "micromark-util-sanitize-uri": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/micromark-extension-gfm-strikethrough": { +      "version": "1.0.7", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", +      "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", +      "dependencies": { +        "micromark-util-chunked": "^1.0.0", +        "micromark-util-classify-character": "^1.0.0", +        "micromark-util-resolve-all": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/micromark-extension-gfm-table": { +      "version": "1.0.7", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", +      "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", +      "dependencies": { +        "micromark-factory-space": "^1.0.0", +        "micromark-util-character": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/micromark-extension-gfm-tagfilter": { +      "version": "1.0.2", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", +      "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", +      "dependencies": { +        "micromark-util-types": "^1.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/micromark-extension-gfm-task-list-item": { +      "version": "1.0.5", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", +      "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", +      "dependencies": { +        "micromark-factory-space": "^1.0.0", +        "micromark-util-character": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "node_modules/micromark-factory-destination": {        "version": "1.0.0",        "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", @@ -2589,6 +3116,22 @@          "node": ">=0.10.0"        }      }, +    "node_modules/not": { +      "version": "0.1.0", +      "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz", +      "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==" +    }, +    "node_modules/nth-check": { +      "version": "2.1.1", +      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", +      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", +      "dependencies": { +        "boolbase": "^1.0.0" +      }, +      "funding": { +        "url": "https://github.com/fb55/nth-check?sponsor=1" +      } +    },      "node_modules/object-assign": {        "version": "4.1.1",        "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2632,6 +3175,16 @@          "url": "https://github.com/sponsors/wooorm"        }      }, +    "node_modules/parse-numeric-range": { +      "version": "1.3.0", +      "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", +      "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" +    }, +    "node_modules/parse5": { +      "version": "6.0.1", +      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", +      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" +    },      "node_modules/path-parse": {        "version": "1.0.7",        "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -3047,6 +3600,257 @@        "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",        "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="      }, +    "node_modules/rehype-attr": { +      "version": "2.1.4", +      "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-2.1.4.tgz", +      "integrity": "sha512-iAeaL5JyF4XxkcvWzpi/0SAF7iV7qOTaHS56tJuEsXziQc3+PEmMn65kV8OFgbO9mRVY7J1fRC/aLvot1PsNkg==", +      "dependencies": { +        "unified": "~10.1.1", +        "unist-util-visit": "~4.1.0" +      }, +      "engines": { +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0" +      } +    }, +    "node_modules/rehype-autolink-headings": { +      "version": "6.1.1", +      "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz", +      "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "extend": "^3.0.0", +        "hast-util-has-property": "^2.0.0", +        "hast-util-heading-rank": "^2.0.0", +        "hast-util-is-element": "^2.0.0", +        "unified": "^10.0.0", +        "unist-util-visit": "^4.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/rehype-ignore": { +      "version": "1.0.5", +      "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-1.0.5.tgz", +      "integrity": "sha512-JQXS5eDwXaYKwB8JEYFJJA/YvGi0sSNUOYuiURMtuPTg8tuWHFB91JMYLbImH1FyvyGQM4fIBqNMAPB50WR2Bw==", +      "dependencies": { +        "hast-util-select": "^5.0.5", +        "unified": "^10.1.2", +        "unist-util-visit": "^4.1.2" +      }, +      "engines": { +        "node": "^14.13.1 || >=16.0.0" +      } +    }, +    "node_modules/rehype-parse": { +      "version": "8.0.4", +      "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", +      "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "hast-util-from-parse5": "^7.0.0", +        "parse5": "^6.0.0", +        "unified": "^10.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/rehype-prism-plus": { +      "version": "1.5.1", +      "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz", +      "integrity": "sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==", +      "dependencies": { +        "hast-util-to-string": "^2.0.0", +        "parse-numeric-range": "^1.3.0", +        "refractor": "^4.7.0", +        "rehype-parse": "^8.0.2", +        "unist-util-filter": "^4.0.0", +        "unist-util-visit": "^4.0.0" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/character-entities-legacy": { +      "version": "3.0.0", +      "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", +      "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/character-reference-invalid": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", +      "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/hast-util-parse-selector": { +      "version": "3.1.1", +      "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", +      "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", +      "dependencies": { +        "@types/hast": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/hastscript": { +      "version": "7.2.0", +      "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", +      "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "comma-separated-tokens": "^2.0.0", +        "hast-util-parse-selector": "^3.0.0", +        "property-information": "^6.0.0", +        "space-separated-tokens": "^2.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/is-alphabetical": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", +      "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/is-alphanumerical": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", +      "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", +      "dependencies": { +        "is-alphabetical": "^2.0.0", +        "is-decimal": "^2.0.0" +      }, +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/is-decimal": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", +      "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/is-hexadecimal": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", +      "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/parse-entities": { +      "version": "4.0.1", +      "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", +      "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", +      "dependencies": { +        "@types/unist": "^2.0.0", +        "character-entities": "^2.0.0", +        "character-entities-legacy": "^3.0.0", +        "character-reference-invalid": "^2.0.0", +        "decode-named-character-reference": "^1.0.0", +        "is-alphanumerical": "^2.0.0", +        "is-decimal": "^2.0.0", +        "is-hexadecimal": "^2.0.0" +      }, +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-prism-plus/node_modules/refractor": { +      "version": "4.8.1", +      "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", +      "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "@types/prismjs": "^1.0.0", +        "hastscript": "^7.0.0", +        "parse-entities": "^4.0.0" +      }, +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    }, +    "node_modules/rehype-raw": { +      "version": "6.1.1", +      "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", +      "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "hast-util-raw": "^7.2.0", +        "unified": "^10.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/rehype-rewrite": { +      "version": "3.0.6", +      "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-3.0.6.tgz", +      "integrity": "sha512-REDTNCvsKcAazy8IQWzKp66AhSUDSOIKssSCqNqCcT9sN7JCwAAm3mWGTUdUzq80ABuy8d0D6RBwbnewu1aY1g==", +      "dependencies": { +        "hast-util-select": "~5.0.1", +        "unified": "~10.1.1", +        "unist-util-visit": "~4.1.0" +      }, +      "engines": { +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0" +      } +    }, +    "node_modules/rehype-slug": { +      "version": "5.1.0", +      "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz", +      "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==", +      "dependencies": { +        "@types/hast": "^2.0.0", +        "github-slugger": "^2.0.0", +        "hast-util-has-property": "^2.0.0", +        "hast-util-heading-rank": "^2.0.0", +        "hast-util-to-string": "^2.0.0", +        "unified": "^10.0.0", +        "unist-util-visit": "^4.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    }, +    "node_modules/remark-gfm": { +      "version": "3.0.1", +      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", +      "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", +      "dependencies": { +        "@types/mdast": "^3.0.0", +        "mdast-util-gfm": "^2.0.0", +        "micromark-extension-gfm": "^2.0.0", +        "unified": "^10.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "node_modules/remark-parse": {        "version": "10.0.1",        "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", @@ -3365,6 +4169,16 @@          "url": "https://opencollective.com/unified"        }      }, +    "node_modules/unist-util-filter": { +      "version": "4.0.1", +      "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-4.0.1.tgz", +      "integrity": "sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==", +      "dependencies": { +        "@types/unist": "^2.0.0", +        "unist-util-is": "^5.0.0", +        "unist-util-visit-parents": "^5.0.0" +      } +    },      "node_modules/unist-util-generated": {        "version": "2.0.1",        "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", @@ -3517,6 +4331,19 @@          "url": "https://opencollective.com/unified"        }      }, +    "node_modules/vfile-location": { +      "version": "4.1.0", +      "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", +      "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", +      "dependencies": { +        "@types/unist": "^2.0.0", +        "vfile": "^5.0.0" +      }, +      "funding": { +        "type": "opencollective", +        "url": "https://opencollective.com/unified" +      } +    },      "node_modules/vfile-message": {        "version": "3.1.4",        "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", @@ -3587,6 +4414,15 @@          "fs-extra": "^10.0.0"        }      }, +    "node_modules/web-namespaces": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", +      "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      } +    },      "node_modules/xtend": {        "version": "4.0.2",        "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -3603,6 +4439,15 @@        "engines": {          "node": ">= 6"        } +    }, +    "node_modules/zwitch": { +      "version": "2.0.4", +      "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", +      "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", +      "funding": { +        "type": "github", +        "url": "https://github.com/sponsors/wooorm" +      }      }    },    "dependencies": { @@ -4143,6 +4988,16 @@        "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",        "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="      }, +    "@types/parse5": { +      "version": "6.0.3", +      "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", +      "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" +    }, +    "@types/prismjs": { +      "version": "1.26.0", +      "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz", +      "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==" +    },      "@types/prop-types": {        "version": "15.7.5",        "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -4207,6 +5062,30 @@        "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz",        "integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ=="      }, +    "@uiw/copy-to-clipboard": { +      "version": "1.0.15", +      "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.15.tgz", +      "integrity": "sha512-1bbGZ3T+SGmA07BoVPK4UCUDcowDN/moctviJGQexfOc9qL8TMLDQPr7mTPvDKhgJkgnlKkAQNFU8PiarIi9sQ==" +    }, +    "@uiw/react-markdown-preview": { +      "version": "4.1.13", +      "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-4.1.13.tgz", +      "integrity": "sha512-fmIGvBpK6HJyDFf7EokjZSIS0713Bq5KwhOsZ8IkbCMYDcDThFlmMkTTqyzGjL3phrkP9ED5O63WSILzefqe6A==", +      "requires": { +        "@babel/runtime": "^7.17.2", +        "@uiw/copy-to-clipboard": "~1.0.12", +        "react-markdown": "~8.0.0", +        "rehype-attr": "~2.1.0", +        "rehype-autolink-headings": "~6.1.1", +        "rehype-ignore": "^1.0.1", +        "rehype-prism-plus": "~1.5.0", +        "rehype-raw": "^6.1.1", +        "rehype-rewrite": "~3.0.6", +        "rehype-slug": "~5.1.0", +        "remark-gfm": "~3.0.1", +        "unist-util-visit": "^4.1.0" +      } +    },      "@vitejs/plugin-react-swc": {        "version": "3.2.0",        "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", @@ -4299,12 +5178,22 @@        "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",        "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="      }, +    "bcp-47-match": { +      "version": "2.0.3", +      "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", +      "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==" +    },      "binary-extensions": {        "version": "2.2.0",        "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",        "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",        "dev": true      }, +    "boolbase": { +      "version": "1.0.0", +      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", +      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" +    },      "braces": {        "version": "3.0.2",        "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -4343,6 +5232,11 @@        "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==",        "dev": true      }, +    "ccount": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", +      "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" +    },      "chalk": {        "version": "2.4.2",        "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4436,6 +5330,11 @@        "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",        "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="      }, +    "css-selector-parser": { +      "version": "1.4.1", +      "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz", +      "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==" +    },      "css-to-react-native": {        "version": "3.2.0",        "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -4506,6 +5405,11 @@        "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",        "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="      }, +    "direction": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", +      "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==" +    },      "dlv": {        "version": "1.1.3",        "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -4672,6 +5576,11 @@        "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",        "dev": true      }, +    "github-slugger": { +      "version": "2.0.0", +      "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", +      "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" +    },      "glob-parent": {        "version": "6.0.2",        "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4705,11 +5614,130 @@        "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",        "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="      }, +    "hast-util-from-parse5": { +      "version": "7.1.2", +      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", +      "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", +      "requires": { +        "@types/hast": "^2.0.0", +        "@types/unist": "^2.0.0", +        "hastscript": "^7.0.0", +        "property-information": "^6.0.0", +        "vfile": "^5.0.0", +        "vfile-location": "^4.0.0", +        "web-namespaces": "^2.0.0" +      }, +      "dependencies": { +        "hast-util-parse-selector": { +          "version": "3.1.1", +          "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", +          "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", +          "requires": { +            "@types/hast": "^2.0.0" +          } +        }, +        "hastscript": { +          "version": "7.2.0", +          "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", +          "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", +          "requires": { +            "@types/hast": "^2.0.0", +            "comma-separated-tokens": "^2.0.0", +            "hast-util-parse-selector": "^3.0.0", +            "property-information": "^6.0.0", +            "space-separated-tokens": "^2.0.0" +          } +        } +      } +    }, +    "hast-util-has-property": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz", +      "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==" +    }, +    "hast-util-heading-rank": { +      "version": "2.1.1", +      "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz", +      "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==", +      "requires": { +        "@types/hast": "^2.0.0" +      } +    }, +    "hast-util-is-element": { +      "version": "2.1.3", +      "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", +      "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==", +      "requires": { +        "@types/hast": "^2.0.0", +        "@types/unist": "^2.0.0" +      } +    },      "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=="      }, +    "hast-util-raw": { +      "version": "7.2.3", +      "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", +      "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", +      "requires": { +        "@types/hast": "^2.0.0", +        "@types/parse5": "^6.0.0", +        "hast-util-from-parse5": "^7.0.0", +        "hast-util-to-parse5": "^7.0.0", +        "html-void-elements": "^2.0.0", +        "parse5": "^6.0.0", +        "unist-util-position": "^4.0.0", +        "unist-util-visit": "^4.0.0", +        "vfile": "^5.0.0", +        "web-namespaces": "^2.0.0", +        "zwitch": "^2.0.0" +      } +    }, +    "hast-util-select": { +      "version": "5.0.5", +      "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-5.0.5.tgz", +      "integrity": "sha512-QQhWMhgTFRhCaQdgTKzZ5g31GLQ9qRb1hZtDPMqQaOhpLBziWcshUS0uCR5IJ0U1jrK/mxg35fmcq+Dp/Cy2Aw==", +      "requires": { +        "@types/hast": "^2.0.0", +        "@types/unist": "^2.0.0", +        "bcp-47-match": "^2.0.0", +        "comma-separated-tokens": "^2.0.0", +        "css-selector-parser": "^1.0.0", +        "direction": "^2.0.0", +        "hast-util-has-property": "^2.0.0", +        "hast-util-to-string": "^2.0.0", +        "hast-util-whitespace": "^2.0.0", +        "not": "^0.1.0", +        "nth-check": "^2.0.0", +        "property-information": "^6.0.0", +        "space-separated-tokens": "^2.0.0", +        "unist-util-visit": "^4.0.0", +        "zwitch": "^2.0.0" +      } +    }, +    "hast-util-to-parse5": { +      "version": "7.1.0", +      "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", +      "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", +      "requires": { +        "@types/hast": "^2.0.0", +        "comma-separated-tokens": "^2.0.0", +        "property-information": "^6.0.0", +        "space-separated-tokens": "^2.0.0", +        "web-namespaces": "^2.0.0", +        "zwitch": "^2.0.0" +      } +    }, +    "hast-util-to-string": { +      "version": "2.0.0", +      "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", +      "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", +      "requires": { +        "@types/hast": "^2.0.0" +      } +    },      "hast-util-whitespace": {        "version": "2.0.1",        "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", @@ -4767,6 +5795,11 @@          }        }      }, +    "html-void-elements": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", +      "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==" +    },      "inline-style-parser": {        "version": "0.1.1",        "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", @@ -4880,6 +5913,11 @@        "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",        "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="      }, +    "longest-streak": { +      "version": "3.1.0", +      "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", +      "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" +    },      "loose-envify": {        "version": "1.4.0",        "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4897,6 +5935,11 @@          "highlight.js": "~10.7.0"        }      }, +    "markdown-table": { +      "version": "3.0.3", +      "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", +      "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==" +    },      "mdast-util-definitions": {        "version": "5.1.2",        "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", @@ -4907,6 +5950,24 @@          "unist-util-visit": "^4.0.0"        }      }, +    "mdast-util-find-and-replace": { +      "version": "2.2.2", +      "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", +      "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "escape-string-regexp": "^5.0.0", +        "unist-util-is": "^5.0.0", +        "unist-util-visit-parents": "^5.0.0" +      }, +      "dependencies": { +        "escape-string-regexp": { +          "version": "5.0.0", +          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", +          "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" +        } +      } +    },      "mdast-util-from-markdown": {        "version": "1.3.0",        "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz", @@ -4926,6 +5987,79 @@          "uvu": "^0.5.0"        }      }, +    "mdast-util-gfm": { +      "version": "2.0.2", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", +      "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", +      "requires": { +        "mdast-util-from-markdown": "^1.0.0", +        "mdast-util-gfm-autolink-literal": "^1.0.0", +        "mdast-util-gfm-footnote": "^1.0.0", +        "mdast-util-gfm-strikethrough": "^1.0.0", +        "mdast-util-gfm-table": "^1.0.0", +        "mdast-util-gfm-task-list-item": "^1.0.0", +        "mdast-util-to-markdown": "^1.0.0" +      } +    }, +    "mdast-util-gfm-autolink-literal": { +      "version": "1.0.3", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", +      "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "ccount": "^2.0.0", +        "mdast-util-find-and-replace": "^2.0.0", +        "micromark-util-character": "^1.0.0" +      } +    }, +    "mdast-util-gfm-footnote": { +      "version": "1.0.2", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", +      "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "mdast-util-to-markdown": "^1.3.0", +        "micromark-util-normalize-identifier": "^1.0.0" +      } +    }, +    "mdast-util-gfm-strikethrough": { +      "version": "1.0.3", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", +      "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "mdast-util-to-markdown": "^1.3.0" +      } +    }, +    "mdast-util-gfm-table": { +      "version": "1.0.7", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", +      "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "markdown-table": "^3.0.0", +        "mdast-util-from-markdown": "^1.0.0", +        "mdast-util-to-markdown": "^1.3.0" +      } +    }, +    "mdast-util-gfm-task-list-item": { +      "version": "1.0.2", +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", +      "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "mdast-util-to-markdown": "^1.3.0" +      } +    }, +    "mdast-util-phrasing": { +      "version": "3.0.1", +      "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", +      "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "unist-util-is": "^5.0.0" +      } +    },      "mdast-util-to-hast": {        "version": "12.3.0",        "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", @@ -4941,6 +6075,21 @@          "unist-util-visit": "^4.0.0"        }      }, +    "mdast-util-to-markdown": { +      "version": "1.5.0", +      "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", +      "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "@types/unist": "^2.0.0", +        "longest-streak": "^3.0.0", +        "mdast-util-phrasing": "^3.0.0", +        "mdast-util-to-string": "^3.0.0", +        "micromark-util-decode-string": "^1.0.0", +        "unist-util-visit": "^4.0.0", +        "zwitch": "^2.0.0" +      } +    },      "mdast-util-to-string": {        "version": "3.1.1",        "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz", @@ -5002,6 +6151,92 @@          "uvu": "^0.5.0"        }      }, +    "micromark-extension-gfm": { +      "version": "2.0.3", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", +      "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", +      "requires": { +        "micromark-extension-gfm-autolink-literal": "^1.0.0", +        "micromark-extension-gfm-footnote": "^1.0.0", +        "micromark-extension-gfm-strikethrough": "^1.0.0", +        "micromark-extension-gfm-table": "^1.0.0", +        "micromark-extension-gfm-tagfilter": "^1.0.0", +        "micromark-extension-gfm-task-list-item": "^1.0.0", +        "micromark-util-combine-extensions": "^1.0.0", +        "micromark-util-types": "^1.0.0" +      } +    }, +    "micromark-extension-gfm-autolink-literal": { +      "version": "1.0.5", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", +      "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", +      "requires": { +        "micromark-util-character": "^1.0.0", +        "micromark-util-sanitize-uri": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0" +      } +    }, +    "micromark-extension-gfm-footnote": { +      "version": "1.1.2", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", +      "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", +      "requires": { +        "micromark-core-commonmark": "^1.0.0", +        "micromark-factory-space": "^1.0.0", +        "micromark-util-character": "^1.0.0", +        "micromark-util-normalize-identifier": "^1.0.0", +        "micromark-util-sanitize-uri": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      } +    }, +    "micromark-extension-gfm-strikethrough": { +      "version": "1.0.7", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", +      "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", +      "requires": { +        "micromark-util-chunked": "^1.0.0", +        "micromark-util-classify-character": "^1.0.0", +        "micromark-util-resolve-all": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      } +    }, +    "micromark-extension-gfm-table": { +      "version": "1.0.7", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", +      "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", +      "requires": { +        "micromark-factory-space": "^1.0.0", +        "micromark-util-character": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      } +    }, +    "micromark-extension-gfm-tagfilter": { +      "version": "1.0.2", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", +      "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", +      "requires": { +        "micromark-util-types": "^1.0.0" +      } +    }, +    "micromark-extension-gfm-task-list-item": { +      "version": "1.0.5", +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", +      "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", +      "requires": { +        "micromark-factory-space": "^1.0.0", +        "micromark-util-character": "^1.0.0", +        "micromark-util-symbol": "^1.0.0", +        "micromark-util-types": "^1.0.0", +        "uvu": "^0.5.0" +      } +    },      "micromark-factory-destination": {        "version": "1.0.0",        "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", @@ -5217,6 +6452,19 @@        "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",        "dev": true      }, +    "not": { +      "version": "0.1.0", +      "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz", +      "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==" +    }, +    "nth-check": { +      "version": "2.1.1", +      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", +      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", +      "requires": { +        "boolbase": "^1.0.0" +      } +    },      "object-assign": {        "version": "4.1.1",        "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5248,6 +6496,16 @@          }        }      }, +    "parse-numeric-range": { +      "version": "1.3.0", +      "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", +      "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" +    }, +    "parse5": { +      "version": "6.0.1", +      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", +      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" +    },      "path-parse": {        "version": "1.0.7",        "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -5510,6 +6768,190 @@        "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",        "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="      }, +    "rehype-attr": { +      "version": "2.1.4", +      "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-2.1.4.tgz", +      "integrity": "sha512-iAeaL5JyF4XxkcvWzpi/0SAF7iV7qOTaHS56tJuEsXziQc3+PEmMn65kV8OFgbO9mRVY7J1fRC/aLvot1PsNkg==", +      "requires": { +        "unified": "~10.1.1", +        "unist-util-visit": "~4.1.0" +      } +    }, +    "rehype-autolink-headings": { +      "version": "6.1.1", +      "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz", +      "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==", +      "requires": { +        "@types/hast": "^2.0.0", +        "extend": "^3.0.0", +        "hast-util-has-property": "^2.0.0", +        "hast-util-heading-rank": "^2.0.0", +        "hast-util-is-element": "^2.0.0", +        "unified": "^10.0.0", +        "unist-util-visit": "^4.0.0" +      } +    }, +    "rehype-ignore": { +      "version": "1.0.5", +      "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-1.0.5.tgz", +      "integrity": "sha512-JQXS5eDwXaYKwB8JEYFJJA/YvGi0sSNUOYuiURMtuPTg8tuWHFB91JMYLbImH1FyvyGQM4fIBqNMAPB50WR2Bw==", +      "requires": { +        "hast-util-select": "^5.0.5", +        "unified": "^10.1.2", +        "unist-util-visit": "^4.1.2" +      } +    }, +    "rehype-parse": { +      "version": "8.0.4", +      "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", +      "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", +      "requires": { +        "@types/hast": "^2.0.0", +        "hast-util-from-parse5": "^7.0.0", +        "parse5": "^6.0.0", +        "unified": "^10.0.0" +      } +    }, +    "rehype-prism-plus": { +      "version": "1.5.1", +      "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz", +      "integrity": "sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==", +      "requires": { +        "hast-util-to-string": "^2.0.0", +        "parse-numeric-range": "^1.3.0", +        "refractor": "^4.7.0", +        "rehype-parse": "^8.0.2", +        "unist-util-filter": "^4.0.0", +        "unist-util-visit": "^4.0.0" +      }, +      "dependencies": { +        "character-entities-legacy": { +          "version": "3.0.0", +          "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", +          "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" +        }, +        "character-reference-invalid": { +          "version": "2.0.1", +          "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", +          "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" +        }, +        "hast-util-parse-selector": { +          "version": "3.1.1", +          "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", +          "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", +          "requires": { +            "@types/hast": "^2.0.0" +          } +        }, +        "hastscript": { +          "version": "7.2.0", +          "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", +          "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", +          "requires": { +            "@types/hast": "^2.0.0", +            "comma-separated-tokens": "^2.0.0", +            "hast-util-parse-selector": "^3.0.0", +            "property-information": "^6.0.0", +            "space-separated-tokens": "^2.0.0" +          } +        }, +        "is-alphabetical": { +          "version": "2.0.1", +          "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", +          "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==" +        }, +        "is-alphanumerical": { +          "version": "2.0.1", +          "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", +          "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", +          "requires": { +            "is-alphabetical": "^2.0.0", +            "is-decimal": "^2.0.0" +          } +        }, +        "is-decimal": { +          "version": "2.0.1", +          "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", +          "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" +        }, +        "is-hexadecimal": { +          "version": "2.0.1", +          "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", +          "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" +        }, +        "parse-entities": { +          "version": "4.0.1", +          "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", +          "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", +          "requires": { +            "@types/unist": "^2.0.0", +            "character-entities": "^2.0.0", +            "character-entities-legacy": "^3.0.0", +            "character-reference-invalid": "^2.0.0", +            "decode-named-character-reference": "^1.0.0", +            "is-alphanumerical": "^2.0.0", +            "is-decimal": "^2.0.0", +            "is-hexadecimal": "^2.0.0" +          } +        }, +        "refractor": { +          "version": "4.8.1", +          "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", +          "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", +          "requires": { +            "@types/hast": "^2.0.0", +            "@types/prismjs": "^1.0.0", +            "hastscript": "^7.0.0", +            "parse-entities": "^4.0.0" +          } +        } +      } +    }, +    "rehype-raw": { +      "version": "6.1.1", +      "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", +      "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", +      "requires": { +        "@types/hast": "^2.0.0", +        "hast-util-raw": "^7.2.0", +        "unified": "^10.0.0" +      } +    }, +    "rehype-rewrite": { +      "version": "3.0.6", +      "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-3.0.6.tgz", +      "integrity": "sha512-REDTNCvsKcAazy8IQWzKp66AhSUDSOIKssSCqNqCcT9sN7JCwAAm3mWGTUdUzq80ABuy8d0D6RBwbnewu1aY1g==", +      "requires": { +        "hast-util-select": "~5.0.1", +        "unified": "~10.1.1", +        "unist-util-visit": "~4.1.0" +      } +    }, +    "rehype-slug": { +      "version": "5.1.0", +      "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz", +      "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==", +      "requires": { +        "@types/hast": "^2.0.0", +        "github-slugger": "^2.0.0", +        "hast-util-has-property": "^2.0.0", +        "hast-util-heading-rank": "^2.0.0", +        "hast-util-to-string": "^2.0.0", +        "unified": "^10.0.0", +        "unist-util-visit": "^4.0.0" +      } +    }, +    "remark-gfm": { +      "version": "3.0.1", +      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", +      "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", +      "requires": { +        "@types/mdast": "^3.0.0", +        "mdast-util-gfm": "^2.0.0", +        "micromark-extension-gfm": "^2.0.0", +        "unified": "^10.0.0" +      } +    },      "remark-parse": {        "version": "10.0.1",        "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", @@ -5722,6 +7164,16 @@          "vfile": "^5.0.0"        }      }, +    "unist-util-filter": { +      "version": "4.0.1", +      "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-4.0.1.tgz", +      "integrity": "sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==", +      "requires": { +        "@types/unist": "^2.0.0", +        "unist-util-is": "^5.0.0", +        "unist-util-visit-parents": "^5.0.0" +      } +    },      "unist-util-generated": {        "version": "2.0.1",        "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", @@ -5819,6 +7271,15 @@          "vfile-message": "^3.0.0"        }      }, +    "vfile-location": { +      "version": "4.1.0", +      "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", +      "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", +      "requires": { +        "@types/unist": "^2.0.0", +        "vfile": "^5.0.0" +      } +    },      "vfile-message": {        "version": "3.1.4",        "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", @@ -5849,6 +7310,11 @@          "fs-extra": "^10.0.0"        }      }, +    "web-namespaces": { +      "version": "2.0.1", +      "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", +      "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" +    },      "xtend": {        "version": "4.0.2",        "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -5859,6 +7325,11 @@        "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",        "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",        "dev": true +    }, +    "zwitch": { +      "version": "2.0.4", +      "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", +      "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="      }    }  } diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 4bedb813..704f520a 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -12,12 +12,12 @@      "@styled-icons/heroicons-outline": "^10.47.0",      "@styled-icons/heroicons-solid": "^10.47.0",      "@types/vscode-webview": "^1.57.1", +    "@uiw/react-markdown-preview": "^4.1.13",      "downshift": "^7.6.0",      "posthog-js": "^1.58.0",      "prismjs": "^1.29.0",      "react": "^18.2.0",      "react-dom": "^18.2.0", -    "react-markdown": "^8.0.5",      "react-redux": "^8.0.5",      "react-switch": "^7.0.0",      "react-syntax-highlighter": "^15.5.0", diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx index c9bd42e0..aa462171 100644 --- a/extension/react-app/src/App.tsx +++ b/extension/react-app/src/App.tsx @@ -2,7 +2,7 @@ import DebugPanel from "./components/DebugPanel";  import GUI from "./pages/gui";  import { createContext } from "react";  import useContinueGUIProtocol from "./hooks/useWebsocket"; -import ContinueGUIClientProtocol from "./hooks/useContinueGUIProtocol"; +import ContinueGUIClientProtocol from "./hooks/ContinueGUIClientProtocol";  export const GUIClientContext = createContext<    ContinueGUIClientProtocol | undefined @@ -13,11 +13,7 @@ function App() {    return (      <GUIClientContext.Provider value={client}> -      <DebugPanel -        tabs={[ -          { element: <GUI />, title: "GUI" } -        ]} -      /> +      <DebugPanel tabs={[{ element: <GUI />, title: "GUI" }]} />      </GUIClientContext.Provider>    );  } diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 7d6541c7..1e2ca135 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -1,30 +1,20 @@ -import React, { -  useCallback, -  useEffect, -  useImperativeHandle, -  useState, -} from "react"; +import React, { useEffect, useImperativeHandle, useState } from "react";  import { useCombobox } from "downshift";  import styled from "styled-components";  import { -  buttonColor,    defaultBorderRadius,    lightGray,    secondaryDark,    vscBackground, +  vscForeground,  } from ".";  import CodeBlock from "./CodeBlock"; -import { RangeInFile } from "../../../src/client";  import PillButton from "./PillButton";  import HeaderButtonWithText from "./HeaderButtonWithText"; -import { -  Trash, -  LockClosed, -  LockOpen, -  Plus, -  DocumentPlus, -} from "@styled-icons/heroicons-outline"; +import { DocumentPlus } from "@styled-icons/heroicons-outline";  import { HighlightedRangeContext } from "../../../schema/FullState"; +import { postVscMessage } from "../vscode"; +import { getMetaKeyLabel } from "../util";  // #region styled components  const mainInputFontSize = 13; @@ -48,21 +38,6 @@ const EmptyPillDiv = styled.div`    }  `; -const ContextDropdown = styled.div` -  position: absolute; -  padding: 4px; -  width: calc(100% - 16px - 8px); -  background-color: ${secondaryDark}; -  color: white; -  border-bottom-right-radius: ${defaultBorderRadius}; -  border-bottom-left-radius: ${defaultBorderRadius}; -  /* border: 1px solid white; */ -  border-top: none; -  margin: 8px; -  outline: 1px solid orange; -  z-index: 5; -`; -  const MainTextInput = styled.textarea`    resize: none; @@ -74,7 +49,7 @@ const MainTextInput = styled.textarea`    height: auto;    width: 100%;    background-color: ${secondaryDark}; -  color: white; +  color: ${vscForeground};    z-index: 1;    border: 1px solid transparent; @@ -96,15 +71,15 @@ const Ul = styled.ul<{        : `transform: translateY(${2 * mainInputFontSize}px);`}    position: absolute;    background: ${vscBackground}; -  background-color: ${secondaryDark}; -  color: white; +  color: ${vscForeground};    max-height: ${UlMaxHeight}px; +  width: calc(100% - 16px);    overflow-y: scroll;    overflow-x: hidden;    padding: 0;    ${({ hidden }) => hidden && "display: none;"}    border-radius: ${defaultBorderRadius}; -  border: 0.5px solid gray; +  outline: 0.5px solid gray;    z-index: 2;    // Get rid of scrollbar and its padding    scrollbar-width: none; @@ -120,6 +95,7 @@ const Li = styled.li<{    selected: boolean;    isLastItem: boolean;  }>` +  background-color: ${vscBackground};    ${({ highlighted }) => highlighted && "background: #ff000066;"}    ${({ selected }) => selected && "font-weight: bold;"}      padding: 0.5rem 0.75rem; @@ -149,10 +125,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {    // The position of the current command you are typing now, so the one that will be appended to history once you press enter    const [positionInHistory, setPositionInHistory] = React.useState<number>(0);    const [items, setItems] = React.useState(props.items); -  const [hoveringButton, setHoveringButton] = React.useState(false); -  const [hoveringContextDropdown, setHoveringContextDropdown] = -    React.useState(false); -  const [pinned, setPinned] = useState(false);    const [highlightedCodeSections, setHighlightedCodeSections] = React.useState(      props.highlightedCodeSections || []    ); @@ -181,6 +153,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {    useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);    const [metaKeyPressed, setMetaKeyPressed] = useState(false); +  const [focused, setFocused] = useState(false);    useEffect(() => {      const handleKeyDown = (e: KeyboardEvent) => {        if (e.key === "Meta") { @@ -241,6 +214,14 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {          )} */}          {highlightedCodeSections.map((section, idx) => (            <PillButton +            warning={ +              section.range.contents.length > 4000 && section.editing +                ? "Editing such a large range may be slow" +                : undefined +            } +            onlyShowDelete={ +              highlightedCodeSections.length <= 1 || section.editing +            }              editing={section.editing}              pinned={section.pinned}              index={idx} @@ -258,15 +239,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {                  return newSections;                });              }} -            onHover={(val: boolean) => { -              if (val) { -                setHoveringButton(val); -              } else { -                setTimeout(() => { -                  setHoveringButton(val); -                }, 100); -              } -            }}            />          ))}          {props.highlightedCodeSections.length > 0 && @@ -276,11 +248,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {                  props.onToggleAddContext();                }}              > -              Highlight to Add Context +              Highlight code section              </EmptyPillDiv>            ) : (              <HeaderButtonWithText -              text="Add to Context" +              text="Add more code to context"                onClick={() => {                  props.onToggleAddContext();                }} @@ -292,7 +264,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {        <div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>          <MainTextInput            disabled={props.disabled} -          placeholder="Ask a question, give instructions, or type '/' to see slash commands. ⌘⏎ to edit." +          placeholder={`Ask a question, give instructions, or type '/' to see slash commands`}            {...getInputProps({              onChange: (e) => {                const target = e.target as HTMLTextAreaElement; @@ -305,6 +277,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {                // setShowContextDropdown(target.value.endsWith("@"));              }, +            onFocus: (e) => { +              setFocused(true); +            }, +            onBlur: (e) => { +              setFocused(false); +              postVscMessage("blurContinueInput", {}); +            },              onKeyDown: (event) => {                if (event.key === "Enter" && event.shiftKey) {                  // Prevent Downshift's default 'Enter' behavior. @@ -359,6 +338,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {            })}            showAbove={showAbove()}            ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0} +          hidden={!downshiftProps.isOpen || items.length === 0}          >            {downshiftProps.isOpen &&              items.map((item, index) => ( @@ -378,29 +358,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {        </div>        {highlightedCodeSections.length === 0 &&          (downshiftProps.inputValue?.startsWith("/edit") || -          (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && ( +          (focused && +            metaKeyPressed && +            downshiftProps.inputValue?.length > 0)) && (            <div className="text-trueGray-400 pr-4 text-xs text-right">              Inserting at cursor            </div>          )} -      <ContextDropdown -        onMouseEnter={() => { -          setHoveringContextDropdown(true); -        }} -        onMouseLeave={() => { -          setHoveringContextDropdown(false); -        }} -        hidden={true || (!hoveringContextDropdown && !hoveringButton)} -      > -        {highlightedCodeSections.map((section, idx) => ( -          <> -            <p>{section.display_name}</p> -            <CodeBlock showCopy={false} key={idx}> -              {section.range.contents} -            </CodeBlock> -          </> -        ))} -      </ContextDropdown>      </>    );  }); diff --git a/extension/react-app/src/components/InputAndButton.tsx b/extension/react-app/src/components/InputAndButton.tsx index 0a8592f2..8019d014 100644 --- a/extension/react-app/src/components/InputAndButton.tsx +++ b/extension/react-app/src/components/InputAndButton.tsx @@ -1,6 +1,6 @@  import React, { useRef } from "react";  import styled from "styled-components"; -import { vscBackground } from "."; +import { vscBackground, vscForeground } from ".";  interface InputAndButtonProps {    onUserInput: (input: string) => void; @@ -16,7 +16,7 @@ const Input = styled.input`    padding: 0.5rem;    border: 1px solid white;    background-color: ${vscBackground}; -  color: white; +  color: ${vscForeground};    border-radius: 4px;    border-top-right-radius: 0;    border-bottom-right-radius: 0; @@ -27,7 +27,7 @@ const Button = styled.button`    padding: 0.5rem;    border: 1px solid white;    background-color: ${vscBackground}; -  color: white; +  color: ${vscForeground};    border-radius: 4px;    border-top-left-radius: 0;    border-bottom-left-radius: 0; @@ -35,8 +35,8 @@ const Button = styled.button`    cursor: pointer;    &:hover { -    background-color: white; -    color: black; +    background-color: ${vscForeground}; +    color: ${vscBackground};    }  `; diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx index 7772a25e..231c1e93 100644 --- a/extension/react-app/src/components/Onboarding.tsx +++ b/extension/react-app/src/components/Onboarding.tsx @@ -22,22 +22,17 @@ const StyledSpan = styled.span`    &:hover {      background-color: #ffffff33;    } +  white-space: nowrap;  `;  const Onboarding = () => {    const [counter, setCounter] = useState(4); -  const gifs = ["intro", "explain", "edit", "generate", "intro"]; +  const gifs = ["intro", "highlight", "question", "help"];    const topMessages = [ -    "Welcome to Continue!", -    "Answer coding questions", -    "Edit in natural language", -    "Generate files from scratch", -  ]; -  const bottomMessages = [ -    "", -    "Ask Continue about a part of your code to get another perspective", -    "Highlight a section of code and instruct Continue to refactor it", -    "Let Continue build the scaffolding of Python scripts, React components, and more", +    "Welcome!", +    "Highlight code", +    "Ask a question", +    "Use /help to learn more",    ];    useEffect(() => { @@ -107,7 +102,6 @@ const Onboarding = () => {              />            )}          </div> -        <p>{bottomMessages[counter]}</p>          <p            style={{              paddingLeft: "50px", @@ -115,6 +109,7 @@ const Onboarding = () => {              paddingBottom: "50px",              textAlign: "center",              cursor: "pointer", +            whiteSpace: "nowrap",            }}          >            <StyledSpan diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx index 31d98c0f..5929d06a 100644 --- a/extension/react-app/src/components/PillButton.tsx +++ b/extension/react-app/src/components/PillButton.tsx @@ -3,15 +3,20 @@ import styled from "styled-components";  import {    StyledTooltip,    defaultBorderRadius, -  lightGray,    secondaryDark, +  vscBackground, +  vscForeground,  } from "."; -import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline"; +import { +  Trash, +  PaintBrush, +  ExclamationTriangle, +} from "@styled-icons/heroicons-outline";  import { GUIClientContext } from "../App";  const Button = styled.button`    border: none; -  color: white; +  color: ${vscForeground};    background-color: ${secondaryDark};    border-radius: ${defaultBorderRadius};    padding: 8px; @@ -28,10 +33,8 @@ const GridDiv = styled.div`    height: 100%;    display: grid;    grid-gap: 0; -  grid-template-columns: 1fr 1fr;    align-items: center;    border-radius: ${defaultBorderRadius}; -  overflow: hidden;    background-color: ${secondaryDark};  `; @@ -48,6 +51,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>`    }  `; +const CircleDiv = styled.div` +  position: absolute; +  top: -10px; +  right: -10px; +  width: 20px; +  height: 20px; +  border-radius: 50%; +  background-color: red; +  color: white; +  display: flex; +  align-items: center; +  justify-content: center; +  padding: 2px; +`; +  interface PillButtonProps {    onHover?: (arg0: boolean) => void;    onDelete?: () => void; @@ -55,6 +73,8 @@ interface PillButtonProps {    index: number;    editing: boolean;    pinned: boolean; +  warning?: string; +  onlyShowDelete?: boolean;  }  const PillButton = (props: PillButtonProps) => { @@ -63,75 +83,103 @@ const PillButton = (props: PillButtonProps) => {    return (      <> -      <Button -        style={{ -          position: "relative", -          borderColor: props.editing -            ? "#8800aa" -            : props.pinned -            ? "#ffff0099" -            : "transparent", -          borderWidth: "1px", -          borderStyle: "solid", -        }} -        onMouseEnter={() => { -          setIsHovered(true); -          if (props.onHover) { -            props.onHover(true); -          } -        }} -        onMouseLeave={() => { -          setIsHovered(false); -          if (props.onHover) { -            props.onHover(false); -          } -        }} -      > -        {isHovered && ( -          <GridDiv> -            <ButtonDiv -              data-tooltip-id={`edit-${props.index}`} -              backgroundColor={"#8800aa55"} -              onClick={() => { -                client?.setEditingAtIndices([props.index]); +      <div style={{ position: "relative" }}> +        <Button +          style={{ +            position: "relative", +            borderColor: props.warning +              ? "red" +              : props.editing +              ? "#8800aa" +              : props.pinned +              ? "#ffff0099" +              : "transparent", +            borderWidth: "1px", +            borderStyle: "solid", +          }} +          onMouseEnter={() => { +            setIsHovered(true); +            if (props.onHover) { +              props.onHover(true); +            } +          }} +          onMouseLeave={() => { +            setIsHovered(false); +            if (props.onHover) { +              props.onHover(false); +            } +          }} +        > +          {isHovered && ( +            <GridDiv +              style={{ +                gridTemplateColumns: props.onlyShowDelete ? "1fr" : "1fr 1fr", +                backgroundColor: vscBackground,                }}              > -              <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush> -            </ButtonDiv> +              {props.onlyShowDelete || ( +                <ButtonDiv +                  data-tooltip-id={`edit-${props.index}`} +                  backgroundColor={"#8800aa55"} +                  onClick={() => { +                    client?.setEditingAtIndices([props.index]); +                  }} +                > +                  <PaintBrush +                    style={{ margin: "auto" }} +                    width="1.6em" +                  ></PaintBrush> +                </ButtonDiv> +              )} -            {/* <ButtonDiv +              {/* <ButtonDiv              data-tooltip-id={`pin-${props.index}`}              backgroundColor={"#ffff0055"}              onClick={() => {                client?.setPinnedAtIndices([props.index]);              }} -          > +            >              <MapPin style={{ margin: "auto" }} width="1.6em"></MapPin>            </ButtonDiv> */} -            <StyledTooltip id={`pin-${props.index}`}> -              Edit this range +              <StyledTooltip id={`pin-${props.index}`}> +                Edit this range +              </StyledTooltip> +              <ButtonDiv +                data-tooltip-id={`delete-${props.index}`} +                backgroundColor={"#cc000055"} +                onClick={() => { +                  if (props.onDelete) { +                    props.onDelete(); +                  } +                }} +              > +                <Trash style={{ margin: "auto" }} width="1.6em"></Trash> +              </ButtonDiv> +            </GridDiv> +          )} +          {props.title} +        </Button> +        <StyledTooltip id={`edit-${props.index}`}> +          {props.editing +            ? "Editing this section (with entire file as context)" +            : "Edit this section"} +        </StyledTooltip> +        <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip> +        {props.warning && ( +          <> +            <CircleDiv data-tooltip-id={`circle-div-${props.title}`}> +              <ExclamationTriangle +                style={{ margin: "auto" }} +                width="1.0em" +                strokeWidth={2} +              /> +            </CircleDiv> +            <StyledTooltip id={`circle-div-${props.title}`}> +              {props.warning}              </StyledTooltip> -            <ButtonDiv -              data-tooltip-id={`delete-${props.index}`} -              backgroundColor={"#cc000055"} -              onClick={() => { -                if (props.onDelete) { -                  props.onDelete(); -                } -              }} -            > -              <Trash style={{ margin: "auto" }} width="1.6em"></Trash> -            </ButtonDiv> -          </GridDiv> +          </>          )} -        {props.title} -      </Button> -      <StyledTooltip id={`edit-${props.index}`}> -        {props.editing -          ? "Editing this range (with rest of file as context)" -          : "Edit this range"} -      </StyledTooltip> -      <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip> +      </div>      </>    );  }; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index d1a8a46a..bc8665fd 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react";  import styled, { keyframes } from "styled-components";  import {    appear, @@ -6,18 +6,21 @@ import {    secondaryDark,    vscBackground,    vscBackgroundTransparent, +  vscForeground,  } from ".";  import {    ChevronDown,    ChevronRight,    ArrowPath,    XMark, +  MagnifyingGlass,  } from "@styled-icons/heroicons-outline";  import { StopCircle } from "@styled-icons/heroicons-solid";  import { HistoryNode } from "../../../schema/HistoryNode"; -import ReactMarkdown from "react-markdown";  import HeaderButtonWithText from "./HeaderButtonWithText"; -import CodeBlock from "./CodeBlock"; +import MarkdownPreview from "@uiw/react-markdown-preview"; +import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util"; +import { GUIClientContext } from "../App";  interface StepContainerProps {    historyNode: HistoryNode; @@ -31,6 +34,7 @@ interface StepContainerProps {    onToggle: () => void;    isFirst: boolean;    isLast: boolean; +  index: number;  }  // #region styled components @@ -38,7 +42,6 @@ interface StepContainerProps {  const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>`    opacity: ${(props) => (props.inFuture ? 0.3 : 1)};    animation: ${appear} 0.3s ease-in-out; -  /* padding-left: ${(props) => props.stepDepth * 20}px; */    overflow: hidden;    margin-left: 0px;    margin-right: 0px; @@ -52,12 +55,7 @@ const StepContainerDiv = styled.div<{ open: boolean }>`  `;  const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>` -  background-color: ${(props) => -    props.error -      ? "#522" -      : props.loading -      ? vscBackgroundTransparent -      : vscBackground}; +  background-color: ${(props) => (props.error ? "#522" : vscBackground)};    display: grid;    grid-template-columns: 1fr auto auto;    grid-gap: 8px; @@ -72,19 +70,6 @@ const ContentDiv = styled.div<{ isUserInput: boolean }>`    font-size: 13px;  `; -const MarkdownPre = styled.pre` -  background-color: ${secondaryDark}; -  padding: 10px; -  border-radius: ${defaultBorderRadius}; -  border: 0.5px solid white; -`; - -const StyledCode = styled.code` -  word-wrap: break-word; -  color: #f69292; -  background: transparent; -`; -  const gradient = keyframes`    0% {      background-position: 0px 0; @@ -124,6 +109,33 @@ const GradientBorder = styled.div<{    background-size: 200% 200%;  `; +const StyledMarkdownPreview = styled(MarkdownPreview)` +  pre { +    background-color: ${secondaryDark}; +    padding: 1px; +    border-radius: ${defaultBorderRadius}; +    border: 0.5px solid white; +  } + +  code { +    color: #f78383; +    word-wrap: break-word; +    border-radius: ${defaultBorderRadius}; +    background-color: ${secondaryDark}; +  } + +  pre > code { +    background-color: ${secondaryDark}; +    color: ${vscForeground}; +  } + +  background-color: ${vscBackground}; +  font-family: "Lexend", sans-serif; +  font-size: 13px; +  padding: 8px; +  color: ${vscForeground}; +`; +  // #endregion  function StepContainer(props: StepContainerProps) { @@ -131,6 +143,7 @@ function StepContainer(props: StepContainerProps) {    const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null);    const userInputRef = useRef<HTMLInputElement>(null);    const isUserInput = props.historyNode.step.name === "UserInputStep"; +  const client = useContext(GUIClientContext);    useEffect(() => {      if (userInputRef?.current) { @@ -158,7 +171,7 @@ function StepContainer(props: StepContainerProps) {      >        <StepContainerDiv open={props.open}>          <GradientBorder -          loading={props.historyNode.active as boolean || false} +          loading={(props.historyNode.active as boolean) || false}            isFirst={props.isFirst}            isLast={props.isLast}            borderColor={ @@ -170,7 +183,7 @@ function StepContainer(props: StepContainerProps) {            }            className="overflow-hidden cursor-pointer"            onClick={(e) => { -            if (e.metaKey) { +            if (isMetaEquivalentKeyPressed(e)) {                props.onToggleAll();              } else {                props.onToggle(); @@ -178,7 +191,7 @@ function StepContainer(props: StepContainerProps) {            }}          >            <HeaderDiv -            loading={props.historyNode.active as boolean || false} +            loading={(props.historyNode.active as boolean) || false}              error={props.historyNode.observation?.error ? true : false}            >              <div className="m-2"> @@ -201,12 +214,27 @@ function StepContainer(props: StepContainerProps) {              </HeaderButton> */}              <> +              {(props.historyNode.logs as any)?.length > 0 && ( +                <HeaderButtonWithText +                  text="Logs" +                  onClick={(e) => { +                    e.stopPropagation(); +                    client?.showLogsAtIndex(props.index); +                  }} +                > +                  <MagnifyingGlass size="1.4em" /> +                </HeaderButtonWithText> +              )}                <HeaderButtonWithText                  onClick={(e) => {                    e.stopPropagation();                    props.onDelete();                  }} -                text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"} +                text={ +                  props.historyNode.active +                    ? `Stop (${getMetaKeyLabel()}⌫)` +                    : "Delete" +                }                >                  {props.historyNode.active ? (                    <StopCircle size="1.6em" onClick={props.onDelete} /> @@ -242,31 +270,19 @@ function StepContainer(props: StepContainerProps) {            )}            {props.historyNode.observation?.error ? ( -            <pre className="overflow-x-scroll"> -              {props.historyNode.observation.error as string} -            </pre> +            <details> +              <summary>View Traceback</summary> +              <pre className="overflow-x-scroll"> +                {props.historyNode.observation.error as string} +              </pre> +            </details>            ) : ( -            <ReactMarkdown -              key={1} -              className="overflow-x-scroll" -              components={{ -                pre: ({ node, ...props }) => { -                  return ( -                    <CodeBlock -                      children={(props.children[0] as any).props.children[0]} -                    /> -                  ); -                }, -                code: ({ node, ...props }) => { -                  return <StyledCode children={props.children[0] as any} />; -                }, -                ul: ({ node, ...props }) => { -                  return <ul className="ml-0" {...props} />; -                }, +            <StyledMarkdownPreview +              source={props.historyNode.step.description || ""} +              wrapperElement={{ +                "data-color-mode": "dark",                }} -            > -              {props.historyNode.step.description as any} -            </ReactMarkdown> +            />            )}          </ContentDiv>        </StepContainerDiv> diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index ea5727f0..9597b578 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -1,7 +1,9 @@  // Write a component that displays a dialog box with a text field and a button.  import React, { useEffect, useState } from "react";  import styled from "styled-components"; -import { Button, buttonColor, secondaryDark, vscBackground } from "."; +import { Button, secondaryDark, vscBackground, vscForeground } from "."; +import { isMetaEquivalentKeyPressed } from "../util"; +import { ReactMarkdown } from "react-markdown/lib/react-markdown";  const ScreenCover = styled.div`    position: absolute; @@ -20,13 +22,13 @@ const DialogContainer = styled.div`  `;  const Dialog = styled.div` -  background-color: white; +  color: ${vscForeground}; +  background-color: ${vscBackground};    border-radius: 8px;    padding: 8px;    display: flex;    flex-direction: column; -  /* box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5); */ -  border: 2px solid ${buttonColor}; +  box-shadow: 0 0 10px 0 ${vscForeground};    width: fit-content;    margin: auto;  `; @@ -37,14 +39,16 @@ const TextArea = styled.textarea`    padding: 8px;    outline: 1px solid black;    resize: none; +  background-color: ${secondaryDark}; +  color: ${vscForeground};    &:focus { -    outline: 1px solid ${buttonColor}; +    outline: 1px solid ${vscForeground};    }  `;  const P = styled.p` -  color: black; +  color: ${vscForeground};    margin: 8px auto;  `; @@ -53,6 +57,7 @@ const TextDialog = (props: {    onEnter: (text: string) => void;    onClose: () => void;    message?: string; +  entryOn?: boolean;  }) => {    const [text, setText] = useState("");    const textAreaRef = React.createRef<HTMLTextAreaElement>(); @@ -76,29 +81,37 @@ const TextDialog = (props: {          }}        >          <Dialog> -          <P>{props.message || ""}</P> -          <TextArea -            rows={10} -            ref={textAreaRef} -            onKeyDown={(e) => { -              if (e.key === "Enter" && e.metaKey && textAreaRef.current) { -                props.onEnter(textAreaRef.current.value); -                setText(""); -              } else if (e.key === "Escape") { -                props.onClose(); -              } -            }} -          /> -          <Button -            onClick={() => { -              if (textAreaRef.current) { -                props.onEnter(textAreaRef.current.value); -                setText(""); -              } -            }} -          > -            Enter -          </Button> +          <ReactMarkdown>{props.message || ""}</ReactMarkdown> +          {props.entryOn && ( +            <> +              <TextArea +                rows={10} +                ref={textAreaRef} +                onKeyDown={(e) => { +                  if ( +                    e.key === "Enter" && +                    isMetaEquivalentKeyPressed(e) && +                    textAreaRef.current +                  ) { +                    props.onEnter(textAreaRef.current.value); +                    setText(""); +                  } else if (e.key === "Escape") { +                    props.onClose(); +                  } +                }} +              /> +              <Button +                onClick={() => { +                  if (textAreaRef.current) { +                    props.onEnter(textAreaRef.current.value); +                    setText(""); +                  } +                }} +              > +                Enter +              </Button> +            </> +          )}          </Dialog>        </DialogContainer>      </ScreenCover> diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index 9ae0f097..cb5e7915 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -3,12 +3,16 @@ import styled, { keyframes } from "styled-components";  export const defaultBorderRadius = "5px";  export const lightGray = "rgb(100 100 100)"; -export const secondaryDark = "rgb(45 45 45)"; -export const vscBackground = "rgb(30 30 30)"; +// export const secondaryDark = "rgb(45 45 45)"; +// export const vscBackground = "rgb(30 30 30)";  export const vscBackgroundTransparent = "#1e1e1ede";  export const buttonColor = "rgb(113 28 59)";  export const buttonColorHover = "rgb(113 28 59 0.67)"; +export const secondaryDark = "var(--vscode-textBlockQuote-background)"; +export const vscBackground = "var(--vscode-editor-background)"; +export const vscForeground = "var(--vscode-editor-foreground)"; +  export const Button = styled.button`    padding: 10px 12px;    margin: 8px 0; @@ -46,8 +50,8 @@ export const TextArea = styled.textarea`    resize: vertical;    padding: 4px; -  caret-color: white; -  color: white; +  caret-color: ${vscForeground}; +  color: #{vscForeground};    &:focus {      outline: 1px solid ${buttonColor}; @@ -120,7 +124,7 @@ export const MainTextInput = styled.textarea`    border: 1px solid #ccc;    margin: 8px 8px;    background-color: ${vscBackground}; -  color: white; +  color: ${vscForeground};    outline: 1px solid orange;    resize: none;  `; @@ -137,8 +141,9 @@ export const appear = keyframes`  `;  export const HeaderButton = styled.button<{ inverted: boolean | undefined }>` -  background-color: ${({ inverted }) => (inverted ? "white" : "transparent")}; -  color: ${({ inverted }) => (inverted ? "black" : "white")}; +  background-color: ${({ inverted }) => +    inverted ? vscForeground : "transparent"}; +  color: ${({ inverted }) => (inverted ? vscBackground : vscForeground)};    border: none;    border-radius: ${defaultBorderRadius}; @@ -146,7 +151,9 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`    &:hover {      background-color: ${({ inverted }) => -      typeof inverted === "undefined" || inverted ? lightGray : "transparent"}; +      typeof inverted === "undefined" || inverted +        ? secondaryDark +        : "transparent"};    }    display: flex;    align-items: center; diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts new file mode 100644 index 00000000..6c0df8fc --- /dev/null +++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts @@ -0,0 +1,35 @@ +abstract class AbstractContinueGUIClientProtocol { +  abstract sendMainInput(input: string): void; + +  abstract reverseToIndex(index: number): void; + +  abstract sendRefinementInput(input: string, index: number): void; + +  abstract sendStepUserInput(input: string, index: number): void; + +  abstract onStateUpdate(state: any): void; + +  abstract onAvailableSlashCommands( +    callback: (commands: { name: string; description: string }[]) => void +  ): void; + +  abstract changeDefaultModel(model: string): void; + +  abstract sendClear(): void; + +  abstract retryAtIndex(index: number): void; + +  abstract deleteAtIndex(index: number): void; + +  abstract deleteContextAtIndices(indices: number[]): void; + +  abstract setEditingAtIndices(indices: number[]): void; + +  abstract setPinnedAtIndices(indices: number[]): void; + +  abstract toggleAddingHighlightedCode(): void; + +  abstract showLogsAtIndex(index: number): void; +} + +export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index a179c2bf..7d6c2a71 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -1,33 +1,92 @@ -abstract class AbstractContinueGUIClientProtocol { -  abstract sendMainInput(input: string): void; +import AbstractContinueGUIClientProtocol from "./AbstractContinueGUIClientProtocol"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; -  abstract reverseToIndex(index: number): void; +class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { +  messenger: Messenger; +  // Server URL must contain the session ID param +  serverUrlWithSessionId: string; -  abstract sendRefinementInput(input: string, index: number): void; +  constructor( +    serverUrlWithSessionId: string, +    useVscodeMessagePassing: boolean +  ) { +    super(); +    this.serverUrlWithSessionId = serverUrlWithSessionId; +    this.messenger = useVscodeMessagePassing +      ? new VscodeMessenger(serverUrlWithSessionId) +      : new WebsocketMessenger(serverUrlWithSessionId); +  } -  abstract sendStepUserInput(input: string, index: number): void; +  sendMainInput(input: string) { +    this.messenger.send("main_input", { input }); +  } -  abstract onStateUpdate(state: any): void; +  reverseToIndex(index: number) { +    this.messenger.send("reverse_to_index", { index }); +  } -  abstract onAvailableSlashCommands( +  sendRefinementInput(input: string, index: number) { +    this.messenger.send("refinement_input", { input, index }); +  } + +  sendStepUserInput(input: string, index: number) { +    this.messenger.send("step_user_input", { input, index }); +  } + +  onStateUpdate(callback: (state: any) => void) { +    this.messenger.onMessageType("state_update", (data: any) => { +      if (data.state) { +        callback(data.state); +      } +    }); +  } + +  onAvailableSlashCommands(      callback: (commands: { name: string; description: string }[]) => void -  ): void; +  ) { +    this.messenger.onMessageType("available_slash_commands", (data: any) => { +      if (data.commands) { +        callback(data.commands); +      } +    }); +  } + +  changeDefaultModel(model: string) { +    this.messenger.send("change_default_model", { model }); +  } -  abstract changeDefaultModel(model: string): void; +  sendClear() { +    this.messenger.send("clear_history", {}); +  } -  abstract sendClear(): void; +  retryAtIndex(index: number) { +    this.messenger.send("retry_at_index", { index }); +  } -  abstract retryAtIndex(index: number): void; +  deleteAtIndex(index: number) { +    this.messenger.send("delete_at_index", { index }); +  } -  abstract deleteAtIndex(index: number): void; +  deleteContextAtIndices(indices: number[]) { +    this.messenger.send("delete_context_at_indices", { indices }); +  } -  abstract deleteContextAtIndices(indices: number[]): void; +  setEditingAtIndices(indices: number[]) { +    this.messenger.send("set_editing_at_indices", { indices }); +  } -  abstract setEditingAtIndices(indices: number[]): void; +  setPinnedAtIndices(indices: number[]) { +    this.messenger.send("set_pinned_at_indices", { indices }); +  } -  abstract setPinnedAtIndices(indices: number[]): void; +  toggleAddingHighlightedCode(): void { +    this.messenger.send("toggle_adding_highlighted_code", {}); +  } -  abstract toggleAddingHighlightedCode(): void; +  showLogsAtIndex(index: number): void { +    this.messenger.send("show_logs_at_index", { index }); +  }  } -export default AbstractContinueGUIClientProtocol; +export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts deleted file mode 100644 index 2060dd7f..00000000 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ /dev/null @@ -1,91 +0,0 @@ -import AbstractContinueGUIClientProtocol from "./ContinueGUIClientProtocol"; -// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; -import { Messenger, WebsocketMessenger } from "./messenger"; -import { VscodeMessenger } from "./vscodeMessenger"; - -class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { -  messenger: Messenger; -  // Server URL must contain the session ID param -  serverUrlWithSessionId: string; - -  constructor( -    serverUrlWithSessionId: string, -    useVscodeMessagePassing: boolean -  ) { -    super(); -    this.serverUrlWithSessionId = serverUrlWithSessionId; -    if (useVscodeMessagePassing) { -      this.messenger = new VscodeMessenger(serverUrlWithSessionId); -    } else { -      this.messenger = new WebsocketMessenger(serverUrlWithSessionId); -    } -  } - -  sendMainInput(input: string) { -    this.messenger.send("main_input", { input }); -  } - -  reverseToIndex(index: number) { -    this.messenger.send("reverse_to_index", { index }); -  } - -  sendRefinementInput(input: string, index: number) { -    this.messenger.send("refinement_input", { input, index }); -  } - -  sendStepUserInput(input: string, index: number) { -    this.messenger.send("step_user_input", { input, index }); -  } - -  onStateUpdate(callback: (state: any) => void) { -    this.messenger.onMessageType("state_update", (data: any) => { -      if (data.state) { -        callback(data.state); -      } -    }); -  } - -  onAvailableSlashCommands( -    callback: (commands: { name: string; description: string }[]) => void -  ) { -    this.messenger.onMessageType("available_slash_commands", (data: any) => { -      if (data.commands) { -        callback(data.commands); -      } -    }); -  } - -  changeDefaultModel(model: string) { -    this.messenger.send("change_default_model", { model }); -  } - -  sendClear() { -    this.messenger.send("clear_history", {}); -  } - -  retryAtIndex(index: number) { -    this.messenger.send("retry_at_index", { index }); -  } - -  deleteAtIndex(index: number) { -    this.messenger.send("delete_at_index", { index }); -  } - -  deleteContextAtIndices(indices: number[]) { -    this.messenger.send("delete_context_at_indices", { indices }); -  } - -  setEditingAtIndices(indices: number[]) { -    this.messenger.send("set_editing_at_indices", { indices }); -  } - -  setPinnedAtIndices(indices: number[]) { -    this.messenger.send("set_pinned_at_indices", { indices }); -  } - -  toggleAddingHighlightedCode(): void { -    this.messenger.send("toggle_adding_highlighted_code", {}); -  } -} - -export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index e762666f..6b36be97 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,7 +1,7 @@  import React, { useEffect, useState } from "react";  import { RootStore } from "../redux/store";  import { useSelector } from "react-redux"; -import ContinueGUIClientProtocol from "./useContinueGUIProtocol"; +import ContinueGUIClientProtocol from "./ContinueGUIClientProtocol";  import { postVscMessage } from "../vscode";  function useContinueGUIProtocol(useVscodeMessagePassing: boolean = true) { diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css index 6e33c89c..bac7fe97 100644 --- a/extension/react-app/src/index.css +++ b/extension/react-app/src/index.css @@ -14,13 +14,13 @@ html,  body,  #root {    height: 100%; -  background-color: var(--vsc-background); +  background-color: var(--vscode-editor-background);    font-family: "Lexend", sans-serif;  }  body {    padding: 0; -  color: white; +  color: var(--vscode-editor-foreground);    padding: 0px;    margin: 0px;    height: 100%; diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 4ff260fa..49f41dcf 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -1,5 +1,9 @@  import styled from "styled-components"; -import { defaultBorderRadius } from "../components"; +import { +  defaultBorderRadius, +  vscBackground, +  vscForeground, +} from "../components";  import Loader from "../components/Loader";  import ContinueButton from "../components/ContinueButton";  import { FullState, HighlightedRangeContext } from "../../../schema/FullState"; @@ -23,6 +27,7 @@ import { RootStore } from "../redux/store";  import { postVscMessage } from "../vscode";  import UserInputContainer from "../components/UserInputContainer";  import Onboarding from "../components/Onboarding"; +import { isMetaEquivalentKeyPressed } from "../util";  const TopGUIDiv = styled.div`    overflow: hidden; @@ -70,7 +75,6 @@ function GUI(props: GUIProps) {      }    }, [dataSwitchOn]); -  const [usingFastModel, setUsingFastModel] = useState(false);    const [waitingForSteps, setWaitingForSteps] = useState(false);    const [userInputQueue, setUserInputQueue] = useState<string[]>([]);    const [highlightedRanges, setHighlightedRanges] = useState< @@ -95,11 +99,8 @@ function GUI(props: GUIProps) {            name: "Welcome to Continue",            hide: false,            description: `- Highlight code and ask a question or give instructions -- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue -- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer -- Add your own OpenAI API key to VS Code Settings with \`cmd+,\` -- Use slash commands when you want fine-grained control -- Past steps are included as part of the context by default`, +          - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue +          - Use \`/help\` to ask questions about how to use Continue`,            system_message: null,            chat_context: [],            manage_own_chat_context: false, @@ -115,6 +116,7 @@ function GUI(props: GUIProps) {    const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);    const [feedbackDialogMessage, setFeedbackDialogMessage] = useState(""); +  const [feedbackEntryOn, setFeedbackEntryOn] = useState(true);    const topGuiDivRef = useRef<HTMLDivElement>(null); @@ -138,14 +140,11 @@ function GUI(props: GUIProps) {    }, [topGuiDivRef.current, scrollTimeout]);    useEffect(() => { +    // Cmd + Backspace to delete current step      const listener = (e: any) => { -      // Cmd + i to toggle fast model -      if (e.key === "i" && e.metaKey && e.shiftKey) { -        setUsingFastModel((prev) => !prev); -        // Cmd + backspace to stop currently running step -      } else if ( +      if (          e.key === "Backspace" && -        e.metaKey && +        isMetaEquivalentKeyPressed(e) &&          typeof history?.current_index !== "undefined" &&          history.timeline[history.current_index]?.active        ) { @@ -162,7 +161,6 @@ function GUI(props: GUIProps) {    useEffect(() => {      client?.onStateUpdate((state: FullState) => {        // Scroll only if user is at very bottom of the window. -      setUsingFastModel(state.default_model === "gpt-3.5-turbo");        const shouldScrollToBottom =          topGuiDivRef.current &&          topGuiDivRef.current?.offsetHeight - window.scrollY < 100; @@ -223,7 +221,7 @@ function GUI(props: GUIProps) {      if (mainTextInputRef.current) {        let input = (mainTextInputRef.current as any).inputValue;        // cmd+enter to /edit -      if (event?.metaKey) { +      if (isMetaEquivalentKeyPressed(event)) {          input = `/edit ${input}`;        }        (mainTextInputRef.current as any).setInputValue(""); @@ -269,15 +267,18 @@ function GUI(props: GUIProps) {    return (      <>        <Onboarding /> -      <TextDialog showDialog={showFeedbackDialog} -      onEnter={(text) => { -        client?.sendMainInput(`/feedback ${text}`); -        setShowFeedbackDialog(false); -      }} -      onClose={() => { -        setShowFeedbackDialog(false); -      }} -      message={feedbackDialogMessage} /> +      <TextDialog +        showDialog={showFeedbackDialog} +        onEnter={(text) => { +          client?.sendMainInput(`/feedback ${text}`); +          setShowFeedbackDialog(false); +        }} +        onClose={() => { +          setShowFeedbackDialog(false); +        }} +        message={feedbackDialogMessage} +        entryOn={feedbackEntryOn} +      />        <TopGUIDiv          ref={topGuiDivRef} @@ -307,6 +308,7 @@ function GUI(props: GUIProps) {              )            ) : (              <StepContainer +              index={index}                isLast={index === history.timeline.length - 1}                isFirst={index === 0}                open={stepsOpen[index]} @@ -371,12 +373,13 @@ function GUI(props: GUIProps) {          style={{            position: "fixed",            bottom: "50px", -          backgroundColor: "white", -          color: "black", +          backgroundColor: vscBackground, +          color: vscForeground,            borderRadius: defaultBorderRadius,            padding: "16px",            margin: "16px",            zIndex: 100, +          boxShadow: `0px 0px 10px 0px ${vscForeground}`,          }}          hidden={!showDataSharingInfo}        > @@ -425,24 +428,26 @@ function GUI(props: GUIProps) {          </div>          <HeaderButtonWithText            onClick={() => { -            // client?.changeDefaultModel( -            //   usingFastModel ? "gpt-4" : "gpt-3.5-turbo" -            // ); -            if (!usingFastModel) { -              // Show the dialog -              setFeedbackDialogMessage( -                "We don't yet support local models, but we're working on it! If privacy is a concern of yours, please write a short note to let us know." -              ); -              setShowFeedbackDialog(true); -            } -            setUsingFastModel((prev) => !prev); +            // Show the dialog +            setFeedbackDialogMessage( +              `Continue uses GPT-4 by default, but works with any model. If you'd like to keep your code completely private, there are few options: + +Run a local model with ggml: [5 minute quickstart](https://github.com/continuedev/ggml-server-example) + +Use Azure OpenAI service, which is GDPR and HIPAA compliant: [Tutorial](https://continue.dev/docs/customization#azure-openai-service) + +If you already have an LLM deployed on your own infrastructure, or would like to do so, please contact us at hi@continue.dev. +              ` +            ); +            setFeedbackEntryOn(false); +            setShowFeedbackDialog(true);            }} -          text={usingFastModel ? "local" : "gpt-4"} +          text={"Use Private Model"}          >            <div              style={{ fontSize: "18px", marginLeft: "2px", marginRight: "2px" }}            > -            {usingFastModel ? "🔒" : "🧠"} +            🔒            </div>          </HeaderButtonWithText>          <HeaderButtonWithText @@ -467,6 +472,7 @@ function GUI(props: GUIProps) {              setFeedbackDialogMessage(                "Having trouble using Continue? Want a new feature? Let us know! This box is anonymous, but we will promptly address your feedback."              ); +            setFeedbackEntryOn(true);              setShowFeedbackDialog(true);            }}            text="Feedback" diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts new file mode 100644 index 00000000..c4168e13 --- /dev/null +++ b/extension/react-app/src/util/index.ts @@ -0,0 +1,43 @@ +type Platform = "mac" | "linux" | "windows" | "unknown"; + +export function getPlatform(): Platform { +  const platform = window.navigator.platform.toUpperCase(); +  if (platform.indexOf("MAC") >= 0) { +    return "mac"; +  } else if (platform.indexOf("LINUX") >= 0) { +    return "linux"; +  } else if (platform.indexOf("WIN") >= 0) { +    return "windows"; +  } else { +    return "unknown"; +  } +} + +export function isMetaEquivalentKeyPressed(event: { +  metaKey: boolean; +  ctrlKey: boolean; +}): boolean { +  const platform = getPlatform(); +  switch (platform) { +    case "mac": +      return event.metaKey; +    case "linux": +    case "windows": +      return event.ctrlKey; +    default: +      return event.metaKey; +  } +} + +export function getMetaKeyLabel(): string { +  const platform = getPlatform(); +  switch (platform) { +    case "mac": +      return "⌘"; +    case "linux": +    case "windows": +      return "^"; +    default: +      return "⌘"; +  } +} diff --git a/extension/schema/FullState.d.ts b/extension/schema/FullState.d.ts index abb0832d..1b7b1f3b 100644 --- a/extension/schema/FullState.d.ts +++ b/extension/schema/FullState.d.ts @@ -21,6 +21,7 @@ export type ManageOwnChatContext = boolean;  export type Depth = number;  export type Deleted = boolean;  export type Active = boolean; +export type Logs = string[];  export type Timeline = HistoryNode[];  export type CurrentIndex = number;  export type Active1 = boolean; @@ -69,6 +70,7 @@ export interface HistoryNode {    depth: Depth;    deleted?: Deleted;    active?: Active; +  logs?: Logs;    [k: string]: unknown;  }  export interface Step { diff --git a/extension/schema/History.d.ts b/extension/schema/History.d.ts index 6eb8ad81..90124f4a 100644 --- a/extension/schema/History.d.ts +++ b/extension/schema/History.d.ts @@ -21,6 +21,7 @@ export type ManageOwnChatContext = boolean;  export type Depth = number;  export type Deleted = boolean;  export type Active = boolean; +export type Logs = string[];  export type Timeline = HistoryNode[];  export type CurrentIndex = number; @@ -41,6 +42,7 @@ export interface HistoryNode {    depth: Depth;    deleted?: Deleted;    active?: Active; +  logs?: Logs;    [k: string]: unknown;  }  export interface Step { diff --git a/extension/schema/HistoryNode.d.ts b/extension/schema/HistoryNode.d.ts index bc77be89..5ad32061 100644 --- a/extension/schema/HistoryNode.d.ts +++ b/extension/schema/HistoryNode.d.ts @@ -21,6 +21,7 @@ export type ManageOwnChatContext = boolean;  export type Depth = number;  export type Deleted = boolean;  export type Active = boolean; +export type Logs = string[];  /**   * A point in history, a list of which make up History @@ -31,6 +32,7 @@ export interface HistoryNode1 {    depth: Depth;    deleted?: Deleted;    active?: Active; +  logs?: Logs;    [k: string]: unknown;  }  export interface Step { diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index b03282e5..0c6e6009 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -1,7 +1,6 @@  import * as vscode from "vscode";  import { registerAllCommands } from "../commands";  import { registerAllCodeLensProviders } from "../lang-server/codeLens"; -import { sendTelemetryEvent, TelemetryEvent } from "../telemetry";  import IdeProtocolClient from "../continueIdeClient";  import { getContinueServerUrl } from "../bridge";  import { ContinueGUIWebviewViewProvider } from "../debugPanel"; @@ -10,6 +9,8 @@ import {    startContinuePythonServer,  } from "./environmentSetup";  import fetch from "node-fetch"; +import registerQuickFixProvider from "../lang-server/codeActions"; +import { get } from "http";  // import { CapturedTerminal } from "../terminal/terminalEmulator";  const PACKAGE_JSON_RAW_GITHUB_URL = @@ -35,21 +36,40 @@ export async function activateExtension(context: vscode.ExtensionContext) {      })      .catch((e) => console.log("Error checking for extension updates: ", e)); +  // Start the server and display loader if taking > 2 seconds    const sessionIdPromise = (async () => { -    // Start the Python server -    await new Promise((resolve, reject) => { -      vscode.window.withProgress( -        { -          location: vscode.ProgressLocation.Notification, -          title: -            "Starting Continue Server... (it may take a minute to download Python packages)", -          cancellable: false, -        }, -        async (progress, token) => { -          await startContinuePythonServer(); -          resolve(null); +    await new Promise((resolve) => { +      let serverStarted = false; + +      // Start the server and set serverStarted to true when done +      startContinuePythonServer().then(() => { +        serverStarted = true; +        resolve(null); +      }); + +      // Wait for 2 seconds +      setTimeout(() => { +        // If the server hasn't started after 2 seconds, show the notification +        if (!serverStarted) { +          vscode.window.withProgress( +            { +              location: vscode.ProgressLocation.Notification, +              title: +                "Starting Continue Server... (it may take a minute to download Python packages)", +              cancellable: false, +            }, +            async (progress, token) => { +              // Wait for the server to start +              while (!serverStarted) { +                await new Promise((innerResolve) => +                  setTimeout(innerResolve, 1000) +                ); +              } +              return Promise.resolve(); +            } +          );          } -      ); +      }, 2000);      });      // Initialize IDE Protocol Client @@ -58,11 +78,10 @@ export async function activateExtension(context: vscode.ExtensionContext) {        `${serverUrl.replace("http", "ws")}/ide/ws`,        context      ); - -    return ideProtocolClient.getSessionId(); +    return await ideProtocolClient.getSessionId();    })(); -  // Register the webview +  // Register Continue GUI as sidebar webview, and beging a new session    const provider = new ContinueGUIWebviewViewProvider(sessionIdPromise);    context.subscriptions.push( @@ -74,9 +93,4 @@ export async function activateExtension(context: vscode.ExtensionContext) {        }      )    ); - -  // Register commands and providers -  sendTelemetryEvent(TelemetryEvent.ExtensionActivated); -  registerAllCodeLensProviders(context); -  registerAllCommands(context);  } diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 7bd08929..5a9345a6 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -9,7 +9,9 @@ import fetch from "node-fetch";  import * as vscode from "vscode";  import * as os from "os";  import fkill from "fkill"; -import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; + +const WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR = +  "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. In order to use Continue, please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again.";  const MAX_RETRIES = 3;  async function retryThenFail( @@ -17,17 +19,43 @@ async function retryThenFail(    retries: number = MAX_RETRIES  ): Promise<any> {    try { +    if (retries < MAX_RETRIES && process.platform === "win32") { +      let [stdout, stderr] = await runCommand("Get-ExecutionPolicy"); +      if (!stdout.includes("RemoteSigned")) { +        [stdout, stderr] = await runCommand( +          "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser" +        ); +        console.log("Execution policy stdout: ", stdout); +        console.log("Execution policy stderr: ", stderr); +      } +    } +      return await fn();    } catch (e: any) {      if (retries > 0) {        return await retryThenFail(fn, retries - 1);      } -    vscode.window.showInformationMessage( -      "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!" -    ); -    sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { -      error: e.message, -    }); + +    // Show corresponding error message depending on the platform +    let msg = +      "Failed to set up Continue extension. Please email hi@continue.dev and we'll get this fixed ASAP!"; +    try { +      switch (process.platform) { +        case "win32": +          msg = WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR; +          break; +        case "darwin": +          break; +        case "linux": +          const [pythonCmd] = await getPythonPipCommands(); +          msg = await getLinuxAptInstallError(pythonCmd); +          break; +      } +    } finally { +      console.log("After retries, failed to set up Continue extension", msg); +      vscode.window.showErrorMessage(msg); +    } +      throw e;    }  } @@ -51,12 +79,6 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> {      stdout = "";    } -  if (stderr) { -    sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { -      error: stderr, -    }); -  } -    return [stdout, stderr];  } @@ -107,7 +129,7 @@ export async function getPythonPipCommands() {      if (!versionExists) {        vscode.window.showErrorMessage( -        "Continue requires Python3 version 3.8 or greater. Please update your Python3 installation, reload VS Code, and try again." +        "Continue requires Python version 3.8 or greater. Please update your Python installation, reload VS Code, and try again."        );        throw new Error("Python3.8 or greater is not installed.");      } @@ -186,16 +208,22 @@ async function checkRequirementsInstalled() {    return fs.existsSync(continuePath);  } -async function setupPythonEnv() { -  console.log("Setting up python env for Continue extension..."); - -  const [pythonCmd, pipCmd] = await getPythonPipCommands(); -  const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( -    pythonCmd, -    pipCmd -  ); +async function getLinuxAptInstallError(pythonCmd: string) { +  // First, try to run the command to install python3-venv +  let [stdout, stderr] = await runCommand(`${pythonCmd} --version`); +  if (stderr) { +    await vscode.window.showErrorMessage( +      "Python3 is not installed. Please install from https://www.python.org/downloads, reload VS Code, and try again." +    ); +    throw new Error(stderr); +  } +  const version = stdout.split(" ")[1].split(".")[1]; +  const installVenvCommand = `apt-get install python3.${version}-venv`; +  await runCommand("apt-get update"); +  return `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`; +} -  // First, create the virtual environment +async function createPythonVenv(pythonCmd: string) {    if (checkEnvExists()) {      console.log("Python env already exists, skipping...");    } else { @@ -210,32 +238,38 @@ async function setupPythonEnv() {        stderr &&        stderr.includes("running scripts is disabled on this system")      ) { -      await vscode.window.showErrorMessage( -        "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. Please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again." -      ); +      console.log("Scripts disabled error when trying to create env"); +      await vscode.window.showErrorMessage(WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR);        throw new Error(stderr);      } else if (        stderr?.includes("On Debian/Ubuntu systems") ||        stdout?.includes("On Debian/Ubuntu systems")      ) { -      // First, try to run the command to install python3-venv -      let [stdout, stderr] = await runCommand(`${pythonCmd} --version`); -      if (stderr) { -        await vscode.window.showErrorMessage( -          "Python3 is not installed. Please install from https://www.python.org/downloads, reload VS Code, and try again." -        ); -        throw new Error(stderr); -      } -      const version = stdout.split(" ")[1].split(".")[1]; -      const installVenvCommand = `apt-get install python3.${version}-venv`; -      await runCommand("apt-get update"); -      // Ask the user to run the command to install python3-venv (requires sudo, so we can't) -      // First, get the python version -      const msg = `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`; +      const msg = await getLinuxAptInstallError(pythonCmd);        console.log(msg);        await vscode.window.showErrorMessage(msg);      } else if (checkEnvExists()) {        console.log("Successfully set up python env at ", `${serverPath()}/env`); +    } else if ( +      stderr?.includes("Permission denied") && +      stderr?.includes("python.exe") +    ) { +      // This might mean that another window is currently using the python.exe file to install requirements +      // So we want to wait and try again +      let i = 0; +      await new Promise((resolve, reject) => +        setInterval(() => { +          if (i > 5) { +            reject("Timed out waiting for other window to create env..."); +          } +          if (checkEnvExists()) { +            resolve(null); +          } else { +            console.log("Waiting for other window to create env..."); +          } +          i++; +        }, 5000) +      );      } else {        const msg = [          "Python environment not successfully created. Trying again. Here was the stdout + stderr: ", @@ -246,9 +280,22 @@ async function setupPythonEnv() {        throw new Error(msg);      }    } +} + +async function setupPythonEnv() { +  console.log("Setting up python env for Continue extension..."); + +  const [pythonCmd, pipCmd] = await getPythonPipCommands(); +  const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( +    pythonCmd, +    pipCmd +  ); -  // Install the requirements    await retryThenFail(async () => { +    // First, create the virtual environment +    await createPythonVenv(pythonCmd); + +    // Install the requirements      if (await checkRequirementsInstalled()) {        console.log("Python requirements already installed, skipping...");      } else { @@ -343,6 +390,14 @@ function serverPath(): string {    return sPath;  } +export function devDataPath(): string { +  const sPath = path.join(getContinueGlobalPath(), "dev_data"); +  if (!fs.existsSync(sPath)) { +    fs.mkdirSync(sPath); +  } +  return sPath; +} +  function serverVersionPath(): string {    return path.join(serverPath(), "server_version.txt");  } @@ -409,21 +464,17 @@ export async function startContinuePythonServer() {            console.log(`stdout: ${data}`);            if (              data.includes("Uvicorn running on") || // Successfully started the server -            data.includes("address already in use") // The server is already running (probably a simultaneously opened VS Code window) +            data.includes("only one usage of each socket address") || // [windows] The server is already running (probably a simultaneously opened VS Code window) +            data.includes("address already in use") // [mac/linux] The server is already running (probably a simultaneously opened VS Code window)            ) {              console.log("Successfully started Continue python server");              resolve(null);            } else if (data.includes("ERROR") || data.includes("Traceback")) { -            sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { -              error: data, -            }); +            console.log("Error starting Continue python server: ", data);            }          });          child.on("error", (error: any) => {            console.log(`error: ${error.message}`); -          sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { -            error: error.message, -          });          });          // Write the current version of vscode to a file called server_version.txt diff --git a/extension/src/commands.ts b/extension/src/commands.ts index 0b002549..1da2f04e 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -16,9 +16,14 @@ import {  import { acceptDiffCommand, rejectDiffCommand } from "./diffs";  import * as bridge from "./bridge";  import { debugPanelWebview } from "./debugPanel"; -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";  import { ideProtocolClient } from "./activation/activate"; +let focusedOnContinueInput = false; + +export const setFocusedOnContinueInput = (value: boolean) => { +  focusedOnContinueInput = value; +}; +  // Copy everything over from extension.ts  const commandsMap: { [command: string]: (...args: any) => any } = {    "continue.suggestionDown": suggestionDownCommand, @@ -29,11 +34,26 @@ const commandsMap: { [command: string]: (...args: any) => any } = {    "continue.rejectDiff": rejectDiffCommand,    "continue.acceptAllSuggestions": acceptAllSuggestionsCommand,    "continue.rejectAllSuggestions": rejectAllSuggestionsCommand, +  "continue.quickFix": async (message: string, code: string, edit: boolean) => { +    ideProtocolClient.sendMainUserInput( +      `${ +        edit ? "/edit " : "" +      }${code}\n\nHow do I fix this problem in the above code?: ${message}` +    ); +    if (!edit) { +      vscode.commands.executeCommand("continue.continueGUIView.focus"); +    } +  },    "continue.focusContinueInput": async () => { -    vscode.commands.executeCommand("continue.continueGUIView.focus"); -    debugPanelWebview?.postMessage({ -      type: "focusContinueInput", -    }); +    if (focusedOnContinueInput) { +      vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup"); +    } else { +      vscode.commands.executeCommand("continue.continueGUIView.focus"); +      debugPanelWebview?.postMessage({ +        type: "focusContinueInput", +      }); +    } +    focusedOnContinueInput = !focusedOnContinueInput;    },    "continue.quickTextEntry": async () => {      const text = await vscode.window.showInputBox({ @@ -53,4 +73,4 @@ export function registerAllCommands(context: vscode.ExtensionContext) {        vscode.commands.registerCommand(command, callback)      );    } -}
\ No newline at end of file +} diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 4c1fdf1e..802afc1d 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -15,6 +15,12 @@ import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";  import fs = require("fs");  import { WebsocketMessenger } from "./util/messenger";  import { diffManager } from "./diffs"; +import path = require("path"); +import { registerAllCodeLensProviders } from "./lang-server/codeLens"; +import { registerAllCommands } from "./commands"; +import registerQuickFixProvider from "./lang-server/codeActions"; + +const continueVirtualDocumentScheme = "continue";  class IdeProtocolClient {    private messenger: WebsocketMessenger | null = null; @@ -73,6 +79,11 @@ class IdeProtocolClient {      this._serverUrl = serverUrl;      this._newWebsocketMessenger(); +    // Register commands and providers +    registerAllCodeLensProviders(context); +    registerAllCommands(context); +    registerQuickFixProvider(); +      // Setup listeners for any file changes in open editors      // vscode.workspace.onDidChangeTextDocument((event) => {      //   if (this._makingEdit === 0) { @@ -103,8 +114,11 @@ class IdeProtocolClient {      //   }      // }); -    // Setup listeners for any file changes in open editors +    // Setup listeners for any selection changes in open editors      vscode.window.onDidChangeTextEditorSelection((event) => { +      if (this.editorIsTerminal(event.textEditor)) { +        return; +      }        if (this._highlightDebounce) {          clearTimeout(this._highlightDebounce);        } @@ -132,6 +146,41 @@ class IdeProtocolClient {          this.sendHighlightedCode(highlightedCode);        }, 100);      }); + +    // Register a content provider for the readonly virtual documents +    const documentContentProvider = new (class +      implements vscode.TextDocumentContentProvider +    { +      // emitter and its event +      onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>(); +      onDidChange = this.onDidChangeEmitter.event; + +      provideTextDocumentContent(uri: vscode.Uri): string { +        return uri.query; +      } +    })(); +    context.subscriptions.push( +      vscode.workspace.registerTextDocumentContentProvider( +        continueVirtualDocumentScheme, +        documentContentProvider +      ) +    ); + +    // Listen for changes to settings.json +    vscode.workspace.onDidChangeConfiguration((event) => { +      if (event.affectsConfiguration("continue")) { +        vscode.window +          .showInformationMessage( +            "Please reload VS Code for changes to Continue settings to take effect.", +            "Reload" +          ) +          .then((selection) => { +            if (selection === "Reload") { +              vscode.commands.executeCommand("workbench.action.reloadWindow"); +            } +          }); +      } +    });    }    async handleMessage( @@ -196,6 +245,9 @@ class IdeProtocolClient {          this.openFile(data.filepath);          // TODO: Close file if False          break; +      case "showVirtualFile": +        this.showVirtualFile(data.name, data.contents); +        break;        case "setSuggestionsLocked":          this.setSuggestionsLocked(data.filepath, data.locked);          break; @@ -291,6 +343,20 @@ class IdeProtocolClient {      openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One);    } +  showVirtualFile(name: string, contents: string) { +    vscode.workspace +      .openTextDocument( +        vscode.Uri.parse( +          `${continueVirtualDocumentScheme}:${name}?${encodeURIComponent( +            contents +          )}` +        ) +      ) +      .then((doc) => { +        vscode.window.showTextDocument(doc, { preview: false }); +      }); +  } +    setSuggestionsLocked(filepath: string, locked: boolean) {      editorSuggestionsLocked.set(filepath, locked);      // TODO: Rerender? @@ -350,44 +416,55 @@ class IdeProtocolClient {    // ------------------------------------ //    // Respond to request +  private editorIsTerminal(editor: vscode.TextEditor) { +    return ( +      !!path.basename(editor.document.uri.fsPath).match(/\d/) || +      (editor.document.languageId === "plaintext" && +        editor.document.getText() === "accessible-buffer-accessible-buffer-") +    ); +  } +    getOpenFiles(): string[] {      return vscode.window.visibleTextEditors -      .filter((editor) => { -        return !( -          editor.document.uri.fsPath.endsWith("/1") || -          (editor.document.languageId === "plaintext" && -            editor.document.getText() === -              "accessible-buffer-accessible-buffer-") -        ); -      }) +      .filter((editor) => !this.editorIsTerminal(editor))        .map((editor) => {          return editor.document.uri.fsPath;        });    }    getVisibleFiles(): string[] { -    return vscode.window.visibleTextEditors.map((editor) => { -      return editor.document.uri.fsPath; -    }); +    return vscode.window.visibleTextEditors +      .filter((editor) => !this.editorIsTerminal(editor)) +      .map((editor) => { +        return editor.document.uri.fsPath; +      });    }    saveFile(filepath: string) { -    vscode.window.visibleTextEditors.forEach((editor) => { -      if (editor.document.uri.fsPath === filepath) { -        editor.document.save(); -      } -    }); +    vscode.window.visibleTextEditors +      .filter((editor) => !this.editorIsTerminal(editor)) +      .forEach((editor) => { +        if (editor.document.uri.fsPath === filepath) { +          editor.document.save(); +        } +      });    }    readFile(filepath: string): string {      let contents: string | undefined; -    vscode.window.visibleTextEditors.forEach((editor) => { -      if (editor.document.uri.fsPath === filepath) { -        contents = editor.document.getText(); +    vscode.window.visibleTextEditors +      .filter((editor) => !this.editorIsTerminal(editor)) +      .forEach((editor) => { +        if (editor.document.uri.fsPath === filepath) { +          contents = editor.document.getText(); +        } +      }); +    if (typeof contents === "undefined") { +      if (fs.existsSync(filepath)) { +        contents = fs.readFileSync(filepath, "utf-8"); +      } else { +        contents = "";        } -    }); -    if (!contents) { -      contents = fs.readFileSync(filepath, "utf-8");      }      return contents;    } @@ -421,25 +498,27 @@ class IdeProtocolClient {    getHighlightedCode(): RangeInFile[] {      // TODO      let rangeInFiles: RangeInFile[] = []; -    vscode.window.visibleTextEditors.forEach((editor) => { -      editor.selections.forEach((selection) => { -        // if (!selection.isEmpty) { -        rangeInFiles.push({ -          filepath: editor.document.uri.fsPath, -          range: { -            start: { -              line: selection.start.line, -              character: selection.start.character, -            }, -            end: { -              line: selection.end.line, -              character: selection.end.character, +    vscode.window.visibleTextEditors +      .filter((editor) => !this.editorIsTerminal(editor)) +      .forEach((editor) => { +        editor.selections.forEach((selection) => { +          // if (!selection.isEmpty) { +          rangeInFiles.push({ +            filepath: editor.document.uri.fsPath, +            range: { +              start: { +                line: selection.start.line, +                character: selection.start.character, +              }, +              end: { +                line: selection.end.line, +                character: selection.end.character, +              },              }, -          }, +          }); +          // }          }); -        // }        }); -    });      return rangeInFiles;    } diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 5e1689d1..dd24a8d8 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -6,6 +6,7 @@ import {    openEditorAndRevealRange,  } from "./util/vscode";  import { RangeInFile } from "./client"; +import { setFocusedOnContinueInput } from "./commands";  const WebSocket = require("ws");  let websocketConnections: { [url: string]: WebsocketConnection | undefined } = @@ -226,6 +227,10 @@ export function setupDebugPanel(          openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One);          break;        } +      case "blurContinueInput": { +        setFocusedOnContinueInput(false); +        break; +      }        case "withProgress": {          // This message allows withProgress to be used in the webview          if (data.done) { diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 28089fc6..1130a06a 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -3,21 +3,30 @@ import * as path from "path";  import * as fs from "fs";  import * as vscode from "vscode";  import { extensionContext, ideProtocolClient } from "./activation/activate"; +import { getMetaKeyLabel } from "./util/util"; +import { devDataPath } from "./activation/environmentSetup";  interface DiffInfo {    originalFilepath: string;    newFilepath: string;    editor?: vscode.TextEditor;    step_index: number; +  range: vscode.Range;  } -export const DIFF_DIRECTORY = path.join(os.homedir(), ".continue", "diffs"); +export const DIFF_DIRECTORY = path +  .join(os.homedir(), ".continue", "diffs") +  .replace(/^C:/, "c:");  class DiffManager {    // Create a temporary file in the global .continue directory which displays the updated version    // Doing this because virtual files are read-only    private diffs: Map<string, DiffInfo> = new Map(); +  diffAtNewFilepath(newFilepath: string): DiffInfo | undefined { +    return this.diffs.get(newFilepath); +  } +    private setupDirectory() {      // Make sure the diff directory exists      if (!fs.existsSync(DIFF_DIRECTORY)) { @@ -29,12 +38,25 @@ class DiffManager {    constructor() {      this.setupDirectory(); + +    // Listen for file closes, and if it's a diff file, clean up +    vscode.workspace.onDidCloseTextDocument((document) => { +      const newFilepath = document.uri.fsPath; +      const diffInfo = this.diffs.get(newFilepath); +      if (diffInfo) { +        this.cleanUpDiff(diffInfo, false); +      } +    });    }    private escapeFilepath(filepath: string): string {      return filepath.replace(/\\/g, "_").replace(/\//g, "_");    } +  private getNewFilepath(originalFilepath: string): string { +    return path.join(DIFF_DIRECTORY, this.escapeFilepath(originalFilepath)); +  } +    private openDiffEditor(      originalFilepath: string,      newFilepath: string @@ -47,7 +69,7 @@ class DiffManager {        return undefined;      } -    const rightUri = vscode.Uri.parse(newFilepath); +    const rightUri = vscode.Uri.file(newFilepath);      const leftUri = vscode.Uri.file(originalFilepath);      const title = "Continue Diff";      console.log( @@ -77,7 +99,7 @@ class DiffManager {      ) {        vscode.window          .showInformationMessage( -          "Accept (⌘⇧↩) or reject (⌘⇧⌫) at the top of the file.", +          `Accept (${getMetaKeyLabel()}⇧↩) or reject (${getMetaKeyLabel()}⇧⌫) at the top of the file.`,            "Got it",            "Don't show again"          ) @@ -95,6 +117,17 @@ class DiffManager {      return editor;    } +  private _findFirstDifferentLine(contentA: string, contentB: string): number { +    const linesA = contentA.split("\n"); +    const linesB = contentB.split("\n"); +    for (let i = 0; i < linesA.length && i < linesB.length; i++) { +      if (linesA[i] !== linesB[i]) { +        return i; +      } +    } +    return 0; +  } +    writeDiff(      originalFilepath: string,      newContent: string, @@ -103,18 +136,20 @@ class DiffManager {      this.setupDirectory();      // Create or update existing diff -    const newFilepath = path.join( -      DIFF_DIRECTORY, -      this.escapeFilepath(originalFilepath) -    ); +    const newFilepath = this.getNewFilepath(originalFilepath);      fs.writeFileSync(newFilepath, newContent);      // Open the diff editor if this is a new diff      if (!this.diffs.has(newFilepath)) { +      // Figure out the first line that is different +      const oldContent = ideProtocolClient.readFile(originalFilepath); +      const line = this._findFirstDifferentLine(oldContent, newContent); +        const diffInfo: DiffInfo = {          originalFilepath,          newFilepath,          step_index, +        range: new vscode.Range(line, 0, line + 1, 0),        };        this.diffs.set(newFilepath, diffInfo);      } @@ -126,12 +161,17 @@ class DiffManager {        this.diffs.set(newFilepath, diffInfo);      } +    vscode.commands.executeCommand( +      "workbench.action.files.revert", +      vscode.Uri.file(newFilepath) +    ); +      return newFilepath;    } -  cleanUpDiff(diffInfo: DiffInfo) { +  cleanUpDiff(diffInfo: DiffInfo, hideEditor: boolean = true) {      // Close the editor, remove the record, delete the file -    if (diffInfo.editor) { +    if (hideEditor && diffInfo.editor) {        vscode.window.showTextDocument(diffInfo.editor.document);        vscode.commands.executeCommand("workbench.action.closeActiveEditor");      } @@ -139,10 +179,38 @@ class DiffManager {      fs.unlinkSync(diffInfo.newFilepath);    } +  private inferNewFilepath() { +    const activeEditorPath = +      vscode.window.activeTextEditor?.document.uri.fsPath; +    if (activeEditorPath && path.dirname(activeEditorPath) === DIFF_DIRECTORY) { +      return activeEditorPath; +    } +    const visibleEditors = vscode.window.visibleTextEditors.map( +      (editor) => editor.document.uri.fsPath +    ); +    for (const editorPath of visibleEditors) { +      if (path.dirname(editorPath) === DIFF_DIRECTORY) { +        for (const otherEditorPath of visibleEditors) { +          if ( +            path.dirname(otherEditorPath) !== DIFF_DIRECTORY && +            this.getNewFilepath(otherEditorPath) === editorPath +          ) { +            return editorPath; +          } +        } +      } +    } + +    if (this.diffs.size === 1) { +      return Array.from(this.diffs.keys())[0]; +    } +    return undefined; +  } +    acceptDiff(newFilepath?: string) { -    // If no newFilepath is provided and there is only one in the dictionary, use that -    if (!newFilepath && this.diffs.size === 1) { -      newFilepath = Array.from(this.diffs.keys())[0]; +    // When coming from a keyboard shortcut, we have to infer the newFilepath from visible text editors +    if (!newFilepath) { +      newFilepath = this.inferNewFilepath();      }      if (!newFilepath) {        console.log("No newFilepath provided to accept the diff"); @@ -166,12 +234,14 @@ class DiffManager {          );          this.cleanUpDiff(diffInfo);        }); + +    recordAcceptReject(true, diffInfo);    }    rejectDiff(newFilepath?: string) {      // If no newFilepath is provided and there is only one in the dictionary, use that -    if (!newFilepath && this.diffs.size === 1) { -      newFilepath = Array.from(this.diffs.keys())[0]; +    if (!newFilepath) { +      newFilepath = this.inferNewFilepath();      }      if (!newFilepath) {        console.log( @@ -195,11 +265,50 @@ class DiffManager {        .then(() => {          this.cleanUpDiff(diffInfo);        }); + +    recordAcceptReject(false, diffInfo);    }  }  export const diffManager = new DiffManager(); +function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) { +  const collectOn = vscode.workspace +    .getConfiguration("continue") +    .get<boolean>("dataSwitch"); + +  if (collectOn) { +    const devDataDir = devDataPath(); +    const suggestionsPath = path.join(devDataDir, "suggestions.json"); + +    // Initialize suggestions list +    let suggestions = []; + +    // Check if suggestions.json exists +    if (fs.existsSync(suggestionsPath)) { +      const rawData = fs.readFileSync(suggestionsPath, "utf-8"); +      suggestions = JSON.parse(rawData); +    } + +    // Add the new suggestion to the list +    suggestions.push({ +      accepted, +      timestamp: Date.now(), +      suggestion: diffInfo.originalFilepath, +    }); + +    // Send the suggestion to the server +    // ideProtocolClient.sendAcceptRejectSuggestion(accepted); + +    // Write the updated suggestions back to the file +    fs.writeFileSync( +      suggestionsPath, +      JSON.stringify(suggestions, null, 4), +      "utf-8" +    ); +  } +} +  export async function acceptDiffCommand(newFilepath?: string) {    diffManager.acceptDiff(newFilepath);    ideProtocolClient.sendAcceptRejectDiff(true); diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 6959ec05..f2e580a1 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -17,15 +17,5 @@ async function dynamicImportAndActivate(context: vscode.ExtensionContext) {  }  export function activate(context: vscode.ExtensionContext) { -  // Only show progress if we have to setup -  vscode.window.withProgress( -    { -      location: vscode.ProgressLocation.Notification, -      title: "Setting up Continue extension...", -      cancellable: false, -    }, -    async () => { -      dynamicImportAndActivate(context); -    } -  ); +  dynamicImportAndActivate(context);  } diff --git a/extension/src/lang-server/codeActions.ts b/extension/src/lang-server/codeActions.ts new file mode 100644 index 00000000..892c69be --- /dev/null +++ b/extension/src/lang-server/codeActions.ts @@ -0,0 +1,58 @@ +import * as vscode from "vscode"; + +class ContinueQuickFixProvider implements vscode.CodeActionProvider { +  public static readonly providedCodeActionKinds = [ +    vscode.CodeActionKind.QuickFix, +  ]; + +  provideCodeActions( +    document: vscode.TextDocument, +    range: vscode.Range | vscode.Selection, +    context: vscode.CodeActionContext, +    token: vscode.CancellationToken +  ): vscode.ProviderResult<(vscode.Command | vscode.CodeAction)[]> { +    if (context.diagnostics.length === 0) { +      return []; +    } + +    const createQuickFix = (edit: boolean) => { +      const diagnostic = context.diagnostics[0]; +      const quickFix = new vscode.CodeAction( +        edit ? "Fix with Continue" : "Ask Continue", +        vscode.CodeActionKind.QuickFix +      ); +      quickFix.isPreferred = false; +      const surroundingRange = new vscode.Range( +        Math.max(0, range.start.line - 3), +        0, +        Math.min(document.lineCount, range.end.line + 3), +        0 +      ); +      quickFix.command = { +        command: "continue.quickFix", +        title: "Continue Quick Fix", +        arguments: [ +          diagnostic.message, +          document.getText(surroundingRange), +          edit, +        ], +      }; +      return quickFix; +    }; +    return [ +      // createQuickFix(true), +      createQuickFix(false), +    ]; +  } +} + +export default function registerQuickFixProvider() { +  // In your extension's activate function: +  vscode.languages.registerCodeActionsProvider( +    { language: "*" }, +    new ContinueQuickFixProvider(), +    { +      providedCodeActionKinds: ContinueQuickFixProvider.providedCodeActionKinds, +    } +  ); +} diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts index 778b98dc..ba80e557 100644 --- a/extension/src/lang-server/codeLens.ts +++ b/extension/src/lang-server/codeLens.ts @@ -2,7 +2,8 @@ import * as vscode from "vscode";  import { editorToSuggestions, editorSuggestionsLocked } from "../suggestions";  import * as path from "path";  import * as os from "os"; -import { DIFF_DIRECTORY } from "../diffs"; +import { DIFF_DIRECTORY, diffManager } from "../diffs"; +import { getMetaKeyLabel } from "../util/util";  class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {    public provideCodeLenses(      document: vscode.TextDocument, @@ -35,7 +36,7 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {        if (codeLenses.length === 2) {          codeLenses.push(            new vscode.CodeLens(range, { -            title: "(⌘⇧↩/⌘⇧⌫ to accept/reject all)", +            title: `(${getMetaKeyLabel()}⇧↩/${getMetaKeyLabel()}⇧⌫ to accept/reject all)`,              command: "",            })          ); @@ -53,15 +54,19 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {    ): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {      if (path.dirname(document.uri.fsPath) === DIFF_DIRECTORY) {        const codeLenses: vscode.CodeLens[] = []; -      const range = new vscode.Range(0, 0, 1, 0); +      let range = new vscode.Range(0, 0, 1, 0); +      const diffInfo = diffManager.diffAtNewFilepath(document.uri.fsPath); +      if (diffInfo) { +        range = diffInfo.range; +      }        codeLenses.push(          new vscode.CodeLens(range, { -          title: "Accept ✅ (⌘⇧↩)", +          title: `Accept All ✅ (${getMetaKeyLabel()}⇧↩)`,            command: "continue.acceptDiff",            arguments: [document.uri.fsPath],          }),          new vscode.CodeLens(range, { -          title: "Reject ❌ (⌘⇧⌫)", +          title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`,            command: "continue.rejectDiff",            arguments: [document.uri.fsPath],          }) diff --git a/extension/src/suggestions.ts b/extension/src/suggestions.ts index 6e5a444f..5c2b8860 100644 --- a/extension/src/suggestions.ts +++ b/extension/src/suggestions.ts @@ -1,9 +1,6 @@  import * as vscode from "vscode"; -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";  import { openEditorAndRevealRange } from "./util/vscode"; -import { translate, readFileAtRange } from "./util/vscode"; -import * as fs from "fs"; -import * as path from "path"; +import { translate } from "./util/vscode";  import { registerAllCodeLensProviders } from "./lang-server/codeLens";  import { extensionContext, ideProtocolClient } from "./activation/activate"; @@ -214,62 +211,6 @@ function selectSuggestion(          : suggestion.newRange;    } -  let workspaceDir = vscode.workspace.workspaceFolders -    ? vscode.workspace.workspaceFolders[0]?.uri.fsPath -    : undefined; - -  let collectOn = vscode.workspace -    .getConfiguration("continue") -    .get<boolean>("dataSwitch"); - -  if (workspaceDir && collectOn) { -    let continueDir = path.join(workspaceDir, ".continue"); - -    // Check if .continue directory doesn't exists -    if (!fs.existsSync(continueDir)) { -      fs.mkdirSync(continueDir); -    } - -    let suggestionsPath = path.join(continueDir, "suggestions.json"); - -    // Initialize suggestions list -    let suggestions = []; - -    // Check if suggestions.json exists -    if (fs.existsSync(suggestionsPath)) { -      let rawData = fs.readFileSync(suggestionsPath, "utf-8"); -      suggestions = JSON.parse(rawData); -    } - -    const accepted = -      accept === "new" || (accept === "selected" && suggestion.newSelected); -    suggestions.push({ -      accepted, -      timestamp: Date.now(), -      suggestion: suggestion.newContent, -    }); -    ideProtocolClient.sendAcceptRejectSuggestion(accepted); - -    // Write the updated suggestions back to the file -    fs.writeFileSync( -      suggestionsPath, -      JSON.stringify(suggestions, null, 4), -      "utf-8" -    ); - -    // If it's not already there, add .continue to .gitignore -    const gitignorePath = path.join(workspaceDir, ".gitignore"); -    if (fs.existsSync(gitignorePath)) { -      const gitignoreData = fs.readFileSync(gitignorePath, "utf-8"); -      const gitIgnoreLines = gitignoreData.split("\n"); -      if (!gitIgnoreLines.includes(".continue")) { -        fs.appendFileSync(gitignorePath, "\n.continue\n"); -      } -    } else { -      fs.writeFileSync(gitignorePath, ".continue\n"); -    } -  } -    rangeToDelete = new vscode.Range(      rangeToDelete.start,      new vscode.Position(rangeToDelete.end.line, 0) @@ -302,7 +243,6 @@ function selectSuggestion(  }  export function acceptSuggestionCommand(key: SuggestionRanges | null = null) { -  sendTelemetryEvent(TelemetryEvent.SuggestionAccepted);    selectSuggestion("selected", key);  } @@ -329,7 +269,6 @@ export function rejectAllSuggestionsCommand() {  export async function rejectSuggestionCommand(    key: SuggestionRanges | null = null  ) { -  sendTelemetryEvent(TelemetryEvent.SuggestionRejected);    selectSuggestion("old", key);  } diff --git a/extension/src/telemetry.ts b/extension/src/telemetry.ts deleted file mode 100644 index db5cb8ca..00000000 --- a/extension/src/telemetry.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as Segment from "@segment/analytics-node"; -import * as vscode from "vscode"; - -// Setup Segment -const SEGMENT_WRITE_KEY = "57yy2uYXH2bwMuy7djm9PorfFlYqbJL1"; -const analytics = new Segment.Analytics({ writeKey: SEGMENT_WRITE_KEY }); -analytics.identify({ -  userId: vscode.env.machineId, -  //   traits: { -  //     name: "Michael Bolton", -  //     email: "mbolton@example.com", -  //     createdAt: new Date("2014-06-14T02:00:19.467Z"), -  //   }, -}); - -// Enum of telemetry events -export enum TelemetryEvent { -  // Extension has been activated -  ExtensionActivated = "ExtensionActivated", -  // Suggestion has been accepted -  SuggestionAccepted = "SuggestionAccepted", -  // Suggestion has been rejected -  SuggestionRejected = "SuggestionRejected", -  // Queried universal prompt -  UniversalPromptQuery = "UniversalPromptQuery", -  // `Explain Code` button clicked -  ExplainCode = "ExplainCode", -  // `Generate Ideas` button clicked -  GenerateIdeas = "GenerateIdeas", -  // `Suggest Fix` button clicked -  SuggestFix = "SuggestFix", -  // `Create Test` button clicked -  CreateTest = "CreateTest", -  // `AutoDebug This Test` button clicked -  AutoDebugThisTest = "AutoDebugThisTest", -  // Command run to generate docstring -  GenerateDocstring = "GenerateDocstring", -  // Error setting up the extension -  ExtensionSetupError = "ExtensionSetupError", -} - -export function sendTelemetryEvent( -  event: TelemetryEvent, -  properties?: Record<string, any> -) { -  if (!vscode.env.isTelemetryEnabled) return; - -  analytics.track({ -    event, -    userId: vscode.env.machineId, -    properties, -  }); -} diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts index 7fd71ddd..3044898e 100644 --- a/extension/src/util/messenger.ts +++ b/extension/src/util/messenger.ts @@ -15,7 +15,7 @@ export abstract class Messenger {    abstract onOpen(callback: () => void): void;    abstract onClose(callback: () => void): void; -   +    abstract onError(callback: () => void): void;    abstract sendAndReceive(messageType: string, data: any): Promise<any>; diff --git a/extension/src/util/util.ts b/extension/src/util/util.ts index d33593e1..dfc10c90 100644 --- a/extension/src/util/util.ts +++ b/extension/src/util/util.ts @@ -1,5 +1,6 @@  import { RangeInFile, SerializedDebugContext } from "../client";  import * as fs from "fs"; +const os = require("os");  function charIsEscapedAtIndex(index: number, str: string): boolean {    if (index === 0) return false; @@ -113,3 +114,31 @@ export function debounced(delay: number, fn: Function) {      }, delay);    };  } + +type Platform = "mac" | "linux" | "windows" | "unknown"; + +function getPlatform(): Platform { +  const platform = os.platform(); +  if (platform === "darwin") { +    return "mac"; +  } else if (platform === "linux") { +    return "linux"; +  } else if (platform === "win32") { +    return "windows"; +  } else { +    return "unknown"; +  } +} + +export function getMetaKeyLabel() { +  const platform = getPlatform(); +  switch (platform) { +    case "mac": +      return "⌘"; +    case "linux": +    case "windows": +      return "^"; +    default: +      return "⌘"; +  } +}  | 
