diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 3d1e15e5..dd59adb6 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,6 +1,6 @@ # Code - OSS Development Container -[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces). diff --git a/.eslint-ignore b/.eslint-ignore index 233c9b3e..969f7a5b 100644 --- a/.eslint-ignore +++ b/.eslint-ignore @@ -11,6 +11,7 @@ **/extensions/markdown-language-features/notebook-out/** **/extensions/markdown-math/notebook-out/** **/extensions/notebook-renderers/renderer-out/index.js +**/extensions/open-remote-ssh/out/extension.js **/extensions/simple-browser/media/index.js **/extensions/terminal-suggest/src/completions/upstream/** **/extensions/terminal-suggest/src/shell/zshBuiltinsCache.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 8da51487..00000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# ensure the API police is aware of changes to the vscode-dts file -# this is only about the final API, not about proposed API changes -src/vscode-dts/vscode.d.ts @jrieken @mjbvz diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index ce9e17c1..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve ---- - - - - - - - -Does this issue occur when all extensions are disabled?: Yes/No - - - -- VS Code Version: -- OS Version: - -Steps to Reproduce: - -1. -2. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index fe112524..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Question - url: https://stackoverflow.com/questions/tagged/visual-studio-code - about: Please ask and answer questions here. - - name: Extension Development - url: https://github.com/microsoft/vscode-discussions/discussions - about: Please use this for extension development questions and ideas. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b9c6c83c..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - - - - - - diff --git a/.github/classifier.json b/.github/classifier.json deleted file mode 100644 index 93ca6f32..00000000 --- a/.github/classifier.json +++ /dev/null @@ -1,311 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/vscode-github-triage-actions/stable/classifier-deep/apply/apply-labels/deep-classifier-config.schema.json", - "vacation": [], - "assignees": { - "nameToOverrideAccuracyOf": {"accuracy": 0.8} - }, - "labels": { - "accessibility": { "assign": ["meganrogge"]}, - "api": {"assign": ["jrieken"]}, - "api-finalization": {"assign": []}, - "api-proposal": {"assign": ["jrieken"]}, - "authentication": {"assign": ["TylerLeonhardt"]}, - "bisect-ext": {"assign": ["jrieken"]}, - "bot-proposal": {"assign": ["lramos15"]}, - "bracket-pair-colorization": {"assign": ["hediet"]}, - "bracket-pair-guides": {"assign": ["hediet"]}, - "breadcrumbs": {"assign": ["jrieken"]}, - "callhierarchy": {"assign": ["jrieken"]}, - "chrome-devtools": {"assign": ["deepak1556"]}, - "cloud-changes": {"assign": ["joyceerhl"]}, - "code-cli": {"assign": ["connor4312"]}, - "code-lens": {"assign": ["jrieken"]}, - "code-server-web": {"assign": ["aeschli"]}, - "command-center": {"assign": ["jrieken"]}, - "comments": {"assign": ["alexr00"]}, - "config": {"assign": ["sandy081"]}, - "containers": {"assign": ["chrmarti"]}, - "context-keys": {"assign": ["ulugbekna"]}, - "continue-working-on": {"assign": ["joyceerhl"]}, - "css-less-scss": {"assign": ["aeschli"]}, - "custom-editors": {"assign": ["mjbvz"]}, - "debug": {"assign": ["roblourens"]}, - "debug-disassembly": {"assign": []}, - "dialogs": {"assign": ["sbatten"]}, - "diff-editor": {"assign": ["hediet"]}, - "dropdown": {"assign": ["lramos15"]}, - "editor-api": {"assign": ["alexdima"]}, - "editor-autoclosing": {"assign": ["alexdima"]}, - "editor-autoindent": {"assign": ["rebornix"]}, - "editor-bracket-matching": {"assign": ["hediet"]}, - "editor-clipboard": {"assign": ["alexdima", "rebornix"]}, - "editor-code-actions": {"assign": ["mjbvz", "justschen"]}, - "editor-color-picker": {"assign": ["aiday-mar"]}, - "editor-columnselect": {"assign": ["alexdima"]}, - "editor-commands": {"assign": ["alexdima"]}, - "editor-comments": {"assign": ["alexdima"]}, - "editor-contrib": {"assign": ["alexdima"]}, - "editor-core": {"assign": ["alexdima"]}, - "editor-drag-and-drop": {"assign": ["rebornix"]}, - "editor-error-widget": {"assign": ["sandy081"]}, - "editor-find": {"assign": ["rebornix"]}, - "editor-folding": {"assign": ["aeschli"]}, - "editor-highlight": {"assign": ["alexdima"]}, - "editor-hover": {"assign": ["aiday-mar"]}, - "editor-indent-detection": {"assign": ["alexdima"]}, - "editor-indent-guides": {"assign": ["hediet"]}, - "editor-input": {"assign": ["alexdima"]}, - "editor-input-IME": {"assign": ["alexdima"]}, - "editor-insets": {"assign": ["jrieken"]}, - "editor-minimap": {"assign": ["alexdima"]}, - "editor-multicursor": {"assign": ["alexdima"]}, - "editor-parameter-hints": {"assign": ["mjbvz"]}, - "editor-render-whitespace": {"assign": ["alexdima"]}, - "editor-rendering": {"assign": ["alexdima"]}, - "editor-RTL": {"assign": ["alexdima"]}, - "editor-scrollbar": {"assign": ["alexdima"]}, - "editor-sorting": {"assign": ["alexdima"]}, - "editor-sticky-scroll": {"assign": ["aiday-mar"]}, - "editor-symbols": {"assign": ["jrieken"]}, - "editor-synced-region": {"assign": ["aeschli"]}, - "editor-textbuffer": {"assign": ["alexdima", "rebornix"]}, - "editor-theming": {"assign": ["alexdima"]}, - "editor-wordnav": {"assign": ["alexdima"]}, - "editor-wrapping": {"assign": ["alexdima"]}, - "emmet": {"assign": ["rzhao271"]}, - "emmet-parse": {"assign": ["rzhao271"]}, - "error-list": {"assign": ["sandy081"]}, - "extension-activation": {"assign": ["joyceerhl", "alexdima"]}, - "extension-host": {"assign": ["alexdima"]}, - "extension-prerelease": {"assign": ["sandy081"]}, - "extension-recommendations": {"assign": ["sandy081"]}, - "extensions": {"assign": ["sandy081"]}, - "extensions-development": {"assign": []}, - "file-decorations": {"assign": ["jrieken"]}, - "file-encoding": {"assign": ["bpasero"]}, - "file-explorer": {"assign": ["lramos15"]}, - "file-glob": {"assign": ["bpasero"]}, - "file-io": {"assign": ["bpasero"]}, - "file-nesting": {"assign": ["lramos15"]}, - "file-watcher": {"assign": ["bpasero"]}, - "font-rendering": {"assign": ["rzhao271"]}, - "formatting": {"assign": ["jrieken"]}, - "getting-started": {"assign": ["bhavyaus"]}, - "ghost-text": {"assign": ["hediet"]}, - "git": {"assign": ["lszomoru"]}, - "github": {"assign": ["lszomoru"]}, - "github-authentication": {"assign": ["TylerLeonhardt"]}, - "github-repositories": {"assign": ["lszomoru"]}, - "gpu": {"assign": ["deepak1556"]}, - "grammar": {"assign": ["mjbvz"]}, - "grid-widget": {"assign": ["joaomoreno"]}, - "html": {"assign": ["aeschli"]}, - "icon-brand": {"assign": ["daviddossett"]}, - "icons-product": {"assign": ["daviddossett"]}, - "image-preview": {"assign": ["mjbvz"]}, - "inlay-hints": {"assign": ["jrieken", "hediet"]}, - "inline-completions": {"assign": ["hediet"]}, - "install-update": {"assign": ["joaomoreno"], "accuracy": 0.85}, - "intellisense-config": {"assign": ["rzhao271"]}, - "interactive-playground": {"assign": ["chrmarti"]}, - "interactive-window": {"assign": ["amunger", "rebornix"]}, - "ipc": {"assign": ["joaomoreno"]}, - "issue-bot": {"assign": ["chrmarti"]}, - "issue-reporter": {"assign": ["justschen"]}, - "javascript": {"assign": ["mjbvz"]}, - "json": {"assign": ["aeschli"]}, - "json-sorting": {"assign": ["aiday-mar"]}, - "keybindings": {"assign": ["ulugbekna"]}, - "keybindings-editor": {"assign": ["ulugbekna"]}, - "keyboard-layout": {"assign": ["ulugbekna"]}, - "L10N": {"assign": ["TylerLeonhardt", "csigs"]}, - "l10n-platform": {"assign": ["TylerLeonhardt"]}, - "label-provider": {"assign": ["lramos15"]}, - "languages-basic": {"assign": ["aeschli"]}, - "languages-diagnostics": {"assign": ["jrieken"]}, - "languages-guessing": {"assign": ["TylerLeonhardt"]}, - "layout": {"assign": ["benibenj"]}, - "lcd-text-rendering": {"assign": []}, - "list-widget": {"assign": ["joaomoreno"]}, - "live-preview": {"assign": []}, - "log": {"assign": ["sandy081"]}, - "markdown": {"assign": ["mjbvz"]}, - "marketplace": {"assign": ["isidorn"]}, - "menus": {"assign": ["sbatten"]}, - "merge-conflict": {"assign": ["chrmarti"]}, - "merge-editor": {"assign": ["hediet"]}, - "merge-editor-workbench": {"assign": ["jrieken"]}, - "monaco-editor": {"assign": []}, - "native-file-dialog": {"assign": ["deepak1556"]}, - "network": {"assign": ["deepak1556"]}, - "notebook": {"assign": ["rebornix"]}, - "notebook-api": {"assign": []}, - "notebook-builtin-renderers": {"assign": []}, - "notebook-cell-editor": {"assign": []}, - "notebook-celltoolbar": {"assign": []}, - "notebook-clipboard": {"assign": []}, - "notebook-commenting": {"assign": []}, - "notebook-debugging": {"assign": []}, - "notebook-diff": {"assign": []}, - "notebook-dnd": {"assign": []}, - "notebook-execution": {"assign": []}, - "notebook-find": {"assign": []}, - "notebook-folding": {"assign": []}, - "notebook-getting-started": {"assign": []}, - "notebook-getting-globaltoolbar": {"assign": []}, - "notebook-ipynb": {"assign": []}, - "notebook-kernel": {"assign": []}, - "notebook-kernel-picker": {"assign": []}, - "notebook-keybinding": {"assign": []}, - "notebook-language": {"assign": []}, - "notebook-layout": {"assign": []}, - "notebook-markdown": {"assign": []}, - "notebook-math": {"assign": []}, - "notebook-minimap": {"assign": []}, - "notebook-multiselect": {"assign": []}, - "notebook-output": {"assign": []}, - "notebook-perf": {"assign": []}, - "notebook-remote": {"assign": []}, - "notebook-rendering": {"assign": []}, - "notebook-serialization": {"assign": []}, - "notebook-serverless-web": {"assign": []}, - "notebook-statusbar": {"assign": []}, - "notebook-toc-outline": {"assign": []}, - "notebook-undo-redo": {"assign": []}, - "notebook-variables": {"assign": []}, - "notebook-workbench-integration": {"assign": []}, - "notebook-workflow": {"assign": []}, - "open-editors": {"assign": ["lramos15"]}, - "opener": {"assign": ["mjbvz"]}, - "outline": {"assign": ["jrieken"]}, - "output": {"assign": ["sandy081"]}, - "perf": {"assign": []}, - "perf-bloat": {"assign": []}, - "perf-startup": {"assign": []}, - "php": {"assign": ["roblourens"]}, - "portable-mode": {"assign": ["joaomoreno"]}, - "proxy": {"assign": ["chrmarti"]}, - "quick-open": {"assign": ["TylerLeonhardt"]}, - "quick-pick": {"assign": ["TylerLeonhardt"]}, - "references-viewlet": {"assign": ["jrieken"]}, - "release-notes": {"assign": []}, - "remote": {"assign": []}, - "remote-connection": {"assign": ["alexdima"]}, - "remote-explorer": {"assign": ["alexr00"]}, - "remote-tunnel": {"assign": ["aeschli", "connor4312"]}, - "rename": {"assign": ["jrieken"]}, - "runCommands": {"assign": ["ulugbekna"]}, - "sandbox": {"assign": ["deepak1556"]}, - "sash-widget": {"assign": ["joaomoreno"]}, - "scm": {"assign": ["lszomoru"]}, - "screencast-mode": {"assign": ["joaomoreno"]}, - "search": {"assign": ["osortega"]}, - "search-api": {"assign": ["osortega"]}, - "search-editor": {"assign": ["osortega"]}, - "search-replace": {"assign": ["sandy081"]}, - "semantic-tokens": {"assign": ["alexdima", "aeschli"]}, - "server": {"assign": ["alexdima"]}, - "settings-editor": {"assign": ["rzhao271"]}, - "settings-search": {"assign": ["rzhao271"]}, - "settings-sync": {"assign": ["sandy081"]}, - "settings-sync-server": {"assign": ["Tyriar", "lszomoru"]}, - "shared-process": {"assign": []}, - "simple-file-dialog": {"assign": ["alexr00"]}, - "smart-select": {"assign": ["jrieken"]}, - "snap": {"assign": ["deepak1556"]}, - "snippets": {"assign": ["jrieken"]}, - "splitview-widget": {"assign": ["joaomoreno"]}, - "suggest": {"assign": ["jrieken"]}, - "table-widget": {"assign": ["joaomoreno"]}, - "tasks": {"assign": ["meganrogge"], "accuracy": 0.85}, - "telemetry": {"assign": ["lramos15"]}, - "terminal": {"assign": ["meganrogge"]}, - "terminal-conpty": {"assign": ["meganrogge"]}, - "terminal-editors": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-external": {"assign": ["meganrogge"]}, - "terminal-find": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-input": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-layout": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-links": {"assign": ["Tyriar"]}, - "terminal-local-echo": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-persistence": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-process": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-profiles": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-quick-fix": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-rendering": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-search": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-bash": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-cmd": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-fish": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-git-bash": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-integration": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-pwsh": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-zsh": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-tabs": {"assign": ["meganrogge"]}, - "terminal-winpty": {"assign": ["Tyriar", "meganrogge"]}, - "testing": {"assign": ["connor4312"]}, - "themes": {"assign": ["aeschli"]}, - "timeline": {"assign": ["lramos15"]}, - "timeline-git": {"assign": ["lszomoru"]}, - "timeline-local-history": {"assign": ["bpasero"]}, - "titlebar": {"assign": ["benibenj"]}, - "tokenization": {"assign": ["alexdima"]}, - "touch/pointer": {"assign": []}, - "trackpad/scroll": {"assign": []}, - "tree-sticky-scroll": {"assign": ["benibenj"]}, - "tree-views": {"assign": ["alexr00"]}, - "tree-widget": {"assign": ["joaomoreno"]}, - "typehierarchy": {"assign": ["jrieken"]}, - "typescript": {"assign": ["mjbvz"]}, - "undo-redo": {"assign": ["alexdima"]}, - "unicode-highlight": {"assign": ["hediet"]}, - "uri": {"assign": ["jrieken"]}, - "user-profiles": {"assign": ["sandy081"]}, - "ux": {"assign": ["daviddossett"]}, - "variable-resolving": {"assign": ["alexr00"]}, - "VIM": {"assign": ["alexdima", "rebornix"]}, - "virtual-workspaces": {"assign": []}, - "vscode.dev": {"assign": []}, - "vscode-build": {"assign": []}, - "vscode-website": {"assign": ["rzhao271"]}, - "web": {"assign": []}, - "webview": {"assign": ["mjbvz"]}, - "webview-views": {"assign": ["mjbvz"]}, - "workbench-actions": {"assign": ["bpasero"]}, - "workbench-auxwindow": {"assign": ["bpasero"]}, - "workbench-banner": {"assign": ["lszomoru", "sbatten"]}, - "workbench-cli": {"assign": ["bpasero"]}, - "workbench-diagnostics": {"assign": ["Tyriar"]}, - "workbench-dnd": {"assign": ["bpasero"]}, - "workbench-editor-grid": {"assign": ["benibenj"]}, - "workbench-editor-groups": {"assign": ["bpasero"]}, - "workbench-editor-resolver": {"assign": ["lramos15"]}, - "workbench-editors": {"assign": ["bpasero"]}, - "workbench-electron": {"assign": ["deepak1556"]}, - "workbench-fonts": {"assign": []}, - "workbench-history": {"assign": ["bpasero"]}, - "workbench-hot-exit": {"assign": ["bpasero"]}, - "workbench-hover": {"assign": ["Tyriar"]}, - "workbench-launch": {"assign": []}, - "workbench-link": {"assign": []}, - "workbench-multiroot": {"assign": ["bpasero"]}, - "workbench-notifications": {"assign": ["bpasero"]}, - "workbench-os-integration": {"assign": ["bpasero"]}, - "workbench-rapid-render": {"assign": ["jrieken"]}, - "workbench-run-as-admin": {"assign": ["bpasero"]}, - "workbench-state": {"assign": ["bpasero"]}, - "workbench-status": {"assign": ["bpasero"]}, - "workbench-tabs": {"assign": ["benibenj"]}, - "workbench-touchbar": {"assign": ["bpasero"]}, - "workbench-untitled-editors": {"assign": ["bpasero"]}, - "workbench-views": {"assign": ["benibenj"]}, - "workbench-welcome": {"assign": ["lramos15"]}, - "workbench-window": {"assign": ["bpasero"]}, - "workbench-workspace": {"assign": []}, - "workbench-zen": {"assign": ["benibenj"]}, - "workspace-edit": {"assign": ["jrieken"]}, - "workspace-symbols": {"assign": []}, - "workspace-trust": {"assign": ["lszomoru", "sbatten"]}, - "zoom": {"assign": ["alexdima"] } - } -} diff --git a/.github/commands.json b/.github/commands.json deleted file mode 100644 index 38da9791..00000000 --- a/.github/commands.json +++ /dev/null @@ -1,542 +0,0 @@ -[ - { - "type": "comment", - "name": "question", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "*question" - }, - { - "type": "comment", - "name": "dev-question", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "*dev-question" - }, - { - "type": "label", - "name": "*question", - "action": "close", - "reason": "not_planned", - "comment": "We closed this issue because it is a question about using VS Code rather than an issue or feature request. Please search for help on [StackOverflow](https://aka.ms/vscodestackoverflow), where the community has already answered thousands of similar questions. You may find their [guide on asking a new question](https://aka.ms/vscodestackoverflowquestion) helpful if your question has not already been asked. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" - }, - { - "type": "label", - "name": "*dev-question", - "action": "close", - "reason": "not_planned", - "comment": "We have a great extension developer community over on [GitHub discussions](https://github.com/microsoft/vscode-discussions/discussions) and [Slack](https://vscode-dev-community.slack.com/) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!" - }, - { - "type": "label", - "name": "*extension-candidate", - "action": "close", - "reason": "not_planned", - "comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" - }, - { - "type": "label", - "name": "*not-reproducible", - "action": "close", - "reason": "not_planned", - "comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) might help you with that.\n\nHappy Coding!" - }, - { - "type": "label", - "name": "*out-of-scope", - "action": "close", - "reason": "not_planned", - "comment": "We closed this issue because we [don't plan to address it](https://aka.ms/vscode-out-of-scope) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nThanks for your understanding, and happy coding!" - }, - { - "type": "label", - "name": "wont-fix", - "action": "close", - "reason": "not_planned", - "comment": "We closed this issue because we [don't plan to address it](https://github.com/microsoft/vscode/wiki/Issue-Grooming#wont-fix-bugs).\n\nThanks for your understanding, and happy coding!" - }, - { - "type": "comment", - "name": "causedByExtension", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "*caused-by-extension" - }, - { - "type": "label", - "name": "*caused-by-extension", - "action": "close", - "reason": "not_planned", - "comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). If you don't know which extension is causing the problem, you can run `Help: Start extension bisect` from the command palette (F1) to help identify the problem extension.\n\nHappy Coding!" - }, - { - "type": "label", - "name": "*as-designed", - "action": "close", - "reason": "not_planned", - "comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" - }, - { - "type": "label", - "name": "L10N", - "assign": [ - "csigs", - "TylerLeonhardt" - ] - }, - { - "type": "comment", - "name": "duplicate", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "*duplicate" - }, - { - "type": "label", - "name": "*duplicate", - "action": "close", - "reason": "not_planned", - "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "verified", - "allowUsers": [ - "@author" - ], - "action": "updateLabels", - "addLabel": "verified", - "removeLabel": "author-verification-requested", - "requireLabel": "author-verification-requested", - "disallowLabel": "unreleased" - }, - { - "type": "comment", - "name": "confirm", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "confirmed", - "removeLabel": "confirmation-pending" - }, - { - "type": "comment", - "name": "confirmationPending", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "confirmation-pending", - "removeLabel": "confirmed" - }, - { - "type": "comment", - "name": "needsMoreInfo", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "updateLabels", - "addLabel": "~info-needed" - }, - { - "type": "comment", - "name": "needsPerfInfo", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "addLabel": "info-needed", - "comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!" - }, - { - "type": "comment", - "name": "jsDebugLogs", - "action": "updateLabels", - "addLabel": "info-needed", - "comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠️ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com" - }, - { - "type": "comment", - "name": "closedWith", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "completed", - "addLabel": "unreleased" - }, - { - "type": "comment", - "name": "spam", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "invalid" - }, - { - "type": "comment", - "name": "a11ymas", - "allowUsers": [ - "AccessibilityTestingTeam-TCS", - "dixitsonali95", - "Mohini78", - "ChitrarupaSharma", - "mspatil110", - "umasarath52", - "v-umnaik" - ], - "action": "updateLabels", - "addLabel": "a11ymas" - }, - { - "type": "label", - "name": "*off-topic", - "action": "close", - "reason": "not_planned", - "comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extPython", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Python extension. Please file the issue to the [Python extension repository](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extJupyter", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Jupyter extension. Please file the issue to the [Jupyter extension repository](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extC", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C extension. Please file the issue to the [C extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extC++", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extCpp", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extTS", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the TypeScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extJS", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extC#", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the C# extension. Please file the issue to the [C# extension repository](https://github.com/OmniSharp/omnisharp-vscode.git). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extGo", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Go extension. Please file the issue to the [Go extension repository](https://github.com/golang/vscode-go). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extPowershell", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the PowerShell extension. Please file the issue to the [PowerShell extension repository](https://github.com/PowerShell/vscode-powershell). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extLiveShare", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the LiveShare extension. Please file the issue to the [LiveShare repository](https://github.com/MicrosoftDocs/live-share). Make sure to check their [contributing guidelines](https://github.com/MicrosoftDocs/live-share/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extDocker", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Docker extension. Please file the issue to the [Docker extension repository](https://github.com/microsoft/vscode-docker). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extJava", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Java extension. Please file the issue to the [Java extension repository](https://github.com/redhat-developer/vscode-java). Make sure to check their [troubleshooting instructions](https://github.com/redhat-developer/vscode-java/wiki/Troubleshooting) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extJavaDebug", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Java Debugger extension. Please file the issue to the [Java Debugger repository](https://github.com/microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extCodespaces", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Codespaces extension. Please file the issue in the [Codespaces Discussion Forum](http://aka.ms/ghcs-feedback). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "extCopilot", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "close", - "reason": "not_planned", - "addLabel": "*caused-by-extension", - "comment": "It looks like this is caused by the Copilot extension. Please file the issue in the [Copilot Discussion Forum](https://github.com/community/community/discussions/categories/copilot). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" - }, - { - "type": "comment", - "name": "gifPlease", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "comment", - "addLabel": "info-needed", - "comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette). Lastly, please attach this file via the GitHub web interface as emailed responses will strip files out from the issue.\n\nHappy coding!" - }, - { - "type": "comment", - "name": "confirmPlease", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "comment", - "addLabel": "info-needed", - "comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!" - }, - { - "__comment__": "Allows folks on the team to label issues by commenting: `\\label My-Label` ", - "type": "comment", - "name": "label", - "allowUsers": [] - }, - { - "type": "comment", - "name": "assign", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ] - }, - { - "type": "label", - "name": "*workspace-trust-docs", - "action": "close", - "reason": "not_planned", - "comment": "This issue appears to be the result of the new workspace trust feature shipped in June 2021. This security-focused feature has major impact on the functionality of VS Code. Due to the volume of issues, we ask that you take some time to review our [comprehensive documentation](https://aka.ms/vscode-workspace-trust) on the feature. If your issue is still not resolved, please let us know." - }, - { - "type": "label", - "name": "~verification-steps-needed", - "action": "updateLabels", - "addLabel": "verification-steps-needed", - "removeLabel": "~verification-steps-needed", - "comment": "Friendly ping! Looks like this issue requires some further steps to be verified. Please provide us with the steps necessary to verify this issue." - }, - { - "type": "label", - "name": "~info-needed", - "action": "updateLabels", - "addLabel": "info-needed", - "removeLabel": "~info-needed", - "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" - }, - { - "type": "label", - "name": "~version-info-needed", - "action": "updateLabels", - "addLabel": "info-needed", - "removeLabel": "~version-info-needed", - "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" - }, - { - "type": "label", - "name": "~confirmation-needed", - "action": "updateLabels", - "addLabel": "info-needed", - "removeLabel": "~confirmation-needed", - "comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!" - } -] diff --git a/.github/commands/codespaces_issue.yml b/.github/commands/codespaces_issue.yml deleted file mode 100644 index 7abacafa..00000000 --- a/.github/commands/codespaces_issue.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Learn more about the syntax here: -# https://docs.github.com/en/early-access/github/save-time-with-slash-commands/syntax-for-user-defined-slash-commands ---- -trigger: codespaces_issue -title: Codespaces Issue -description: Report downstream - -steps: - - type: fill - template: |- - This looks like an issue with the Codespaces service which we don't track in this repository. You can report this to the Codespaces team at https://github.com/orgs/community/discussions/categories/codespaces diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 6f20c66c..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,57 +0,0 @@ -# Coding Guidelines - -## Introduction - -These are VS Code coding guidelines. Please also review our [Source Code Organisation](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) page. - -## Indentation - -We use tabs, not spaces. - -## Naming Conventions - -* Use PascalCase for `type` names -* Use PascalCase for `enum` values -* Use camelCase for `function` and `method` names -* Use camelCase for `property` names and `local variables` -* Use whole words in names when possible - -## Types - -* Do not export `types` or `functions` unless you need to share it across multiple components -* Do not introduce new `types` or `values` to the global namespace - -## Comments - -* When there are comments for `functions`, `interfaces`, `enums`, and `classes` use JSDoc style comments - -## Strings - -* Use "double quotes" for strings shown to the user that need to be externalized (localized) -* Use 'single quotes' otherwise -* All strings visible to the user need to be externalized - -## Style - -* Use arrow functions `=>` over anonymous function expressions -* Only surround arrow function parameters when necessary. For example, `(x) => x + x` is wrong but the following are correct: - -```javascript -x => x + x -(x, y) => x + y -(x: T, y: T) => x === y -``` - -* Always surround loop and conditional bodies with curly braces -* Open curly braces always go on the same line as whatever necessitates them -* Parenthesized constructs should have no surrounding whitespace. A single space follows commas, colons, and semicolons in those constructs. For example: - -```javascript -for (let i = 0, n = str.length; i < 10; i++) { - if (x < 10) { - foo(); - } -} - -function f(x: number, y: string): void { } -``` diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5ace4600..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/endgame/insiders.yml b/.github/endgame/insiders.yml deleted file mode 100644 index 4996474f..00000000 --- a/.github/endgame/insiders.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - insidersLabel: "insiders", - insidersColor: "006b75", - action: "add", - perform: true, -} diff --git a/.github/insiders.yml b/.github/insiders.yml deleted file mode 100644 index ab59e873..00000000 --- a/.github/insiders.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - insidersLabel: "insiders", - insidersColor: "006b75", - action: "remove", - perform: true, -} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 5335e645..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/.github/similarity.yml b/.github/similarity.yml deleted file mode 100644 index cb00a4c9..00000000 --- a/.github/similarity.yml +++ /dev/null @@ -1,5 +0,0 @@ -{ - perform: true, - whenCreatedByTeam: false, - comment: "(Experimental duplicate detection)\nThanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}", -} diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml deleted file mode 100644 index d5f166a4..00000000 --- a/.github/workflows/basic.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: Basic checks - -on: workflow_dispatch - -# on: -# push: -# branches: -# - main -# pull_request: -# branches: -# - main - -jobs: - main: - if: github.ref != 'refs/heads/main' - name: Compilation, Unit and Integration Tests - runs-on: ubuntu-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - - name: Setup Build Environment - run: | - sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb - sudo chmod +x /etc/init.d/xvfb - sudo update-rc.d xvfb defaults - sudo service xvfb start - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci - - - name: Compile and Download - run: npm exec -- npm-run-all -lp compile "electron x64" - - - name: Run Unit Tests - id: electron-unit-tests - run: DISPLAY=:10 ./scripts/test.sh - - - name: Run Integration Tests (Electron) - id: electron-integration-tests - run: DISPLAY=:10 ./scripts/test-integration.sh - - hygiene: - if: github.ref != 'refs/heads/main' - name: Hygiene and Layering - runs-on: ubuntu-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci - - - name: Run Hygiene Checks - run: npm run gulp hygiene - - - name: Run Valid Layers Checks - run: npm run valid-layers-check - - - name: Compile /build/ - run: npm run compile - working-directory: build - - - name: Check clean git state - run: ./.github/workflows/check-clean-git-state.sh - - - name: Run eslint - run: npm run eslint - - - name: Run vscode-dts Compile Checks - run: npm run vscode-dts-compile-check - - - name: Run Trusted Types Checks - run: npm run tsec-compile-check - - warm-cache: - name: Warm up node modules cache - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci diff --git a/.github/workflows/check-clean-git-state.sh b/.github/workflows/check-clean-git-state.sh deleted file mode 100755 index cd09d4db..00000000 --- a/.github/workflows/check-clean-git-state.sh +++ /dev/null @@ -1,6 +0,0 @@ -R=`git status --porcelain | wc -l` -if [ "$R" -ne "0" ]; then - echo "The git repo is not clean after compiling the /build/ folder. Did you forget to commit .js output for .ts files?"; - git status --porcelain - exit 1; -fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 344213e0..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,316 +0,0 @@ -name: CI - -on: workflow_dispatch - -# on: -# push: -# branches: -# - main -# - release/* -# pull_request: -# branches: -# - main -# - release/* - -jobs: - windows: - name: Windows - runs-on: windows-2022 - timeout-minutes: 60 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node_modules archive - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: ".build/node_modules_cache" - key: "${{ runner.os }}-cacheNodeModulesArchive-${{ steps.nodeModulesCacheKey.outputs.value }}" - - name: Extract node_modules archive - if: ${{ steps.cacheNodeModules.outputs.cache-hit == 'true' }} - run: 7z.exe x .build/node_modules_cache/cache.7z -aos - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - npm_config_foreground_scripts: "true" - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci - - name: Create node_modules archive - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: | - mkdir -Force .build - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -Force .build/node_modules_cache - 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt - - - name: Compile and Download - run: npm exec -- npm-run-all -lp compile "electron x64" playwright-install download-builtin-extensions - - - name: Compile Integration Tests - run: npm run compile - working-directory: test/integration/browser - - - name: Run Unit Tests (Electron) - run: .\scripts\test.bat - - - name: Run Unit Tests (node.js) - run: npm run test-node - - - name: Run Unit Tests (Browser, Chromium) - run: npm run test-browser-no-install -- --browser chromium - - - name: Run Integration Tests (Electron) - run: .\scripts\test-integration.bat - - - name: Run Integration Tests (Browser, Firefox) - timeout-minutes: 20 - run: .\scripts\test-web-integration.bat --browser firefox - - - name: Run Integration Tests (Remote) - timeout-minutes: 20 - run: .\scripts\test-remote-integration.bat - - linux: - name: Linux - runs-on: ubuntu-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - - name: Setup Build Environment - run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libkrb5-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 - sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb - sudo chmod +x /etc/init.d/xvfb - sudo update-rc.d xvfb defaults - sudo service xvfb start - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci - - - name: Compile and Download - run: npm exec -- npm-run-all -lp compile "electron x64" playwright-install download-builtin-extensions - - - name: Compile Integration Tests - run: npm run compile - working-directory: test/integration/browser - - - name: Run Unit Tests (Electron) - id: electron-unit-tests - run: DISPLAY=:10 ./scripts/test.sh - - - name: Run Unit Tests (node.js) - id: nodejs-unit-tests - run: npm run test-node - - - name: Run Unit Tests (Browser, Chromium) - id: browser-unit-tests - run: DISPLAY=:10 npm run test-browser-no-install -- --browser chromium - - - name: Run Integration Tests (Electron) - id: electron-integration-tests - run: DISPLAY=:10 ./scripts/test-integration.sh - - - name: Run Integration Tests (Browser, Chromium) - id: browser-integration-tests - run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser chromium - - - name: Run Integration Tests (Remote) - id: electron-remote-integration-tests - timeout-minutes: 15 - run: DISPLAY=:10 ./scripts/test-remote-integration.sh - - darwin: - name: macOS - runs-on: macos-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModulesMacOS-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci - - - name: Compile and Download - run: npm exec -- npm-run-all -lp compile "electron x64" playwright-install download-builtin-extensions - - - name: Compile Integration Tests - run: npm run compile - working-directory: test/integration/browser - - # This is required for SecretStorage unittests - - name: Create temporary keychain - run: | - security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - security default-keychain -s $RUNNER_TEMP/buildagent.keychain - security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - - - name: Run Unit Tests (Electron) - run: DISPLAY=:10 ./scripts/test.sh - - - name: Run Unit Tests (node.js) - run: npm run test-node - - - name: Run Unit Tests (Browser, Chromium) - run: DISPLAY=:10 npm run test-browser-no-install -- --browser chromium - - - name: Run Integration Tests (Electron) - run: DISPLAY=:10 ./scripts/test-integration.sh - - - name: Run Integration Tests (Browser, Webkit) - run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser webkit - - - name: Run Integration Tests (Remote) - timeout-minutes: 15 - run: DISPLAY=:10 ./scripts/test-remote-integration.sh - - hygiene: - name: Hygiene and Layering - runs-on: ubuntu-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: npm ci - - - name: Download Playwright - run: npm run playwright-install - - - name: Run Hygiene Checks - run: npm run gulp hygiene - - - name: Run Valid Layers Checks - run: npm run valid-layers-check - - - name: Compile /build/ - run: npm run compile - working-directory: build - - - name: Check clean git state - run: ./.github/workflows/check-clean-git-state.sh - - - name: Run eslint - run: npm run eslint - - - name: Run vscode-dts Compile Checks - run: npm run vscode-dts-compile-check - - - name: Run Trusted Types Checks - run: npm run tsec-compile-check diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml deleted file mode 100644 index 1f5694fa..00000000 --- a/.github/workflows/monaco-editor.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Monaco Editor checks - -on: - push: - branches: - - main - - release/* - pull_request: - branches: - - main - - release/* - -jobs: - main: - name: Monaco Editor checks - runs-on: ubuntu-latest - timeout-minutes: 40 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - - name: Compute node modules cache key - id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT - - name: Cache node modules - id: cacheNodeModules - uses: actions/cache@v4 - with: - path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules20- - - name: Get npm cache directory path - id: npmCacheDirPath - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - - name: Cache npm directory - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 - with: - path: ${{ steps.npmCacheDirPath.outputs.dir }} - key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-npmCacheDir- - - name: Install system dependencies - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: | - sudo apt update - sudo apt install -y libxkbfile-dev pkg-config libkrb5-dev libxss1 - - name: Execute npm - if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - run: | - npm ci - - - name: Download Playwright - run: npm run playwright-install - - - name: Run Monaco Editor Checks - run: npm run monaco-compile-check - - - name: Editor Distro & ESM - run: npm run gulp editor-esm - - - name: Editor ESM sources check - working-directory: ./test/monaco - run: npm run esm-check - - - name: Typings validation prep - run: | - mkdir typings-test - - - name: Typings validation - working-directory: ./typings-test - run: | - npm init -yp - ../node_modules/.bin/tsc --init - echo "import '../out-monaco-editor-core';" > a.ts - ../node_modules/.bin/tsc --noEmit - - - name: Package Editor with Webpack - working-directory: ./test/monaco - run: npm run bundle-webpack - - - name: Compile Editor Tests - working-directory: ./test/monaco - run: npm run compile - - - name: Run Editor Tests - timeout-minutes: 5 - working-directory: ./test/monaco - run: npm run test diff --git a/.github/workflows/no-package-lock-changes.yml b/.github/workflows/no-package-lock-changes.yml deleted file mode 100644 index 45d5d174..00000000 --- a/.github/workflows/no-package-lock-changes.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Prevent package-lock.json changes in PRs -on: [pull_request] - -jobs: - main: - name: Prevent package-lock.json changes in PRs - runs-on: ubuntu-latest - steps: - - uses: octokit/request-action@v2.x - id: get_permissions - with: - route: GET /repos/microsoft/vscode/collaborators/{username}/permission - username: ${{ github.event.pull_request.user.login }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Set control output variable - id: control - run: | - echo "user: ${{ github.event.pull_request.user.login }}" - echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" - echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}" - echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" - echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT - - name: Get file changes - uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272 - if: ${{ steps.control.outputs.should_run == 'true' }} - - name: Check for lockfile changes - if: ${{ steps.control.outputs.should_run == 'true' }} - run: | - cat $HOME/files.json | jq -e 'any(test("package-lock\\.json$|Cargo\\.lock$")) | not' \ - || (echo "Changes to package-lock.json/Cargo.lock files aren't allowed in PRs." && exit 1) diff --git a/.github/workflows/no-yarn-lock-changes.yml b/.github/workflows/no-yarn-lock-changes.yml deleted file mode 100644 index 57082a28..00000000 --- a/.github/workflows/no-yarn-lock-changes.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Prevent yarn.lock changes in PRs -on: [pull_request] - -jobs: - main: - name: Prevent yarn.lock changes in PRs - runs-on: ubuntu-latest - steps: - - uses: octokit/request-action@v2.x - id: get_permissions - with: - route: GET /repos/microsoft/vscode/collaborators/{username}/permission - username: ${{ github.event.pull_request.user.login }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Set control output variable - id: control - run: | - echo "user: ${{ github.event.pull_request.user.login }}" - echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" - echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}" - echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" - echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT - - name: Get file changes - uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272 - if: ${{ steps.control.outputs.should_run == 'true' }} - - name: Check for lockfile changes - if: ${{ steps.control.outputs.should_run == 'true' }} - run: | - cat $HOME/files.json | jq -e 'any(test("yarn\\.lock$|Cargo\\.lock$")) | not' \ - || (echo "Changes to yarn.lock/Cargo.lock files aren't allowed in PRs." && exit 1) diff --git a/.github/workflows/rich-navigation.yml.off b/.github/workflows/rich-navigation.yml.off deleted file mode 100644 index 7850fed1..00000000 --- a/.github/workflows/rich-navigation.yml.off +++ /dev/null @@ -1,39 +0,0 @@ -name: "Rich Navigation Indexing" -on: - workflow_dispatch: - push: - branches: - - main - tags: - - '[0-9]+.[0-9]+.[0-9]+' - -jobs: - richnav: - runs-on: windows-2022 - steps: - - uses: actions/checkout@v3 - - - uses: actions/cache@v3 - id: caching-stage - name: Cache VS Code dependencies - with: - path: node_modules - key: ${{ runner.os }}-dependencies-${{ hashfiles('package-lock.json') }} - restore-keys: ${{ runner.os }}-dependencies- - - - uses: actions/setup-node@v3 - with: - node-version-file: .nvmrc - - - name: Install dependencies - if: steps.caching-stage.outputs.cache-hit != 'true' - run: npm ci - env: - npm_config_foreground_scripts: "true" - - - uses: microsoft/RichCodeNavIndexer@v0.1 - with: - languages: typescript - repo-token: ${{ secrets.GITHUB_TOKEN }} - configFiles: .lsifrc.json - continue-on-error: true diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml deleted file mode 100644 index a5ac3be4..00000000 --- a/.github/workflows/telemetry.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Telemetry' -on: - pull_request: -jobs: - check-metdata: - name: 'Check metadata' - runs-on: 'ubuntu-latest' - - steps: - - uses: 'actions/checkout@v4' - - - uses: 'actions/setup-node@v4' - with: - node-version: 'lts/*' - - - name: 'Run vscode-telemetry-extractor' - run: 'npx --package=@vscode/telemetry-extractor@1.14.0 --yes vscode-telemetry-extractor -s .' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index b73ce578..17d12e30 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ vscode.db product.overrides.json *.snap.actual .vscode-test +.tmp/ +.tmp2/ +.tool-versions diff --git a/.idx/dev.nix b/.idx/dev.nix new file mode 100644 index 00000000..c20c14b4 --- /dev/null +++ b/.idx/dev.nix @@ -0,0 +1,48 @@ +# Created for Void +# To learn more about how to use Nix to configure your environment +# see: https://developers.google.com/idx/guides/customize-idx-env +{pkgs}: { + # Which nixpkgs channel to use. + channel = "stable-23.11"; # or "unstable" + # Use https://search.nixos.org/packages to find packages + packages = [ + pkgs.nodejs_20 + pkgs.yarn + pkgs.nodePackages.pnpm + pkgs.bun + pkgs.gh + ]; + # Sets environment variables in the workspace + env = {}; + idx = { + # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" + extensions = [ + # "vscodevim.vim" + ]; + workspace = { + # Runs when a workspace is first created with this `dev.nix` file + onCreate = { + npm-install = "npm ci --no-audit --prefer-offline --no-progress --timing"; + # Open editors for the following files by default, if they exist: + default.openFiles = [ + # Cover all the variations of language, src-dir, router (app/pages) + "pages/index.tsx" "pages/index.jsx" + "src/pages/index.tsx" "src/pages/index.jsx" + "app/page.tsx" "app/page.jsx" + "src/app/page.tsx" "src/app/page.jsx" + ]; + }; + # To run something each time the workspace is (re)started, use the `onStart` hook + }; + # Enable previews and customize configuration + previews = { + enable = true; + previews = { + web = { + command = ["npm" "run" "dev" "--" "--port" "$PORT" "--hostname" "0.0.0.0"]; + manager = "web"; + }; + }; + }; + }; +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3bcb5d5c..bfce54d4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -29,6 +29,34 @@ } } }, + { + "type": "npm", + "script": "watchreactd", + "label": "React - Build", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "buildWatchers", + "close": false + }, + "problemMatcher": { + "owner": "typescript", + "applyTo": "closedDocuments", + "fileLocation": [ + "absolute" + ], + "pattern": { + "regexp": "Error: ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): (.*)$", + "file": 1, + "location": 2, + "message": 3 + }, + "background": { + "beginsPattern": "Starting compilation", + "endsPattern": "Finished compilation" + } + } + }, { "type": "npm", "script": "watch-extensionsd", @@ -61,7 +89,8 @@ "label": "VS Code - Build", "dependsOn": [ "Core - Build", - "Ext - Build" + "Ext - Build", + "React - Build" ], "group": { "kind": "build", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6bece0ef..2b4c1d00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,99 +1,164 @@ -# Contributing to VS Code +# Contributing to Void +### Welcome! 👋 +This is the official guide on how to contribute to Void. We want to make it as easy as possible to contribute, so if you have any questions or comments, reach out via email or discord! -Welcome, and thank you for your interest in contributing to VS Code! +There are a few ways to contribute: -There are several ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. - -## Asking Questions +- 💫 Complete items on the [Roadmap](https://github.com/orgs/voideditor/projects/2). +- 💡 Make suggestions in our [Discord](https://discord.gg/RSNjgaugJs). +- 🪴 Start new Issues - see [Issues](https://github.com/voideditor/void/issues). -Have a question? Instead of opening an issue, please ask on [Stack Overflow](https://stackoverflow.com/questions/tagged/visual-studio-code) using the tag `visual-studio-code`. -The active community will be eager to assist you. Your well-worded question will serve as a resource to others searching for help. +### Codebase Guide -## Providing Feedback +We highly recommend reading [this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization. -Your comments and feedback are welcome, and the development team is available via a handful of different channels. + -See the [Feedback Channels](https://github.com/microsoft/vscode/wiki/Feedback-Channels) wiki page for details on how to share your thoughts. +Most of Void's code lives in the folder `src/vs/workbench/contrib/void/`. -## Reporting Issues -Have you identified a reproducible problem in VS Code? Do you have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible. -### Identify Where to Report -The VS Code project is distributed across multiple repositories. Try to file the issue against the correct repository. Check the list of [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) if you aren't sure which repo is correct. +## Building Void -Can you recreate the issue even after [disabling all extensions](https://code.visualstudio.com/docs/editor/extension-gallery#_disable-an-extension)? If you find the issue is caused by an extension you have installed, please file an issue on the extension's repo directly. +### a. Build Prerequisites - Mac -### Look For an Existing Issue +If you're using a Mac, you need Python and XCode. You probably have these by default. -Before you create a new issue, please do a search in [open issues](https://github.com/microsoft/vscode/issues) to see if the issue or feature request has already been filed. +### b. Build Prerequisites - Windows -Be sure to scan through the [most popular](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) feature requests. +If you're using a Windows computer, first get [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) (recommended) or [VS Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) (not recommended). If you already have both, you might need to run the next few steps on both of them. -If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment: +Go to the "Workloads" tab and select: +- `Desktop development with C++` +- `Node.js build tools` -* 👍 - upvote -* 👎 - downvote +Go to the "Individual Components" tab and select: +- `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)` +- `C++ ATL for latest build tools with Spectre Mitigations` +- `C++ MFC for latest build tools with Spectre Mitigations` -If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below. +Finally, click Install. -### Writing Good Bug Reports and Feature Requests +### c. Build Prerequisites - Linux -File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue. +First, run `npm install -g node-gyp`. Then: -Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar but have different causes. +- Debian (Ubuntu, etc): `sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3`. +- Red Hat (Fedora, etc): `sudo dnf install @development-tools gcc gcc-c++ make libsecret-devel krb5-devel libX11-devel libxkbfile-devel`. +- Others: see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute). -The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a fix. +### d. Building Void -The built-in tool for reporting an issue, which you can access by using `Report Issue` in VS Code's Help menu, can help streamline this process by automatically providing the version of VS Code, all your installed extensions, and your system info. Additionally, the tool will search among existing issues to see if a similar issue already exists. +To build Void, open `void/` inside VSCode. Then open your terminal and run: -Please include the following with each issue: +1. `npm install` to install all dependencies. +2. `npm run watchreact` to build Void's browser dependencies like React. (If this doesn't work, try `npm run buildreact`). +3. Build Void. + - Press Cmd+Shift+B (Mac). + - Press Ctrl+Shift+B (Windows/Linux). + - This step can take ~5 min. The build is done when you see two check marks. +4. Run Void. + - Run `./scripts/code.sh` (Mac/Linux). + - Run `./scripts/code.bat` (Windows). +6. Nice-to-knows. + - You can always press Ctrl+R (Cmd+R) inside the new window to reload and see your new changes. It's faster than Ctrl+Shift+P and `Reload Window`. + - You might want to add the flags `--user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions` to the above run command, which lets you delete the `.tmp` folder to reset any IDE changes you made when testing. -* Version of VS Code -* Your operating system -* List of extensions that you have installed -* Reproducible steps (1... 2... 3...) that cause the issue -* What you expected to see, versus what you actually saw -* Images, animations, or a link to a video showing the issue occurring -* A code snippet that demonstrates the issue or a link to a code repository the developers can easily pull down to recreate the issue locally - * **Note:** Because the developers need to copy and paste the code snippet, including a code snippet as a media file (i.e. .gif) is not sufficient. -* Errors from the Dev Tools Console (open from the menu: Help > Toggle Developer Tools) +#### Building Void from Terminal -### Creating Pull Requests +Alternatively, if you want to build Void from the terminal, instead of pressing Cmd+Shift+B you can run `npm run watch`. The build is done when you see something like this: -* Please refer to the article on [creating pull requests](https://github.com/microsoft/vscode/wiki/How-to-Contribute#pull-requests) and contributing to this project. +``` +[watch-extensions] [00:37:39] Finished compilation extensions with 0 errors after 19303 ms +[watch-client ] [00:38:06] Finished compilation with 0 errors after 46248 ms +[watch-client ] [00:38:07] Starting compilation... +[watch-client ] [00:38:07] Finished compilation with 0 errors after 5 ms +``` -### Final Checklist -Please remember to do the following: -* [ ] Search the issue repository to ensure your report is a new issue -* [ ] Recreate the issue after disabling all extensions -* [ ] Simplify your code around the issue to better isolate the problem +#### Common Fixes -Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information! +- Make sure you followed the prerequisite steps. +- Make sure you have Node version `20.16.0` (the version in `.nvmrc`)! +- If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`. +- If you see missing styles, wait a few seconds and then reload. +- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page. -### Follow Your Issue -Once submitted, your report will go into the [issue tracking](https://github.com/microsoft/vscode/wiki/Issue-Tracking) workflow. Be sure to understand what will happen next, so you know what to expect and how to continue to assist throughout the process. -## Automated Issue Management +## Packaging -We use GitHub Actions to help us manage issues. These Actions and their descriptions can be [viewed here](https://github.com/microsoft/vscode-github-triage-actions). Some examples of what these Actions do are: +We don't usually recommend packaging. Instead, you should probably just build. If you're sure you want to package Void into an executable app, make sure you've built first, then run one of the following commands. This will create a folder named `VSCode-darwin-arm64` or similar outside of the void/ repo (see below). Be patient - packaging can take ~25 minutes. -* Automatically close any issue marked `info-needed` if there has been no response in the past 7 days. -* Automatically lock issues 45 days after they are closed. -* Automatically implement the VS Code [feature request pipeline](https://github.com/microsoft/vscode/wiki/Issues-Triaging#managing-feature-requests). -If you believe the bot got something wrong, please open a new issue and let us know. +### Mac +- `npm run gulp vscode-darwin-arm64` - most common (Apple Silicon) +- `npm run gulp vscode-darwin-x64` (Intel) -## Contributing Fixes +### Windows +- `npm run gulp vscode-win32-x64` - most common +- `npm run gulp vscode-win32-ia32` -If you are interested in writing code to fix issues, please see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) in the wiki. +### Linux +- `npm run gulp vscode-linux-x64` - most common +- `npm run gulp vscode-linux-arm` +- `npm run gulp vscode-linux-ia32` -## Thank You -Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute. +### Output + +This will generate a folder outside of `void/`: +```bash +workspace/ +├── void/ # Your Void fork +└── VSCode-darwin-arm64/ # Generated output +``` + +### Distributing +Void's maintainers distribute Void on our website and in releases. If you'd like to see the scripts to convert `Mac .app -> .dmg`, `Windows folder -> .exe`, and `Linux folder -> appimage` for distribution, feel free to reach out. + +## Pull Request Guidelines + + +- Please submit a pull request once you've made a change. +- No need to submit an Issue unless you're creating a new feature that might involve multiple PRs. +- Please don't use AI to write your PR 🙂 + + + + + + diff --git a/LICENSE-VS-Code.txt b/LICENSE-VS-Code.txt new file mode 100644 index 00000000..d34de290 --- /dev/null +++ b/LICENSE-VS-Code.txt @@ -0,0 +1,26 @@ +Void is a fork of VS Code, which is licensed under the MIT License (below). +Void's additions and modifications are licensed under the Apache 2.0 License (see LICENSE.txt). + +-------------------- + +MIT License + +Copyright (c) 2015 - present Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt index 0ac28ee2..7637992d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,21 +1,201 @@ -MIT License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2015 - present Microsoft Corporation + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + 1. Definitions. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Glass Devtools, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 61df8fc6..609456cb 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,39 @@ -# Visual Studio Code - Open Source ("Code - OSS") +# Welcome to Void. -[![Feature Requests](https://img.shields.io/github/issues/microsoft/vscode/feature-request.svg)](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) -[![Bugs](https://img.shields.io/github/issues/microsoft/vscode/bug.svg)](https://github.com/microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug) -[![Gitter](https://img.shields.io/badge/chat-on%20gitter-yellow.svg)](https://gitter.im/Microsoft/vscode) +
+ Void Welcome +
-## The Repository +Void is the open-source Cursor alternative. -This repository ("`Code - OSS`") is where we (Microsoft) develop the [Visual Studio Code](https://code.visualstudio.com) product together with the community. Not only do we work on code and issues here, we also publish our [roadmap](https://github.com/microsoft/vscode/wiki/Roadmap), [monthly iteration plans](https://github.com/microsoft/vscode/wiki/Iteration-Plans), and our [endgame plans](https://github.com/microsoft/vscode/wiki/Running-the-Endgame). This source code is available to everyone under the standard [MIT license](https://github.com/microsoft/vscode/blob/main/LICENSE.txt). +This repo contains the full sourcecode for Void. We are currently in [open beta](https://voideditor.com/email) for Discord members (see the `announcements` channel), with a waitlist for our official release. If you're new, welcome! -## Visual Studio Code +- 👋 [Discord](https://discord.gg/RSNjgaugJs) -

- VS Code in action -

+- 🔨 [Contribute](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md) -[Visual Studio Code](https://code.visualstudio.com) is a distribution of the `Code - OSS` repository with Microsoft-specific customizations released under a traditional [Microsoft product license](https://code.visualstudio.com/License/). +- 🚙 [Roadmap](https://github.com/orgs/voideditor/projects/2) -[Visual Studio Code](https://code.visualstudio.com) combines the simplicity of a code editor with what developers need for their core edit-build-debug cycle. It provides comprehensive code editing, navigation, and understanding support along with lightweight debugging, a rich extensibility model, and lightweight integration with existing tools. +- 📝 [Changelog](https://voideditor.com/changelog) -Visual Studio Code is updated monthly with new features and bug fixes. You can download it for Windows, macOS, and Linux on [Visual Studio Code's website](https://code.visualstudio.com/Download). To get the latest releases every day, install the [Insiders build](https://code.visualstudio.com/insiders). ## Contributing -There are many ways in which you can participate in this project, for example: +1. Feel free to attend a weekly meeting in our Discord channel if you'd like to contribute! -* [Submit bugs and feature requests](https://github.com/microsoft/vscode/issues), and help us verify as they are checked in -* Review [source code changes](https://github.com/microsoft/vscode/pulls) -* Review the [documentation](https://github.com/microsoft/vscode-docs) and make pull requests for anything from typos to additional and new content +2. To get started working on Void, see [Contributing](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md). -If you are interested in fixing issues and contributing directly to the code base, -please see the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute), which covers the following: +3. We're open to collaborations and suggestions of all types - just reach out. -* [How to build and run from source](https://github.com/microsoft/vscode/wiki/How-to-Contribute) -* [The development workflow, including debugging and running tests](https://github.com/microsoft/vscode/wiki/How-to-Contribute#debugging) -* [Coding guidelines](https://github.com/microsoft/vscode/wiki/Coding-Guidelines) -* [Submitting pull requests](https://github.com/microsoft/vscode/wiki/How-to-Contribute#pull-requests) -* [Finding an issue to work on](https://github.com/microsoft/vscode/wiki/How-to-Contribute#where-to-contribute) -* [Contributing to translations](https://aka.ms/vscodeloc) -## Feedback +## Reference -* Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/vscode) -* [Request a new feature](CONTRIBUTING.md) -* Upvote [popular feature requests](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) -* [File an issue](https://github.com/microsoft/vscode/issues) -* Connect with the extension author community on [GitHub Discussions](https://github.com/microsoft/vscode-discussions/discussions) or [Slack](https://aka.ms/vscode-dev-community) -* Follow [@code](https://twitter.com/code) and let us know what you think! +Void is a fork of the [vscode](https://github.com/microsoft/vscode) repository. For some useful links on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md). -See our [wiki](https://github.com/microsoft/vscode/wiki/Feedback-Channels) for a description of each of these channels and information on some other available community-driven channels. - -## Related Projects - -Many of the core components and extensions to VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) repositories are separate from each other. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki). - -## Bundled Extensions - -VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`. - -## Development Container - -This repository includes a Visual Studio Code Dev Containers / GitHub Codespaces development container. - -* For [Dev Containers](https://aka.ms/vscode-remote/download/containers), use the **Dev Containers: Clone Repository in Container Volume...** command which creates a Docker volume for better disk I/O on macOS and Windows. - * If you already have VS Code and Docker installed, you can also click [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. This will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use. - -* For Codespaces, install the [GitHub Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces) extension in VS Code, and use the **Codespaces: Create New Codespace** command. - -Docker / the Codespace should have at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run full build. See the [development container README](.devcontainer/README.md) for more information. - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## License - -Copyright (c) Microsoft Corporation. All rights reserved. - -Licensed under the [MIT](LICENSE.txt) license. +## Support +Feel free to reach out in our Discord or contact us via email: hello@voideditor.com. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 82db58aa..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,41 +0,0 @@ - - -## Security - -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). - -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). - - diff --git a/VOID_USEFUL_LINKS.md b/VOID_USEFUL_LINKS.md new file mode 100644 index 00000000..abf9a6e4 --- /dev/null +++ b/VOID_USEFUL_LINKS.md @@ -0,0 +1,37 @@ +# Useful links + +The Void team put together this list of links to get up and running with VSCode's sourcecode. We hope it's helpful! + +## Contributing + +- [How VSCode's sourcecode is organized](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) - this explains where the entry point files are, what `browser/` and `common/` mean, etc. This is the most important read on this whole list! We recommend reading the whole thing. + +- [Built-in VSCode styles](https://code.visualstudio.com/api/references/theme-color) - CSS variables that are built into VSCode. Use `var(--vscode-{theme but replacing . with -})`. You can also see their [Webview theming guide](https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content). + +## Beginners / Getting started + +- [VSCode UI guide](https://code.visualstudio.com/docs/getstarted/userinterface) - covers auxbar, panels, etc. + +- [UX guide](https://code.visualstudio.com/api/ux-guidelines/overview) - covers Containers, Views, Items, etc. + + +## Misc + +- [Every command](https://code.visualstudio.com/api/references/commands) built-in to VSCode - not used often, but here for reference. + + +## VSCode's Extension API + +Void is no longer an extension, so these links are no longer required, but they might be useful if we ever build an extension again. + +- [Files you need in an extension](https://code.visualstudio.com/api/get-started/extension-anatomy). + +- [An extension's `package.json` schema](https://code.visualstudio.com/api/references/extension-manifest). + +- ["Contributes" Guide](https://code.visualstudio.com/api/references/contribution-points) - the `"contributes"` part of `package.json` is how an extension mounts. + +- [The Full VSCode Extension API](https://code.visualstudio.com/api/references/vscode-api) - look on the right side for organization. The [bottom](https://code.visualstudio.com/api/references/vscode-api#api-patterns) of the page is easy to miss but is useful - cancellation tokens, events, disposables. + +- [Activation events](https://code.visualstudio.com/api/references/activation-events) you can define in `package.json` (not the most useful). + + diff --git a/build/darwin/sign.js b/build/darwin/sign.js index dff30fd0..8cb34a08 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -81,7 +81,7 @@ async function main(buildDir) { // universal will get its copy from the x64 build. if (arch !== 'universal') { await (0, cross_spawn_promise_1.spawn)('plutil', [ - '-insert', + '-replace', // Void changed this to replace 'NSAppleEventsUsageDescription', '-string', 'An application in Visual Studio Code wants to use AppleScript.', diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index ecf16274..c509dd01 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -89,7 +89,7 @@ async function main(buildDir?: string): Promise { // universal will get its copy from the x64 build. if (arch !== 'universal') { await spawn('plutil', [ - '-insert', + '-replace', // Void changed this to replace 'NSAppleEventsUsageDescription', '-string', 'An application in Visual Studio Code wants to use AppleScript.', diff --git a/build/filters.js b/build/filters.js index 716d7be1..f972d9c1 100644 --- a/build/filters.js +++ b/build/filters.js @@ -137,6 +137,8 @@ module.exports.indentationFilter = [ '!extensions/ipynb/notebook-out/**', '!extensions/notebook-renderers/renderer-out/*.js', '!extensions/simple-browser/media/*.js', + + '!extensions/open-remote-ssh/out/*.js', // Void added this ]; module.exports.copyrightFilter = [ diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index f05738fa..b7f5b7d1 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -71,6 +71,9 @@ const compilations = [ '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', '.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json', + + 'extensions/open-remote-ssh/tsconfig.json', // Void added this + ]; const getBaseUrl = out => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index a63f693c..99db93ac 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -281,6 +281,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op this.emit('data', file); })); + // Void - this is important, creates the product.json in .app let productJsonContents; const productJsonStream = gulp.src(['product.json'], { base: '.' }) .pipe(json({ commit, date: readISODate('out-build'), checksums, version })) diff --git a/build/hygiene.js b/build/hygiene.js index fe32fe33..84982259 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -261,53 +261,54 @@ function createGitIndexVinyls(paths) { return pall(fns, { concurrency: 4 }).then((r) => r.filter((p) => !!p)); } -// this allows us to run hygiene as a git pre-commit hook -if (require.main === module) { - const cp = require('child_process'); +// Void - NO PRE COMMIT HOOKS!!!! for now... - Void team +// // this allows us to run hygiene as a git pre-commit hook +// if (require.main === module) { +// const cp = require('child_process'); - process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); - process.exit(1); - }); +// process.on('unhandledRejection', (reason, p) => { +// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); +// process.exit(1); +// }); - if (process.argv.length > 2) { - hygiene(process.argv.slice(2)).on('error', (err) => { - console.error(); - console.error(err); - process.exit(1); - }); - } else { - cp.exec( - 'git diff --cached --name-only', - { maxBuffer: 2000 * 1024 }, - (err, out) => { - if (err) { - console.error(); - console.error(err); - process.exit(1); - } +// if (process.argv.length > 2) { +// hygiene(process.argv.slice(2)).on('error', (err) => { +// console.error(); +// console.error(err); +// process.exit(1); +// }); +// } else { +// cp.exec( +// 'git diff --cached --name-only', +// { maxBuffer: 2000 * 1024 }, +// (err, out) => { +// if (err) { +// console.error(); +// console.error(err); +// process.exit(1); +// } - const some = out.split(/\r?\n/).filter((l) => !!l); +// const some = out.split(/\r?\n/).filter((l) => !!l); - if (some.length > 0) { - console.log('Reading git index versions...'); +// if (some.length > 0) { +// console.log('Reading git index versions...'); - createGitIndexVinyls(some) - .then( - (vinyls) => - new Promise((c, e) => - hygiene(es.readArray(vinyls).pipe(filter(all))) - .on('end', () => c()) - .on('error', e) - ) - ) - .catch((err) => { - console.error(); - console.error(err); - process.exit(1); - }); - } - } - ); - } -} +// createGitIndexVinyls(some) +// .then( +// (vinyls) => +// new Promise((c, e) => +// hygiene(es.readArray(vinyls).pipe(filter(all))) +// .on('end', () => c()) +// .on('error', e) +// ) +// ) +// .catch((err) => { +// console.error(); +// console.error(err); +// process.exit(1); +// }); +// } +// } +// ); +// } +// } diff --git a/build/lib/electron.js b/build/lib/electron.js index 56992d8a..f7814d5d 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -90,7 +90,7 @@ function darwinBundleDocumentType(extensions, icon, nameOrSuffix, utis) { role: 'Editor', ostypes: ['TEXT', 'utxt', 'TUTX', '****'], extensions, - iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', + iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', // <-- Void icon code.icns utis }; } @@ -215,7 +215,7 @@ exports.config = { darwinForceDarkModeSupport: true, darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, linuxExecutableName: product.applicationName, - winIcon: 'resources/win32/code.ico', + winIcon: 'resources/win32/code.ico', // <-- Void icon token: process.env['GITHUB_TOKEN'], repo: product.electronRepository || undefined, validateChecksum: true, diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 3bb047df..9cdf58dd 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -68,7 +68,7 @@ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuff role: 'Editor', ostypes: ['TEXT', 'utxt', 'TUTX', '****'], extensions, - iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', + iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', // <-- Void icon code.icns utis }; } @@ -196,7 +196,7 @@ export const config = { darwinForceDarkModeSupport: true, darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, linuxExecutableName: product.applicationName, - winIcon: 'resources/win32/code.ico', + winIcon: 'resources/win32/code.ico', // <-- Void icon token: process.env['GITHUB_TOKEN'], repo: product.electronRepository || undefined, validateChecksum: true, diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 9f653c72..ed0b97d7 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -54,6 +54,8 @@ const dirs = [ 'test/smoke', '.vscode/extensions/vscode-selfhost-import-aid', '.vscode/extensions/vscode-selfhost-test-provider', + 'extensions/open-remote-ssh', // Void added this + ]; if (fs.existsSync(`${__dirname}/../../.build/distro/npm`)) { diff --git a/build/win32/code.iss b/build/win32/code.iss index cad6e399..1d45a25b 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -20,8 +20,10 @@ Compression=lzma SolidCompression=yes AppMutex={code:GetAppMutex} SetupMutex={#AppMutex}setup -WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp" -WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp" +; this is a // Void icon comment. Old: WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp" +; this is a // Void icon comment. Old: WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp" +; COMMENTED OUT WizardImageFile="" +WizardSmallImageFile="{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp,{#RepoDir}\resources\win32\inno-void.bmp" SetupIconFile={#RepoDir}\resources\win32\code.ico UninstallDisplayIcon={app}\{#ExeBasename}.exe ChangesEnvironment=true diff --git a/create-appimage.sh b/create-appimage.sh new file mode 100644 index 00000000..c79a351c --- /dev/null +++ b/create-appimage.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -e # Exit on error +set -x # Print commands as they are executed + +# Configuration +APP_NAME="void" +APP_VERSION="1.0.0" +ARCH="x86_64" + +export ARCH + +# Check if void binary exists in current directory +if [ ! -f "./void" ]; then + echo "Error: void binary not found in current directory" + exit 1 +fi + +# Check if icon exists +if [ ! -f "./void.png" ]; then + echo "Error: void.png icon not found in current directory" + exit 1 +fi + +# Create temporary directory +TEMP_DIR="$(mktemp -d)" +echo "Created temporary directory: $TEMP_DIR" +APP_DIR="$TEMP_DIR/$APP_NAME.AppDir" + +# Create basic AppDir structure +mkdir -pv "$APP_DIR/usr/bin" +mkdir -pv "$APP_DIR/usr/lib" +mkdir -pv "$APP_DIR/usr/share/applications" +mkdir -pv "$APP_DIR/usr/share/icons/hicolor/256x256/apps" + +# Exclude create-appimage.sh and appimagetool-x86_64.AppImage from being copied +echo "Copying files excluding create-appimage.sh and appimagetool-x86_64.AppImage..." +for file in ./*; do + if [[ "$file" != "./create-appimage.sh" && "$file" != "./appimagetool-x86_64.AppImage" ]]; then + cp -rv "$file" "$APP_DIR/usr/bin/" + fi +done + +# Copy the icon to required locations +cp -v ./void.png "$APP_DIR/void.png" +cp -v ./void.png "$APP_DIR/usr/share/icons/hicolor/256x256/apps/void.png" + +# Copy dependencies with error checking +echo "Copying dependencies..." +for lib in $(ldd ./void | grep "=> /" | awk '{print $3}'); do + if [ -f "$lib" ]; then + cp -v "$lib" "$APP_DIR/usr/lib/" || echo "Failed to copy $lib" + else + echo "Warning: Library $lib not found" + fi +done + +# Create desktop file with error checking +echo "Creating desktop file..." +if ! cat > "$APP_DIR/$APP_NAME.desktop" < "$APP_DIR/AppRun" <=6.9.0" + } + }, + "node_modules/@jeanp413/ssh-config": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@jeanp413/ssh-config/-/ssh-config-4.3.1.tgz", + "integrity": "sha512-x0EaWRdjs5sPDNmYr11wVB1GdwWQgRekc7SbueuO5FK7YZUav98qZKtZZU5iSDKyxJkooCs3rgVizB1wIWrF7g==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/ssh2": { + "version": "0.5.52", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", + "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, + "node_modules/@types/ssh2-streams": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz", + "integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/core-js-pure": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz", + "integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "license": "MIT", + "optional": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/simple-socks": { + "version": "2.2.2", + "resolved": "git+ssh://git@github.com/jeanp413/simple-socks.git#2ac739301a82d6baff04804ed494436a026acb60", + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.8", + "binary": "^0.3.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/ssh2": { + "version": "1.14.0", + "resolved": "git+ssh://git@github.com/jeanp413/ssh2.git#a169f627213aa663e0aa2fd2f0ef5c8931890c26", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.9", + "nan": "^2.17.0" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/extensions/open-remote-ssh/package.json b/extensions/open-remote-ssh/package.json new file mode 100644 index 00000000..f6fc75a8 --- /dev/null +++ b/extensions/open-remote-ssh/package.json @@ -0,0 +1,351 @@ +{ + "name": "open-remote-ssh", + "displayName": "Open Remote - SSH", + "publisher": "voideditor", + "description": "Use any remote machine with a SSH server as your development environment.", + "version": "0.0.48", + "icon": "resources/icon.png", + "engines": { + "vscode": "^1.70.2" + }, + "extensionKind": [ + "ui" + ], + "enabledApiProposals": [ + "resolvers", + "contribViewsRemote" + ], + "keywords": [ + "remote development", + "remote", + "ssh" + ], + "api": "none", + "activationEvents": [ + "onCommand:openremotessh.openEmptyWindow", + "onCommand:openremotessh.openEmptyWindowInCurrentWindow", + "onCommand:openremotessh.openConfigFile", + "onCommand:openremotessh.showLog", + "onResolveRemoteAuthority:ssh-remote", + "onView:sshHosts" + ], + "main": "./out/extension.js", + "contributes": { + "configuration": { + "title": "Remote - SSH", + "properties": { + "remote.SSH.configFile": { + "type": "string", + "description": "The absolute file path to a custom SSH config file.", + "default": "", + "scope": "application" + }, + "remote.SSH.connectTimeout": { + "type": "number", + "description": "Specifies the timeout in seconds used for the SSH command that connects to the remote.", + "default": 60, + "scope": "application", + "minimum": 1 + }, + "remote.SSH.defaultExtensions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of extensions that should be installed automatically on all SSH hosts.", + "scope": "application" + }, + "remote.SSH.enableDynamicForwarding": { + "type": "boolean", + "description": "Whether to use SSH dynamic forwarding to allow setting up new port tunnels over an existing SSH connection.", + "scope": "application", + "default": true + }, + "remote.SSH.enableAgentForwarding": { + "type": "boolean", + "markdownDescription": "Enable fixing the remote environment so that the SSH config option `ForwardAgent` will take effect as expected from VS Code's remote extension host.", + "scope": "application", + "default": true + }, + "remote.SSH.serverDownloadUrlTemplate": { + "type": "string", + "description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number", + "scope": "application", + "default": "https://github.com/voideditor/${NAME_OF_REPO}/releases/download/${version}.${release}/void-server-${os}-${arch}-${version}.${release}.tar.gz" + }, + "remote.SSH.remotePlatform": { + "type": "object", + "description": "A map of the remote hostname to the platform for that remote. Valid values: linux, macos, windows.", + "scope": "application", + "default": {}, + "additionalProperties": { + "type": "string", + "enum": [ + "linux", + "macos", + "windows" + ] + } + }, + "remote.SSH.remoteServerListenOnSocket": { + "type": "boolean", + "description": "When true, the remote vscode server will listen on a socket path instead of opening a port. Only valid for Linux and macOS remotes. Requires `AllowStreamLocalForwarding` to be enabled for the SSH server.", + "default": false + }, + "remote.SSH.experimental.serverBinaryName": { + "type": "string", + "description": "**Experimental:** The name of the server binary, use this **only if** you are using a client without a corresponding server release", + "scope": "application", + "default": "" + } + } + }, + "views": { + "remote": [ + { + "id": "sshHosts", + "name": "SSH Targets", + "group": "targets@1", + "remoteName": "ssh-remote" + } + ] + }, + "commands": [ + { + "command": "openremotessh.openEmptyWindow", + "title": "Connect to Host...", + "category": "Remote-SSH" + }, + { + "command": "openremotessh.openEmptyWindowInCurrentWindow", + "title": "Connect Current Window to Host...", + "category": "Remote-SSH" + }, + { + "command": "openremotessh.openConfigFile", + "title": "Open SSH Configuration File...", + "category": "Remote-SSH" + }, + { + "command": "openremotessh.showLog", + "title": "Show Log", + "category": "Remote-SSH" + }, + { + "command": "openremotessh.explorer.emptyWindowInNewWindow", + "title": "Connect to Host in New Window", + "icon": "$(empty-window)" + }, + { + "command": "openremotessh.explorer.emptyWindowInCurrentWindow", + "title": "Connect to Host in Current Window" + }, + { + "command": "openremotessh.explorer.reopenFolderInCurrentWindow", + "title": "Open on SSH Host in Current Window" + }, + { + "command": "openremotessh.explorer.reopenFolderInNewWindow", + "title": "Open on SSH Host in New Window", + "icon": "$(folder-opened)" + }, + { + "command": "openremotessh.explorer.deleteFolderHistoryItem", + "title": "Remove From Recent List", + "icon": "$(x)" + }, + { + "command": "openremotessh.explorer.refresh", + "title": "Refresh", + "icon": "$(refresh)" + }, + { + "command": "openremotessh.explorer.configure", + "title": "Configure", + "icon": "$(gear)" + }, + { + "command": "openremotessh.explorer.add", + "title": "Add New", + "icon": "$(plus)" + } + ], + "resourceLabelFormatters": [ + { + "scheme": "vscode-remote", + "authority": "ssh-remote+*", + "formatting": { + "label": "${path}", + "separator": "/", + "tildify": true, + "workspaceSuffix": "SSH" + } + } + ], + "menus": { + "statusBar/remoteIndicator": [ + { + "command": "openremotessh.openEmptyWindow", + "when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected", + "group": "remote_20_ssh_1general@1" + }, + { + "command": "openremotessh.openEmptyWindowInCurrentWindow", + "when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected", + "group": "remote_20_ssh_1general@2" + }, + { + "command": "openremotessh.openConfigFile", + "when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected", + "group": "remote_20_ssh_1general@3" + }, + { + "command": "openremotessh.showLog", + "when": "remoteName =~ /^ssh-remote$/ && remoteConnectionState == connected", + "group": "remote_20_ssh_1general@4" + }, + { + "command": "openremotessh.openEmptyWindow", + "when": "remoteConnectionState == disconnected", + "group": "remote_20_ssh_3local@1" + }, + { + "command": "openremotessh.openEmptyWindowInCurrentWindow", + "when": "remoteConnectionState == disconnected", + "group": "remote_20_ssh_3local@2" + }, + { + "command": "openremotessh.openConfigFile", + "when": "remoteConnectionState == disconnected", + "group": "remote_20_ssh_3local@3" + }, + { + "command": "openremotessh.openEmptyWindow", + "when": "!remoteName && !virtualWorkspace", + "group": "remote_20_ssh_3local@5" + }, + { + "command": "openremotessh.openEmptyWindowInCurrentWindow", + "when": "!remoteName && !virtualWorkspace", + "group": "remote_20_ssh_3local@6" + }, + { + "command": "openremotessh.openConfigFile", + "when": "!remoteName && !virtualWorkspace", + "group": "remote_20_ssh_3local@7" + } + ], + "commandPalette": [ + { + "command": "openremotessh.explorer.refresh", + "when": "false" + }, + { + "command": "openremotessh.explorer.configure", + "when": "false" + }, + { + "command": "openremotessh.explorer.add", + "when": "false" + }, + { + "command": "openremotessh.explorer.emptyWindowInNewWindow", + "when": "false" + }, + { + "command": "openremotessh.explorer.emptyWindowInCurrentWindow", + "when": "false" + }, + { + "command": "openremotessh.explorer.reopenFolderInCurrentWindow", + "when": "false" + }, + { + "command": "openremotessh.explorer.reopenFolderInNewWindow", + "when": "false" + }, + { + "command": "openremotessh.explorer.deleteFolderHistoryItem", + "when": "false" + } + ], + "view/title": [ + { + "command": "openremotessh.explorer.add", + "when": "view == sshHosts", + "group": "navigation" + }, + { + "command": "openremotessh.explorer.configure", + "when": "view == sshHosts", + "group": "navigation" + }, + { + "command": "openremotessh.explorer.refresh", + "when": "view == sshHosts", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "openremotessh.explorer.emptyWindowInNewWindow", + "when": "viewItem =~ /^openremotessh.explorer.host$/", + "group": "inline@1" + }, + { + "command": "openremotessh.explorer.emptyWindowInNewWindow", + "when": "viewItem =~ /^openremotessh.explorer.host$/", + "group": "navigation@2" + }, + { + "command": "openremotessh.explorer.emptyWindowInCurrentWindow", + "when": "viewItem =~ /^openremotessh.explorer.host$/", + "group": "navigation@1" + }, + { + "command": "openremotessh.explorer.reopenFolderInNewWindow", + "when": "viewItem == openremotessh.explorer.folder", + "group": "inline@1" + }, + { + "command": "openremotessh.explorer.reopenFolderInNewWindow", + "when": "viewItem == openremotessh.explorer.folder", + "group": "navigation@2" + }, + { + "command": "openremotessh.explorer.reopenFolderInCurrentWindow", + "when": "viewItem == openremotessh.explorer.folder", + "group": "navigation@1" + }, + { + "command": "openremotessh.explorer.deleteFolderHistoryItem", + "when": "viewItem =~ /^openremotessh.explorer.folder/", + "group": "navigation@3" + }, + { + "command": "openremotessh.explorer.deleteFolderHistoryItem", + "when": "viewItem =~ /^openremotessh.explorer.folder/", + "group": "inline@2" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "gulp compile-extension:open-remote-ssh", + "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", + "watch": "gulp watch-extension:open-remote-ssh", + "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + }, + "devDependencies": { + "@types/ssh2": "^0.5.52", + "@types/ssh2-streams": "0.1.12" + }, + "dependencies": { + "glob": "^9.3.1", + "simple-socks": "git+https://github.com/jeanp413/simple-socks#main", + "socks": "^2.5.0", + "@jeanp413/ssh-config": "^4.3.1", + "ssh2": "git+https://github.com/jeanp413/ssh2#master" + } +} diff --git a/extensions/open-remote-ssh/resources/icon.png b/extensions/open-remote-ssh/resources/icon.png new file mode 100644 index 00000000..aa5a7b9f Binary files /dev/null and b/extensions/open-remote-ssh/resources/icon.png differ diff --git a/extensions/open-remote-ssh/src/authResolver.ts b/extensions/open-remote-ssh/src/authResolver.ts new file mode 100644 index 00000000..16db657f --- /dev/null +++ b/extensions/open-remote-ssh/src/authResolver.ts @@ -0,0 +1,464 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as net from 'net'; +import * as stream from 'stream'; +import { SocksClient, SocksClientOptions } from 'socks'; +import * as vscode from 'vscode'; +import * as ssh2 from 'ssh2'; +import type { ParsedKey } from 'ssh2-streams'; +import Log from './common/logger'; +import SSHDestination from './ssh/sshDestination'; +import SSHConnection, { SSHTunnelConfig } from './ssh/sshConnection'; +import SSHConfiguration from './ssh/sshConfig'; +import { gatherIdentityFiles } from './ssh/identityFiles'; +import { untildify, exists as fileExists } from './common/files'; +import { findRandomPort } from './common/ports'; +import { disposeAll } from './common/disposable'; +import { installCodeServer, ServerInstallError } from './serverSetup'; +import { isWindows } from './common/platform'; +import * as os from 'os'; + +const PASSWORD_RETRY_COUNT = 3; +const PASSPHRASE_RETRY_COUNT = 3; + +export const REMOTE_SSH_AUTHORITY = 'ssh-remote'; + +export function getRemoteAuthority(host: string) { + return `${REMOTE_SSH_AUTHORITY}+${host}`; +} + +class TunnelInfo implements vscode.Disposable { + constructor( + readonly localPort: number, + readonly remotePortOrSocketPath: number | string, + private disposables: vscode.Disposable[] + ) { + } + + dispose() { + disposeAll(this.disposables); + } +} + +interface SSHKey { + filename: string; + parsedKey: ParsedKey; + fingerprint: string; + agentSupport?: boolean; + isPrivate?: boolean; +} + +export class RemoteSSHResolver implements vscode.RemoteAuthorityResolver, vscode.Disposable { + + private proxyConnections: SSHConnection[] = []; + private sshConnection: SSHConnection | undefined; + private sshAgentSock: string | undefined; + private proxyCommandProcess: cp.ChildProcessWithoutNullStreams | undefined; + + private socksTunnel: SSHTunnelConfig | undefined; + private tunnels: TunnelInfo[] = []; + + private labelFormatterDisposable: vscode.Disposable | undefined; + + constructor( + readonly context: vscode.ExtensionContext, + readonly logger: Log + ) { + } + + resolve(authority: string, context: vscode.RemoteAuthorityResolverContext): Thenable { + const [type, dest] = authority.split('+'); + if (type !== REMOTE_SSH_AUTHORITY) { + throw new Error(`Invalid authority type for SSH resolver: ${type}`); + } + + this.logger.info(`Resolving ssh remote authority '${authority}' (attemp #${context.resolveAttempt})`); + + const sshDest = SSHDestination.parseEncoded(dest); + + // It looks like default values are not loaded yet when resolving a remote, + // so let's hardcode the default values here + const remoteSSHconfig = vscode.workspace.getConfiguration('remote.SSH'); + const enableDynamicForwarding = remoteSSHconfig.get('enableDynamicForwarding', true)!; + const enableAgentForwarding = remoteSSHconfig.get('enableAgentForwarding', true)!; + const serverDownloadUrlTemplate = remoteSSHconfig.get('serverDownloadUrlTemplate'); + const defaultExtensions = remoteSSHconfig.get('defaultExtensions', []); + const remotePlatformMap = remoteSSHconfig.get>('remotePlatform', {}); + const remoteServerListenOnSocket = remoteSSHconfig.get('remoteServerListenOnSocket', false)!; + const connectTimeout = remoteSSHconfig.get('connectTimeout', 60)!; + + return vscode.window.withProgress({ + title: `Setting up SSH Host ${sshDest.hostname}`, + location: vscode.ProgressLocation.Notification, + cancellable: false + }, async () => { + try { + const sshconfig = await SSHConfiguration.loadFromFS(); + const sshHostConfig = sshconfig.getHostConfiguration(sshDest.hostname); + const sshHostName = sshHostConfig['HostName'] ? sshHostConfig['HostName'].replace('%h', sshDest.hostname) : sshDest.hostname; + const sshUser = sshHostConfig['User'] || sshDest.user || os.userInfo().username || ''; // https://github.com/openssh/openssh-portable/blob/5ec5504f1d328d5bfa64280cd617c3efec4f78f3/sshconnect.c#L1561-L1562 + const sshPort = sshHostConfig['Port'] ? parseInt(sshHostConfig['Port'], 10) : (sshDest.port || 22); + + this.sshAgentSock = sshHostConfig['IdentityAgent'] || process.env['SSH_AUTH_SOCK'] || (isWindows ? '\\\\.\\pipe\\openssh-ssh-agent' : undefined); + this.sshAgentSock = this.sshAgentSock ? untildify(this.sshAgentSock) : undefined; + const agentForward = enableAgentForwarding && (sshHostConfig['ForwardAgent'] || 'no').toLowerCase() === 'yes'; + const agent = agentForward && this.sshAgentSock ? new ssh2.OpenSSHAgent(this.sshAgentSock) : undefined; + + const preferredAuthentications = sshHostConfig['PreferredAuthentications'] ? sshHostConfig['PreferredAuthentications'].split(',').map(s => s.trim()) : ['publickey', 'password', 'keyboard-interactive']; + + const identityFiles: string[] = (sshHostConfig['IdentityFile'] as unknown as string[]) || []; + const identitiesOnly = (sshHostConfig['IdentitiesOnly'] || 'no').toLowerCase() === 'yes'; + const identityKeys = await gatherIdentityFiles(identityFiles, this.sshAgentSock, identitiesOnly, this.logger); + + // Create proxy jump connections if any + let proxyStream: ssh2.ClientChannel | stream.Duplex | undefined; + if (sshHostConfig['ProxyJump']) { + const proxyJumps = sshHostConfig['ProxyJump'].split(',').filter(i => !!i.trim()) + .map(i => { + const proxy = SSHDestination.parse(i); + const proxyHostConfig = sshconfig.getHostConfiguration(proxy.hostname); + return [proxy, proxyHostConfig] as [SSHDestination, Record]; + }); + for (let i = 0; i < proxyJumps.length; i++) { + const [proxy, proxyHostConfig] = proxyJumps[i]; + const proxyHostName = proxyHostConfig['HostName'] || proxy.hostname; + const proxyUser = proxyHostConfig['User'] || proxy.user || sshUser; + const proxyPort = proxyHostConfig['Port'] ? parseInt(proxyHostConfig['Port'], 10) : (proxy.port || sshPort); + + const proxyAgentForward = enableAgentForwarding && (proxyHostConfig['ForwardAgent'] || 'no').toLowerCase() === 'yes'; + const proxyAgent = proxyAgentForward && this.sshAgentSock ? new ssh2.OpenSSHAgent(this.sshAgentSock) : undefined; + + const proxyIdentityFiles: string[] = (proxyHostConfig['IdentityFile'] as unknown as string[]) || []; + const proxyIdentitiesOnly = (proxyHostConfig['IdentitiesOnly'] || 'no').toLowerCase() === 'yes'; + const proxyIdentityKeys = await gatherIdentityFiles(proxyIdentityFiles, this.sshAgentSock, proxyIdentitiesOnly, this.logger); + + const proxyAuthHandler = this.getSSHAuthHandler(proxyUser, proxyHostName, proxyIdentityKeys, preferredAuthentications); + const proxyConnection = new SSHConnection({ + host: !proxyStream ? proxyHostName : undefined, + port: !proxyStream ? proxyPort : undefined, + sock: proxyStream, + username: proxyUser, + readyTimeout: connectTimeout * 1000, + strictVendor: false, + agentForward: proxyAgentForward, + agent: proxyAgent, + authHandler: (arg0, arg1, arg2) => (proxyAuthHandler(arg0, arg1, arg2), undefined) + }); + this.proxyConnections.push(proxyConnection); + + const nextProxyJump = i < proxyJumps.length - 1 ? proxyJumps[i + 1] : undefined; + const destIP = nextProxyJump ? (nextProxyJump[1]['HostName'] || nextProxyJump[0].hostname) : sshHostName; + const destPort = nextProxyJump ? ((nextProxyJump[1]['Port'] && parseInt(nextProxyJump[1]['Port'], 10)) || nextProxyJump[0].port || 22) : sshPort; + proxyStream = await proxyConnection.forwardOut('127.0.0.1', 0, destIP, destPort); + } + } else if (sshHostConfig['ProxyCommand']) { + let proxyArgs = (sshHostConfig['ProxyCommand'] as unknown as string[]) + .map((arg) => arg.replace('%h', sshHostName).replace('%n', sshDest.hostname).replace('%p', sshPort.toString()).replace('%r', sshUser)); + let proxyCommand = proxyArgs.shift()!; + + let options = {}; + if (isWindows && /\.(bat|cmd)$/.test(proxyCommand)) { + proxyCommand = `"${proxyCommand}"`; + proxyArgs = proxyArgs.map((arg) => arg.includes(' ') ? `"${arg}"` : arg); + options = { shell: true, windowsHide: true, windowsVerbatimArguments: true }; + } + + this.logger.trace(`Spawning ProxyCommand: ${proxyCommand} ${proxyArgs.join(' ')}`); + + const child = cp.spawn(proxyCommand, proxyArgs, options); + proxyStream = stream.Duplex.from({ readable: child.stdout, writable: child.stdin }); + this.proxyCommandProcess = child; + } + + // Create final shh connection + const sshAuthHandler = this.getSSHAuthHandler(sshUser, sshHostName, identityKeys, preferredAuthentications); + + this.sshConnection = new SSHConnection({ + host: !proxyStream ? sshHostName : undefined, + port: !proxyStream ? sshPort : undefined, + sock: proxyStream, + username: sshUser, + readyTimeout: connectTimeout * 1000, + strictVendor: false, + agentForward, + agent, + authHandler: (arg0, arg1, arg2) => (sshAuthHandler(arg0, arg1, arg2), undefined), + }); + await this.sshConnection.connect(); + + const envVariables: Record = {}; + if (agentForward) { + envVariables['SSH_AUTH_SOCK'] = null; + } + + const installResult = await installCodeServer(this.sshConnection, serverDownloadUrlTemplate, defaultExtensions, Object.keys(envVariables), remotePlatformMap[sshDest.hostname], remoteServerListenOnSocket, this.logger); + + for (const key of Object.keys(envVariables)) { + if (installResult[key] !== undefined) { + envVariables[key] = installResult[key]; + } + } + + // Update terminal env variables + this.context.environmentVariableCollection.persistent = false; + for (const [key, value] of Object.entries(envVariables)) { + if (value) { + this.context.environmentVariableCollection.replace(key, value); + } + } + + if (enableDynamicForwarding) { + const socksPort = await findRandomPort(); + this.socksTunnel = await this.sshConnection!.addTunnel({ + name: `ssh_tunnel_socks_${socksPort}`, + localPort: socksPort, + socks: true + }); + } + + const tunnelConfig = await this.openTunnel(0, installResult.listeningOn); + this.tunnels.push(tunnelConfig); + + // Enable ports view + vscode.commands.executeCommand('setContext', 'forwardedPortsViewEnabled', true); + + this.labelFormatterDisposable?.dispose(); + this.labelFormatterDisposable = vscode.workspace.registerResourceLabelFormatter({ + scheme: 'vscode-remote', + authority: `${REMOTE_SSH_AUTHORITY}+*`, + formatting: { + label: '${path}', + separator: '/', + tildify: true, + workspaceSuffix: `SSH: ${sshDest.hostname}` + (sshDest.port && sshDest.port !== 22 ? `:${sshDest.port}` : '') + } + }); + + const resolvedResult: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', tunnelConfig.localPort, installResult.connectionToken); + resolvedResult.extensionHostEnv = envVariables; + return resolvedResult; + } catch (e: unknown) { + this.logger.error(`Error resolving authority`, e); + + // Initial connection + if (context.resolveAttempt === 1) { + this.logger.show(); + + const closeRemote = 'Close Remote'; + const retry = 'Retry'; + const result = await vscode.window.showErrorMessage(`Could not establish connection to "${sshDest.hostname}"`, { modal: true }, closeRemote, retry); + if (result === closeRemote) { + await vscode.commands.executeCommand('workbench.action.remote.close'); + } else if (result === retry) { + await vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + } + + if (e instanceof ServerInstallError || !(e instanceof Error)) { + throw vscode.RemoteAuthorityResolverError.NotAvailable(e instanceof Error ? e.message : String(e)); + } else { + throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable(e.message); + } + } + }); + } + + private async openTunnel(localPort: number, remotePortOrSocketPath: number | string) { + localPort = localPort > 0 ? localPort : await findRandomPort(); + + const disposables: vscode.Disposable[] = []; + const remotePort = typeof remotePortOrSocketPath === 'number' ? remotePortOrSocketPath : undefined; + const remoteSocketPath = typeof remotePortOrSocketPath === 'string' ? remotePortOrSocketPath : undefined; + if (this.socksTunnel && remotePort) { + const forwardingServer = await new Promise((resolve, reject) => { + this.logger.trace(`Creating forwarding server ${localPort}(local) => ${this.socksTunnel!.localPort!}(socks) => ${remotePort}(remote)`); + const socksOptions: SocksClientOptions = { + proxy: { + host: '127.0.0.1', + port: this.socksTunnel!.localPort!, + type: 5 + }, + command: 'connect', + destination: { + host: '127.0.0.1', + port: remotePort + } + }; + const server: net.Server = net.createServer() + .on('error', reject) + .on('connection', async (socket: net.Socket) => { + try { + const socksConn = await SocksClient.createConnection(socksOptions); + socket.pipe(socksConn.socket); + socksConn.socket.pipe(socket); + } catch (error) { + this.logger.error(`Error while creating SOCKS connection`, error); + } + }) + .on('listening', () => resolve(server)) + .listen(localPort); + }); + disposables.push({ + dispose: () => forwardingServer.close(() => { + this.logger.trace(`SOCKS forwading server closed`); + }), + }); + } else { + this.logger.trace(`Opening tunnel ${localPort}(local) => ${remotePortOrSocketPath}(remote)`); + const tunnelConfig = await this.sshConnection!.addTunnel({ + name: `ssh_tunnel_${localPort}_${remotePortOrSocketPath}`, + remoteAddr: '127.0.0.1', + remotePort, + remoteSocketPath, + localPort + }); + disposables.push({ + dispose: () => { + this.sshConnection?.closeTunnel(tunnelConfig.name); + this.logger.trace(`Tunnel ${tunnelConfig.name} closed`); + } + }); + } + + return new TunnelInfo(localPort, remotePortOrSocketPath, disposables); + } + + private getSSHAuthHandler(sshUser: string, sshHostName: string, identityKeys: SSHKey[], preferredAuthentications: string[]) { + let passwordRetryCount = PASSWORD_RETRY_COUNT; + let keyboardRetryCount = PASSWORD_RETRY_COUNT; + identityKeys = identityKeys.slice(); + return async (methodsLeft: string[] | null, _partialSuccess: boolean | null, callback: (nextAuth: ssh2.AuthHandlerResult) => void) => { + if (methodsLeft === null) { + this.logger.info(`Trying no-auth authentication`); + + return callback({ + type: 'none', + username: sshUser, + }); + } + if (methodsLeft.includes('publickey') && identityKeys.length && preferredAuthentications.includes('publickey')) { + const identityKey = identityKeys.shift()!; + + this.logger.info(`Trying publickey authentication: ${identityKey.filename} ${identityKey.parsedKey.type} SHA256:${identityKey.fingerprint}`); + + if (identityKey.agentSupport) { + return callback({ + type: 'agent', + username: sshUser, + agent: new class extends ssh2.OpenSSHAgent { + // Only return the current key + override getIdentities(callback: (err: Error | undefined, publicKeys?: ParsedKey[]) => void): void { + callback(undefined, [identityKey.parsedKey]); + } + }(this.sshAgentSock!) + }); + } + if (identityKey.isPrivate) { + return callback({ + type: 'publickey', + username: sshUser, + key: identityKey.parsedKey + }); + } + if (!await fileExists(identityKey.filename)) { + // Try next identity file + return callback(null as any); + } + + const keyBuffer = await fs.promises.readFile(identityKey.filename); + let result = ssh2.utils.parseKey(keyBuffer); // First try without passphrase + if (result instanceof Error && result.message === 'Encrypted private OpenSSH key detected, but no passphrase given') { + let passphraseRetryCount = PASSPHRASE_RETRY_COUNT; + while (result instanceof Error && passphraseRetryCount > 0) { + const passphrase = await vscode.window.showInputBox({ + title: `Enter passphrase for ${identityKey.filename}`, + password: true, + ignoreFocusOut: true + }); + if (!passphrase) { + break; + } + result = ssh2.utils.parseKey(keyBuffer, passphrase); + passphraseRetryCount--; + } + } + if (!result || result instanceof Error) { + // Try next identity file + return callback(null as any); + } + + const key = Array.isArray(result) ? result[0] : result; + return callback({ + type: 'publickey', + username: sshUser, + key + }); + } + if (methodsLeft.includes('password') && passwordRetryCount > 0 && preferredAuthentications.includes('password')) { + if (passwordRetryCount === PASSWORD_RETRY_COUNT) { + this.logger.info(`Trying password authentication`); + } + + const password = await vscode.window.showInputBox({ + title: `Enter password for ${sshUser}@${sshHostName}`, + password: true, + ignoreFocusOut: true + }); + passwordRetryCount--; + + return callback(password + ? { + type: 'password', + username: sshUser, + password + } + : false); + } + if (methodsLeft.includes('keyboard-interactive') && keyboardRetryCount > 0 && preferredAuthentications.includes('keyboard-interactive')) { + if (keyboardRetryCount === PASSWORD_RETRY_COUNT) { + this.logger.info(`Trying keyboard-interactive authentication`); + } + + return callback({ + type: 'keyboard-interactive', + username: sshUser, + prompt: async (_name, _instructions, _instructionsLang, prompts, finish) => { + const responses: string[] = []; + for (const prompt of prompts) { + const response = await vscode.window.showInputBox({ + title: `(${sshUser}@${sshHostName}) ${prompt.prompt}`, + password: !prompt.echo, + ignoreFocusOut: true + }); + if (response === undefined) { + keyboardRetryCount = 0; + break; + } + responses.push(response); + } + keyboardRetryCount--; + finish(responses); + } + }); + } + + callback(false); + }; + } + + dispose() { + disposeAll(this.tunnels); + // If there's proxy connections then just close the parent connection + if (this.proxyConnections.length) { + this.proxyConnections[0].close(); + } else { + this.sshConnection?.close(); + } + this.proxyCommandProcess?.kill(); + this.labelFormatterDisposable?.dispose(); + } +} diff --git a/extensions/open-remote-ssh/src/commands.ts b/extensions/open-remote-ssh/src/commands.ts new file mode 100644 index 00000000..ccc9c7ff --- /dev/null +++ b/extensions/open-remote-ssh/src/commands.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { getRemoteAuthority } from './authResolver'; +import { getSSHConfigPath } from './ssh/sshConfig'; +import { exists as fileExists } from './common/files'; +import SSHDestination from './ssh/sshDestination'; + +export async function promptOpenRemoteSSHWindow(reuseWindow: boolean) { + const host = await vscode.window.showInputBox({ + title: 'Enter [user@]hostname[:port]' + }); + + if (!host) { + return; + } + + const sshDest = new SSHDestination(host); + openRemoteSSHWindow(sshDest.toEncodedString(), reuseWindow); +} + +export function openRemoteSSHWindow(host: string, reuseWindow: boolean) { + vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: getRemoteAuthority(host), reuseWindow }); +} + +export function openRemoteSSHLocationWindow(host: string, path: string, reuseWindow: boolean) { + vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.from({ scheme: 'vscode-remote', authority: getRemoteAuthority(host), path }), { forceNewWindow: !reuseWindow }); +} + +export async function addNewHost() { + const sshConfigPath = getSSHConfigPath(); + if (!await fileExists(sshConfigPath)) { + await fs.promises.appendFile(sshConfigPath, ''); + } + + await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(sshConfigPath), { preview: false }); + + const textEditor = vscode.window.activeTextEditor; + if (textEditor?.document.uri.fsPath !== sshConfigPath) { + return; + } + + const textDocument = textEditor.document; + const lastLine = textDocument.lineAt(textDocument.lineCount - 1); + + if (!lastLine.isEmptyOrWhitespace) { + await textEditor.edit((editBuilder: vscode.TextEditorEdit) => { + editBuilder.insert(lastLine.range.end, '\n'); + }); + } + + const snippet = '\nHost ${1:dev}\n\tHostName ${2:dev.example.com}\n\tUser ${3:john}'; + await textEditor.insertSnippet( + new vscode.SnippetString(snippet), + new vscode.Position(textDocument.lineCount, 0) + ); +} + +export async function openSSHConfigFile() { + const sshConfigPath = getSSHConfigPath(); + if (!await fileExists(sshConfigPath)) { + await fs.promises.appendFile(sshConfigPath, ''); + } + vscode.commands.executeCommand('vscode.open', vscode.Uri.file(sshConfigPath)); +} diff --git a/extensions/open-remote-ssh/src/common/disposable.ts b/extensions/open-remote-ssh/src/common/disposable.ts new file mode 100644 index 00000000..c81eb6cd --- /dev/null +++ b/extensions/open-remote-ssh/src/common/disposable.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +export function disposeAll(disposables: vscode.Disposable[]): void { + while (disposables.length) { + const item = disposables.pop(); + if (item) { + item.dispose(); + } + } +} + +export abstract class Disposable { + private _isDisposed = false; + + protected _disposables: vscode.Disposable[] = []; + + public dispose(): any { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + disposeAll(this._disposables); + } + + protected _register(value: T): T { + if (this._isDisposed) { + value.dispose(); + } else { + this._disposables.push(value); + } + return value; + } + + protected get isDisposed(): boolean { + return this._isDisposed; + } +} diff --git a/extensions/open-remote-ssh/src/common/files.ts b/extensions/open-remote-ssh/src/common/files.ts new file mode 100644 index 00000000..6d9ff46f --- /dev/null +++ b/extensions/open-remote-ssh/src/common/files.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; +import * as os from 'os'; + +const homeDir = os.homedir(); + +export async function exists(path: string) { + try { + await fs.promises.access(path); + return true; + } catch { + return false; + } +} + +export function untildify(path: string) { + return path.replace(/^~(?=$|\/|\\)/, homeDir); +} + +export function normalizeToSlash(path: string) { + return path.replace(/\\/g, '/'); +} diff --git a/extensions/open-remote-ssh/src/common/logger.ts b/extensions/open-remote-ssh/src/common/logger.ts new file mode 100644 index 00000000..6489d0c0 --- /dev/null +++ b/extensions/open-remote-ssh/src/common/logger.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +type LogLevel = 'Trace' | 'Info' | 'Error'; + +export default class Log { + private output: vscode.OutputChannel; + + constructor(name: string) { + this.output = vscode.window.createOutputChannel(name); + } + + private data2String(data: any): string { + if (data instanceof Error) { + return data.stack || data.message; + } + if (data.success === false && data.message) { + return data.message; + } + return data.toString(); + } + + public trace(message: string, data?: any): void { + this.logLevel('Trace', message, data); + } + + public info(message: string, data?: any): void { + this.logLevel('Info', message, data); + } + + public error(message: string, data?: any): void { + this.logLevel('Error', message, data); + } + + public logLevel(level: LogLevel, message: string, data?: any): void { + this.output.appendLine(`[${level} - ${this.now()}] ${message}`); + if (data) { + this.output.appendLine(this.data2String(data)); + } + } + + private now(): string { + const now = new Date(); + return padLeft(now.getUTCHours() + '', 2, '0') + + ':' + padLeft(now.getMinutes() + '', 2, '0') + + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); + } + + public show() { + this.output.show(); + } + + public dispose() { + this.output.dispose(); + } +} + +function padLeft(s: string, n: number, pad = ' ') { + return pad.repeat(Math.max(0, n - s.length)) + s; +} diff --git a/extensions/open-remote-ssh/src/common/platform.ts b/extensions/open-remote-ssh/src/common/platform.ts new file mode 100644 index 00000000..73c65ca9 --- /dev/null +++ b/extensions/open-remote-ssh/src/common/platform.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +export const isWindows = process.platform === 'win32'; +export const isMacintosh = process.platform === 'darwin'; +export const isLinux = process.platform === 'linux'; diff --git a/extensions/open-remote-ssh/src/common/ports.ts b/extensions/open-remote-ssh/src/common/ports.ts new file mode 100644 index 00000000..e135441b --- /dev/null +++ b/extensions/open-remote-ssh/src/common/ports.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as net from 'net'; + +/** + * Finds a random unused port assigned by the operating system. Will reject in case no free port can be found. + */ +export function findRandomPort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer({ pauseOnConnect: true }); + server.on('error', reject); + server.on('listening', () => { + const port = (server.address() as net.AddressInfo).port; + server.close(() => resolve(port)); + }); + server.listen(0, '127.0.0.1'); + }); +} + +/** + * Given a start point and a max number of retries, will find a port that + * is openable. Will return 0 in case no free port can be found. + */ +export function findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride = 1): Promise { + let done = false; + + return new Promise(resolve => { + const timeoutHandle = setTimeout(() => { + if (!done) { + done = true; + return resolve(0); + } + }, timeout); + + doFindFreePort(startPort, giveUpAfter, stride, (port) => { + if (!done) { + done = true; + clearTimeout(timeoutHandle); + return resolve(port); + } + }); + }); +} + +function doFindFreePort(startPort: number, giveUpAfter: number, stride: number, clb: (port: number) => void): void { + if (giveUpAfter === 0) { + return clb(0); + } + + const client = new net.Socket(); + + // If we can connect to the port it means the port is already taken so we continue searching + client.once('connect', () => { + dispose(client); + + return doFindFreePort(startPort + stride, giveUpAfter - 1, stride, clb); + }); + + client.once('data', () => { + // this listener is required since node.js 8.x + }); + + client.once('error', (err: Error & { code?: string }) => { + dispose(client); + + // If we receive any non ECONNREFUSED error, it means the port is used but we cannot connect + if (err.code !== 'ECONNREFUSED') { + return doFindFreePort(startPort + stride, giveUpAfter - 1, stride, clb); + } + + // Otherwise it means the port is free to use! + return clb(startPort); + }); + + client.connect(startPort, '127.0.0.1'); +} + +/** + * Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener. + */ +export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number): Promise { + let resolved = false; + let timeoutHandle: NodeJS.Timeout | undefined = undefined; + let countTried = 1; + const server = net.createServer({ pauseOnConnect: true }); + function doResolve(port: number, resolve: (port: number) => void) { + if (!resolved) { + resolved = true; + server.removeAllListeners(); + server.close(); + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + resolve(port); + } + } + return new Promise(resolve => { + timeoutHandle = setTimeout(() => { + doResolve(0, resolve); + }, timeout); + + server.on('listening', () => { + doResolve(startPort, resolve); + }); + server.on('error', err => { + if (err && ((err).code === 'EADDRINUSE' || (err).code === 'EACCES') && (countTried < giveUpAfter)) { + startPort++; + countTried++; + server.listen(startPort, '127.0.0.1'); + } else { + doResolve(0, resolve); + } + }); + server.on('close', () => { + doResolve(0, resolve); + }); + server.listen(startPort, '127.0.0.1'); + }); +} + +function dispose(socket: net.Socket): void { + try { + socket.removeAllListeners('connect'); + socket.removeAllListeners('error'); + socket.end(); + socket.destroy(); + socket.unref(); + } catch (error) { + console.error(error); // otherwise this error would get lost in the callback chain + } +} diff --git a/extensions/open-remote-ssh/src/extension.ts b/extensions/open-remote-ssh/src/extension.ts new file mode 100644 index 00000000..e950f545 --- /dev/null +++ b/extensions/open-remote-ssh/src/extension.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import Log from './common/logger'; +import { RemoteSSHResolver, REMOTE_SSH_AUTHORITY } from './authResolver'; +import { openSSHConfigFile, promptOpenRemoteSSHWindow } from './commands'; +import { HostTreeDataProvider } from './hostTreeView'; +import { getRemoteWorkspaceLocationData, RemoteLocationHistory } from './remoteLocationHistory'; + +export async function activate(context: vscode.ExtensionContext) { + const logger = new Log('Remote - SSH'); + context.subscriptions.push(logger); + + const remoteSSHResolver = new RemoteSSHResolver(context, logger); + context.subscriptions.push(vscode.workspace.registerRemoteAuthorityResolver(REMOTE_SSH_AUTHORITY, remoteSSHResolver)); + context.subscriptions.push(remoteSSHResolver); + + const locationHistory = new RemoteLocationHistory(context); + const locationData = getRemoteWorkspaceLocationData(); + if (locationData) { + await locationHistory.addLocation(locationData[0], locationData[1]); + } + + const hostTreeDataProvider = new HostTreeDataProvider(locationHistory); + context.subscriptions.push(vscode.window.createTreeView('sshHosts', { treeDataProvider: hostTreeDataProvider })); + context.subscriptions.push(hostTreeDataProvider); + + context.subscriptions.push(vscode.commands.registerCommand('openremotessh.openEmptyWindow', () => promptOpenRemoteSSHWindow(false))); + context.subscriptions.push(vscode.commands.registerCommand('openremotessh.openEmptyWindowInCurrentWindow', () => promptOpenRemoteSSHWindow(true))); + context.subscriptions.push(vscode.commands.registerCommand('openremotessh.openConfigFile', () => openSSHConfigFile())); + context.subscriptions.push(vscode.commands.registerCommand('openremotessh.showLog', () => logger.show())); +} + +export function deactivate() { +} diff --git a/extensions/open-remote-ssh/src/hostTreeView.ts b/extensions/open-remote-ssh/src/hostTreeView.ts new file mode 100644 index 00000000..5874c53e --- /dev/null +++ b/extensions/open-remote-ssh/src/hostTreeView.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as path from 'path'; +import SSHConfiguration, { getSSHConfigPath } from './ssh/sshConfig'; +import { RemoteLocationHistory } from './remoteLocationHistory'; +import { Disposable } from './common/disposable'; +import { addNewHost, openRemoteSSHLocationWindow, openRemoteSSHWindow, openSSHConfigFile } from './commands'; +import SSHDestination from './ssh/sshDestination'; + +class HostItem { + constructor( + public hostname: string, + public locations: string[] + ) { + } +} + +class HostLocationItem { + constructor( + public path: string, + public hostname: string + ) { + } +} + +type DataTreeItem = HostItem | HostLocationItem; + +export class HostTreeDataProvider extends Disposable implements vscode.TreeDataProvider { + + private readonly _onDidChangeTreeData = this._register(new vscode.EventEmitter()); + public readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + constructor( + private locationHistory: RemoteLocationHistory + ) { + super(); + + this._register(vscode.commands.registerCommand('openremotessh.explorer.add', () => addNewHost())); + this._register(vscode.commands.registerCommand('openremotessh.explorer.configure', () => openSSHConfigFile())); + this._register(vscode.commands.registerCommand('openremotessh.explorer.refresh', () => this.refresh())); + this._register(vscode.commands.registerCommand('openremotessh.explorer.emptyWindowInNewWindow', e => this.openRemoteSSHWindow(e, false))); + this._register(vscode.commands.registerCommand('openremotessh.explorer.emptyWindowInCurrentWindow', e => this.openRemoteSSHWindow(e, true))); + this._register(vscode.commands.registerCommand('openremotessh.explorer.reopenFolderInNewWindow', e => this.openRemoteSSHLocationWindow(e, false))); + this._register(vscode.commands.registerCommand('openremotessh.explorer.reopenFolderInCurrentWindow', e => this.openRemoteSSHLocationWindow(e, true))); + this._register(vscode.commands.registerCommand('openremotessh.explorer.deleteFolderHistoryItem', e => this.deleteHostLocation(e))); + + this._register(vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('remote.SSH.configFile')) { + this.refresh(); + } + })); + this._register(vscode.workspace.onDidSaveTextDocument(e => { + if (e.uri.fsPath === getSSHConfigPath()) { + this.refresh(); + } + })); + } + + getTreeItem(element: DataTreeItem): vscode.TreeItem { + if (element instanceof HostLocationItem) { + const label = path.posix.basename(element.path).replace(/\.code-workspace$/, ' (Workspace)'); + const treeItem = new vscode.TreeItem(label); + treeItem.description = path.posix.dirname(element.path); + treeItem.iconPath = new vscode.ThemeIcon('folder'); + treeItem.contextValue = 'openremotessh.explorer.folder'; + return treeItem; + } + + const treeItem = new vscode.TreeItem(element.hostname); + treeItem.collapsibleState = element.locations.length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None; + treeItem.iconPath = new vscode.ThemeIcon('vm'); + treeItem.contextValue = 'openremotessh.explorer.host'; + return treeItem; + } + + async getChildren(element?: HostItem): Promise { + if (!element) { + const sshConfigFile = await SSHConfiguration.loadFromFS(); + const hosts = sshConfigFile.getAllConfiguredHosts(); + return hosts.map(hostname => new HostItem(hostname, this.locationHistory.getHistory(hostname))); + } + if (element instanceof HostItem) { + return element.locations.map(location => new HostLocationItem(location, element.hostname)); + } + return []; + } + + private refresh() { + this._onDidChangeTreeData.fire(); + } + + private async deleteHostLocation(element: HostLocationItem) { + await this.locationHistory.removeLocation(element.hostname, element.path); + this.refresh(); + } + + private async openRemoteSSHWindow(element: HostItem, reuseWindow: boolean) { + const sshDest = new SSHDestination(element.hostname); + openRemoteSSHWindow(sshDest.toEncodedString(), reuseWindow); + } + + private async openRemoteSSHLocationWindow(element: HostLocationItem, reuseWindow: boolean) { + const sshDest = new SSHDestination(element.hostname); + openRemoteSSHLocationWindow(sshDest.toEncodedString(), element.path, reuseWindow); + } +} diff --git a/extensions/open-remote-ssh/src/remoteLocationHistory.ts b/extensions/open-remote-ssh/src/remoteLocationHistory.ts new file mode 100644 index 00000000..6e6b7777 --- /dev/null +++ b/extensions/open-remote-ssh/src/remoteLocationHistory.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import { REMOTE_SSH_AUTHORITY } from './authResolver'; +import SSHDestination from './ssh/sshDestination'; + +export class RemoteLocationHistory { + private static STORAGE_KEY = 'remoteLocationHistory_v0'; + + private remoteLocationHistory: Record = {}; + + constructor(private context: vscode.ExtensionContext) { + // context.globalState.update(RemoteLocationHistory.STORAGE_KEY, undefined); + this.remoteLocationHistory = context.globalState.get(RemoteLocationHistory.STORAGE_KEY) || {}; + } + + getHistory(host: string): string[] { + return this.remoteLocationHistory[host] || []; + } + + async addLocation(host: string, path: string) { + const hostLocations = this.remoteLocationHistory[host] || []; + if (!hostLocations.includes(path)) { + hostLocations.unshift(path); + this.remoteLocationHistory[host] = hostLocations; + + await this.context.globalState.update(RemoteLocationHistory.STORAGE_KEY, this.remoteLocationHistory); + } + } + + async removeLocation(host: string, path: string) { + let hostLocations = this.remoteLocationHistory[host] || []; + hostLocations = hostLocations.filter(l => l !== path); + this.remoteLocationHistory[host] = hostLocations; + + await this.context.globalState.update(RemoteLocationHistory.STORAGE_KEY, this.remoteLocationHistory); + } +} + +export function getRemoteWorkspaceLocationData(): [string, string] | undefined { + let location = vscode.workspace.workspaceFile; + if (location && location.scheme === 'vscode-remote' && location.authority.startsWith(REMOTE_SSH_AUTHORITY) && location.path.endsWith('.code-workspace')) { + const [, host] = location.authority.split('+'); + const sshDest = SSHDestination.parseEncoded(host); + return [sshDest.hostname, location.path]; + } + + location = vscode.workspace.workspaceFolders?.[0].uri; + if (location && location.scheme === 'vscode-remote' && location.authority.startsWith(REMOTE_SSH_AUTHORITY)) { + const [, host] = location.authority.split('+'); + const sshDest = SSHDestination.parseEncoded(host); + return [sshDest.hostname, location.path]; + } + + return undefined; +} diff --git a/extensions/open-remote-ssh/src/serverConfig.ts b/extensions/open-remote-ssh/src/serverConfig.ts new file mode 100644 index 00000000..3f2758fa --- /dev/null +++ b/extensions/open-remote-ssh/src/serverConfig.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +let vscodeProductJson: any; +async function getVSCodeProductJson() { + if (!vscodeProductJson) { + const productJsonStr = await fs.promises.readFile(path.join(vscode.env.appRoot, 'product.json'), 'utf8'); + vscodeProductJson = JSON.parse(productJsonStr); + } + + return vscodeProductJson; +} + +export interface IServerConfig { + version: string; + commit: string; + quality: string; + release?: string; // void-like specific + serverApplicationName: string; + serverDataFolderName: string; + serverDownloadUrlTemplate?: string; // void-like specific +} + +export async function getVSCodeServerConfig(): Promise { + const productJson = await getVSCodeProductJson(); + + const customServerBinaryName = vscode.workspace.getConfiguration('remote.SSH.experimental').get('serverBinaryName', ''); + + return { + version: vscode.version.replace('-insider', ''), + commit: productJson.commit, + quality: productJson.quality, + release: productJson.release, + serverApplicationName: customServerBinaryName || productJson.serverApplicationName, + serverDataFolderName: productJson.serverDataFolderName, + serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate + }; +} diff --git a/extensions/open-remote-ssh/src/serverSetup.ts b/extensions/open-remote-ssh/src/serverSetup.ts new file mode 100644 index 00000000..d74ff8e9 --- /dev/null +++ b/extensions/open-remote-ssh/src/serverSetup.ts @@ -0,0 +1,626 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as crypto from 'crypto'; +import Log from './common/logger'; +import { getVSCodeServerConfig } from './serverConfig'; +import SSHConnection from './ssh/sshConnection'; + +export interface ServerInstallOptions { + id: string; + quality: string; + commit: string; + version: string; + release?: string; // void specific + extensionIds: string[]; + envVariables: string[]; + useSocketPath: boolean; + serverApplicationName: string; + serverDataFolderName: string; + serverDownloadUrlTemplate: string; +} + +export interface ServerInstallResult { + exitCode: number; + listeningOn: number | string; + connectionToken: string; + logFile: string; + osReleaseId: string; + arch: string; + platform: string; + tmpDir: string; + [key: string]: any; +} + +export class ServerInstallError extends Error { + constructor(message: string) { + super(message); + } +} + +const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/void-updates-server/releases/download/test/void-server-${os}-${arch}.tar.gz'; + +export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log): Promise { + let shell = 'powershell'; + + // detect platform and shell for windows + if (!platform || platform === 'windows') { + const result = await conn.exec('uname -s'); + + if (result.stdout) { + if (result.stdout.includes('windows32')) { + platform = 'windows'; + } else if (result.stdout.includes('MINGW64')) { + platform = 'windows'; + shell = 'bash'; + } + } else if (result.stderr) { + if (result.stderr.includes('FullyQualifiedErrorId : CommandNotFoundException')) { + platform = 'windows'; + } + + if (result.stderr.includes('is not recognized as an internal or external command')) { + platform = 'windows'; + shell = 'cmd'; + } + } + + if (platform) { + logger.trace(`Detected platform: ${platform}, ${shell}`); + } + } + + const scriptId = crypto.randomBytes(12).toString('hex'); + + const vscodeServerConfig = await getVSCodeServerConfig(); + const installOptions: ServerInstallOptions = { + id: scriptId, + version: vscodeServerConfig.version, + commit: vscodeServerConfig.commit, + quality: vscodeServerConfig.quality, + release: vscodeServerConfig.release, + extensionIds, + envVariables, + useSocketPath, + serverApplicationName: vscodeServerConfig.serverApplicationName, + serverDataFolderName: vscodeServerConfig.serverDataFolderName, + serverDownloadUrlTemplate: serverDownloadUrlTemplate ?? vscodeServerConfig.serverDownloadUrlTemplate ?? DEFAULT_DOWNLOAD_URL_TEMPLATE, + }; + + let commandOutput: { stdout: string; stderr: string }; + if (platform === 'windows') { + const installServerScript = generatePowerShellInstallScript(installOptions); + + logger.trace('Server install command:', installServerScript); + + const installDir = `$HOME\\${vscodeServerConfig.serverDataFolderName}\\install`; + const installScript = `${installDir}\\${vscodeServerConfig.commit}.ps1`; + const endRegex = new RegExp(`${scriptId}: end`); + // investigate if it's possible to use `-EncodedCommand` flag + // https://devblogs.microsoft.com/powershell/invoking-powershell-with-complex-expressions-using-scriptblocks/ + let command = ''; + if (shell === 'powershell') { + command = `md -Force ${installDir}; echo @'\n${installServerScript}\n'@ | Set-Content ${installScript}; powershell -ExecutionPolicy ByPass -File "${installScript}"`; + } else if (shell === 'bash') { + command = `mkdir -p ${installDir.replace(/\\/g, '/')} && echo '\n${installServerScript.replace(/'/g, '\'"\'"\'')}\n' > ${installScript.replace(/\\/g, '/')} && powershell -ExecutionPolicy ByPass -File "${installScript}"`; + } else if (shell === 'cmd') { + const script = installServerScript.trim() + // remove comments + .replace(/^#.*$/gm, '') + // remove empty lines + .replace(/\n{2,}/gm, '\n') + // remove leading spaces + .replace(/^\s*/gm, '') + // escape double quotes (from powershell/cmd) + .replace(/"/g, '"""') + // escape single quotes (from cmd) + .replace(/'/g, `''`) + // escape redirect (from cmd) + .replace(/>/g, `^>`) + // escape new lines (from powershell/cmd) + .replace(/\n/g, '\'`n\''); + + command = `powershell "md -Force ${installDir}" && powershell "echo '${script}'" > ${installScript.replace('$HOME', '%USERPROFILE%')} && powershell -ExecutionPolicy ByPass -File "${installScript.replace('$HOME', '%USERPROFILE%')}"`; + + logger.trace('Command length (8191 max):', command.length); + + if (command.length > 8191) { + throw new ServerInstallError(`Command line too long`); + } + } else { + throw new ServerInstallError(`Not supported shell: ${shell}`); + } + + commandOutput = await conn.execPartial(command, (stdout: string) => endRegex.test(stdout)); + } else { + const installServerScript = generateBashInstallScript(installOptions); + + logger.trace('Server install command:', installServerScript); + // Fish shell does not support heredoc so let's workaround it using -c option, + // also replace single quotes (') within the script with ('\'') as there's no quoting within single quotes, see https://unix.stackexchange.com/a/24676 + commandOutput = await conn.exec(`bash -c '${installServerScript.replace(/'/g, `'\\''`)}'`); + } + + if (commandOutput.stderr) { + logger.trace('Server install command stderr:', commandOutput.stderr); + } + logger.trace('Server install command stdout:', commandOutput.stdout); + + const resultMap = parseServerInstallOutput(commandOutput.stdout, scriptId); + if (!resultMap) { + throw new ServerInstallError(`Failed parsing install script output`); + } + + const exitCode = parseInt(resultMap.exitCode, 10); + if (exitCode !== 0) { + throw new ServerInstallError(`Couldn't install vscode server on remote server, install script returned non-zero exit status`); + } + + const listeningOn = resultMap.listeningOn.match(/^\d+$/) + ? parseInt(resultMap.listeningOn, 10) + : resultMap.listeningOn; + + const remoteEnvVars = Object.fromEntries(Object.entries(resultMap).filter(([key,]) => envVariables.includes(key))); + + return { + exitCode, + listeningOn, + connectionToken: resultMap.connectionToken, + logFile: resultMap.logFile, + osReleaseId: resultMap.osReleaseId, + arch: resultMap.arch, + platform: resultMap.platform, + tmpDir: resultMap.tmpDir, + ...remoteEnvVars + }; +} + +function parseServerInstallOutput(str: string, scriptId: string): { [k: string]: string } | undefined { + const startResultStr = `${scriptId}: start`; + const endResultStr = `${scriptId}: end`; + + const startResultIdx = str.indexOf(startResultStr); + if (startResultIdx < 0) { + return undefined; + } + + const endResultIdx = str.indexOf(endResultStr, startResultIdx + startResultStr.length); + if (endResultIdx < 0) { + return undefined; + } + + const installResult = str.substring(startResultIdx + startResultStr.length, endResultIdx); + + const resultMap: { [k: string]: string } = {}; + const resultArr = installResult.split(/\r?\n/); + for (const line of resultArr) { + const [key, value] = line.split('=='); + resultMap[key] = value; + } + + return resultMap; +} + +function generateBashInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, useSocketPath, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) { + const extensions = extensionIds.map(id => '--install-extension ' + id).join(' '); + return ` +# Server installation script + +TMP_DIR="\${XDG_RUNTIME_DIR:-"/tmp"}" + +DISTRO_VERSION="${version}" +DISTRO_COMMIT="${commit}" +DISTRO_QUALITY="${quality}" +DISTRO_VOID_RELEASE="${release ?? ''}" + +SERVER_APP_NAME="${serverApplicationName}" +SERVER_INITIAL_EXTENSIONS="${extensions}" +SERVER_LISTEN_FLAG="${useSocketPath ? `--socket-path="$TMP_DIR/vscode-server-sock-${crypto.randomUUID()}"` : '--port=0'}" +SERVER_DATA_DIR="$HOME/${serverDataFolderName}" +SERVER_DIR="$SERVER_DATA_DIR/bin/$DISTRO_COMMIT" +SERVER_SCRIPT="$SERVER_DIR/bin/$SERVER_APP_NAME" +SERVER_LOGFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.log" +SERVER_PIDFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.pid" +SERVER_TOKENFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.token" +SERVER_ARCH= +SERVER_CONNECTION_TOKEN= +SERVER_DOWNLOAD_URL= + +LISTENING_ON= +OS_RELEASE_ID= +ARCH= +PLATFORM= + +# Mimic output from logs of remote-ssh extension +print_install_results_and_exit() { + echo "${id}: start" + echo "exitCode==$1==" + echo "listeningOn==$LISTENING_ON==" + echo "connectionToken==$SERVER_CONNECTION_TOKEN==" + echo "logFile==$SERVER_LOGFILE==" + echo "osReleaseId==$OS_RELEASE_ID==" + echo "arch==$ARCH==" + echo "platform==$PLATFORM==" + echo "tmpDir==$TMP_DIR==" + ${envVariables.map(envVar => `echo "${envVar}==$${envVar}=="`).join('\n')} + echo "${id}: end" + exit 0 +} + +# Check if platform is supported +KERNEL="$(uname -s)" +case $KERNEL in + Darwin) + PLATFORM="darwin" + ;; + Linux) + PLATFORM="linux" + ;; + FreeBSD) + PLATFORM="freebsd" + ;; + DragonFly) + PLATFORM="dragonfly" + ;; + *) + echo "Error platform not supported: $KERNEL" + print_install_results_and_exit 1 + ;; +esac + +# Check machine architecture +ARCH="$(uname -m)" +case $ARCH in + x86_64 | amd64) + SERVER_ARCH="x64" + ;; + armv7l | armv8l) + SERVER_ARCH="armhf" + ;; + arm64 | aarch64) + SERVER_ARCH="arm64" + ;; + ppc64le) + SERVER_ARCH="ppc64le" + ;; + riscv64) + SERVER_ARCH="riscv64" + ;; + loongarch64) + SERVER_ARCH="loong64" + ;; + s390x) + SERVER_ARCH="s390x" + ;; + *) + echo "Error architecture not supported: $ARCH" + print_install_results_and_exit 1 + ;; +esac + +# https://www.freedesktop.org/software/systemd/man/os-release.html +OS_RELEASE_ID="$(grep -i '^ID=' /etc/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')" +if [[ -z $OS_RELEASE_ID ]]; then + OS_RELEASE_ID="$(grep -i '^ID=' /usr/lib/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')" + if [[ -z $OS_RELEASE_ID ]]; then + OS_RELEASE_ID="unknown" + fi +fi + +# Create installation folder +if [[ ! -d $SERVER_DIR ]]; then + mkdir -p $SERVER_DIR + if (( $? > 0 )); then + echo "Error creating server install directory" + print_install_results_and_exit 1 + fi +fi + +# adjust platform for void download, if needed +if [[ $OS_RELEASE_ID = alpine ]]; then + PLATFORM=$OS_RELEASE_ID +fi + +SERVER_DOWNLOAD_URL="$(echo "${serverDownloadUrlTemplate.replace(/\$\{/g, '\\${')}" | sed "s/\\\${quality}/$DISTRO_QUALITY/g" | sed "s/\\\${version}/$DISTRO_VERSION/g" | sed "s/\\\${commit}/$DISTRO_COMMIT/g" | sed "s/\\\${os}/$PLATFORM/g" | sed "s/\\\${arch}/$SERVER_ARCH/g" | sed "s/\\\${release}/$DISTRO_VOID_RELEASE/g")" + +# Check if server script is already installed +if [[ ! -f $SERVER_SCRIPT ]]; then + case "$PLATFORM" in + darwin | linux | alpine ) + ;; + *) + echo "Error '$PLATFORM' needs manual installation of remote extension host" + print_install_results_and_exit 1 + ;; + esac + + pushd $SERVER_DIR > /dev/null + + if [[ ! -z $(which wget) ]]; then + wget --tries=3 --timeout=10 --continue --no-verbose -O vscode-server.tar.gz $SERVER_DOWNLOAD_URL + elif [[ ! -z $(which curl) ]]; then + curl --retry 3 --connect-timeout 10 --location --show-error --silent --output vscode-server.tar.gz $SERVER_DOWNLOAD_URL + else + echo "Error no tool to download server binary" + print_install_results_and_exit 1 + fi + + if (( $? > 0 )); then + echo "Error downloading server from $SERVER_DOWNLOAD_URL" + print_install_results_and_exit 1 + fi + + tar -xf vscode-server.tar.gz --strip-components 1 + if (( $? > 0 )); then + echo "Error while extracting server contents" + print_install_results_and_exit 1 + fi + + if [[ ! -f $SERVER_SCRIPT ]]; then + echo "Error server contents are corrupted" + print_install_results_and_exit 1 + fi + + rm -f vscode-server.tar.gz + + popd > /dev/null +else + echo "Server script already installed in $SERVER_SCRIPT" +fi + +# Try to find if server is already running +if [[ -f $SERVER_PIDFILE ]]; then + SERVER_PID="$(cat $SERVER_PIDFILE)" + SERVER_RUNNING_PROCESS="$(ps -o pid,args -p $SERVER_PID | grep $SERVER_SCRIPT)" +else + SERVER_RUNNING_PROCESS="$(ps -o pid,args -A | grep $SERVER_SCRIPT | grep -v grep)" +fi + +if [[ -z $SERVER_RUNNING_PROCESS ]]; then + if [[ -f $SERVER_LOGFILE ]]; then + rm $SERVER_LOGFILE + fi + if [[ -f $SERVER_TOKENFILE ]]; then + rm $SERVER_TOKENFILE + fi + + touch $SERVER_TOKENFILE + chmod 600 $SERVER_TOKENFILE + SERVER_CONNECTION_TOKEN="${crypto.randomUUID()}" + echo $SERVER_CONNECTION_TOKEN > $SERVER_TOKENFILE + + $SERVER_SCRIPT --start-server --host=127.0.0.1 $SERVER_LISTEN_FLAG $SERVER_INITIAL_EXTENSIONS --connection-token-file $SERVER_TOKENFILE --telemetry-level off --enable-remote-auto-shutdown --accept-server-license-terms &> $SERVER_LOGFILE & + echo $! > $SERVER_PIDFILE +else + echo "Server script is already running $SERVER_SCRIPT" +fi + +if [[ -f $SERVER_TOKENFILE ]]; then + SERVER_CONNECTION_TOKEN="$(cat $SERVER_TOKENFILE)" +else + echo "Error server token file not found $SERVER_TOKENFILE" + print_install_results_and_exit 1 +fi + +if [[ -f $SERVER_LOGFILE ]]; then + for i in {1..5}; do + LISTENING_ON="$(cat $SERVER_LOGFILE | grep -E 'Extension host agent listening on .+' | sed 's/Extension host agent listening on //')" + if [[ -n $LISTENING_ON ]]; then + break + fi + sleep 0.5 + done + + if [[ -z $LISTENING_ON ]]; then + echo "Error server did not start successfully" + print_install_results_and_exit 1 + fi +else + echo "Error server log file not found $SERVER_LOGFILE" + print_install_results_and_exit 1 +fi + +# Finish server setup +print_install_results_and_exit 0 +`; +} + +function generatePowerShellInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, useSocketPath, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) { + const extensions = extensionIds.map(id => '--install-extension ' + id).join(' '); + const downloadUrl = serverDownloadUrlTemplate + .replace(/\$\{quality\}/g, quality) + .replace(/\$\{version\}/g, version) + .replace(/\$\{commit\}/g, commit) + .replace(/\$\{os\}/g, 'win32') + .replace(/\$\{arch\}/g, 'x64') + .replace(/\$\{release\}/g, release ?? ''); + + return ` +# Server installation script + +$TMP_DIR="$env:TEMP\\$([System.IO.Path]::GetRandomFileName())" +$ProgressPreference = "SilentlyContinue" + +$DISTRO_VERSION="${version}" +$DISTRO_COMMIT="${commit}" +$DISTRO_QUALITY="${quality}" +$DISTRO_VOID_RELEASE="${release ?? ''}" + +$SERVER_APP_NAME="${serverApplicationName}" +$SERVER_INITIAL_EXTENSIONS="${extensions}" +$SERVER_LISTEN_FLAG="${useSocketPath ? `--socket-path="$TMP_DIR/vscode-server-sock-${crypto.randomUUID()}"` : '--port=0'}" +$SERVER_DATA_DIR="$(Resolve-Path ~)\\${serverDataFolderName}" +$SERVER_DIR="$SERVER_DATA_DIR\\bin\\$DISTRO_COMMIT" +$SERVER_SCRIPT="$SERVER_DIR\\bin\\$SERVER_APP_NAME.cmd" +$SERVER_LOGFILE="$SERVER_DATA_DIR\\.$DISTRO_COMMIT.log" +$SERVER_PIDFILE="$SERVER_DATA_DIR\\.$DISTRO_COMMIT.pid" +$SERVER_TOKENFILE="$SERVER_DATA_DIR\\.$DISTRO_COMMIT.token" +$SERVER_ARCH= +$SERVER_CONNECTION_TOKEN= +$SERVER_DOWNLOAD_URL= + +$LISTENING_ON= +$OS_RELEASE_ID= +$ARCH= +$PLATFORM="win32" + +function printInstallResults($code) { + "${id}: start" + "exitCode==$code==" + "listeningOn==$LISTENING_ON==" + "connectionToken==$SERVER_CONNECTION_TOKEN==" + "logFile==$SERVER_LOGFILE==" + "osReleaseId==$OS_RELEASE_ID==" + "arch==$ARCH==" + "platform==$PLATFORM==" + "tmpDir==$TMP_DIR==" + ${envVariables.map(envVar => `"${envVar}==$${envVar}=="`).join('\n')} + "${id}: end" +} + +# Check machine architecture +$ARCH=$env:PROCESSOR_ARCHITECTURE +# Use x64 version for ARM64, as it's not yet available. +if(($ARCH -eq "AMD64") -or ($ARCH -eq "IA64") -or ($ARCH -eq "ARM64")) { + $SERVER_ARCH="x64" +} +else { + "Error architecture not supported: $ARCH" + printInstallResults 1 + exit 0 +} + +# Create installation folder +if(!(Test-Path $SERVER_DIR)) { + try { + ni -it d $SERVER_DIR -f -ea si + } catch { + "Error creating server install directory - $($_.ToString())" + exit 1 + } + + if(!(Test-Path $SERVER_DIR)) { + "Error creating server install directory" + exit 1 + } +} + +cd $SERVER_DIR + +# Check if server script is already installed +if(!(Test-Path $SERVER_SCRIPT)) { + del vscode-server.tar.gz + + $REQUEST_ARGUMENTS = @{ + Uri="${downloadUrl}" + TimeoutSec=20 + OutFile="vscode-server.tar.gz" + UseBasicParsing=$True + } + + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + + Invoke-RestMethod @REQUEST_ARGUMENTS + + if(Test-Path "vscode-server.tar.gz") { + tar -xf vscode-server.tar.gz --strip-components 1 + + del vscode-server.tar.gz + } + + if(!(Test-Path $SERVER_SCRIPT)) { + "Error while installing the server binary" + exit 1 + } +} +else { + "Server script already installed in $SERVER_SCRIPT" +} + +# Try to find if server is already running +if(Get-Process node -ErrorAction SilentlyContinue | Where-Object Path -Like "$SERVER_DIR\\*") { + echo "Server script is already running $SERVER_SCRIPT" +} +else { + if(Test-Path $SERVER_LOGFILE) { + del $SERVER_LOGFILE + } + if(Test-Path $SERVER_PIDFILE) { + del $SERVER_PIDFILE + } + if(Test-Path $SERVER_TOKENFILE) { + del $SERVER_TOKENFILE + } + + $SERVER_CONNECTION_TOKEN="${crypto.randomUUID()}" + [System.IO.File]::WriteAllLines($SERVER_TOKENFILE, $SERVER_CONNECTION_TOKEN) + + $SCRIPT_ARGUMENTS="--start-server --host=127.0.0.1 $SERVER_LISTEN_FLAG $SERVER_INITIAL_EXTENSIONS --connection-token-file $SERVER_TOKENFILE --telemetry-level off --enable-remote-auto-shutdown --accept-server-license-terms *> '$SERVER_LOGFILE'" + + $START_ARGUMENTS = @{ + FilePath = "powershell.exe" + WindowStyle = "hidden" + ArgumentList = @( + "-ExecutionPolicy", "Unrestricted", "-NoLogo", "-NoProfile", "-NonInteractive", "-c", "$SERVER_SCRIPT $SCRIPT_ARGUMENTS" + ) + PassThru = $True + } + + $SERVER_ID = (start @START_ARGUMENTS).ID + + if($SERVER_ID) { + [System.IO.File]::WriteAllLines($SERVER_PIDFILE, $SERVER_ID) + } +} + +if(Test-Path $SERVER_TOKENFILE) { + $SERVER_CONNECTION_TOKEN="$(cat $SERVER_TOKENFILE)" +} +else { + "Error server token file not found $SERVER_TOKENFILE" + printInstallResults 1 + exit 0 +} + +sleep -Milliseconds 500 + +$SELECT_ARGUMENTS = @{ + Path = $SERVER_LOGFILE + Pattern = "Extension host agent listening on (\\d+)" +} + +for($I = 1; $I -le 5; $I++) { + if(Test-Path $SERVER_LOGFILE) { + $GROUPS = (Select-String @SELECT_ARGUMENTS).Matches.Groups + + if($GROUPS) { + $LISTENING_ON = $GROUPS[1].Value + break + } + } + + sleep -Milliseconds 500 +} + +if(!(Test-Path $SERVER_LOGFILE)) { + "Error server log file not found $SERVER_LOGFILE" + printInstallResults 1 + exit 0 +} + +# Finish server setup +printInstallResults 0 + +if($SERVER_ID) { + while($True) { + if(!(gps -Id $SERVER_ID)) { + "server died, exit" + exit 0 + } + + sleep 30 + } +} +`; +} diff --git a/extensions/open-remote-ssh/src/ssh/hostfile.ts b/extensions/open-remote-ssh/src/ssh/hostfile.ts new file mode 100644 index 00000000..51d34a9e --- /dev/null +++ b/extensions/open-remote-ssh/src/ssh/hostfile.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; +import { exists as folderExists } from '../common/files'; + +const PATH_SSH_USER_DIR = path.join(os.homedir(), '.ssh'); +const KNOW_HOST_FILE = path.join(PATH_SSH_USER_DIR, 'known_hosts'); +const HASH_MAGIC = '|1|'; +const HASH_DELIM = '|'; + +export async function checkNewHostInHostkeys(host: string): Promise { + const fileContent = await fs.promises.readFile(KNOW_HOST_FILE, { encoding: 'utf8' }); + const lines = fileContent.split(/\r?\n/); + for (let line of lines) { + line = line.trim(); + if (!line.startsWith(HASH_MAGIC)) { + continue; + } + + const [hostEncripted_] = line.split(' '); + const [salt_, hostHash_] = hostEncripted_.substring(HASH_MAGIC.length).split(HASH_DELIM); + const hostHash = crypto.createHmac('sha1', Buffer.from(salt_, 'base64')).update(host).digest(); + if (hostHash.toString('base64') === hostHash_) { + return false; + } + } + + return true; +} + +export async function addHostToHostFile(host: string, hostKey: Buffer, type: string): Promise { + if (!folderExists(PATH_SSH_USER_DIR)) { + await fs.promises.mkdir(PATH_SSH_USER_DIR, 0o700); + } + + const salt = crypto.randomBytes(20); + const hostHash = crypto.createHmac('sha1', salt).update(host).digest(); + + const entry = `${HASH_MAGIC}${salt.toString('base64')}${HASH_DELIM}${hostHash.toString('base64')} ${type} ${hostKey.toString('base64')}\n`; + await fs.promises.appendFile(KNOW_HOST_FILE, entry); +} diff --git a/extensions/open-remote-ssh/src/ssh/identityFiles.ts b/extensions/open-remote-ssh/src/ssh/identityFiles.ts new file mode 100644 index 00000000..ffb33161 --- /dev/null +++ b/extensions/open-remote-ssh/src/ssh/identityFiles.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as crypto from 'crypto'; +import type { ParsedKey } from 'ssh2-streams'; +import * as ssh2 from 'ssh2'; +import { untildify, exists as fileExists } from '../common/files'; +import Log from '../common/logger'; + +const homeDir = os.homedir(); +const PATH_SSH_CLIENT_ID_DSA = path.join(homeDir, '.ssh', '/id_dsa'); +const PATH_SSH_CLIENT_ID_ECDSA = path.join(homeDir, '.ssh', '/id_ecdsa'); +const PATH_SSH_CLIENT_ID_RSA = path.join(homeDir, '.ssh', '/id_rsa'); +const PATH_SSH_CLIENT_ID_ED25519 = path.join(homeDir, '.ssh', '/id_ed25519'); +const PATH_SSH_CLIENT_ID_XMSS = path.join(homeDir, '.ssh', '/id_xmss'); +const PATH_SSH_CLIENT_ID_ECDSA_SK = path.join(homeDir, '.ssh', '/id_ecdsa_sk'); +const PATH_SSH_CLIENT_ID_ED25519_SK = path.join(homeDir, '.ssh', '/id_ed25519_sk'); + +const DEFAULT_IDENTITY_FILES: string[] = [ + PATH_SSH_CLIENT_ID_RSA, + PATH_SSH_CLIENT_ID_ECDSA, + PATH_SSH_CLIENT_ID_ECDSA_SK, + PATH_SSH_CLIENT_ID_ED25519, + PATH_SSH_CLIENT_ID_ED25519_SK, + PATH_SSH_CLIENT_ID_XMSS, + PATH_SSH_CLIENT_ID_DSA, +]; + +export interface SSHKey { + filename: string; + parsedKey: ParsedKey; + fingerprint: string; + agentSupport?: boolean; + isPrivate?: boolean; +} + +// From https://github.com/openssh/openssh-portable/blob/acb2059febaddd71ee06c2ebf63dcf211d9ab9f2/sshconnect2.c#L1689-L1690 +export async function gatherIdentityFiles(identityFiles: string[], sshAgentSock: string | undefined, identitiesOnly: boolean, logger: Log) { + identityFiles = identityFiles.map(untildify).map(i => i.replace(/\.pub$/, '')); + if (identityFiles.length === 0) { + identityFiles.push(...DEFAULT_IDENTITY_FILES); + } + + const identityFileContentsResult = await Promise.allSettled(identityFiles.map(async keyPath => { + keyPath = await fileExists(keyPath + '.pub') ? keyPath + '.pub' : keyPath; + return fs.promises.readFile(keyPath); + })); + const fileKeys: SSHKey[] = identityFileContentsResult.map((result, i) => { + if (result.status === 'rejected') { + return undefined; + } + + const parsedResult = ssh2.utils.parseKey(result.value); + if (parsedResult instanceof Error || !parsedResult) { + logger.error(`Error while parsing SSH public key ${identityFiles[i]}:`, parsedResult); + return undefined; + } + + const parsedKey = Array.isArray(parsedResult) ? parsedResult[0] : parsedResult; + const fingerprint = crypto.createHash('sha256').update(parsedKey.getPublicSSH()).digest('base64'); + + return { + filename: identityFiles[i], + parsedKey, + fingerprint + }; + }).filter((v: T | undefined): v is T => !!v); + + let sshAgentParsedKeys: ParsedKey[] = []; + try { + if (!sshAgentSock) { + throw new Error(`SSH_AUTH_SOCK environment variable not defined`); + } + + sshAgentParsedKeys = await new Promise((resolve, reject) => { + const sshAgent = new ssh2.OpenSSHAgent(sshAgentSock); + sshAgent.getIdentities((err, publicKeys) => { + if (err) { + reject(err); + } else { + resolve(publicKeys || []); + } + }); + }); + } catch (e) { + logger.error(`Couldn't get identities from OpenSSH agent`, e); + } + + const sshAgentKeys: SSHKey[] = sshAgentParsedKeys.map(parsedKey => { + const fingerprint = crypto.createHash('sha256').update(parsedKey.getPublicSSH()).digest('base64'); + return { + filename: parsedKey.comment, + parsedKey, + fingerprint, + agentSupport: true + }; + }); + + const agentKeys: SSHKey[] = []; + const preferredIdentityKeys: SSHKey[] = []; + for (const agentKey of sshAgentKeys) { + const foundIdx = fileKeys.findIndex(k => agentKey.parsedKey.type === k.parsedKey.type && agentKey.fingerprint === k.fingerprint); + if (foundIdx >= 0) { + preferredIdentityKeys.push({ ...fileKeys[foundIdx], agentSupport: true }); + fileKeys.splice(foundIdx, 1); + } else if (!identitiesOnly) { + agentKeys.push(agentKey); + } + } + preferredIdentityKeys.push(...agentKeys); + preferredIdentityKeys.push(...fileKeys); + + logger.trace(`Identity keys:`, preferredIdentityKeys.length ? preferredIdentityKeys.map(k => `${k.filename} ${k.parsedKey.type} SHA256:${k.fingerprint}`).join('\n') : 'None'); + + return preferredIdentityKeys; +} diff --git a/extensions/open-remote-ssh/src/ssh/sshConfig.ts b/extensions/open-remote-ssh/src/ssh/sshConfig.ts new file mode 100644 index 00000000..6ce6ed32 --- /dev/null +++ b/extensions/open-remote-ssh/src/ssh/sshConfig.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; +import * as fs from 'fs'; +import * as path from 'path'; +import SSHConfig, { Directive, Line, Section } from '@jeanp413/ssh-config'; +import * as vscode from 'vscode'; +import { exists as fileExists, normalizeToSlash, untildify } from '../common/files'; +import { isWindows } from '../common/platform'; +import { glob } from 'glob'; + +const systemSSHConfig = isWindows ? path.resolve(process.env.ALLUSERSPROFILE || 'C:\\ProgramData', 'ssh\\ssh_config') : '/etc/ssh/ssh_config'; +const defaultSSHConfigPath = path.resolve(os.homedir(), '.ssh/config'); + +export function getSSHConfigPath() { + const sshConfigPath = vscode.workspace.getConfiguration('remote.SSH').get('configFile'); + return sshConfigPath ? untildify(sshConfigPath) : defaultSSHConfigPath; +} + +function isDirective(line: Line): line is Directive { + return line.type === SSHConfig.DIRECTIVE; +} + +function isHostSection(line: Line): line is Section { + return isDirective(line) && line.param === 'Host' && !!line.value && !!(line as Section).config; +} + +function isIncludeDirective(line: Line): line is Section { + return isDirective(line) && line.param === 'Include' && !!line.value; +} + +const SSH_CONFIG_PROPERTIES: Record = { + 'host': 'Host', + 'hostname': 'HostName', + 'user': 'User', + 'port': 'Port', + 'identityagent': 'IdentityAgent', + 'identitiesonly': 'IdentitiesOnly', + 'identityfile': 'IdentityFile', + 'forwardagent': 'ForwardAgent', + 'preferredauthentications': 'PreferredAuthentications', + 'proxyjump': 'ProxyJump', + 'proxycommand': 'ProxyCommand', + 'include': 'Include', +}; + +function normalizeProp(prop: Directive) { + prop.param = SSH_CONFIG_PROPERTIES[prop.param.toLowerCase()] || prop.param; +} + +function normalizeSSHConfig(config: SSHConfig) { + for (const line of config) { + if (isDirective(line)) { + normalizeProp(line); + } + if (isHostSection(line)) { + normalizeSSHConfig(line.config); + } + } + return config; +} + +async function parseSSHConfigFromFile(filePath: string, userConfig: boolean) { + let content = ''; + if (await fileExists(filePath)) { + content = (await fs.promises.readFile(filePath, 'utf8')).trim(); + } + const config = normalizeSSHConfig(SSHConfig.parse(content)); + + const includedConfigs: [number, SSHConfig[]][] = []; + for (let i = 0; i < config.length; i++) { + const line = config[i]; + if (isIncludeDirective(line)) { + const values = (line.value as string).split(',').map(s => s.trim()); + const configs: SSHConfig[] = []; + for (const value of values) { + const includePaths = await glob(normalizeToSlash(untildify(value)), { + absolute: true, + cwd: normalizeToSlash(path.dirname(userConfig ? defaultSSHConfigPath : systemSSHConfig)) + }); + for (const p of includePaths) { + configs.push(await parseSSHConfigFromFile(p, userConfig)); + } + } + includedConfigs.push([i, configs]); + } + } + for (const [idx, includeConfigs] of includedConfigs.reverse()) { + config.splice(idx, 1, ...includeConfigs.flat()); + } + + return config; +} + +export default class SSHConfiguration { + + static async loadFromFS(): Promise { + const config = await parseSSHConfigFromFile(getSSHConfigPath(), true); + config.push(...await parseSSHConfigFromFile(systemSSHConfig, false)); + + return new SSHConfiguration(config); + } + + constructor(private sshConfig: SSHConfig) { + } + + getAllConfiguredHosts(): string[] { + const hosts = new Set(); + for (const line of this.sshConfig) { + if (isHostSection(line)) { + const value = Array.isArray(line.value) ? line.value[0] : line.value; + const isPattern = /^!/.test(value) || /[?*]/.test(value); + if (!isPattern) { + hosts.add(value); + } + } + } + + return [...hosts.keys()]; + } + + getHostConfiguration(host: string): Record { + // Only a few directives return an array + // https://github.com/jeanp413/ssh-config/blob/8d187bb8f1d83a51ff2b1d127e6b6269d24092b5/src/ssh-config.ts#L9C1-L9C118 + return this.sshConfig.compute(host) as Record; + } +} diff --git a/extensions/open-remote-ssh/src/ssh/sshConnection.ts b/extensions/open-remote-ssh/src/ssh/sshConnection.ts new file mode 100644 index 00000000..abaf1471 --- /dev/null +++ b/extensions/open-remote-ssh/src/ssh/sshConnection.ts @@ -0,0 +1,367 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EventEmitter } from 'events'; +import * as net from 'net'; +import * as fs from 'fs'; +import * as stream from 'stream'; +import { Client, ClientChannel, ClientErrorExtensions, ExecOptions, ShellOptions, ConnectConfig } from 'ssh2'; +import { Server } from 'net'; +import { SocksConnectionInfo, createServer as createSocksServer } from 'simple-socks'; + +export interface SSHConnectConfig extends ConnectConfig { + /** Optional Unique ID attached to ssh connection. */ + uniqueId?: string; + /** Automatic retry to connect, after disconnect. Default true */ + reconnect?: boolean; + /** Number of reconnect retry, after disconnect. Default 10 */ + reconnectTries?: number; + /** Delay after which reconnect should be done. Default 5000ms */ + reconnectDelay?: number; + /** Path to private key */ + identity?: string | Buffer; +} + +export interface SSHTunnelConfig { + /** Remote Address to connect */ + remoteAddr?: string; + /** Local port to bind to. By default, it will bind to a random port, if not passed */ + localPort?: number; + /** Remote Port to connect */ + remotePort?: number; + /** Remote socket path to connect */ + remoteSocketPath?: string; + socks?: boolean; + /** Unique name */ + name?: string; +} + +const defaultOptions: Partial = { + reconnect: false, + port: 22, + reconnectTries: 3, + reconnectDelay: 5000 +}; + +const SSHConstants = { + 'CHANNEL': { + SSH: 'ssh', + TUNNEL: 'tunnel', + X11: 'x11' + }, + 'STATUS': { + BEFORECONNECT: 'beforeconnect', + CONNECT: 'connect', + BEFOREDISCONNECT: 'beforedisconnect', + DISCONNECT: 'disconnect' + } +}; + +export default class SSHConnection extends EventEmitter { + public config: SSHConnectConfig; + + private activeTunnels: { [index: string]: SSHTunnelConfig & { server: Server } } = {}; + private __$connectPromise: Promise | null = null; + private __retries: number = 0; + private __err: Error & ClientErrorExtensions & { code?: string } | null = null; + private sshConnection: Client | null = null; + + constructor(options: SSHConnectConfig) { + super(); + this.config = Object.assign({}, defaultOptions, options); + this.config.uniqueId = this.config.uniqueId || `${this.config.username}@${this.config.host}`; + } + + /** + * Emit message on this channel + */ + override emit(channel: string, status: string, payload?: any): boolean { + super.emit(channel, status, this, payload); + return super.emit(`${channel}:${status}`, this, payload); + } + + /** + * Get shell socket + */ + shell(options: ShellOptions = {}): Promise { + return this.connect().then(() => { + return new Promise((resolve, reject) => { + this.sshConnection!.shell(options, (err, stream) => err ? reject(err) : resolve(stream)); + }); + }); + } + + /** + * Exec a command + */ + exec(cmd: string, params?: Array, options: ExecOptions = {}): Promise<{ stdout: string; stderr: string }> { + cmd += (Array.isArray(params) ? (' ' + params.join(' ')) : ''); + return this.connect().then(() => { + return new Promise((resolve, reject) => { + this.sshConnection!.exec(cmd, options, (err, stream) => { + if (err) { + return reject(err); + } + let stdout = ''; + let stderr = ''; + stream.on('close', function () { + return resolve({ stdout, stderr }); + }).on('data', function (data: Buffer | string) { + stdout += data.toString(); + }).stderr.on('data', function (data: Buffer | string) { + stderr += data.toString(); + }); + }); + }); + }); + } + + /** + * Exec a command + */ + execPartial(cmd: string, tester: (stdout: string, stderr: string) => boolean, params?: Array, options: ExecOptions = {}): Promise<{ stdout: string; stderr: string }> { + cmd += (Array.isArray(params) ? (' ' + params.join(' ')) : ''); + return this.connect().then(() => { + return new Promise((resolve, reject) => { + this.sshConnection!.exec(cmd, options, (err, stream) => { + if (err) { + return reject(err); + } + let stdout = ''; + let stderr = ''; + let resolved = false; + stream.on('close', function () { + if (!resolved) { + return resolve({ stdout, stderr }); + } + }).on('data', function (data: Buffer | string) { + stdout += data.toString(); + + if (tester(stdout, stderr)) { + resolved = true; + + return resolve({ stdout, stderr }); + } + }).stderr.on('data', function (data: Buffer | string) { + stderr += data.toString(); + + if (tester(stdout, stderr)) { + resolved = true; + + return resolve({ stdout, stderr }); + } + }); + }); + }); + }); + } + + /** + * Forward out + */ + forwardOut(srcIP: string, srcPort: number, destIP: string, destPort: number): Promise { + return this.connect().then(() => { + return new Promise((resolve, reject) => { + this.sshConnection!.forwardOut(srcIP, srcPort, destIP, destPort, (err, stream) => { + if (err) { + return reject(err); + } + resolve(stream); + }); + }); + }); + } + + /** + * Get a Socks Port + */ + getSocksPort(localPort: number): Promise { + return this.addTunnel({ name: '__socksServer', socks: true, localPort: localPort }).then((tunnel) => { + return tunnel.localPort!; + }); + } + + /** + * Close SSH Connection + */ + close(): Promise { + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.BEFOREDISCONNECT); + return this.closeTunnel().then(() => { + if (this.sshConnection) { + this.sshConnection.end(); + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT); + } + }); + } + + /** + * Connect the SSH Connection + */ + connect(c?: SSHConnectConfig): Promise { + this.config = Object.assign(this.config, c); + ++this.__retries; + + if (this.__$connectPromise) { + return this.__$connectPromise; + } + + this.__$connectPromise = new Promise((resolve, reject) => { + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.BEFORECONNECT); + if (!this.config || typeof this.config === 'function' || !(this.config.host || this.config.sock) || !this.config.username) { + reject(`Invalid SSH connection configuration host/username can't be empty`); + this.__$connectPromise = null; + return; + } + + if (this.config.identity) { + if (fs.existsSync(this.config.identity)) { + this.config.privateKey = fs.readFileSync(this.config.identity); + } + delete this.config.identity; + } + + //Start ssh server connection + this.sshConnection = new Client(); + this.sshConnection.on('ready', (err: Error & ClientErrorExtensions) => { + if (err) { + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT, { err: err }); + this.__$connectPromise = null; + return reject(err); + } + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.CONNECT); + this.__retries = 0; + this.__err = null; + resolve(this); + }).on('error', (err) => { + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT, { err: err }); + this.__err = err; + }).on('close', () => { + this.emit(SSHConstants.CHANNEL.SSH, SSHConstants.STATUS.DISCONNECT, { err: this.__err }); + if (this.config.reconnect && this.__retries <= this.config.reconnectTries! && this.__err && this.__err.level !== 'client-authentication' && this.__err.code !== 'ENOTFOUND') { + setTimeout(() => { + this.__$connectPromise = null; + resolve(this.connect()); + }, this.config.reconnectDelay); + } else { + reject(this.__err); + } + }).connect(this.config); + }); + return this.__$connectPromise; + } + + /** + * Get existing tunnel by name + */ + getTunnel(name: string) { + return this.activeTunnels[name]; + } + + /** + * Add new tunnel if not exist + */ + addTunnel(SSHTunnelConfig: SSHTunnelConfig): Promise { + SSHTunnelConfig.name = SSHTunnelConfig.name || `${SSHTunnelConfig.remoteAddr}@${SSHTunnelConfig.remotePort || SSHTunnelConfig.remoteSocketPath}`; + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.BEFORECONNECT, { SSHTunnelConfig: SSHTunnelConfig }); + if (this.getTunnel(SSHTunnelConfig.name)) { + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.CONNECT, { SSHTunnelConfig: SSHTunnelConfig }); + return Promise.resolve(this.getTunnel(SSHTunnelConfig.name)); + } else { + return new Promise((resolve, reject) => { + let server: net.Server; + if (SSHTunnelConfig.socks) { + server = createSocksServer({ + connectionFilter: (destination: SocksConnectionInfo, origin: SocksConnectionInfo, callback: (err?: any, dest?: stream.Duplex) => void) => { + this.connect().then(() => { + this.sshConnection!.forwardOut( + origin.address, + origin.port, + destination.address, + destination.port, + (err, stream) => { + if (err) { + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err }); + return callback(err); + } + return callback(null, stream); + }); + }); + } + }).on('proxyError', (err: any) => { + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err }); + }); + } else { + server = net.createServer() + .on('connection', (socket) => { + this.connect().then(() => { + if (SSHTunnelConfig.remotePort) { + this.sshConnection!.forwardOut('127.0.0.1', 0, SSHTunnelConfig.remoteAddr!, SSHTunnelConfig.remotePort!, (err, stream) => { + if (err) { + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err }); + return; + } + stream.pipe(socket); + socket.pipe(stream); + }); + } else { + this.sshConnection!.openssh_forwardOutStreamLocal(SSHTunnelConfig.remoteSocketPath!, (err, stream) => { + if (err) { + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err }); + return; + } + stream.pipe(socket); + socket.pipe(stream); + }); + } + }); + }); + } + + SSHTunnelConfig.localPort = SSHTunnelConfig.localPort || 0; + server.on('listening', () => { + SSHTunnelConfig.localPort = (server.address() as net.AddressInfo).port; + this.activeTunnels[SSHTunnelConfig.name!] = Object.assign({}, { server }, SSHTunnelConfig); + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.CONNECT, { SSHTunnelConfig: SSHTunnelConfig }); + resolve(this.activeTunnels[SSHTunnelConfig.name!]); + }).on('error', (err: any) => { + this.emit(SSHConstants.CHANNEL.TUNNEL, SSHConstants.STATUS.DISCONNECT, { SSHTunnelConfig: SSHTunnelConfig, err: err }); + server.close(); + reject(err); + delete this.activeTunnels[SSHTunnelConfig.name!]; + }).listen(SSHTunnelConfig.localPort); + }); + } + } + + /** + * Close the tunnel + */ + closeTunnel(name?: string): Promise { + if (name && this.activeTunnels[name]) { + return new Promise((resolve) => { + const tunnel = this.activeTunnels[name]; + this.emit( + SSHConstants.CHANNEL.TUNNEL, + SSHConstants.STATUS.BEFOREDISCONNECT, + { SSHTunnelConfig: tunnel } + ); + tunnel.server.close(() => { + this.emit( + SSHConstants.CHANNEL.TUNNEL, + SSHConstants.STATUS.DISCONNECT, + { SSHTunnelConfig: this.activeTunnels[name] } + ); + delete this.activeTunnels[name]; + resolve(); + }); + }); + } else if (!name) { + const tunnels = Object.keys(this.activeTunnels).map((key) => this.closeTunnel(key)); + return Promise.all(tunnels).then(() => { }); + } + + return Promise.resolve(); + } +} diff --git a/extensions/open-remote-ssh/src/ssh/sshDestination.ts b/extensions/open-remote-ssh/src/ssh/sshDestination.ts new file mode 100644 index 00000000..db0b4406 --- /dev/null +++ b/extensions/open-remote-ssh/src/ssh/sshDestination.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +export default class SSHDestination { + constructor( + public readonly hostname: string, + public readonly user?: string, + public readonly port?: number + ) { + } + + static parse(dest: string): SSHDestination { + let user: string | undefined; + const atPos = dest.lastIndexOf('@'); + if (atPos !== -1) { + user = dest.substring(0, atPos); + } + + let port: number | undefined; + const colonPos = dest.lastIndexOf(':'); + if (colonPos !== -1) { + port = parseInt(dest.substring(colonPos + 1), 10); + } + + const start = atPos !== -1 ? atPos + 1 : 0; + const end = colonPos !== -1 ? colonPos : dest.length; + const hostname = dest.substring(start, end); + + return new SSHDestination(hostname, user, port); + } + + toString(): string { + let result = this.hostname; + if (this.user) { + result = `${this.user}@` + result; + } + if (this.port) { + result = result + `:${this.port}`; + } + return result; + } + + // vscode.uri implementation lowercases the authority, so when reopen or restore + // a remote session from the recently openend list the connection fails + static parseEncoded(dest: string): SSHDestination { + try { + const data = JSON.parse(Buffer.from(dest, 'hex').toString()); + return new SSHDestination(data.hostName, data.user, data.port); + } catch { + } + return SSHDestination.parse(dest.replace(/\\x([0-9a-f]{2})/g, (_, charCode) => String.fromCharCode(parseInt(charCode, 16)))); + } + + toEncodedString(): string { + return this.toString().replace(/[A-Z]/g, (ch) => `\\x${ch.charCodeAt(0).toString(16).toLowerCase()}`); + } +} diff --git a/extensions/open-remote-ssh/tsconfig.json b/extensions/open-remote-ssh/tsconfig.json new file mode 100644 index 00000000..2e08e0d2 --- /dev/null +++ b/extensions/open-remote-ssh/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.resolvers.d.ts", + "../../src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts", + ] +} diff --git a/package-lock.json b/package-lock.json index 1c163334..41a91db3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,12 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@anthropic-ai/sdk": "^0.39.0", + "@floating-ui/react": "^0.27.5", + "@google/generative-ai": "^0.22.0", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", + "@mistralai/mistralai": "^1.5.0", "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", @@ -37,6 +41,9 @@ "@xterm/addon-webgl": "^0.19.0-beta.98", "@xterm/headless": "^5.6.0-beta.98", "@xterm/xterm": "^5.6.0-beta.98", + "cross-spawn": "^7.0.6", + "diff": "^7.0.0", + "groq-sdk": "^0.15.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -46,20 +53,28 @@ "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", "node-pty": "1.1.0-beta30", + "ollama": "^0.5.14", "open": "^8.4.2", + "openai": "^4.86.1", + "posthog-node": "^4.8.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "tas-client-umd": "0.2.0", "v8-inspect-profiler": "^0.1.1", + "vscode-html-languageservice": "^5.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.2.0", "yauzl": "^3.0.0", - "yazl": "^2.4.3" + "yazl": "^2.5.1", + "zod": "^3.24.2" }, "devDependencies": { "@playwright/test": "^1.50.0", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", + "@types/diff": "^7.0.1", "@types/eslint": "^9.6.1", "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", @@ -67,6 +82,8 @@ "@types/minimist": "^1.2.1", "@types/mocha": "^9.1.1", "@types/node": "20.x", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", @@ -136,7 +153,10 @@ "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", + "next": "^15.2.0", + "nodemon": "^3.1.9", "npm-run-all": "^4.1.5", + "original-fs": "^1.2.0", "os-browserify": "^0.3.0", "p-all": "^1.0.0", "path-browserify": "^1.0.1", @@ -144,17 +164,20 @@ "postcss-nesting": "^12.0.2", "pump": "^1.0.1", "rcedit": "^1.1.0", - "rimraf": "^2.2.8", + "rimraf": "^2.7.1", + "scope-tailwind": "^1.0.6", "sinon": "^12.0.1", "sinon-test": "^3.1.3", "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^3.3.2", + "tailwindcss": "^4.0.9", "ts-loader": "^9.5.1", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.8.0-dev.20250207", + "tsup": "^8.4.0", + "typescript": "^5.8.2", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -180,6 +203,30 @@ "node": ">=6.0.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", + "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.78", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.78.tgz", + "integrity": "sha512-m1ilZCTwKLkk9rruBJXFeYN0Bc5SbjirwYX/Td3MqPfioYbgun3IvK/m8dQxMCnrPGZPg1kvXjp3SIekCN/ynw==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@azure-rest/ai-translation-text": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/@azure-rest/ai-translation-text/-/ai-translation-text-1.0.0-beta.1.tgz", @@ -481,110 +528,52 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", - "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", - "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, + "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.10", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.10", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.10", - "@babel/types": "^7.18.10", - "convert-source-map": "^1.7.0", + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -594,6 +583,13 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -604,44 +600,108 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", + "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.26.9", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -650,226 +710,170 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", + "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -877,35 +881,123 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz", + "integrity": "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", + "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -917,19 +1009,20 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1065,6 +1158,17 @@ "node": ">= 4.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.48.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", @@ -1079,6 +1183,431 @@ "node": ">=16" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1179,6 +1708,68 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.5.tgz", + "integrity": "sha512-BX3jKxo39Ba05pflcQmqPPwc0qdNsdNi/eweAFtoIdrJWNen2sVEWMEac3i6jU55Qfx+lOcdMNKYn2CtWmlnOQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@google/generative-ai": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.22.0.tgz", + "integrity": "sha512-mLR3PDWCk5O/BWNyDvFDIiwKeXQmFGZ+kJFd9m73QrUPCFREttJyVbBPTW4y9CwTbaltLMDaLDfroCrRv5Bl8Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -1306,6 +1897,386 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1551,6 +2522,160 @@ "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, + "node_modules/@mistralai/mistralai": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.0.tgz", + "integrity": "sha512-AIn8pwAwA/fDvEUvmkt+40zH1ZmfaG3Q7oUWl17GUEC1tU7ZPwYz8Cv9P59lyS1SisHdDSu81oknO7f1ywkz8Q==", + "dependencies": { + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "zod": ">= 3" + } + }, + "node_modules/@next/env": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.0.tgz", + "integrity": "sha512-eMgJu1RBXxxqqnuRJQh5RozhskoNUDHBFybvi+Z+yK9qzKeG7dadhv/Vp1YooSZmCnegf7JxWuapV77necLZNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.0.tgz", + "integrity": "sha512-rlp22GZwNJjFCyL7h5wz9vtpBVuCt3ZYjFWpEPBGzG712/uL1bbSkS675rVAUCRZ4hjoTJ26Q7IKhr5DfJrHDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.0.tgz", + "integrity": "sha512-DiU85EqSHogCz80+sgsx90/ecygfCSGl5P3b4XDRVZpgujBm5lp4ts7YaHru7eVTyZMjHInzKr+w0/7+qDrvMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.0.tgz", + "integrity": "sha512-VnpoMaGukiNWVxeqKHwi8MN47yKGyki5q+7ql/7p/3ifuU2341i/gDwGK1rivk0pVYbdv5D8z63uu9yMw0QhpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.0.tgz", + "integrity": "sha512-ka97/ssYE5nPH4Qs+8bd8RlYeNeUVBhcnsNUmFM6VWEob4jfN9FTr0NBhXVi1XEJpj3cMfgSRW+LdE3SUZbPrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.0.tgz", + "integrity": "sha512-zY1JduE4B3q0k2ZCE+DAF/1efjTXUsKP+VXRtrt/rJCTgDlUyyryx7aOgYXNc1d8gobys/Lof9P9ze8IyRDn7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.0.tgz", + "integrity": "sha512-QqvLZpurBD46RhaVaVBepkVQzh8xtlUN00RlG4Iq1sBheNugamUNPuZEH1r9X1YGQo1KqAe1iiShF0acva3jHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.0.tgz", + "integrity": "sha512-ODZ0r9WMyylTHAN6pLtvUtQlGXBL9voljv6ujSlcsjOxhtXPI1Ag6AhZK0SE8hEpR1374WZZ5w33ChpJd5fsjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.0.tgz", + "integrity": "sha512-8+4Z3Z7xa13NdUuUAcpVNA6o76lNPniBd9Xbo02bwXQXnZgFvEopwY2at5+z7yHl47X9qbZpvwatZ2BRo3EdZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2086,6 +3211,272 @@ "node": ">=18" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -2162,6 +3553,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -2311,11 +3719,19 @@ "@types/ms": "*" } }, + "node_modules/@types/diff": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.1.tgz", + "integrity": "sha512-R/BHQFripuhW6XPXy05hIvXJQdQ4540KnTvEFHSLjXfHYM41liOLKgIJEyYYiQe796xpaMHfe4Uj/p7Uvng2vA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2426,33 +3842,38 @@ "version": "20.14.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz", "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/node-fetch": { - "version": "2.5.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", - "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", - "dev": true, + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", "dependencies": { "@types/node": "*", - "form-data": "^3.0.0" + "form-data": "^4.0.0" } }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dev": true, + "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/@types/responselike": { @@ -2762,6 +4183,12 @@ "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "license": "MIT" + }, "node_modules/@vscode/l10n-dev": { "version": "0.0.35", "resolved": "https://registry.npmjs.org/@vscode/l10n-dev/-/l10n-dev-0.0.35.tgz", @@ -3553,6 +4980,18 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -3616,6 +5055,18 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4094,8 +5545,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/atob": { "version": "2.1.2", @@ -4109,6 +5559,44 @@ "node": ">= 4.5.0" } }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -4121,6 +5609,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -4448,6 +5947,34 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4482,6 +6009,16 @@ "node": ">=14.14.0" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -4882,6 +6419,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true, + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5062,6 +6606,21 @@ "node": ">=0.10.0" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5080,6 +6639,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -5106,7 +6677,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5118,7 +6688,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -5208,6 +6777,16 @@ "proto-list": "~1.2.1" } }, + "node_modules/consola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5382,7 +6961,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5595,6 +7174,13 @@ "node": ">=8.0.0" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -5887,9 +7473,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -5911,10 +7498,9 @@ "optional": true }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -6349,6 +7935,47 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6678,6 +8305,15 @@ "through": "~2.3.1" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", @@ -7031,6 +8667,21 @@ "pend": "~1.2.0" } }, + "node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7375,6 +9026,26 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/font-finder": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", @@ -7470,7 +9141,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7480,6 +9150,39 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -8296,6 +9999,30 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/groq-sdk": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.15.0.tgz", + "integrity": "sha512-aYDEdr4qczx3cLCRRe+Beb37I7g/9bD5kHF+EEDxcrREWw1vKoRcfP3vHEkJB7Ud/8oOuF0scRwDpwWostTWuQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/groq-sdk/node_modules/@types/node": { + "version": "18.19.78", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.78.tgz", + "integrity": "sha512-m1ilZCTwKLkk9rruBJXFeYN0Bc5SbjirwYX/Td3MqPfioYbgun3IvK/m8dQxMCnrPGZPg1kvXjp3SIekCN/ynw==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -9967,6 +11694,15 @@ "node": ">= 14" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/husky/-/husky-0.13.4.tgz", @@ -10093,6 +11829,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -10672,8 +12415,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -10840,6 +12582,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-base64": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", @@ -10900,7 +12652,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -10938,15 +12691,16 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -11414,6 +13168,13 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -11438,6 +13199,16 @@ "node": ">=4" } }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -11525,6 +13296,13 @@ "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", "dev": true }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -11997,7 +13775,6 @@ "version": "1.45.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -12006,7 +13783,6 @@ "version": "2.1.28", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", - "dev": true, "dependencies": { "mime-db": "1.45.0" }, @@ -12231,6 +14007,16 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/mocha/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -12422,6 +14208,18 @@ "node": ">= 0.10" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nan": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", @@ -12523,12 +14321,96 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/next": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/next/-/next-15.2.0.tgz", + "integrity": "sha512-VaiM7sZYX8KIAHBrRGSFytKknkrexNfGb8GlG6e93JqueCspuGte8i4ybn8z4ww1x3f2uzY4YpTaBEW4/hvsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/env": "15.2.0", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.2.0", + "@next/swc-darwin-x64": "15.2.0", + "@next/swc-linux-arm64-gnu": "15.2.0", + "@next/swc-linux-arm64-musl": "15.2.0", + "@next/swc-linux-x64-gnu": "15.2.0", + "@next/swc-linux-x64-musl": "15.2.0", + "@next/swc-win32-arm64-msvc": "15.2.0", + "@next/swc-win32-x64-msvc": "15.2.0", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -12583,11 +14465,29 @@ "node": "^16 || ^18 || >= 20" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -12642,6 +14542,48 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nopt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", @@ -12685,6 +14627,16 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -13084,6 +15036,15 @@ "node": ">=0.10.0" } }, + "node_modules/ollama": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.14.tgz", + "integrity": "sha512-pvOuEYa2WkkAumxzJP0RdEYHkbZ64AYyyUszXVX7ruLvk5L+EiO2G71da2GqEQ4IAk4j6eLoUbGk5arzFT1wJA==", + "license": "MIT", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13150,6 +15111,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.86.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.86.1.tgz", + "integrity": "sha512-x3iCLyaC3yegFVZaxOmrYJjitKxZ9hpVbLi+ZlT5UHuHTMlEQEbKXkGOM78z9qm2T5GF+XRUZCP2/aV4UPFPJQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.78", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.78.tgz", + "integrity": "sha512-m1ilZCTwKLkk9rruBJXFeYN0Bc5SbjirwYX/Td3MqPfioYbgun3IvK/m8dQxMCnrPGZPg1kvXjp3SIekCN/ynw==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/opentype.js": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz", @@ -13293,6 +15293,13 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/original-fs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/original-fs/-/original-fs-1.2.0.tgz", + "integrity": "sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==", + "dev": true, + "license": "Unlicense" + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -13543,7 +15550,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -13667,9 +15673,9 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA= sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -13726,6 +15732,16 @@ "node": ">=0.10.0" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -13894,9 +15910,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -13912,10 +15928,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -14026,6 +16043,49 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/postcss-merge-longhand": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", @@ -14189,6 +16249,46 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-7.0.2.tgz", + "integrity": "sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-nesting": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.2.tgz", @@ -14553,6 +16653,18 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/posthog-node": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.8.1.tgz", + "integrity": "sha512-ApMEC1+DbctP/88+VhaCl8SRKpIoReibMf7Mb3rxw3yMthr1rKaM4opbHdZJ0buLhwS5zX8B2ckqLjpwpSjRPg==", + "license": "MIT", + "dependencies": { + "axios": "^1.7.4" + }, + "engines": { + "node": ">=15.0.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", @@ -14726,6 +16838,13 @@ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz", @@ -14757,6 +16876,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -14836,6 +16965,27 @@ "integrity": "sha512-JkXJ0IrUcdupLoIx6gE4YcFaMVSGtu7kQf4NJoDJUnfBZGuATmJ2Yal2v55KTltp+WV8dGr7A0RtOzx6jmtM6Q==", "dev": true }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -15388,11 +17538,12 @@ } }, "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -15439,6 +17590,45 @@ "node": ">=8.0" } }, + "node_modules/rollup": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", @@ -15479,6 +17669,12 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, "node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -15532,6 +17728,35 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/scope-tailwind": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.6.tgz", + "integrity": "sha512-tkISLsaesYKKXL9YrLsRWFOD/FhrRVGKeinjgTuFtEidryLzwlBB3G17ArmHWHYcfdMp00XwnRMcGFkF8wwG6w==", + "dev": true, + "license": "AGPL-3.0-only", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", + "@babel/types": "^7.26.0", + "autoprefixer": "^10.4.20", + "commander": "^12.1.0", + "postcss": "^8.4.48", + "postcss-nested": "^7.0.2" + }, + "bin": { + "scope-tailwind": "dist/main.js" + } + }, + "node_modules/scope-tailwind/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -15705,11 +17930,51 @@ "node": ">=0.10.0" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -15721,7 +17986,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -15793,6 +18057,38 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sinon": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", @@ -15830,6 +18126,16 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/sinon/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -16106,10 +18412,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -16451,6 +18758,15 @@ "node": ">=0.10" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/streamx": { "version": "2.18.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", @@ -16652,6 +18968,30 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/stylehacks": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", @@ -16669,6 +19009,127 @@ "postcss": "^8.4.31" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -16841,6 +19302,19 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.9.tgz", + "integrity": "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", @@ -16932,6 +19406,42 @@ "node": ">=6.0.0" } }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/terser": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.0.tgz", @@ -17074,6 +19584,29 @@ "integrity": "sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI= sha512-jm9KjEWiDmtGLBrTqXEduGzlYTTlPaoDKdq5YRQhD0rYjo61ZNTYKZ/x5J4ajPSBH9wIYY5qm9GNG5otIKjtOA==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -17150,6 +19683,43 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -17163,15 +19733,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -17268,6 +19829,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -17283,15 +19854,6 @@ "node": ">=6" } }, - "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/tough-cookie/node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -17304,8 +19866,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tree-kill": { "version": "1.2.2", @@ -17328,6 +19889,13 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/ts-loader": { "version": "9.5.1", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", @@ -17368,10 +19936,11 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -17459,10 +20028,11 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -17473,6 +20043,140 @@ "node": ">=0.6.x" } }, + "node_modules/tsup": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz", + "integrity": "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.25.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tsup/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tsup/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tsup/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tsup/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tsup/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/tsup/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -17540,9 +20244,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.8.0-dev.20250207", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.0-dev.20250207.tgz", - "integrity": "sha512-bRCO1GkVxTLd/UFJWOg9R1oRiSMidcfpICzuQlDJlHspv6hlcJvvIJP0BvQxrBYpu4dbzqp/Fh8rRYIkEjbSlQ==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -17685,6 +20389,13 @@ "node": ">=0.10.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undertaker": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", @@ -17733,8 +20444,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/union-value": { "version": "1.0.1", @@ -17882,15 +20592,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -18172,6 +20873,30 @@ "node": ">= 0.10" } }, + "node_modules/vscode-html-languageservice": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.3.1.tgz", + "integrity": "sha512-ysUh4hFeW/WOWz/TO9gm08xigiSsV/FOAZ+DolgJfeLftna54YdmZ4A+lIn46RbdO3/Qv5QHTn1ZGqmrXQhZyA==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -18197,8 +20922,7 @@ "node_modules/vscode-uri": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "dev": true + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" }, "node_modules/watchpack": { "version": "2.4.1", @@ -18213,6 +20937,15 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/web-tree-sitter": { "version": "0.20.8", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.8.tgz", @@ -18222,8 +20955,7 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { "version": "5.94.0", @@ -18480,11 +21212,16 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0= sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -18494,7 +21231,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -18784,9 +21520,10 @@ } }, "node_modules/yazl": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.3.tgz", - "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE= sha512-cIUrm3/81iF/BzuyORI7ppz1vGHAhA62JYzAFFC+rwJ2jQF1LcYxY9UXx4XyUXojkCnol0SvPuc+Toc7FO4W8g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3" } @@ -18820,6 +21557,24 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz", + "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 3abe2f26..79482ab8 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "type": "module", "private": true, "scripts": { + "buildreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js && cd ../../../../../../../", + "watchreact": "cd ./src/vs/workbench/contrib/void/browser/react/ && node build.js --watch && cd ../../../../../../../", + "watchreactd": "deemon npm run watchreact", "test": "echo Please run any of the test scripts from the scripts folder.", "test-browser": "npx playwright install && node test/unit/browser/index.js", "test-browser-no-install": "node test/unit/browser/index.js", @@ -68,8 +71,12 @@ "update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json" }, "dependencies": { + "@anthropic-ai/sdk": "^0.39.0", + "@floating-ui/react": "^0.27.5", + "@google/generative-ai": "^0.22.0", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", + "@mistralai/mistralai": "^1.5.0", "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", @@ -95,6 +102,9 @@ "@xterm/addon-webgl": "^0.19.0-beta.98", "@xterm/headless": "^5.6.0-beta.98", "@xterm/xterm": "^5.6.0-beta.98", + "cross-spawn": "^7.0.6", + "diff": "^7.0.0", + "groq-sdk": "^0.15.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -104,20 +114,28 @@ "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", "node-pty": "1.1.0-beta30", + "ollama": "^0.5.14", "open": "^8.4.2", + "openai": "^4.86.1", + "posthog-node": "^4.8.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "tas-client-umd": "0.2.0", "v8-inspect-profiler": "^0.1.1", + "vscode-html-languageservice": "^5.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.2.0", "yauzl": "^3.0.0", - "yazl": "^2.4.3" + "yazl": "^2.5.1", + "zod": "^3.24.2" }, "devDependencies": { "@playwright/test": "^1.50.0", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", + "@types/diff": "^7.0.1", "@types/eslint": "^9.6.1", "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", @@ -125,6 +143,8 @@ "@types/minimist": "^1.2.1", "@types/mocha": "^9.1.1", "@types/node": "20.x", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", @@ -194,7 +214,10 @@ "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", + "next": "^15.2.0", + "nodemon": "^3.1.9", "npm-run-all": "^4.1.5", + "original-fs": "^1.2.0", "os-browserify": "^0.3.0", "p-all": "^1.0.0", "path-browserify": "^1.0.1", @@ -202,17 +225,20 @@ "postcss-nesting": "^12.0.2", "pump": "^1.0.1", "rcedit": "^1.1.0", - "rimraf": "^2.2.8", + "rimraf": "^2.7.1", + "scope-tailwind": "^1.0.6", "sinon": "^12.0.1", "sinon-test": "^3.1.3", "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^3.3.2", + "tailwindcss": "^4.0.9", "ts-loader": "^9.5.1", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.8.0-dev.20250207", + "tsup": "^8.4.0", + "typescript": "^5.8.2", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", diff --git a/product.json b/product.json index 873ea0ea..6939af1d 100644 --- a/product.json +++ b/product.json @@ -1,84 +1,38 @@ { - "nameShort": "Code - OSS", - "nameLong": "Code - OSS", - "applicationName": "code-oss", - "dataFolderName": ".vscode-oss", - "win32MutexName": "vscodeoss", + "nameShort": "Void", + "nameLong": "Void", + "applicationName": "void", + "dataFolderName": ".void-editor", + "win32MutexName": "voideditor", "licenseName": "MIT", - "licenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt", - "serverLicenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt", + "licenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt", + "serverLicenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt", "serverGreeting": [], "serverLicense": [], "serverLicensePrompt": "", - "serverApplicationName": "code-server-oss", - "serverDataFolderName": ".vscode-server-oss", - "tunnelApplicationName": "code-tunnel-oss", - "win32DirName": "Microsoft Code OSS", - "win32NameVersion": "Microsoft Code OSS", - "win32RegValueName": "CodeOSS", - "win32x64AppId": "{{D77B7E06-80BA-4137-BCF4-654B95CCEBC5}", - "win32arm64AppId": "{{D1ACE434-89C5-48D1-88D3-E2991DF85475}", - "win32x64UserAppId": "{{CC6B787D-37A0-49E8-AE24-8559A032BE0C}", - "win32arm64UserAppId": "{{3AEBF0C8-F733-4AD4-BADE-FDB816D53D7B}", - "win32AppUserModelId": "Microsoft.CodeOSS", - "win32ShellNameShort": "C&ode - OSS", - "win32TunnelServiceMutex": "vscodeoss-tunnelservice", - "win32TunnelMutex": "vscodeoss-tunnel", - "darwinBundleIdentifier": "com.visualstudio.code.oss", - "linuxIconName": "code-oss", + "serverApplicationName": "void-server", + "serverDataFolderName": ".void-server", + "tunnelApplicationName": "void-tunnel", + "win32DirName": "Void", + "win32NameVersion": "Void", + "win32RegValueName": "VoidEditor", + "win32x64AppId": "{{9D394D01-1728-45A7-B997-A6C82C5452C3}", + "win32arm64AppId": "{{0668DD58-2BDE-4101-8CDA-40252DF8875D}", + "win32x64UserAppId": "{{8BED5DC1-6C55-46E6-9FE6-18F7E6F7C7F1}", + "win32arm64UserAppId": "{{F6C87466-BC82-4A8F-B0FF-18CA366BA4D8}", + "win32AppUserModelId": "Void.Editor", + "win32ShellNameShort": "V&oid", + "win32TunnelServiceMutex": "void-tunnelservice", + "win32TunnelMutex": "void-tunnel", + "darwinBundleIdentifier": "com.voideditor.code", + "linuxIconName": "void-editor", "licenseFileName": "LICENSE.txt", - "reportIssueUrl": "https://github.com/microsoft/vscode/issues/new", + "reportIssueUrl": "https://github.com/voideditor/void/issues/new", "nodejsRepository": "https://nodejs.org", - "urlProtocol": "code-oss", - "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/", - "builtInExtensions": [ - { - "name": "ms-vscode.js-debug-companion", - "version": "1.1.3", - "sha256": "7380a890787452f14b2db7835dfa94de538caf358ebc263f9d46dd68ac52de93", - "repo": "https://github.com/microsoft/vscode-js-debug-companion", - "metadata": { - "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.js-debug", - "version": "1.97.1", - "sha256": "977dd854805547702e312e176f68a1b142fa123f228258f47f0964560ad32496", - "repo": "https://github.com/microsoft/vscode-js-debug", - "metadata": { - "id": "25629058-ddac-4e17-abba-74678e126c5d", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.vscode-js-profile-table", - "version": "1.0.10", - "sha256": "7361748ddf9fd09d8a2ed1f2a2d7376a2cf9aae708692820b799708385c38e08", - "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", - "metadata": { - "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - } - ] + "urlProtocol": "void", + "extensionsGallery": { + "serviceUrl": "https://open-vsx.org/vscode/gallery", + "itemUrl": "https://open-vsx.org/vscode/item" + }, + "builtInExtensions": [] } diff --git a/remote/package-lock.json b/remote/package-lock.json index 2349babd..7d602235 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -31,6 +31,7 @@ "@xterm/headless": "^5.6.0-beta.98", "@xterm/xterm": "^5.6.0-beta.98", "cookie": "^0.7.0", + "debounced": "1.0.2", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -39,6 +40,7 @@ "native-watchdog": "^1.4.1", "node-pty": "1.1.0-beta30", "tas-client-umd": "0.2.0", + "tslib": "^2.8.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.2.0", @@ -720,6 +722,12 @@ "node": ">= 0.6" } }, + "node_modules/debounced": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/debounced/-/debounced-1.0.2.tgz", + "integrity": "sha512-6GPv+l/OOtdb1DKNY70k5ubuJhVjtBjUnujC5vQAHHrMuvBpDXsTc91xEMTdeA3/v4swYHamtdB9XIN7DcKxpw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1398,6 +1406,12 @@ "node": ">=8.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/remote/package.json b/remote/package.json index a25b884d..f654787e 100644 --- a/remote/package.json +++ b/remote/package.json @@ -26,6 +26,7 @@ "@xterm/headless": "^5.6.0-beta.98", "@xterm/xterm": "^5.6.0-beta.98", "cookie": "^0.7.0", + "debounced": "1.0.2", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -34,6 +35,7 @@ "native-watchdog": "^1.4.1", "node-pty": "1.1.0-beta30", "tas-client-umd": "0.2.0", + "tslib": "^2.8.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.2.0", diff --git a/resources/darwin/code.icns b/resources/darwin/code.icns index a91b7c58..1fa6e9d0 100644 Binary files a/resources/darwin/code.icns and b/resources/darwin/code.icns differ diff --git a/resources/linux/code.png b/resources/linux/code.png index 8d8646f8..97ca4fac 100644 Binary files a/resources/linux/code.png and b/resources/linux/code.png differ diff --git a/resources/server/code-192.png b/resources/server/code-192.png index 8d8646f8..d0380464 100644 Binary files a/resources/server/code-192.png and b/resources/server/code-192.png differ diff --git a/resources/server/code-512.png b/resources/server/code-512.png index 8d8646f8..d0380464 100644 Binary files a/resources/server/code-512.png and b/resources/server/code-512.png differ diff --git a/resources/server/favicon.ico b/resources/server/favicon.ico index f9f0de3e..2659eb19 100644 Binary files a/resources/server/favicon.ico and b/resources/server/favicon.ico differ diff --git a/resources/win32/VisualElementsManifest.xml b/resources/win32/VisualElementsManifest.xml index 40efd0a3..71b18418 100644 --- a/resources/win32/VisualElementsManifest.xml +++ b/resources/win32/VisualElementsManifest.xml @@ -1,9 +1,9 @@ + ForegroundText="light" + ShortDisplayName="Void" /> diff --git a/resources/win32/code.ico b/resources/win32/code.ico index e95d71fc..26774fa2 100644 Binary files a/resources/win32/code.ico and b/resources/win32/code.ico differ diff --git a/resources/win32/code_150x150.png b/resources/win32/code_150x150.png index 55e9f8f3..e790eced 100644 Binary files a/resources/win32/code_150x150.png and b/resources/win32/code_150x150.png differ diff --git a/resources/win32/code_70x70.png b/resources/win32/code_70x70.png index ff1bdc9d..08fc0079 100644 Binary files a/resources/win32/code_70x70.png and b/resources/win32/code_70x70.png differ diff --git a/resources/win32/inno-void.bmp b/resources/win32/inno-void.bmp new file mode 100644 index 00000000..3c986c74 Binary files /dev/null and b/resources/win32/inno-void.bmp differ diff --git a/resources/win32/logo_cube_noshadow.png b/resources/win32/logo_cube_noshadow.png new file mode 100644 index 00000000..225179f8 Binary files /dev/null and b/resources/win32/logo_cube_noshadow.png differ diff --git a/src/tsconfig.json b/src/tsconfig.json index 88d3daa0..c309f5e9 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "./tsconfig.base.json", + // "jsx": "react", // <-- Void added this "compilerOptions": { "esModuleInterop": true, "removeComments": false, diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 4081c2f2..cacfa163 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -160,7 +160,7 @@ export class InputBox extends Widget { this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto }); if (this.options.flexibleWidth) { - this.input.setAttribute('wrap', 'off'); + this.input.setAttribute('wrap', 'off'); // Void changed this to 'on' at one point, but we're no longer using InputBox this.mirror.style.whiteSpace = 'pre'; this.mirror.style.wordWrap = 'initial'; } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 21a297a1..ce66f600 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -119,6 +119,14 @@ import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/elec import { normalizeNFC } from '../../base/common/normalization.js'; import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; +// in theory this is not allowed +// ignore the eslint errors below +import { IMetricsService } from '../../workbench/contrib/void/common/metricsService.js'; +import { IVoidUpdateService } from '../../workbench/contrib/void/common/voidUpdateService.js'; +import { MetricsMainService } from '../../workbench/contrib/void/electron-main/metricsMainService.js'; +import { VoidMainUpdateService } from '../../workbench/contrib/void/electron-main/voidUpdateMainService.js'; +import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/llmMessageChannel.js'; + /** * The main VS Code application. There will only ever be one instance, * even if the user starts many instances (e.g. from the command line). @@ -146,7 +154,7 @@ export class CodeApplication extends Disposable { @IStateService private readonly stateService: IStateService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, - @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService + @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService, ) { super(); @@ -522,6 +530,16 @@ export class CodeApplication extends Disposable { }); //#endregion + + // //#region Void IPC + // validatedIpcMain.handle('vscode:sendLLMMessage', async (event, data) => { + // try { + // await this.sendLLMMessage(data); + // } catch (error) { + // console.error('Error sending LLM message:', error); + // } + // }); + // //#endregion } private onUnexpectedError(error: Error): void { @@ -1109,6 +1127,10 @@ export class CodeApplication extends Disposable { services.set(ITelemetryService, NullTelemetryService); } + // Void main process services (required for services with a channel for comm between browser and electron-main (node)) + services.set(IMetricsService, new SyncDescriptor(MetricsMainService, undefined, false)); + services.set(IVoidUpdateService, new SyncDescriptor(VoidMainUpdateService, undefined, false)); + // Default Extensions Profile Init services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); @@ -1227,6 +1249,16 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); + // Void - use loggerChannel as reference + const metricsChannel = ProxyChannel.fromService(accessor.get(IMetricsService), disposables); + mainProcessElectronServer.registerChannel('void-channel-metrics', metricsChannel); + + const voidUpdatesChannel = ProxyChannel.fromService(accessor.get(IVoidUpdateService), disposables); + mainProcessElectronServer.registerChannel('void-channel-update', voidUpdatesChannel); + + const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService)); + mainProcessElectronServer.registerChannel('void-channel-llmMessageService', llmMessageChannel); + // Extension Host Debug Broadcasting const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService)); mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); diff --git a/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts b/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts index c111f826..94312698 100644 --- a/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts +++ b/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts @@ -21,7 +21,7 @@ export class ExpandLineSelectionAction extends EditorAction { kbOpts: { weight: KeybindingWeight.EditorCore, kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KeyL + primary: KeyMod.CtrlCmd | KeyCode.KeyM // Void changed this to Cmd+M }, }); } diff --git a/src/vs/platform/encryption/electron-main/encryptionMainService.ts b/src/vs/platform/encryption/electron-main/encryptionMainService.ts index 1b4b1e05..79b3f83a 100644 --- a/src/vs/platform/encryption/electron-main/encryptionMainService.ts +++ b/src/vs/platform/encryption/electron-main/encryptionMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { safeStorage as safeStorageElectron, app } from 'electron'; -import { isMacintosh, isWindows } from '../../../base/common/platform.js'; +import { isMacintosh, isWindows, isLinux } from '../../../base/common/platform.js'; import { KnownStorageProvider, IEncryptionMainService, PasswordStoreCLIOption } from '../common/encryptionService.js'; import { ILogService } from '../../log/common/log.js'; @@ -23,6 +23,13 @@ export class EncryptionMainService implements IEncryptionMainService { constructor( @ILogService private readonly logService: ILogService ) { + + // Void added this as a nice default for linux so you don't need to specify encryption provider + if (isLinux && !app.commandLine.getSwitchValue('password-store')) { + this.logService.trace('[EncryptionMainService] No password-store switch, defaulting to basic...'); + app.commandLine.appendSwitch('password-store', PasswordStoreCLIOption.basic); + } + // if this commandLine switch is set, the user has opted in to using basic text encryption if (app.commandLine.getSwitchValue('password-store') === PasswordStoreCLIOption.basic) { this.logService.trace('[EncryptionMainService] setting usePlainTextEncryption to true...'); diff --git a/src/vs/platform/extensions/common/extensionValidator.ts b/src/vs/platform/extensions/common/extensionValidator.ts index 87a92881..3bfbf57f 100644 --- a/src/vs/platform/extensions/common/extensionValidator.ts +++ b/src/vs/platform/extensions/common/extensionValidator.ts @@ -410,6 +410,7 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers } if (!isValidVersion(currentVersion, date, desiredVersion)) { + // Void - this is where the 'not compatible with current version' comes from notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion)); return false; } diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index eeabcc55..e83230ae 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -64,7 +64,8 @@ export const enum KeybindingWeight { EditorContrib = 100, WorkbenchContrib = 200, BuiltinExtension = 300, - ExternalExtension = 400 + ExternalExtension = 400, + VoidExtension = 605, // Void - must trump any external extension } export interface ICommandAndKeybindingRule extends IKeybindingRule { diff --git a/src/vs/platform/telemetry/common/serverTelemetryService.ts b/src/vs/platform/telemetry/common/serverTelemetryService.ts index f6fc225a..8839ecf7 100644 --- a/src/vs/platform/telemetry/common/serverTelemetryService.ts +++ b/src/vs/platform/telemetry/common/serverTelemetryService.ts @@ -31,25 +31,29 @@ export class ServerTelemetryService extends TelemetryService implements IServerT } override publicLog(eventName: string, data?: ITelemetryData) { - if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) { - return; - } - return super.publicLog(eventName, data); + // Void commented this out + // if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) { + // return; + // } + // return super.publicLog(eventName, data); } override publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck) { - return this.publicLog(eventName, data as ITelemetryData | undefined); + // Void commented this out + // return this.publicLog(eventName, data as ITelemetryData | undefined); } override publicLogError(errorEventName: string, data?: ITelemetryData) { - if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) { - return Promise.resolve(undefined); - } - return super.publicLogError(errorEventName, data); + // Void commented this out + // if (this._injectedTelemetryLevel < TelemetryLevel.ERROR) { + // return Promise.resolve(undefined); + // } + // return super.publicLogError(errorEventName, data); } override publicLogError2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck) { - return this.publicLogError(eventName, data as ITelemetryData | undefined); + // Void commented this out + // return this.publicLogError(eventName, data as ITelemetryData | undefined); } async updateInjectedTelemetryLevel(telemetryLevel: TelemetryLevel): Promise { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 51fbb8fa..c7cb8715 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from '../../../base/common/lifecycle.js'; -import { mixin } from '../../../base/common/objects.js'; +// import { mixin } from '../../../base/common/objects.js'; import { isWeb } from '../../../base/common/platform.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { localize } from '../../../nls.js'; @@ -15,7 +15,7 @@ import { IProductService } from '../../product/common/productService.js'; import { Registry } from '../../registry/common/platform.js'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from './gdprTypings.js'; import { ITelemetryData, ITelemetryService, TelemetryConfiguration, TelemetryLevel, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SECTION_ID, TELEMETRY_SETTING_ID, ICommonProperties } from './telemetry.js'; -import { cleanData, getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js'; +import { /*cleanData,*/ getTelemetryLevel, ITelemetryAppender } from './telemetryUtils.js'; export interface ITelemetryServiceConfig { appenders: ITelemetryAppender[]; @@ -38,7 +38,8 @@ export class TelemetryService implements ITelemetryService { readonly firstSessionDate: string; readonly msftInternal: boolean | undefined; - private _appenders: ITelemetryAppender[]; + // Void commented this out (no longer used) + // private _appenders: ITelemetryAppender[]; private _commonProperties: ICommonProperties; private _experimentProperties: { [name: string]: string } = {}; private _piiPaths: string[]; @@ -53,7 +54,8 @@ export class TelemetryService implements ITelemetryService { @IConfigurationService private _configurationService: IConfigurationService, @IProductService private _productService: IProductService ) { - this._appenders = config.appenders; + // Void commented this out (no longer used) + // this._appenders = config.appenders; this._commonProperties = config.commonProperties ?? Object.create(null); this.sessionId = this._commonProperties['sessionID'] as string; @@ -121,44 +123,49 @@ export class TelemetryService implements ITelemetryService { this._disposables.dispose(); } - private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) { - // don't send events when the user is optout - if (this._telemetryLevel < eventLevel) { - return; - } + // Void commented this out + // private _log(eventName: string, eventLevel: TelemetryLevel, data?: ITelemetryData) { + // // don't send events when the user is optout + // if (this._telemetryLevel < eventLevel) { + // return; + // } - // add experiment properties - data = mixin(data, this._experimentProperties); + // // add experiment properties + // data = mixin(data, this._experimentProperties); - // remove all PII from data - data = cleanData(data as Record, this._cleanupPatterns); + // // remove all PII from data + // data = cleanData(data as Record, this._cleanupPatterns); - // add common properties - data = mixin(data, this._commonProperties); + // // add common properties + // data = mixin(data, this._commonProperties); - // Log to the appenders of sufficient level - this._appenders.forEach(a => a.log(eventName, data)); - } + // // Log to the appenders of sufficient level + // this._appenders.forEach(a => a.log(eventName, data)); + // } publicLog(eventName: string, data?: ITelemetryData) { - this._log(eventName, TelemetryLevel.USAGE, data); + // Void commented this out + // this._log(eventName, TelemetryLevel.USAGE, data); } publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck) { - this.publicLog(eventName, data as ITelemetryData); + // Void commented this out + // this.publicLog(eventName, data as ITelemetryData); } publicLogError(errorEventName: string, data?: ITelemetryData) { - if (!this._sendErrorTelemetry) { - return; - } + // Void commented this out + // if (!this._sendErrorTelemetry) { + // return; + // } - // Send error event and anonymize paths - this._log(errorEventName, TelemetryLevel.ERROR, data); + // // Send error event and anonymize paths + // this._log(errorEventName, TelemetryLevel.ERROR, data); } publicLogError2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck) { - this.publicLogError(eventName, data as ITelemetryData); + // Void commented this out + // this.publicLogError(eventName, data as ITelemetryData); } } diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index a1ec3fed..7173044c 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { timeout } from '../../../base/common/async.js'; +// import { timeout } from '../../../base/common/async.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; +// import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; import { ILifecycleMainService, LifecycleMainPhase } from '../../lifecycle/electron-main/lifecycleMainService.js'; import { ILogService } from '../../log/common/log.js'; import { IProductService } from '../../product/common/productService.js'; @@ -15,7 +16,10 @@ import { IRequestService } from '../../request/common/request.js'; import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js'; export function createUpdateURL(platform: string, quality: string, productService: IProductService): string { - return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`; + // return `https://voideditor.dev/api/update/${platform}/stable`; + // return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`; + // https://github.com/VSCodium/update-api + return `https://updates.voideditor.dev/api/update/${platform}/${quality}/${productService.commit}`; } export type UpdateErrorClassification = { @@ -51,7 +55,7 @@ export abstract class AbstractUpdateService implements IUpdateService { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IRequestService protected requestService: IRequestService, @ILogService protected logService: ILogService, - @IProductService protected readonly productService: IProductService + @IProductService protected readonly productService: IProductService, ) { lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen) .finally(() => this.initialize()); @@ -64,75 +68,35 @@ export abstract class AbstractUpdateService implements IUpdateService { */ protected async initialize(): Promise { if (!this.environmentMainService.isBuilt) { + console.log('is NOT built, canceling update service') this.setState(State.Disabled(DisablementReason.NotBuilt)); return; // updates are never enabled when running out of sources } + console.log('is built, continuing with update service') - if (this.environmentMainService.disableUpdates) { - this.setState(State.Disabled(DisablementReason.DisabledByEnvironment)); - this.logService.info('update#ctor - updates are disabled by the environment'); - return; - } - - if (!this.productService.updateUrl || !this.productService.commit) { - this.setState(State.Disabled(DisablementReason.MissingConfiguration)); - this.logService.info('update#ctor - updates are disabled as there is no update URL'); - return; - } - - const updateMode = this.configurationService.getValue<'none' | 'manual' | 'start' | 'default'>('update.mode'); - const quality = this.getProductQuality(updateMode); - - if (!quality) { - this.setState(State.Disabled(DisablementReason.ManuallyDisabled)); - this.logService.info('update#ctor - updates are disabled by user preference'); - return; - } - - this.url = this.buildUpdateFeedUrl(quality); + this.url = this.doBuildUpdateFeedUrl('stable'); if (!this.url) { this.setState(State.Disabled(DisablementReason.InvalidConfiguration)); this.logService.info('update#ctor - updates are disabled as the update URL is badly formed'); return; } - // hidden setting - if (this.configurationService.getValue('_update.prss')) { - const url = new URL(this.url); - url.searchParams.set('prss', 'true'); - this.url = url.toString(); - } + this.setState(State.Disabled(DisablementReason.ManuallyDisabled)); - this.setState(State.Idle(this.getUpdateType())); - if (updateMode === 'manual') { - this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference'); - return; - } + // Void - temporarily disabled while we figure out how to do this the right way - if (updateMode === 'start') { - this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference'); + // this.setState(State.Idle(this.getUpdateType())); - // Check for updates only once after 30 seconds - setTimeout(() => this.checkForUpdates(false), 30 * 1000); - } else { - // Start checking for updates after 30 seconds - this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); - } + // start checking for updates after 10 seconds + // this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err)); } - private getProductQuality(updateMode: string): string | undefined { - return updateMode === 'none' ? undefined : this.productService.quality; - } - - private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise { - return timeout(delay) - .then(() => this.checkForUpdates(false)) - .then(() => { - // Check again after 1 hour - return this.scheduleCheckForUpdates(60 * 60 * 1000); - }); - } + // private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise { + // await timeout(delay); + // await this.checkForUpdates(false); + // return await this.scheduleCheckForUpdates(60 * 60 * 1000); + // } async checkForUpdates(explicit: boolean): Promise { this.logService.trace('update#checkForUpdates, state = ', this.state.type); @@ -154,6 +118,7 @@ export abstract class AbstractUpdateService implements IUpdateService { await this.doDownloadUpdate(this.state); } + // override implemented by windows and linux protected async doDownloadUpdate(state: AvailableForDownload): Promise { // noop } @@ -168,6 +133,7 @@ export abstract class AbstractUpdateService implements IUpdateService { await this.doApplyUpdate(); } + // windows overrides this protected async doApplyUpdate(): Promise { // noop } @@ -230,6 +196,6 @@ export abstract class AbstractUpdateService implements IUpdateService { // noop } - protected abstract buildUpdateFeedUrl(quality: string): string | undefined; + protected abstract doBuildUpdateFeedUrl(quality: string): string | undefined; protected abstract doCheckForUpdates(context: any): void; } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 57398fba..a610d1a9 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -73,7 +73,8 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau this.setState(State.Idle(UpdateType.Archive, message)); } - protected buildUpdateFeedUrl(quality: string): string | undefined { + // Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl + protected doBuildUpdateFeedUrl(quality: string): string | undefined { let assetID: string; if (!this.productService.darwinUniversalAssetId) { assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64'; diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index dd189005..6612e909 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -28,7 +28,8 @@ export class LinuxUpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - protected buildUpdateFeedUrl(quality: string): string { + // Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl + protected doBuildUpdateFeedUrl(quality: string): string { return createUpdateURL(`linux-${process.arch}`, quality, this.productService); } diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index db92de2f..a805da29 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -99,7 +99,8 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun await super.initialize(); } - protected buildUpdateFeedUrl(quality: string): string | undefined { + // Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl + protected doBuildUpdateFeedUrl(quality: string): string | undefined { let platform = `win32-${process.arch}`; if (getUpdateType() === UpdateType.Archive) { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index fd3a6842..df7e2230 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -205,7 +205,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: localize('move second sidebar left', "Move Secondary Side Bar Left") + title: localize('move second sidebar left', "Move Void Side Bar Left") // Void renamed "Secondary" Side Bar to "Void" Side Bar }, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 @@ -216,7 +216,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: localize('move second sidebar right', "Move Secondary Side Bar Right") + title: localize('move second sidebar right', "Move Void Side Bar Right") }, when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 @@ -908,7 +908,7 @@ registerAction2(class extends Action2 { if (!hasAddedView) { results.push({ type: 'separator', - label: localize('secondarySideBarContainer', "Secondary Side Bar / {0}", containerModel.title) + label: localize('secondarySideBarContainer', "Void Side Bar / {0}", containerModel.title) }); hasAddedView = true; } @@ -1015,7 +1015,7 @@ class MoveFocusedViewAction extends Action2 { if (!(isViewSolo && currentLocation === ViewContainerLocation.AuxiliaryBar)) { items.push({ id: '_.auxiliarybar.newcontainer', - label: localize('moveFocusedView.newContainerInSidePanel', "New Secondary Side Bar Entry") + label: localize('moveFocusedView.newContainerInSidePanel', "New Void Side Bar Entry") }); } @@ -1063,7 +1063,7 @@ class MoveFocusedViewAction extends Action2 { items.push({ type: 'separator', - label: localize('secondarySideBar', "Secondary Side Bar") + label: localize('secondarySideBar', "Void Side Bar") }); const pinnedAuxPanels = paneCompositePartService.getPinnedPaneCompositeIds(ViewContainerLocation.AuxiliaryBar); @@ -1381,7 +1381,7 @@ if (!isMacintosh || !isNative) { ToggleVisibilityActions.push(...[ CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }), - CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), + CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Void Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon), CreateToggleLayoutItem(ToggleStatusbarVisibilityAction.ID, ContextKeyExpr.equals('config.workbench.statusBar.visible', true), localize('statusBar', "Status Bar"), statusBarIcon), ]); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index fc33edf3..463b0491 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -2490,7 +2490,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi comment: 'Information about the layout of the workbench during statup'; activityBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the activity bar is visible' }; sideBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the primary side bar is visible' }; - auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the secondary side bar is visible' }; + auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the Void side bar is visible' }; // Void side bar panelVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the panel is visible' }; statusbarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the status bar is visible' }; sideBarPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the primary side bar is on the left or right' }; @@ -2589,7 +2589,7 @@ const LayoutStateKeys = { // Part Sizing SIDEBAR_SIZE: new InitializationStateKey('sideBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 200), - AUXILIARYBAR_SIZE: new InitializationStateKey('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 200), + AUXILIARYBAR_SIZE: new InitializationStateKey('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 800), // Void changed this from 200 to 800 PANEL_SIZE: new InitializationStateKey('panel.size', StorageScope.PROFILE, StorageTarget.MACHINE, 300), PANEL_LAST_NON_MAXIMIZED_HEIGHT: new RuntimeStateKey('panel.lastNonMaximizedHeight', StorageScope.PROFILE, StorageTarget.MACHINE, 300), diff --git a/src/vs/workbench/browser/media/code-icon.svg b/src/vs/workbench/browser/media/code-icon.svg deleted file mode 100644 index cc61f81e..00000000 --- a/src/vs/workbench/browser/media/code-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/media/void-icon-sm.png b/src/vs/workbench/browser/media/void-icon-sm.png new file mode 100644 index 00000000..225179f8 Binary files /dev/null and b/src/vs/workbench/browser/media/void-icon-sm.png differ diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index e5cc8c36..1b396775 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -27,7 +27,7 @@ const auxiliaryBarLeftOffIcon = registerIcon('auxiliarybar-left-off-layout-icon' export class ToggleAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.toggleAuxiliaryBar'; - static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Secondary Side Bar Visibility"); + static readonly LABEL = localize2('toggleAuxiliaryBar', "Toggle Void Side Bar Visibility"); // Void renamed Secondary to Void constructor() { super({ @@ -35,14 +35,14 @@ export class ToggleAuxiliaryBarAction extends Action2 { title: ToggleAuxiliaryBarAction.LABEL, toggled: { condition: AuxiliaryBarVisibleContext, - title: localize('closeSecondarySideBar', 'Hide Secondary Side Bar'), + title: localize('closeSecondarySideBar', 'Hide Void Side Bar'), icon: closeIcon, mnemonicTitle: localize({ key: 'secondary sidebar mnemonic', comment: ['&& denotes a mnemonic'] }, "Secondary Si&&de Bar"), }, icon: closeIcon, // Ensures no flickering when using toggled.icon category: Categories.View, metadata: { - description: localize('openAndCloseAuxiliaryBar', 'Open/Show and Close/Hide Secondary Side Bar'), + description: localize('openAndCloseAuxiliaryBar', 'Open/Show and Close/Hide Void Side Bar'), }, f1: true, keybinding: { @@ -81,7 +81,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closeAuxiliaryBar', - title: localize2('closeSecondarySideBar', 'Hide Secondary Side Bar'), + title: localize2('closeSecondarySideBar', 'Hide Void Side Bar'), category: Categories.View, precondition: AuxiliaryBarVisibleContext, f1: true, @@ -95,7 +95,7 @@ registerAction2(class extends Action2 { registerAction2(class FocusAuxiliaryBarAction extends Action2 { static readonly ID = 'workbench.action.focusAuxiliaryBar'; - static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Secondary Side Bar"); + static readonly LABEL = localize2('focusAuxiliaryBar', "Focus into Void Side Bar"); constructor() { super({ @@ -128,7 +128,7 @@ MenuRegistry.appendMenuItems([ group: '2_pane_toggles', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize('toggleSecondarySideBar', "Toggle Secondary Side Bar"), + title: localize('toggleSecondarySideBar', "Toggle Void Side Bar"), toggled: { condition: AuxiliaryBarVisibleContext, icon: auxiliaryBarLeftIcon }, icon: auxiliaryBarLeftOffIcon, }, @@ -141,7 +141,7 @@ MenuRegistry.appendMenuItems([ group: '2_pane_toggles', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize('toggleSecondarySideBar', "Toggle Secondary Side Bar"), + title: localize('toggleSecondarySideBar', "Toggle Void Side Bar"), toggled: { condition: AuxiliaryBarVisibleContext, icon: auxiliaryBarRightIcon }, icon: auxiliaryBarRightOffIcon, }, @@ -154,7 +154,7 @@ MenuRegistry.appendMenuItems([ group: '3_workbench_layout_move', command: { id: ToggleAuxiliaryBarAction.ID, - title: localize2('hideAuxiliaryBar', 'Hide Secondary Side Bar'), + title: localize2('hideAuxiliaryBar', 'Hide Void Side Bar'), }, when: ContextKeyExpr.and(AuxiliaryBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 2 @@ -166,7 +166,7 @@ registerAction2(class extends SwitchCompositeViewAction { constructor() { super({ id: 'workbench.action.previousAuxiliaryBarView', - title: localize2('previousAuxiliaryBarView', 'Previous Secondary Side Bar View'), + title: localize2('previousAuxiliaryBarView', 'Previous Void Side Bar View'), category: Categories.View, f1: true }, ViewContainerLocation.AuxiliaryBar, -1); @@ -177,7 +177,7 @@ registerAction2(class extends SwitchCompositeViewAction { constructor() { super({ id: 'workbench.action.nextAuxiliaryBarView', - title: localize2('nextAuxiliaryBarView', 'Next Secondary Side Bar View'), + title: localize2('nextAuxiliaryBarView', 'Next Void Side Bar View'), category: Categories.View, f1: true }, ViewContainerLocation.AuxiliaryBar, 1); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 508322d3..e940f626 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -52,7 +52,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { static readonly viewContainersWorkspaceStateKey = 'workbench.auxiliarybar.viewContainersWorkspaceState'; // Use the side bar dimensions - override readonly minimumWidth: number = 170; + override readonly minimumWidth: number = 230; // Void changed this (was 170) override readonly maximumWidth: number = Number.POSITIVE_INFINITY; override readonly minimumHeight: number = 0; override readonly maximumHeight: number = Number.POSITIVE_INFINITY; @@ -230,9 +230,9 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { actions.push(...[ new Separator(), new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), - toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Secondary Side Bar Left") : localize('move second side bar right', "Move Secondary Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }), + toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Void Side Bar Left") : localize('move second side bar right', "Move Void Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }), // Void Side bar toggleShowLabelsAction, - toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Secondary Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) }) + toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Void Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) }) // Void Side bar ]); } diff --git a/src/vs/workbench/browser/parts/banner/media/bannerpart.css b/src/vs/workbench/browser/parts/banner/media/bannerpart.css index a0de81f2..5b2a91e7 100644 --- a/src/vs/workbench/browser/parts/banner/media/bannerpart.css +++ b/src/vs/workbench/browser/parts/banner/media/bannerpart.css @@ -30,7 +30,7 @@ background-repeat: no-repeat; background-position: center center; background-size: 16px; - background-image: url('../../../../browser/media/code-icon.svg'); + background-image: url('../../../../browser/media/void-icon-sm.png'); /* // Void */ width: 16px; padding: 0; margin: 0 6px 0 10px; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index 541b7ab1..287299ff 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -4,188 +4,314 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../nls.js'; -import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { isMacintosh, isWeb, OS } from '../../../../base/common/platform.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { isMacintosh, isNative, OS } from '../../../../base/common/platform.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { append, clearNode, $, h } from '../../../../base/browser/dom.js'; import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; -import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { editorForeground, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js'; -import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js'; -import { coalesce, shuffle } from '../../../../base/common/arrays.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { isRecentFolder, IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js'; +import { IHostService } from '../../../services/host/browser/host.js'; +import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js'; +import { ColorScheme } from '../../web.api.js'; +import { OpenFileFolderAction, OpenFolderAction } from '../../actions/workspaceActions.js'; +import { IWindowOpenable } from '../../../../platform/window/common/window.js'; +import { splitRecentLabel } from '../../../../base/common/labels.js'; -interface WatermarkEntry { - readonly id: string; - readonly text: string; - readonly when?: { - native?: ContextKeyExpression; - web?: ContextKeyExpression; - }; -} +/* eslint-disable */ // Void +import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../contrib/void/browser/actionIDs.js'; +import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../contrib/void/browser/voidSettingsPane.js'; +/* eslint-enable */ -const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' }; -const gotoFile: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; -const openFile: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile' }; -const openFolder: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder' }; -const openFileOrFolder: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder' }; -const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; -const newUntitledFile: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile' }; -const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; -const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } }; -const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } }; -const openSettings: WatermarkEntry = { text: localize('watermark.openSettings', "Open Settings"), id: 'workbench.action.openSettings' }; +// interface WatermarkEntry { +// readonly id: string; +// readonly text: string; +// readonly when?: { +// native?: ContextKeyExpression; +// web?: ContextKeyExpression; +// }; +// } -const showCopilot = ContextKeyExpr.or(ContextKeyExpr.equals('chatSetupHidden', false), ContextKeyExpr.equals('chatSetupInstalled', true)); -const openChat: WatermarkEntry = { text: localize('watermark.openChat', "Open Chat"), id: 'workbench.action.chat.open', when: { native: showCopilot, web: showCopilot } }; -const openCopilotEdits: WatermarkEntry = { text: localize('watermark.openCopilotEdits', "Open Copilot Edits"), id: 'workbench.action.chat.openEditSession', when: { native: showCopilot, web: showCopilot } }; +// const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' }; +// const gotoFile: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; +// const openFile: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile' }; +// const openFolder: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder' }; +// const openFileOrFolder: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder' }; +// const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; +// const newUntitledFile: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile' }; +// const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; +// const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } }; +// const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } }; +// const openSettings: WatermarkEntry = { text: localize('watermark.openSettings', "Open Settings"), id: 'workbench.action.openSettings' }; -const emptyWindowEntries: WatermarkEntry[] = coalesce([ - showCommands, - ...(isMacintosh && !isWeb ? [openFileOrFolder] : [openFile, openFolder]), - openRecent, - isMacintosh && !isWeb ? newUntitledFile : undefined, // fill in one more on macOS to get to 5 entries - openChat -]); +// const showCopilot = ContextKeyExpr.or(ContextKeyExpr.equals('chatSetupHidden', false), ContextKeyExpr.equals('chatSetupInstalled', true)); +// const openChat: WatermarkEntry = { text: localize('watermark.openChat', "Open Chat"), id: 'workbench.action.chat.open', when: { native: showCopilot, web: showCopilot } }; +// const openCopilotEdits: WatermarkEntry = { text: localize('watermark.openCopilotEdits', "Open Copilot Edits"), id: 'workbench.action.chat.openEditSession', when: { native: showCopilot, web: showCopilot } }; -const randomEmptyWindowEntries: WatermarkEntry[] = [ - /* Nothing yet */ -]; +// const emptyWindowEntries: WatermarkEntry[] = coalesce([ +// showCommands, +// ...(isMacintosh && !isWeb ? [openFileOrFolder] : [openFile, openFolder]), +// openRecent, +// isMacintosh && !isWeb ? newUntitledFile : undefined, // fill in one more on macOS to get to 5 entries +// openChat +// ]); -const workspaceEntries: WatermarkEntry[] = [ - showCommands, - gotoFile, - openChat -]; +// const randomEmptyWindowEntries: WatermarkEntry[] = [ +// /* Nothing yet */ +// ]; + +// const workspaceEntries: WatermarkEntry[] = [ +// showCommands, +// gotoFile, +// openChat +// ]; + +// const randomWorkspaceEntries: WatermarkEntry[] = [ +// findInFiles, +// startDebugging, +// toggleTerminal, +// openSettings, +// openCopilotEdits +// ]; -const randomWorkspaceEntries: WatermarkEntry[] = [ - findInFiles, - startDebugging, - toggleTerminal, - openSettings, - openCopilotEdits -]; export class EditorGroupWatermark extends Disposable { - - private static readonly CACHED_WHEN = 'editorGroupWatermark.whenConditions'; - - private readonly cachedWhen: { [when: string]: boolean } = this.storageService.getObject(EditorGroupWatermark.CACHED_WHEN, StorageScope.PROFILE, Object.create(null)); - private readonly shortcuts: HTMLElement; private readonly transientDisposables = this._register(new DisposableStore()); - private readonly keybindingLabels = this._register(new DisposableStore()); - - private enabled = false; - private workbenchState = this.contextService.getWorkbenchState(); + // private enabled: boolean = false; + private workbenchState: WorkbenchState; + private currentDisposables = new Set(); constructor( container: HTMLElement, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + // @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService + @IThemeService private readonly themeService: IThemeService, + @IWorkspacesService private readonly workspacesService: IWorkspacesService, + @ICommandService private readonly commandService: ICommandService, + @IHostService private readonly hostService: IHostService, + @ILabelService private readonly labelService: ILabelService, ) { super(); const elements = h('.editor-group-watermark', [ - h('.letterpress'), + h('.letterpress@icon'), h('.shortcuts@shortcuts'), ]); append(container, elements.root); - this.shortcuts = elements.shortcuts; + this.shortcuts = elements.shortcuts; // shortcuts div is modified on render() + + // void icon style + const updateTheme = () => { + const theme = this.themeService.getColorTheme().type + const isDark = theme === ColorScheme.DARK || theme === ColorScheme.HIGH_CONTRAST_DARK + elements.icon.style.maxWidth = '220px' + elements.icon.style.opacity = '50%' + elements.icon.style.filter = isDark ? '' : 'invert(1)' //brightness(.5) + } + updateTheme() + this._register( + this.themeService.onDidColorThemeChange(updateTheme) + ) this.registerListeners(); + this.workbenchState = contextService.getWorkbenchState(); this.render(); } private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.tips.enabled') && this.enabled !== this.configurationService.getValue('workbench.tips.enabled')) { + if (e.affectsConfiguration('workbench.tips.enabled')) { this.render(); } })); this._register(this.contextService.onDidChangeWorkbenchState(workbenchState => { - if (this.workbenchState !== workbenchState) { - this.workbenchState = workbenchState; - this.render(); + if (this.workbenchState === workbenchState) { + return; } + + this.workbenchState = workbenchState; + this.render(); })); - this._register(this.storageService.onWillSaveState(e => { - if (e.reason === WillSaveStateReason.SHUTDOWN) { - const entries = [...emptyWindowEntries, ...randomEmptyWindowEntries, ...workspaceEntries, ...randomWorkspaceEntries]; - for (const entry of entries) { - const when = isWeb ? entry.when?.web : entry.when?.native; - if (when) { - this.cachedWhen[entry.id] = this.contextKeyService.contextMatchesRules(when); - } - } - - this.storageService.store(EditorGroupWatermark.CACHED_WHEN, JSON.stringify(this.cachedWhen), StorageScope.PROFILE, StorageTarget.MACHINE); - } - })); + // const allEntriesWhenClauses = [...noFolderEntries, ...folderEntries].filter(entry => entry.when !== undefined).map(entry => entry.when!); + // const allKeys = new Set(); + // allEntriesWhenClauses.forEach(when => when.keys().forEach(key => allKeys.add(key))); + // this._register(this.contextKeyService.onDidChangeContext(e => { + // if (e.affectsSome(allKeys)) { + // this.render(); + // } + // })); } + + private render(): void { - this.enabled = this.configurationService.getValue('workbench.tips.enabled'); - clearNode(this.shortcuts); - this.transientDisposables.clear(); + this.clear(); + const voidIconBox = append(this.shortcuts, $('.watermark-box')); + const recentsBox = append(this.shortcuts, $('div')); + recentsBox.style.display = 'flex' + recentsBox.style.flex = 'row' + recentsBox.style.justifyContent = 'center' - if (!this.enabled) { - return; - } - const fixedEntries = this.filterEntries(this.workbenchState !== WorkbenchState.EMPTY ? workspaceEntries : emptyWindowEntries, false /* not shuffled */); - const randomEntries = this.filterEntries(this.workbenchState !== WorkbenchState.EMPTY ? randomWorkspaceEntries : randomEmptyWindowEntries, true /* shuffled */).slice(0, Math.max(0, 5 - fixedEntries.length)); - const entries = [...fixedEntries, ...randomEntries]; + const update = async () => { - const box = append(this.shortcuts, $('.watermark-box')); + // put async at top so don't need to wait (this prevents a jitter on load) + const recentlyOpened = await this.workspacesService.getRecentlyOpened() + .catch(() => ({ files: [], workspaces: [] })).then(w => w.workspaces); - const update = () => { - clearNode(box); - this.keybindingLabels.clear(); + clearNode(voidIconBox); + clearNode(recentsBox); - for (const entry of entries) { - const keys = this.keybindingService.lookupKeybinding(entry.id); - if (!keys) { - continue; + this.currentDisposables.forEach(label => label.dispose()); + this.currentDisposables.clear(); + + + // Void - if the workbench is empty, show open + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + + // Open a folder + const openFolderButton = h('button') + openFolderButton.root.classList.add('void-watermark-button') + openFolderButton.root.style.display = 'block' + openFolderButton.root.style.marginLeft = 'auto' + openFolderButton.root.style.marginRight = 'auto' + openFolderButton.root.style.marginBottom = '16px' + openFolderButton.root.textContent = 'Open a folder' + openFolderButton.root.onclick = () => { + this.commandService.executeCommand(isMacintosh && isNative ? OpenFileFolderAction.ID : OpenFolderAction.ID) + // if (this.contextKeyService.contextMatchesRules(ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace')))) { + // this.commandService.executeCommand(OpenFolderViaWorkspaceAction.ID); + // } else { + // this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder'); + // } + } + voidIconBox.appendChild(openFolderButton.root); + + + // Recents + if (recentlyOpened.length !== 0) { + + voidIconBox.append( + ...recentlyOpened.map((w, i) => { + + let fullPath: string; + let windowOpenable: IWindowOpenable; + if (isRecentFolder(w)) { + windowOpenable = { folderUri: w.folderUri }; + fullPath = w.label || this.labelService.getWorkspaceLabel(w.folderUri, { verbose: Verbosity.LONG }); + } + else { + return null + // fullPath = w.label || this.labelService.getWorkspaceLabel(w.workspace, { verbose: Verbosity.LONG }); + // windowOpenable = { workspaceUri: w.workspace.configPath }; + } + + + const { name, parentPath } = splitRecentLabel(fullPath); + + const linkSpan = $('span'); + linkSpan.classList.add('void-link') + linkSpan.style.display = 'flex' + linkSpan.style.gap = '4px' + linkSpan.style.padding = '8px' + + linkSpan.addEventListener('click', e => { + this.hostService.openWindow([windowOpenable], { + forceNewWindow: e.ctrlKey || e.metaKey, + remoteAuthority: w.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); + e.preventDefault(); + e.stopPropagation(); + }); + + const nameSpan = $('span'); + nameSpan.innerText = name; + nameSpan.title = fullPath; + linkSpan.appendChild(nameSpan); + + const dirSpan = $('span'); + dirSpan.style.paddingLeft = '4px'; + dirSpan.innerText = parentPath; + dirSpan.title = fullPath; + + linkSpan.appendChild(dirSpan); + + return linkSpan + }).filter(v => !!v) + ) } - const dl = append(box, $('dl')); - const dt = append(dl, $('dt')); - dt.textContent = entry.text; - - const dd = append(dl, $('dd')); - - const label = this.keybindingLabels.add(new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles })); - label.set(keys); } + else { + + // show them Void keybindings + const keys = this.keybindingService.lookupKeybinding(VOID_CTRL_L_ACTION_ID); + const dl = append(voidIconBox, $('dl')); + const dt = append(dl, $('dt')); + dt.textContent = 'Chat' + const dd = append(dl, $('dd')); + const label = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); + if (keys) + label.set(keys); + this.currentDisposables.add(label); + + + const keys2 = this.keybindingService.lookupKeybinding(VOID_CTRL_K_ACTION_ID); + const dl2 = append(voidIconBox, $('dl')); + const dt2 = append(dl2, $('dt')); + dt2.textContent = 'Quick Edit' + const dd2 = append(dl2, $('dd')); + const label2 = new KeybindingLabel(dd2, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); + if (keys2) + label2.set(keys2); + this.currentDisposables.add(label2); + + const keys3 = this.keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings'); + const button3 = append(recentsBox, $('button')); + button3.textContent = `Void Settings` + button3.style.display = 'block' + button3.style.marginLeft = 'auto' + button3.style.marginRight = 'auto' + button3.classList.add('void-settings-watermark-button') + + const label3 = new KeybindingLabel(button3, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); + if (keys3) + label3.set(keys3); + button3.onclick = () => { + this.commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) + } + this.currentDisposables.add(label3); + + } + }; update(); this.transientDisposables.add(this.keybindingService.onDidUpdateKeybindings(update)); } - private filterEntries(entries: WatermarkEntry[], shuffleEntries: boolean): WatermarkEntry[] { - const filteredEntries = entries - .filter(entry => (isWeb && !entry.when?.web) || (!isWeb && !entry.when?.native) || this.cachedWhen[entry.id]) - .filter(entry => !!CommandsRegistry.getCommand(entry.id)) - .filter(entry => !!this.keybindingService.lookupKeybinding(entry.id)); + private clear(): void { + clearNode(this.shortcuts); + this.transientDisposables.clear(); + } - if (shuffleEntries) { - shuffle(filteredEntries); - } - - return filteredEntries; + override dispose(): void { + super.dispose(); + this.clear(); + this.currentDisposables.forEach(label => label.dispose()); } } diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 76c8a561..93cf0be6 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -49,26 +49,27 @@ height: calc(100% - 70px); } +/* light */ .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-watermark > .letterpress { width: 100%; max-height: 100%; aspect-ratio: 1/1; - background-image: url('./letterpress-light.svg'); + background-image: url('./void_cube_noshadow.png'); /* // Void */ background-size: contain; background-position-x: center; background-repeat: no-repeat; } .monaco-workbench.vs-dark .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress { - background-image: url('./letterpress-dark.svg'); + background-image: url('./void_cube_noshadow.png'); /* // Void */ } .monaco-workbench.hc-light .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress { - background-image: url('./letterpress-hcLight.svg'); + background-image: url('./void_cube_noshadow.png'); /* // Void */ } .monaco-workbench.hc-black .part.editor > .content .editor-group-container .editor-group-watermark > .letterpress { - background-image: url('./letterpress-hcDark.svg'); + background-image: url('./void_cube_noshadow.png'); /* // Void */ } .monaco-workbench .part.editor > .content:not(.empty) .editor-group-container > .editor-group-watermark > .shortcuts, diff --git a/src/vs/workbench/browser/parts/editor/media/slice_of_void.png b/src/vs/workbench/browser/parts/editor/media/slice_of_void.png new file mode 100644 index 00000000..3b2c5319 Binary files /dev/null and b/src/vs/workbench/browser/parts/editor/media/slice_of_void.png differ diff --git a/src/vs/workbench/browser/parts/editor/media/void_cube_noshadow.png b/src/vs/workbench/browser/parts/editor/media/void_cube_noshadow.png new file mode 100644 index 00000000..225179f8 Binary files /dev/null and b/src/vs/workbench/browser/parts/editor/media/void_cube_noshadow.png differ diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 3c14098d..257f1b4b 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -204,7 +204,7 @@ export class PaneCompositeBar extends Disposable { private createMoveAction(viewContainer: ViewContainer, newLocation: ViewContainerLocation, defaultLocation: ViewContainerLocation): IAction { return toAction({ id: `moveViewContainerTo${newLocation}`, - label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Secondary Side Bar"), + label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Void Side Bar"), run: () => { let index: number | undefined; if (newLocation !== defaultLocation) { diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 6e43176a..ff0c6a80 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -355,14 +355,14 @@ class MoveViewsBetweenPanelsAction extends Action2 { } } -// --- Move Panel Views To Secondary Side Bar +// --- Move Panel Views To Void Side Bar // Void Side Bar (for command pallette) class MovePanelToSidePanelAction extends MoveViewsBetweenPanelsAction { static readonly ID = 'workbench.action.movePanelToSidePanel'; constructor() { super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, { id: MovePanelToSidePanelAction.ID, - title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), + title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Void Side Bar"), // Void Side Bar (for command pallette) category: Categories.View, f1: false }); @@ -374,7 +374,7 @@ export class MovePanelToSecondarySideBarAction extends MoveViewsBetweenPanelsAct constructor() { super(ViewContainerLocation.Panel, ViewContainerLocation.AuxiliaryBar, { id: MovePanelToSecondarySideBarAction.ID, - title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Secondary Side Bar"), + title: localize2('movePanelToSecondarySideBar', "Move Panel Views To Void Side Bar"), // Void Side Bar (for command pallette) category: Categories.View, f1: true }); @@ -384,7 +384,7 @@ export class MovePanelToSecondarySideBarAction extends MoveViewsBetweenPanelsAct registerAction2(MovePanelToSidePanelAction); registerAction2(MovePanelToSecondarySideBarAction); -// --- Move Secondary Side Bar Views To Panel +// --- Move Void Side Bar Views To Panel // Void Side Bar (for command pallette) class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction { static readonly ID = 'workbench.action.moveSidePanelToPanel'; @@ -392,7 +392,7 @@ class MoveSidePanelToPanelAction extends MoveViewsBetweenPanelsAction { constructor() { super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, { id: MoveSidePanelToPanelAction.ID, - title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), + title: localize2('moveSidePanelToPanel', "Move Void Side Bar Views To Panel"), // Void Side Bar (for command pallette) category: Categories.View, f1: false }); @@ -405,7 +405,7 @@ export class MoveSecondarySideBarToPanelAction extends MoveViewsBetweenPanelsAct constructor() { super(ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel, { id: MoveSecondarySideBarToPanelAction.ID, - title: localize2('moveSidePanelToPanel', "Move Secondary Side Bar Views To Panel"), + title: localize2('moveSidePanelToPanel', "Move Void Side Bar Views To Panel"), // Void Side Bar (for command pallette) category: Categories.View, f1: true }); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 2f16f7a1..2df48c0a 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -242,7 +242,7 @@ } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-left > .window-appicon:not(.codicon) { - background-image: url('../../../media/code-icon.svg'); + background-image: url('../../../media/void-icon-sm.png'); background-repeat: no-repeat; background-position: center center; background-size: 16px; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 5e785e81..16d645a8 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -509,7 +509,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['left', 'right'], 'default': 'left', - 'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The secondary side bar will show on the opposite side of the workbench.") + 'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The Void side bar will show on the opposite side of the workbench.") // Void side bar }, 'workbench.panel.showLabels': { 'type': 'boolean', @@ -547,12 +547,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['default', 'top', 'bottom', 'hidden'], 'default': 'default', - 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Secondary Side Bars."), + 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the Activity Bar relative to the Primary and Void Side Bars."), // Void side bar 'enumDescriptions': [ - localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Secondary Side Bar."), - localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Secondary Side Bars."), - localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Secondary Side Bars."), - localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Secondary Side Bars.") + localize('workbench.activityBar.location.default', "Show the Activity Bar on the side of the Primary Side Bar and on top of the Void Side Bar."), // Void side bar + localize('workbench.activityBar.location.top', "Show the Activity Bar on top of the Primary and Void Side Bars."), // Void side bar + localize('workbench.activityBar.location.bottom', "Show the Activity Bar at the bottom of the Primary and Void Side Bars."), // Void side bar + localize('workbench.activityBar.location.hide', "Hide the Activity Bar in the Primary and Void Side Bars.") // Void side bar ], }, 'workbench.activityBar.iconClickBehavior': { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 4de8cc1c..b78d5859 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -894,6 +894,19 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + // // console.log('ON CLICK', templateData.currentContext?.children) + // // }) + // const voidLabels = this.labels.create(voidButtonsContainer, { supportHighlights: false, supportIcons: false, }); + // voidLabels.element.textContent = 'hi333' renderTemplate(container: HTMLElement): IFileTemplateData { const templateDisposables = new DisposableStore(); const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true })); @@ -918,6 +931,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer, index: number, templateData: IFileTemplateData): void { const stat = node.element; templateData.currentContext = stat; diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 164b2e98..b01a519c 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -151,7 +151,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { const paneComposites = this.paneCompositeService.getPaneComposites(location); diff --git a/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css b/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css index 4210055b..7fdbb366 100644 --- a/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css +++ b/src/vs/workbench/contrib/update/browser/media/releasenoteseditor.css @@ -5,5 +5,5 @@ .file-icons-enabled .show-file-icons .webview-vs_code_release_notes-name-file-icon.file-icon::before { content: ' '; - background-image: url('../../../../browser/media/code-icon.svg'); + background-image: url('../../../../browser/media/void-icon-sm.png'); /* // Void */ } diff --git a/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts b/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts new file mode 100644 index 00000000..4ea36c4c --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts @@ -0,0 +1,135 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { CodeActionContext, CodeActionTriggerType } from '../../../../editor/common/languages.js'; +import { URI } from '../../../../base/common/uri.js'; + +export interface IMarkerCheckService { + readonly _serviceBrand: undefined; +} + +export const IMarkerCheckService = createDecorator('markerCheckService'); + +class MarkerCheckService extends Disposable implements IMarkerCheckService { + _serviceBrand: undefined; + + constructor( + @IMarkerService private readonly _markerService: IMarkerService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ITextModelService private readonly _textModelService: ITextModelService, + ) { + super(); + + setInterval(async () => { + const allMarkers = this._markerService.read(); + const errors = allMarkers.filter(marker => marker.severity === MarkerSeverity.Error); + + if (errors.length > 0) { + for (const error of errors) { + + console.log(`----------------------------------------------`); + + console.log(`${error.resource.toString()}: ${error.startLineNumber} ${error.message} ${error.severity}`); // ! all errors in the file + + try { + // Get the text model for the file + const modelReference = await this._textModelService.createModelReference(error.resource); + const model = modelReference.object.textEditorModel; + + // Create a range from the marker + const range = new Range( + error.startLineNumber, + error.startColumn, + error.endLineNumber, + error.endColumn + ); + + // Get code action providers for this model + const codeActionProvider = this._languageFeaturesService.codeActionProvider; + const providers = codeActionProvider.ordered(model); + + if (providers.length > 0) { + // Request code actions from each provider + for (const provider of providers) { + const context: CodeActionContext = { + trigger: CodeActionTriggerType.Invoke, // keeping 'trigger' since it works + only: 'quickfix' // adding this to filter for quick fixes + }; + + const actions = await provider.provideCodeActions( + model, + range, + context, + CancellationToken.None + ); + + if (actions?.actions?.length) { + + const quickFixes = actions.actions.filter(action => action.isPreferred); // ! all quickFixes for the error + const quickFixesForImports = actions.actions.filter(action => action.isPreferred && action.title.includes('import')); // ! all possible imports + quickFixesForImports + + if (quickFixes.length > 0) { + console.log('Available Quick Fixes:'); + quickFixes.forEach(action => { + console.log(`- ${action.title}`); + }); + } + } + } + } + + // Dispose the model reference + modelReference.dispose(); + } catch (e) { + console.error('Error getting quick fixes:', e); + } + } + } + }, 5000); + } + + + + + fixErrorsInFiles(uris: URI[], contextSoFar: []) { + // const allMarkers = this._markerService.read(); + + + // check errors in files + + + // give LLM errors in files + + + + } + + // private _onMarkersChanged = (changedResources: readonly URI[]): void => { + // for (const resource of changedResources) { + // const markers = this._markerService.read({ resource }); + + // if (markers.length === 0) { + // console.log(`${resource.toString()}: No diagnostics`); + // continue; + // } + + // console.log(`Diagnostics for ${resource.toString()}:`); + // markers.forEach(marker => this._logMarker(marker)); + // } + // }; + + +} + +registerSingleton(IMarkerCheckService, MarkerCheckService, InstantiationType.Eager); diff --git a/src/vs/workbench/contrib/void/browser/actionIDs.ts b/src/vs/workbench/contrib/void/browser/actionIDs.ts new file mode 100644 index 00000000..b237ecf8 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/actionIDs.ts @@ -0,0 +1,6 @@ +// Normally you'd want to put these exports in the files that register them, but if you do that you'll get an import order error if you import them in certain cases. +// (importing them runs the whole file to get the ID, causing an import error). I guess it's best practice to separate out IDs, pretty annoying... + +export const VOID_CTRL_L_ACTION_ID = 'void.ctrlLAction' + +export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction' diff --git a/src/vs/workbench/contrib/void/browser/aiRegexService.ts b/src/vs/workbench/contrib/void/browser/aiRegexService.ts new file mode 100644 index 00000000..f38236a9 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/aiRegexService.ts @@ -0,0 +1,187 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +// import { URI } from '../../../../base/common/uri.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +// import { IToolService, ToolService } from '../common/toolsService.js'; + + + +export type ChatMessageLocation = { + threadId: string; + messageIdx: number; +} + + +export type SearchAndReplaceBlock = { + search: string; + replace: string; +} + +// service that manages state +export type ApplyState = { + [applyBoxId: string]: { + searchAndReplaceBlocks: SearchAndReplaceBlock; + } +} + +// the purpose of this service is to generate search and replace blocks for a given codeblock `codeblockId` and on a file `fileName` and version `fileVersion` + +export interface IFastApplyService { + readonly _serviceBrand: undefined; + + // readonly state: ApplyState; // readonly to the user + // setState(newState: Partial): void; + // onDidChangeState: Event; +} + +export const IVoidFastApplyService = createDecorator('voidFastApplyService'); +class VoidFastApplyService extends Disposable implements IFastApplyService { + _serviceBrand: undefined; + + // static readonly ID = 'voidFastApplyService'; + + private readonly _onDidChangeState = new Emitter(); + readonly onDidChangeState: Event = this._onDidChangeState.event; + + + // state + // state: ApplyState + + constructor( + // @IToolService private readonly toolService: ToolService + ) { + super() + + // initial state + // this.state = { currentUri: undefined } + } + + setState(newState: Partial) { + + // this.state = { ...this.state, ...newState } + this._onDidChangeState.fire() + } + + aiSearch(searchStr: string) { + + } + + aiReplace(searchStr: string, replaceStr: string) { + + } + + + // 1. search(ai) + // - tool use to find all possible changes + // - if search only: is this file related to the search? + // - if search + replace: should I modify this file? + // 2. replace(ai) + // - what changes to make? + // 3. postprocess errors + // -fastapply changes simultaneously + // -iterate on syntax errors (all files can be changed from a syntax error, not just the one with the error) + + + // private async _searchUsingAI({ searchClause }: { searchClause: string }) { + + // // const relevantURIs: URI[] = [] + // // const gatherPrompt = `\ + // // asdasdas + // // ` + // // const filterPrompt = `\ + // // Is this file relevant? + // // ` + + + // // // optimizations (DO THESE LATER!!!!!!) + // // // if tool includes a uri in uriSet, skip it obviously + // // let uriSet = new Set() + // // // gather + // // let messages = [] + // // while (true) { + // // const result = await new Promise((res, rej) => { + // // sendLLMMessage({ + // // messages, + // // tools: ['search'], + // // onFinalMessage: ({ result: r, }) => { + // // res(r) + // // }, + // // onError: (error) => { + // // rej(error) + // // } + // // }) + // // }) + + // // messages.push({ role: 'tool', content: turnToString(result) }) + + // // sendLLMMessage({ + // // messages: { 'Output ': result }, + // // onFinalMessage: (r) => { + // // // output is file1\nfile2\nfile3\n... + // // } + // // }) + + // // uriSet.add(...) + // // } + + // // // writes + // // if (!replaceClause) return + + // // for (const uri of uriSet) { + // // // in future, batch these + // // applyWorkflow({ uri, applyStr: replaceClause }) + // // } + + + + + + + // // while (true) { + // // const result = new Promise((res, rej) => { + // // sendLLMMessage({ + // // messages, + // // tools: ['search'], + // // onResult: (r) => { + // // res(r) + // // } + // // }) + // // }) + + // // messages.push(result) + + // // } + + + // } + + + // private async _replaceUsingAI({ searchClause, replaceClause, relevantURIs }: { searchClause: string, replaceClause: string, relevantURIs: URI[] }) { + + // for (const uri of relevantURIs) { + + // uri + + // } + + + + // // should I change this file? + // // if so what changes to make? + + + + // // fast apply the changes + // } + + + +} + +registerSingleton(IVoidFastApplyService, VoidFastApplyService, InstantiationType.Eager); diff --git a/src/vs/workbench/contrib/void/browser/autocompleteService.ts b/src/vs/workbench/contrib/void/browser/autocompleteService.ts new file mode 100644 index 00000000..f9cbec7b --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/autocompleteService.ts @@ -0,0 +1,932 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { InlineCompletion, InlineCompletionContext, } from '../../../../editor/common/languages.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { EditorResourceAccessor } from '../../../common/editor.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { ILLMMessageService } from '../common/llmMessageService.js'; +import { _ln, allLinebreakSymbols } from '../common/voidFileService.js'; +// import { IContextGatheringService } from './contextGatheringService.js'; + +// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts + + +/* +A summary of autotab: + +Postprocessing +-one common problem for all models is outputting unbalanced parentheses +we solve this by trimming all extra closing parentheses from the generated string +in future, should make sure parentheses are always balanced + +-another problem is completing the middle of a string, eg. "const [x, CURSOR] = useState()" +we complete up to first matchup character +but should instead complete the whole line / block (difficult because of parenthesis accuracy) + +-too much info is bad. usually we want to show the user 1 line, and have a preloaded response afterwards +this should happen automatically with caching system +should break preloaded responses into \n\n chunks + +Preprocessing +- we don't generate if cursor is at end / beginning of a line (no spaces) +- we generate 1 line if there is text to the right of cursor +- we generate 1 line if variable declaration +- (in many cases want to show 1 line but generate multiple) + +State +- cache based on prefix (and do some trimming first) +- when press tab on one line, should have an immediate followup response +to do this, show autocompletes before they're fully finished +- [todo] remove each autotab when accepted +!- [todo] provide type information + +Details +-generated results are trimmed up to 1 leading/trailing space +-prefixes are cached up to 1 trailing newline +- +*/ + +class LRUCache { + public items: Map; + private keyOrder: K[]; + private maxSize: number; + private disposeCallback?: (value: V, key?: K) => void; + + constructor(maxSize: number, disposeCallback?: (value: V, key?: K) => void) { + if (maxSize <= 0) throw new Error('Cache size must be greater than 0'); + + this.items = new Map(); + this.keyOrder = []; + this.maxSize = maxSize; + this.disposeCallback = disposeCallback; + } + + set(key: K, value: V): void { + // If key exists, remove it from the order list + if (this.items.has(key)) { + this.keyOrder = this.keyOrder.filter(k => k !== key); + } + // If cache is full, remove least recently used item + else if (this.items.size >= this.maxSize) { + const key = this.keyOrder[0]; + const value = this.items.get(key); + + // Call dispose callback if it exists + if (this.disposeCallback && value !== undefined) { + this.disposeCallback(value, key); + } + + this.items.delete(key); + this.keyOrder.shift(); + } + + // Add new item + this.items.set(key, value); + this.keyOrder.push(key); + } + + delete(key: K): boolean { + const value = this.items.get(key); + + if (value !== undefined) { + // Call dispose callback if it exists + if (this.disposeCallback) { + this.disposeCallback(value, key); + } + + this.items.delete(key); + this.keyOrder = this.keyOrder.filter(k => k !== key); + return true; + } + + return false; + } + + clear(): void { + // Call dispose callback for all items if it exists + if (this.disposeCallback) { + for (const [key, value] of this.items.entries()) { + this.disposeCallback(value, key); + } + } + + this.items.clear(); + this.keyOrder = []; + } + + get size(): number { + return this.items.size; + } + + has(key: K): boolean { + return this.items.has(key); + } +} + +type AutocompletionPredictionType = + | 'single-line-fill-middle' + | 'single-line-redo-suffix' + // | 'multi-line-start-here' + | 'multi-line-start-on-next-line' + | 'do-not-predict' + +type Autocompletion = { + id: number, + prefix: string, + suffix: string, + llmPrefix: string, + llmSuffix: string, + startTime: number, + endTime: number | undefined, + status: 'pending' | 'finished' | 'error', + type: AutocompletionPredictionType, + llmPromise: Promise | undefined, + insertText: string, + requestId: string | null, + _newlineCount: number, +} + +const DEBOUNCE_TIME = 500 +const TIMEOUT_TIME = 60000 +const MAX_CACHE_SIZE = 20 +const MAX_PENDING_REQUESTS = 2 + +// postprocesses the result +const processStartAndEndSpaces = (result: string) => { + + // trim all whitespace except for a single leading/trailing space + // return result.trim() + + [result,] = extractCodeFromRegular({ text: result, recentlyAddedTextLen: result.length }) + + const hasLeadingSpace = result.startsWith(' '); + const hasTrailingSpace = result.endsWith(' '); + + return (hasLeadingSpace ? ' ' : '') + + result.trim() + + (hasTrailingSpace ? ' ' : ''); + +} + + +// trims the end of the prefix to improve cache hit rate +const removeLeftTabsAndTrimEnds = (s: string): string => { + const trimmedString = s.trimEnd(); + const trailingEnd = s.slice(trimmedString.length); + + // keep only a single trailing newline + if (trailingEnd.includes(_ln)) { + s = trimmedString + _ln; + } + + s = s.replace(/^\s+/gm, ''); // remove left tabs + + return s; +} + + + +const removeAllWhitespace = (str: string): string => str.replace(/\s+/g, ''); + + + +function getIsSubsequence({ of, subsequence }: { of: string, subsequence: string }): [boolean, string] { + if (subsequence.length === 0) return [true, '']; + if (of.length === 0) return [false, '']; + + let subsequenceIndex = 0; + let lastMatchChar = ''; + + for (let i = 0; i < of.length; i++) { + if (of[i] === subsequence[subsequenceIndex]) { + lastMatchChar = of[i]; + subsequenceIndex++; + } + if (subsequenceIndex === subsequence.length) { + return [true, lastMatchChar]; + } + } + + return [false, lastMatchChar]; +} + + +function getStringUpToUnbalancedClosingParenthesis(s: string, prefix: string): string { + + const pairs: Record = { ')': '(', '}': '{', ']': '[' }; + + // process all bracets in prefix + let stack: string[] = [] + const firstOpenIdx = prefix.search(/[[({]/); + if (firstOpenIdx !== -1) { + const brackets = prefix.slice(firstOpenIdx).split('').filter(c => '()[]{}'.includes(c)); + + for (const bracket of brackets) { + if (bracket === '(' || bracket === '{' || bracket === '[') { + stack.push(bracket); + } else { + if (stack.length > 0 && stack[stack.length - 1] === pairs[bracket]) { + stack.pop(); + } else { + stack.push(bracket); + } + } + } + } + + // iterate through each character + for (let i = 0; i < s.length; i++) { + const char = s[i]; + + if (char === '(' || char === '{' || char === '[') { stack.push(char); } + else if (char === ')' || char === '}' || char === ']') { + if (stack.length === 0 || stack.pop() !== pairs[char]) { return s.substring(0, i); } + } + } + return s; +} + + +// further trim the autocompletion +const postprocessAutocompletion = ({ autocompletionMatchup, autocompletion, prefixAndSuffix }: { autocompletionMatchup: AutocompletionMatchupBounds, autocompletion: Autocompletion, prefixAndSuffix: PrefixAndSuffixInfo }) => { + + const { prefix, prefixToTheLeftOfCursor, suffixToTheRightOfCursor } = prefixAndSuffix + + const generatedMiddle = autocompletion.insertText + + let startIdx = autocompletionMatchup.startIdx + let endIdx = generatedMiddle.length // exclusive bounds + + // const naiveReturnValue = generatedMiddle.slice(startIdx) + // console.log('naiveReturnValue: ', JSON.stringify(naiveReturnValue)) + // return [{ insertText: naiveReturnValue, }] + + // do postprocessing for better ux + // this is a bit hacky but may change a lot + + // if there is space at the start of the completion and user has added it, remove it + const charToLeftOfCursor = prefixToTheLeftOfCursor.slice(-1)[0] || '' + const userHasAddedASpace = charToLeftOfCursor === ' ' || charToLeftOfCursor === '\t' + const rawFirstNonspaceIdx = generatedMiddle.slice(startIdx).search(/[^\t ]/) + if (rawFirstNonspaceIdx > -1 && userHasAddedASpace) { + const firstNonspaceIdx = rawFirstNonspaceIdx + startIdx; + // console.log('p0', startIdx, rawFirstNonspaceIdx) + startIdx = Math.max(startIdx, firstNonspaceIdx) + } + + // if user is on a blank line and the generation starts with newline(s), remove them + const numStartingNewlines = generatedMiddle.slice(startIdx).match(new RegExp(`^${_ln}+`))?.[0].length || 0; + if ( + !prefixToTheLeftOfCursor.trim() + && !suffixToTheRightOfCursor.trim() + && numStartingNewlines > 0 + ) { + // console.log('p1', numStartingNewlines) + startIdx += numStartingNewlines + } + + // if the generated FIM text matches with the suffix on the current line, stop + if (autocompletion.type === 'single-line-fill-middle' && suffixToTheRightOfCursor.trim()) { // completing in the middle of a line + // complete until there is a match + const rawMatchIndex = generatedMiddle.slice(startIdx).lastIndexOf(suffixToTheRightOfCursor.trim()[0]) + if (rawMatchIndex > -1) { + // console.log('p2', rawMatchIndex, startIdx, suffixToTheRightOfCursor.trim()[0], 'AAA', generatedMiddle.slice(startIdx)) + const matchIdx = rawMatchIndex + startIdx; + const matchChar = generatedMiddle[matchIdx] + if (`{}()[]<>\`'"`.includes(matchChar)) { + endIdx = Math.min(endIdx, matchIdx) + } + } + } + + const restOfLineToGenerate = generatedMiddle.slice(startIdx).split(_ln)[0] ?? '' + // condition to complete as a single line completion + if ( + prefixToTheLeftOfCursor.trim() + && !suffixToTheRightOfCursor.trim() + && restOfLineToGenerate.trim() + ) { + + const rawNewlineIdx = generatedMiddle.slice(startIdx).indexOf(_ln) + if (rawNewlineIdx > -1) { + // console.log('p3', startIdx, rawNewlineIdx) + const newlineIdx = rawNewlineIdx + startIdx; + endIdx = Math.min(endIdx, newlineIdx) + } + } + + // // if a generated line matches with a suffix line, stop + // if (suffixLines.length > 1) { + // console.log('4') + // const lines = [] + // for (const generatedLine of generatedLines) { + // if (suffixLines.slice(0, 10).some(suffixLine => + // generatedLine.trim() !== '' && suffixLine.trim() !== '' + // && generatedLine.trim().startsWith(suffixLine.trim()) + // )) break; + // lines.push(generatedLine) + // } + // endIdx = lines.join('\n').length // this is hacky, remove or refactor in future + // } + + // console.log('pFinal', startIdx, endIdx) + let completionStr = generatedMiddle.slice(startIdx, endIdx) + + // filter out unbalanced parentheses + completionStr = getStringUpToUnbalancedClosingParenthesis(completionStr, prefix) + // console.log('originalCompletionStr: ', JSON.stringify(generatedMiddle.slice(startIdx))) + // console.log('finalCompletionStr: ', JSON.stringify(completionStr)) + + + return completionStr + +} + +// returns the text in the autocompletion to display, assuming the prefix is already matched +const toInlineCompletions = ({ autocompletionMatchup, autocompletion, prefixAndSuffix, position, debug }: { autocompletionMatchup: AutocompletionMatchupBounds, autocompletion: Autocompletion, prefixAndSuffix: PrefixAndSuffixInfo, position: Position, debug?: boolean }): { insertText: string, range: Range }[] => { + + let trimmedInsertText = postprocessAutocompletion({ autocompletionMatchup, autocompletion, prefixAndSuffix, }) + let rangeToReplace: Range = new Range(position.lineNumber, position.column, position.lineNumber, position.column) + + // handle special cases + + // if we redid the suffix, replace the suffix + if (autocompletion.type === 'single-line-redo-suffix') { + + const oldSuffix = prefixAndSuffix.suffixToTheRightOfCursor + const newSuffix = autocompletion.insertText + + const [isSubsequence, lastMatchingChar] = getIsSubsequence({ // check that the old text contains the same brackets + symbols as the new text + subsequence: removeAllWhitespace(oldSuffix), // old suffix + of: removeAllWhitespace(newSuffix), // new suffix + }) + if (isSubsequence) { + rangeToReplace = new Range(position.lineNumber, position.column, position.lineNumber, Number.MAX_SAFE_INTEGER) + } + else { + + const lastMatchupIdx = trimmedInsertText.lastIndexOf(lastMatchingChar) + trimmedInsertText = trimmedInsertText.slice(0, lastMatchupIdx + 1) + const numCharsToReplace = oldSuffix.lastIndexOf(lastMatchingChar) + 1 + rangeToReplace = new Range(position.lineNumber, position.column, position.lineNumber, position.column + numCharsToReplace) + // console.log('show____', trimmedInsertText, rangeToReplace) + } + } + + return [{ + insertText: trimmedInsertText, + range: rangeToReplace, + }] + +} + + + + + +// returns whether this autocompletion is in the cache +// const doesPrefixMatchAutocompletion = ({ prefix, autocompletion }: { prefix: string, autocompletion: Autocompletion }): boolean => { + +// const originalPrefix = autocompletion.prefix +// const generatedMiddle = autocompletion.result +// const originalPrefixTrimmed = trimPrefix(originalPrefix) +// const currentPrefixTrimmed = trimPrefix(prefix) + +// if (currentPrefixTrimmed.length < originalPrefixTrimmed.length) { +// return false +// } + +// const isMatch = (originalPrefixTrimmed + generatedMiddle).startsWith(currentPrefixTrimmed) +// return isMatch + +// } + + +type PrefixAndSuffixInfo = { prefix: string, suffix: string, prefixLines: string[], suffixLines: string[], prefixToTheLeftOfCursor: string, suffixToTheRightOfCursor: string } +const getPrefixAndSuffixInfo = (model: ITextModel, position: Position): PrefixAndSuffixInfo => { + + const fullText = model.getValue(); + + const cursorOffset = model.getOffsetAt(position) + const prefix = fullText.substring(0, cursorOffset) + const suffix = fullText.substring(cursorOffset) + + + const prefixLines = prefix.split(_ln) + const suffixLines = suffix.split(_ln) + + const prefixToTheLeftOfCursor = prefixLines.slice(-1)[0] ?? '' + const suffixToTheRightOfCursor = suffixLines[0] ?? '' + + return { prefix, suffix, prefixLines, suffixLines, prefixToTheLeftOfCursor, suffixToTheRightOfCursor } + +} + +const getIndex = (str: string, line: number, char: number) => { + return str.split(_ln).slice(0, line).join(_ln).length + (line > 0 ? 1 : 0) + char; +} +const getLastLine = (s: string): string => { + const matches = s.match(new RegExp(`[^${_ln}]*$`)) + return matches ? matches[0] : '' +} + +type AutocompletionMatchupBounds = { + startLine: number, + startCharacter: number, + startIdx: number, +} +// returns the startIdx of the match if there is a match, or undefined if there is no match +// all results are wrt `autocompletion.result` +const getAutocompletionMatchup = ({ prefix, autocompletion }: { prefix: string, autocompletion: Autocompletion }): AutocompletionMatchupBounds | undefined => { + + const trimmedCurrentPrefix = removeLeftTabsAndTrimEnds(prefix) + const trimmedCompletionPrefix = removeLeftTabsAndTrimEnds(autocompletion.prefix) + const trimmedCompletionMiddle = removeLeftTabsAndTrimEnds(autocompletion.insertText) + + // console.log('@result: ', JSON.stringify(autocompletion.insertText)) + // console.log('@trimmedCurrentPrefix: ', JSON.stringify(trimmedCurrentPrefix)) + // console.log('@trimmedCompletionPrefix: ', JSON.stringify(trimmedCompletionPrefix)) + // console.log('@trimmedCompletionMiddle: ', JSON.stringify(trimmedCompletionMiddle)) + + if (trimmedCurrentPrefix.length < trimmedCompletionPrefix.length) { // user must write text beyond the original prefix at generation time + // console.log('@undefined1') + return undefined + } + + if ( // check that completion starts with the prefix + !(trimmedCompletionPrefix + trimmedCompletionMiddle) + .startsWith(trimmedCurrentPrefix) + ) { + // console.log('@undefined2') + return undefined + } + + // reverse map to find position wrt `autocompletion.result` + const lineStart = + trimmedCurrentPrefix.split(_ln).length - + trimmedCompletionPrefix.split(_ln).length; + + if (lineStart < 0) { + // console.log('@undefined3') + + console.error('Error: No line found.'); + return undefined; + } + const currentPrefixLine = getLastLine(trimmedCurrentPrefix) + const completionPrefixLine = lineStart === 0 ? getLastLine(trimmedCompletionPrefix) : '' + const completionMiddleLine = autocompletion.insertText.split(_ln)[lineStart] + const fullCompletionLine = completionPrefixLine + completionMiddleLine + + // console.log('currentPrefixLine', currentPrefixLine) + // console.log('completionPrefixLine', completionPrefixLine) + // console.log('completionMiddleLine', completionMiddleLine) + + const charMatchIdx = fullCompletionLine.indexOf(currentPrefixLine) + if (charMatchIdx < 0) { + // console.log('@undefined4', charMatchIdx) + + console.error('Warning: Found character with negative index. This should never happen.') + return undefined + } + + const character = (charMatchIdx + + currentPrefixLine.length + - completionPrefixLine.length + ) + + const startIdx = getIndex(autocompletion.insertText, lineStart, character) + + return { + startLine: lineStart, + startCharacter: character, + startIdx, + } + + +} + + +type CompletionOptions = { + predictionType: AutocompletionPredictionType, + shouldGenerate: boolean, + llmPrefix: string, + llmSuffix: string, + stopTokens: string[], +} +const getCompletionOptions = (prefixAndSuffix: PrefixAndSuffixInfo, relevantContext: string, justAcceptedAutocompletion: boolean): CompletionOptions => { + + let { prefix, suffix, prefixToTheLeftOfCursor, suffixToTheRightOfCursor, suffixLines, prefixLines } = prefixAndSuffix + + // trim prefix and suffix to not be very large + suffixLines = suffix.split(_ln).slice(0, 25) + prefixLines = prefix.split(_ln).slice(-25) + prefix = prefixLines.join(_ln) + suffix = suffixLines.join(_ln) + + let completionOptions: CompletionOptions + + // if line is empty, do multiline completion + const isLineEmpty = !prefixToTheLeftOfCursor.trim() && !suffixToTheRightOfCursor.trim() + const isLinePrefixEmpty = removeAllWhitespace(prefixToTheLeftOfCursor).length === 0 + const isLineSuffixEmpty = removeAllWhitespace(suffixToTheRightOfCursor).length === 0 + + // TODO add context to prefix + // llmPrefix = '\n\n/* Relevant context:\n' + relevantContext + '\n*/\n' + llmPrefix + + // if we just accepted an autocompletion, predict a multiline completion starting on the next line + if (justAcceptedAutocompletion && isLineSuffixEmpty) { + const prefixWithNewline = prefix + _ln + completionOptions = { + predictionType: 'multi-line-start-on-next-line', + shouldGenerate: true, + llmPrefix: prefixWithNewline, + llmSuffix: suffix, + stopTokens: [`${_ln}${_ln}`] // double newlines + } + } + // if the current line is empty, predict a single-line completion + else if (isLineEmpty) { + completionOptions = { + predictionType: 'single-line-fill-middle', + shouldGenerate: true, + llmPrefix: prefix, + llmSuffix: suffix, + stopTokens: allLinebreakSymbols + } + } + // if suffix is 3 or fewer characters, attempt to complete the line ignorning it + else if (removeAllWhitespace(suffixToTheRightOfCursor).length <= 3) { + const suffixLinesIgnoringThisLine = suffixLines.slice(1) + const suffixStringIgnoringThisLine = suffixLinesIgnoringThisLine.length === 0 ? '' : _ln + suffixLinesIgnoringThisLine.join(_ln) + completionOptions = { + predictionType: 'single-line-redo-suffix', + shouldGenerate: true, + llmPrefix: prefix, + llmSuffix: suffixStringIgnoringThisLine, + stopTokens: allLinebreakSymbols + } + } + // else attempt to complete the middle of the line if there is a prefix (the completion looks bad if there is no prefix) + else if (!isLinePrefixEmpty) { + completionOptions = { + predictionType: 'single-line-fill-middle', + shouldGenerate: true, + llmPrefix: prefix, + llmSuffix: suffix, + stopTokens: allLinebreakSymbols + } + } else { + completionOptions = { + predictionType: 'do-not-predict', + shouldGenerate: false, + llmPrefix: prefix, + llmSuffix: suffix, + stopTokens: [] + } + } + + return completionOptions + +} + +export interface IAutocompleteService { + readonly _serviceBrand: undefined; +} + +export const IAutocompleteService = createDecorator('AutocompleteService'); + +export class AutocompleteService extends Disposable implements IAutocompleteService { + + static readonly ID = 'void.autocompleteService' + + _serviceBrand: undefined; + + private _autocompletionId: number = 0; + private _autocompletionsOfDocument: { [docUriStr: string]: LRUCache } = {} + + private _lastCompletionStart = 0 + private _lastCompletionAccept = 0 + // private _lastPrefix: string = '' + + // used internally by vscode + // fires after every keystroke and returns the completion to show + async _provideInlineCompletionItems( + model: ITextModel, + position: Position, + context: InlineCompletionContext, + token: CancellationToken, + ): Promise { + + const testMode = false + + const docUriStr = model.uri.toString(); + + const prefixAndSuffix = getPrefixAndSuffixInfo(model, position) + const { prefix, suffix } = prefixAndSuffix + + // initialize cache if it doesnt exist + // note that whenever an autocompletion is accepted, it is removed from cache + if (!this._autocompletionsOfDocument[docUriStr]) { + this._autocompletionsOfDocument[docUriStr] = new LRUCache( + MAX_CACHE_SIZE, + (autocompletion: Autocompletion) => { + if (autocompletion.requestId) + this._llmMessageService.abort(autocompletion.requestId) + } + ) + } + // this._lastPrefix = prefix + + // print all pending autocompletions + // let _numPending = 0 + // this._autocompletionsOfDocument[docUriStr].items.forEach((a: Autocompletion) => { if (a.status === 'pending') _numPending += 1 }) + // console.log('@numPending: ' + _numPending) + + // get autocompletion from cache + let cachedAutocompletion: Autocompletion | undefined = undefined + let autocompletionMatchup: AutocompletionMatchupBounds | undefined = undefined + for (const autocompletion of this._autocompletionsOfDocument[docUriStr].items.values()) { + // if the user's change matches with the autocompletion + autocompletionMatchup = getAutocompletionMatchup({ prefix, autocompletion }) + if (autocompletionMatchup !== undefined) { + cachedAutocompletion = autocompletion + break; + } + } + + // if there is a cached autocompletion, return it + if (cachedAutocompletion && autocompletionMatchup) { + + console.log('AA') + + + // console.log('id: ' + cachedAutocompletion.id) + + if (cachedAutocompletion.status === 'finished') { + console.log('A1') + + const inlineCompletions = toInlineCompletions({ autocompletionMatchup, autocompletion: cachedAutocompletion, prefixAndSuffix, position, debug: true }) + return inlineCompletions + + } else if (cachedAutocompletion.status === 'pending') { + console.log('A2') + + try { + await cachedAutocompletion.llmPromise; + const inlineCompletions = toInlineCompletions({ autocompletionMatchup, autocompletion: cachedAutocompletion, prefixAndSuffix, position }) + return inlineCompletions + + } catch (e) { + this._autocompletionsOfDocument[docUriStr].delete(cachedAutocompletion.id) + console.error('Error creating autocompletion (1): ' + e) + } + + } else if (cachedAutocompletion.status === 'error') { + console.log('A3') + } else { + console.log('A4') + } + + return [] + } + + // else if no more typing happens, then go forwards with the request + + // wait DEBOUNCE_TIME for the user to stop typing + const thisTime = Date.now() + + const justAcceptedAutocompletion = thisTime - this._lastCompletionAccept < 500 + + this._lastCompletionStart = thisTime + const didTypingHappenDuringDebounce = await new Promise((resolve, reject) => + setTimeout(() => { + if (this._lastCompletionStart === thisTime) { + resolve(false) + } else { + resolve(true) + } + }, DEBOUNCE_TIME) + ) + + // if more typing happened, then do not go forwards with the request + if (didTypingHappenDuringDebounce) { + return [] + } + + + // if there are too many pending requests, cancel the oldest one + let numPending = 0 + let oldestPending: Autocompletion | undefined = undefined + for (const autocompletion of this._autocompletionsOfDocument[docUriStr].items.values()) { + if (autocompletion.status === 'pending') { + numPending += 1 + if (oldestPending === undefined) { + oldestPending = autocompletion + } + if (numPending >= MAX_PENDING_REQUESTS) { + // cancel the oldest pending request and remove it from cache + this._autocompletionsOfDocument[docUriStr].delete(oldestPending.id) + break + } + } + } + + + // gather relevant context from the code around the user's selection and definitions + // const relevantSnippetsList = await this._contextGatheringService.readCachedSnippets(model, position, 3); + // const relevantSnippetsList = this._contextGatheringService.getCachedSnippets(); + // const relevantSnippets = relevantSnippetsList.map((text) => `${text}`).join('\n-------------------------------\n') + // console.log('@@---------------------\n' + relevantSnippets) + const relevantContext = '' + + const { shouldGenerate, predictionType, llmPrefix, llmSuffix, stopTokens } = getCompletionOptions(prefixAndSuffix, relevantContext, justAcceptedAutocompletion) + + if (!shouldGenerate) return [] + + if (testMode && this._autocompletionId !== 0) { // TODO remove this + return [] + } + + + + // console.log('B') + + // create a new autocompletion and add it to cache + const newAutocompletion: Autocompletion = { + id: this._autocompletionId++, + prefix: prefix, // the actual prefix and suffix + suffix: suffix, + llmPrefix: llmPrefix, // the prefix and suffix the llm sees + llmSuffix: llmSuffix, + startTime: Date.now(), + endTime: undefined, + type: predictionType, + status: 'pending', + llmPromise: undefined, + insertText: '', + requestId: null, + _newlineCount: 0, + } + + console.log('starting autocomplete...', predictionType) + + // set parameters of `newAutocompletion` appropriately + newAutocompletion.llmPromise = new Promise((resolve, reject) => { + + const requestId = this._llmMessageService.sendLLMMessage({ + messagesType: 'FIMMessage', + messages: { + prefix: llmPrefix, + suffix: llmSuffix, + stopTokens: stopTokens, + }, + useProviderFor: 'Autocomplete', + logging: { loggingName: 'Autocomplete' }, + onText: () => { }, // unused in FIMMessage + // onText: async ({ fullText, newText }) => { + + // newAutocompletion.insertText = fullText + + // // count newlines in newText + // const numNewlines = newText.match(/\n|\r\n/g)?.length || 0 + // newAutocompletion._newlineCount += numNewlines + + // // if too many newlines, resolve up to last newline + // if (newAutocompletion._newlineCount > 10) { + // const lastNewlinePos = fullText.lastIndexOf('\n') + // newAutocompletion.insertText = fullText.substring(0, lastNewlinePos) + // resolve(newAutocompletion.insertText) + // return + // } + + // // if (!getAutocompletionMatchup({ prefix: this._lastPrefix, autocompletion: newAutocompletion })) { + // // reject('LLM response did not match user\'s text.') + // // } + // }, + onFinalMessage: ({ fullText }) => { + + // console.log('____res: ', JSON.stringify(newAutocompletion.insertText)) + + newAutocompletion.endTime = Date.now() + newAutocompletion.status = 'finished' + const [text, _] = extractCodeFromRegular({ text: fullText, recentlyAddedTextLen: 0 }) + newAutocompletion.insertText = processStartAndEndSpaces(text) + + // handle special case for predicting starting on the next line, add a newline character + if (newAutocompletion.type === 'multi-line-start-on-next-line') { + newAutocompletion.insertText = _ln + newAutocompletion.insertText + } + + resolve(newAutocompletion.insertText) + + }, + onError: ({ message }) => { + newAutocompletion.endTime = Date.now() + newAutocompletion.status = 'error' + reject(message) + }, + }) + newAutocompletion.requestId = requestId + + // if the request hasnt resolved in TIMEOUT_TIME seconds, reject it + setTimeout(() => { + if (newAutocompletion.status === 'pending') { + reject('Timeout receiving message to LLM.') + } + }, TIMEOUT_TIME) + + }) + + + + // add autocompletion to cache + this._autocompletionsOfDocument[docUriStr].set(newAutocompletion.id, newAutocompletion) + + // show autocompletion + try { + await newAutocompletion.llmPromise + // console.log('id: ' + newAutocompletion.id) + + const autocompletionMatchup: AutocompletionMatchupBounds = { startIdx: 0, startLine: 0, startCharacter: 0 } + const inlineCompletions = toInlineCompletions({ autocompletionMatchup, autocompletion: newAutocompletion, prefixAndSuffix, position }) + return inlineCompletions + + } catch (e) { + this._autocompletionsOfDocument[docUriStr].delete(newAutocompletion.id) + console.error('Error creating autocompletion (2): ' + e) + return [] + } + + } + + constructor( + @ILanguageFeaturesService private _langFeatureService: ILanguageFeaturesService, + @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, + @IEditorService private readonly _editorService: IEditorService, + @IModelService private readonly _modelService: IModelService, + // @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService, + ) { + super() + + this._langFeatureService.inlineCompletionsProvider.register('*', { + provideInlineCompletions: async (model, position, context, token) => { + const items = await this._provideInlineCompletionItems(model, position, context, token) + + // console.log('item: ', items?.[0]?.insertText) + return { items: items, } + }, + freeInlineCompletions: (completions) => { + // get the `docUriStr` and the `position` of the cursor + const activePane = this._editorService.activeEditorPane; + if (!activePane) return; + const control = activePane.getControl(); + if (!control || !isCodeEditor(control)) return; + const position = control.getPosition(); + if (!position) return; + const resource = EditorResourceAccessor.getCanonicalUri(this._editorService.activeEditor); + if (!resource) return; + const model = this._modelService.getModel(resource) + if (!model) return; + const docUriStr = resource.toString(); + if (!this._autocompletionsOfDocument[docUriStr]) return; + + const { prefix, } = getPrefixAndSuffixInfo(model, position) + + // go through cached items and remove matching ones + // autocompletion.prefix + autocompletion.insertedText ~== insertedText + this._autocompletionsOfDocument[docUriStr].items.forEach((autocompletion: Autocompletion) => { + + // we can do this more efficiently, I just didn't want to deal with all of the edge cases + const matchup = removeAllWhitespace(prefix) === removeAllWhitespace(autocompletion.prefix + autocompletion.insertText) + + if (matchup) { + console.log('ACCEPT', autocompletion.id) + this._lastCompletionAccept = Date.now() + this._autocompletionsOfDocument[docUriStr].delete(autocompletion.id); + } + }); + + }, + }) + } + + +} + +registerWorkbenchContribution2(AutocompleteService.ID, AutocompleteService, WorkbenchPhase.BlockRestore); + + + diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts new file mode 100644 index 00000000..d92fb772 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -0,0 +1,655 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; + +import { URI } from '../../../../base/common/uri.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { ILLMMessageService } from '../common/llmMessageService.js'; +import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo as chat_userMessageContentWithAllFiles, chat_selectionsString } from './prompt/prompts.js'; +import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolFns, ToolName, voidTools } from '../common/toolsService.js'; +import { toLLMChatMessage } from '../common/llmMessageTypes.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IVoidFileService } from '../common/voidFileService.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; + + +const findLastIndex = (arr: T[], condition: (t: T) => boolean): number => { + for (let i = arr.length - 1; i >= 0; i--) { + if (condition(arr[i])) { + return i; + } + } + return -1; +} + + +// one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text) +export type CodeSelection = { + type: 'Selection'; + fileURI: URI; + selectionStr: string; + range: IRange; +} + +export type FileSelection = { + type: 'File'; + fileURI: URI; + selectionStr: null; + range: null; +} + +export type StagingSelectionItem = CodeSelection | FileSelection + + +export type ToolMessage = { + role: 'tool'; + name: T; // internal use + params: string; // internal use + id: string; // apis require this tool use id + content: string; // result + result: ToolCallReturnType[T]; // text message of result +} + + +// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors. +export type ChatMessage = + | { + role: 'system'; + content: string; + displayContent?: undefined; + } | { + role: 'user'; + content: string | null; // content displayed to the LLM on future calls - allowed to be '', will be replaced with (empty) + displayContent: string | null; // content displayed to user - allowed to be '', will be ignored + selections: StagingSelectionItem[] | null; // the user's selection + state: { + stagingSelections: StagingSelectionItem[]; + isBeingEdited: boolean; + } + } | { + role: 'assistant'; + content: string | null; // content received from LLM - allowed to be '', will be replaced with (empty) + displayContent: string | null; // content displayed to user (this is the same as content for now) - allowed to be '', will be ignored + } + | ToolMessage + +type UserMessageType = ChatMessage & { role: 'user' } +type UserMessageState = UserMessageType['state'] + +export const defaultMessageState: UserMessageState = { + stagingSelections: [], + isBeingEdited: false, +} + +// a 'thread' means a chat message history +export type ChatThreads = { + [id: string]: { + id: string; // store the id here too + createdAt: string; // ISO string + lastModified: string; // ISO string + messages: ChatMessage[]; + state: { + stagingSelections: StagingSelectionItem[]; + focusedMessageIdx: number | undefined; // index of the message that is being edited (undefined if none) + isCheckedOfSelectionId: { [selectionId: string]: boolean }; + } + }; +} + +type ThreadType = ChatThreads[string] + +const defaultThreadState: ThreadType['state'] = { stagingSelections: [], focusedMessageIdx: undefined, isCheckedOfSelectionId: {} } + +export type ThreadsState = { + allThreads: ChatThreads; + currentThreadId: string; // intended for internal use only +} + +export type ThreadStreamState = { + [threadId: string]: undefined | { + error?: { message: string, fullError: Error | null, }; + messageSoFar?: string; + streamingToken?: string; + } +} + + +const newThreadObject = () => { + const now = new Date().toISOString() + return { + id: generateUuid(), + createdAt: now, + lastModified: now, + messages: [], + state: { + stagingSelections: [], + focusedMessageIdx: undefined, + isCheckedOfSelectionId: {} + }, + + } satisfies ChatThreads[string] +} + +const THREAD_VERSION_KEY = 'void.chatThreadVersion' +const LATEST_THREAD_VERSION = 'v2' + +const THREAD_STORAGE_KEY = 'void.chatThreadStorage' + + +type ChatMode = 'agent' | 'chat' +export interface IChatThreadService { + readonly _serviceBrand: undefined; + + readonly state: ThreadsState; + readonly streamState: ThreadStreamState; + + onDidChangeCurrentThread: Event; + onDidChangeStreamState: Event<{ threadId: string }> + + getCurrentThread(): ChatThreads[string]; + openNewThread(): void; + switchToThread(threadId: string): void; + + // you can edit multiple messages + // the one you're currently editing is "focused", and we add items to that one when you press cmd+L. + getFocusedMessageIdx(): number | undefined; + isFocusingMessage(): boolean; + setFocusedMessageIdx(messageIdx: number | undefined): void; + + // exposed getters/setters + getCurrentMessageState: (messageIdx: number) => UserMessageState + setCurrentMessageState: (messageIdx: number, newState: Partial) => void + getCurrentThreadStagingSelections: () => StagingSelectionItem[] + setCurrentThreadStagingSelections: (stagingSelections: StagingSelectionItem[]) => void + + + // call to edit a message + editUserMessageAndStreamResponse({ userMessage, chatMode, messageIdx }: { userMessage: string, chatMode: ChatMode, messageIdx: number }): Promise; + + // call to add a message + addUserMessageAndStreamResponse({ userMessage, chatMode }: { userMessage: string, chatMode: ChatMode }): Promise; + + cancelStreaming(threadId: string): void; + dismissStreamError(threadId: string): void; + +} + +export const IChatThreadService = createDecorator('voidChatThreadService'); +class ChatThreadService extends Disposable implements IChatThreadService { + _serviceBrand: undefined; + + // this fires when the current thread changes at all (a switch of currentThread, or a message added to it, etc) + private readonly _onDidChangeCurrentThread = new Emitter(); + readonly onDidChangeCurrentThread: Event = this._onDidChangeCurrentThread.event; + + readonly streamState: ThreadStreamState = {} + private readonly _onDidChangeStreamState = new Emitter<{ threadId: string }>(); + readonly onDidChangeStreamState: Event<{ threadId: string }> = this._onDidChangeStreamState.event; + + state: ThreadsState // allThreads is persisted, currentThread is not + + constructor( + @IStorageService private readonly _storageService: IStorageService, + @IVoidFileService private readonly _voidFileService: IVoidFileService, + @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, + @IToolsService private readonly _toolsService: IToolsService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, + ) { + super() + + const oldVersionNum = this._storageService.get(THREAD_VERSION_KEY, StorageScope.APPLICATION) + + + const readThreads = this._readAllThreads() + const updatedThreads = this._updatedThreadsToVersion(readThreads, oldVersionNum) + + if (updatedThreads !== null) { + this._storeAllThreads(updatedThreads) + } + + const allThreads = updatedThreads ?? readThreads + this.state = { + allThreads: allThreads, + currentThreadId: null as unknown as string, // gets set in startNewThread() + } + + // always be in a thread + this.openNewThread() + + this._storageService.store(THREAD_VERSION_KEY, LATEST_THREAD_VERSION, StorageScope.APPLICATION, StorageTarget.USER) + + } + + + private _readAllThreads(): ChatThreads { + const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION) + const threads: ChatThreads = threadsStr ? JSON.parse(threadsStr) : {} + + return threads + } + + + // returns if should update + private _updatedThreadsToVersion(oldThreadsObject: any, oldVersion: string | undefined): ChatThreads | null { + + if (!oldVersion) { + + // unknown, just reset chat? + return null + } + + /** v1 -> v2 + - threads.state.currentStagingSelections: CodeStagingSelection[] | null; + + thread[threadIdx].state + + message.state + + chatMessage.staging: StagingInfo | null + */ + else if (oldVersion === 'v1') { + const threads = oldThreadsObject as Omit + // update the threads + for (const thread of Object.values(threads)) { + if (!thread.state) { + thread.state = defaultThreadState + } + for (const chatMessage of Object.values(thread.messages)) { + if (chatMessage.role === 'user' && !chatMessage.state) { + chatMessage.state = defaultMessageState + } + } + } + + // push the update + return threads + } + else if (oldVersion === 'v2') { + return null + } + + // up to date + return null + + } + + private _storeAllThreads(threads: ChatThreads) { + this._storageService.store(THREAD_STORAGE_KEY, JSON.stringify(threads), StorageScope.APPLICATION, StorageTarget.USER) + } + + // this should be the only place this.state = ... appears besides constructor + private _setState(state: Partial, affectsCurrent: boolean) { + this.state = { + ...this.state, + ...state + } + if (affectsCurrent) + this._onDidChangeCurrentThread.fire() + } + + private _getAllSelections() { + const thread = this.getCurrentThread() + return thread.messages.flatMap(m => m.role === 'user' && m.selections || []) + } + + private _getSelectionsUpToMessageIdx(messageIdx: number) { + const thread = this.getCurrentThread() + const prevMessages = thread.messages.slice(0, messageIdx) + return prevMessages.flatMap(m => m.role === 'user' && m.selections || []) + } + + private _setStreamState(threadId: string, state: Partial>) { + this.streamState[threadId] = { + ...this.streamState[threadId], + ...state + } + this._onDidChangeStreamState.fire({ threadId }) + } + + + // ---------- streaming ---------- + + private _finishStreamingTextMessage = (threadId: string, content: string, error?: { message: string, fullError: Error | null }) => { + // add assistant's message to chat history, and clear selection + this._addMessageToThread(threadId, { role: 'assistant', content, displayContent: content || null }) + this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error }) + } + + + + + async editUserMessageAndStreamResponse({ userMessage, chatMode, messageIdx }: { userMessage: string, chatMode: ChatMode, messageIdx: number }) { + + const thread = this.getCurrentThread() + + if (thread.messages?.[messageIdx]?.role !== 'user') { + throw new Error("Error: editing a message with role !=='user'") + } + + // get prev and curr selections before clearing the message + const prevSelns = this._getSelectionsUpToMessageIdx(messageIdx) + const currSelns = thread.messages[messageIdx].selections || [] + + // clear messages up to the index + const slicedMessages = thread.messages.slice(0, messageIdx) + this._setState({ + allThreads: { + ...this.state.allThreads, + [thread.id]: { + ...thread, + messages: slicedMessages + } + } + }, true) + + // re-add the message and stream it + this.addUserMessageAndStreamResponse({ userMessage, chatMode, chatSelections: { prevSelns, currSelns } }) + + } + + + + async addUserMessageAndStreamResponse({ userMessage, chatMode, chatSelections }: { userMessage: string, chatMode: ChatMode, chatSelections?: { prevSelns?: StagingSelectionItem[], currSelns?: StagingSelectionItem[] } }) { + + const thread = this.getCurrentThread() + const threadId = thread.id + + // selections in all past chats, then in current chat (can have many duplicates here) + const prevSelns: StagingSelectionItem[] = chatSelections?.prevSelns ?? this._getAllSelections() + const currSelns: StagingSelectionItem[] = chatSelections?.currSelns ?? thread.state.stagingSelections + + // add user's message to chat history + const instructions = userMessage + const userMessageContent = await chat_userMessageContent(instructions, currSelns) + const selectionsStr = await chat_selectionsString(prevSelns, currSelns, this._voidFileService) + const userMessageFullContent = chat_userMessageContentWithAllFiles(userMessageContent, selectionsStr) + + const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState } + this._addMessageToThread(threadId, userHistoryElt) + + this._setStreamState(threadId, { error: undefined }) + + + const tools: InternalToolInfo[] | undefined = ( + chatMode === 'chat' ? undefined + : chatMode === 'agent' ? Object.keys(voidTools).map(toolName => voidTools[toolName as ToolName]) + : undefined) + + // agent loop + const agentLoop = async () => { + + let shouldSendAnotherMessage = true + let nMessagesSent = 0 + + while (shouldSendAnotherMessage) { + shouldSendAnotherMessage = false + nMessagesSent += 1 + + let res_: () => void + const awaitable = new Promise((res, rej) => { res_ = res }) + + // replace last userMessage with userMessageFullContent (which contains all the files too) + const messages_ = this.getCurrentThread().messages.map(m => (toLLMChatMessage(m))) + const lastUserMsgIdx = findLastIndex(messages_, m => m.role === 'user') + let messages = messages_ + if (lastUserMsgIdx !== -1) { // should never be -1 + messages = [ + ...messages.slice(0, lastUserMsgIdx), + { role: 'user', content: userMessageFullContent }, + ...messages.slice(lastUserMsgIdx + 1, Infinity)] + } + + const llmCancelToken = this._llmMessageService.sendLLMMessage({ + messagesType: 'chatMessages', + useProviderFor: 'Ctrl+L', + logging: { loggingName: `Agent` }, + messages: [ + { role: 'system', content: chat_systemMessage(this._workspaceContextService.getWorkspace().folders.map(f => f.uri.fsPath)) }, + ...messages, + ], + + tools: tools, + + onText: ({ fullText }) => { + this._setStreamState(threadId, { messageSoFar: fullText }) + }, + onFinalMessage: async ({ fullText, toolCalls }) => { + + if ((toolCalls?.length ?? 0) === 0) { + this._finishStreamingTextMessage(threadId, fullText) + } + else { + this._addMessageToThread(threadId, { role: 'assistant', content: fullText, displayContent: fullText }) + this._setStreamState(threadId, { messageSoFar: undefined }) // clear streaming message + for (const tool of toolCalls ?? []) { + const toolName = tool.name as ToolName + + // 1. + let toolResult: Awaited> + let toolResultVal: ToolCallReturnType[ToolName] + try { + toolResult = await this._toolsService.toolFns[toolName](tool.params) + toolResultVal = toolResult + } catch (error) { + this._setStreamState(threadId, { error }) + shouldSendAnotherMessage = false + break + } + + // 2. + let toolResultStr: string + try { + toolResultStr = this._toolsService.toolResultToString[toolName](toolResult as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here + } catch (error) { + this._setStreamState(threadId, { error }) + shouldSendAnotherMessage = false + break + } + + this._addMessageToThread(threadId, { role: 'tool', name: toolName, params: tool.params, id: tool.id, content: toolResultStr, result: toolResultVal, }) + shouldSendAnotherMessage = true + } + + } + res_() + }, + onError: (error) => { + this._finishStreamingTextMessage(threadId, this.streamState[threadId]?.messageSoFar ?? '', error) + res_() + }, + }) + if (llmCancelToken === null) break + this._setStreamState(threadId, { streamingToken: llmCancelToken }) + + await awaitable + } + } + + agentLoop() // DO NOT AWAIT THIS, this fn should resolve when ready to clear inputs + + } + + cancelStreaming(threadId: string) { + const llmCancelToken = this.streamState[threadId]?.streamingToken + if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken) + this._finishStreamingTextMessage(threadId, this.streamState[threadId]?.messageSoFar ?? '') + } + + dismissStreamError(threadId: string): void { + this._setStreamState(threadId, { error: undefined }) + } + + + + // ---------- the rest ---------- + + getCurrentThread(): ChatThreads[string] { + const state = this.state + return state.allThreads[state.currentThreadId] + } + + getFocusedMessageIdx() { + const thread = this.getCurrentThread() + + // get the focusedMessageIdx + const focusedMessageIdx = thread.state.focusedMessageIdx + if (focusedMessageIdx === undefined) return; + + // check that the message is actually being edited + const focusedMessage = thread.messages[focusedMessageIdx] + if (focusedMessage.role !== 'user') return; + if (!focusedMessage.state) return; + + return focusedMessageIdx + } + + isFocusingMessage() { + return this.getFocusedMessageIdx() !== undefined + } + + switchToThread(threadId: string) { + this._setState({ currentThreadId: threadId }, true) + } + + + openNewThread() { + // if a thread with 0 messages already exists, switch to it + const { allThreads: currentThreads } = this.state + for (const threadId in currentThreads) { + if (currentThreads[threadId].messages.length === 0) { + this.switchToThread(threadId) + return + } + } + // otherwise, start a new thread + const newThread = newThreadObject() + + // update state + const newThreads: ChatThreads = { + ...currentThreads, + [newThread.id]: newThread + } + this._storeAllThreads(newThreads) + this._setState({ allThreads: newThreads, currentThreadId: newThread.id }, true) + } + + + _addMessageToThread(threadId: string, message: ChatMessage) { + const { allThreads } = this.state + + const oldThread = allThreads[threadId] + + // update state and store it + const newThreads = { + ...allThreads, + [oldThread.id]: { + ...oldThread, + lastModified: new Date().toISOString(), + messages: [...oldThread.messages, message], + } + } + this._storeAllThreads(newThreads) + this._setState({ allThreads: newThreads }, true) // the current thread just changed (it had a message added to it) + } + + // sets the currently selected message (must be undefined if no message is selected) + setFocusedMessageIdx(messageIdx: number | undefined) { + + const threadId = this.state.currentThreadId + const thread = this.state.allThreads[threadId] + if (!thread) return + + this._setState({ + allThreads: { + ...this.state.allThreads, + [threadId]: { + ...thread, + state: { + ...thread.state, + focusedMessageIdx: messageIdx, + } + } + } + }, true) + } + + // set message.state + private _setCurrentMessageState(state: Partial, messageIdx: number): void { + + const threadId = this.state.currentThreadId + const thread = this.state.allThreads[threadId] + if (!thread) return + + this._setState({ + allThreads: { + ...this.state.allThreads, + [threadId]: { + ...thread, + messages: thread.messages.map((m, i) => + i === messageIdx && m.role === 'user' ? { + ...m, + state: { + ...m.state, + ...state + }, + } : m + ) + } + } + }, true) + + } + + // set thread.state + private _setCurrentThreadState(state: Partial): void { + + const threadId = this.state.currentThreadId + const thread = this.state.allThreads[threadId] + if (!thread) return + + this._setState({ + allThreads: { + ...this.state.allThreads, + [thread.id]: { + ...thread, + state: { + ...thread.state, + ...state + } + } + } + }, true) + + } + + getCurrentThreadStagingSelections = () => { + return this.getCurrentThread().state.stagingSelections + } + + setCurrentThreadStagingSelections = (stagingSelections: StagingSelectionItem[]) => { + this._setCurrentThreadState({ stagingSelections }) + } + + // gets `staging` and `setStaging` of the currently focused element, given the index of the currently selected message (or undefined if no message is selected) + + getCurrentMessageState(messageIdx: number): UserMessageState { + const currMessage = this.getCurrentThread()?.messages?.[messageIdx] + if (!currMessage || currMessage.role !== 'user') return defaultMessageState + return currMessage.state + } + setCurrentMessageState(messageIdx: number, newState: Partial) { + const currMessage = this.getCurrentThread()?.messages?.[messageIdx] + if (!currMessage || currMessage.role !== 'user') return + this._setCurrentMessageState(newState, messageIdx) + } + + + +} + +registerSingleton(IChatThreadService, ChatThreadService, InstantiationType.Eager); + diff --git a/src/vs/workbench/contrib/void/browser/contextGatheringService.ts b/src/vs/workbench/contrib/void/browser/contextGatheringService.ts new file mode 100644 index 00000000..c20e8fb8 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/contextGatheringService.ts @@ -0,0 +1,354 @@ +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { DocumentSymbol, SymbolKind } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { Range, IRange } from '../../../../editor/common/core/range.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { URI } from '../../../../base/common/uri.js'; + + +// make sure snippet logic works +// change logic for `visited` to intervals +// atomically set new snippets at end +// throttle cache setting + +interface IVisitedInterval { + uri: string; + startLine: number; + endLine: number; +} + +export interface IContextGatheringService { + readonly _serviceBrand: undefined; + updateCache(model: ITextModel, pos: Position): Promise; + getCachedSnippets(): string[]; +} + +export const IContextGatheringService = createDecorator('contextGatheringService'); + +class ContextGatheringService extends Disposable implements IContextGatheringService { + _serviceBrand: undefined; + private readonly _NUM_LINES = 3; + private readonly _MAX_SNIPPET_LINES = 7; // Reasonable size for context + // Cache holds the most recent list of snippets. + private _cache: string[] = []; + private _snippetIntervals: IVisitedInterval[] = []; + + constructor( + @ILanguageFeaturesService private readonly _langFeaturesService: ILanguageFeaturesService, + @IModelService private readonly _modelService: IModelService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService + ) { + super(); + this._modelService.getModels().forEach(model => this._subscribeToModel(model)); + this._register(this._modelService.onModelAdded(model => this._subscribeToModel(model))); + } + + private _subscribeToModel(model: ITextModel): void { + console.log("Subscribing to model:", model.uri.toString()); + this._register(model.onDidChangeContent(() => { + const editor = this._codeEditorService.getFocusedCodeEditor(); + if (editor && editor.getModel() === model) { + const pos = editor.getPosition(); + console.log("updateCache called at position:", pos); + if (pos) { + this.updateCache(model, pos); + } + } + })); + } + + public async updateCache(model: ITextModel, pos: Position): Promise { + const snippets = new Set(); + this._snippetIntervals = []; // Reset intervals for new cache update + + await this._gatherNearbySnippets(model, pos, this._NUM_LINES, 3, snippets, this._snippetIntervals); + await this._gatherParentSnippets(model, pos, this._NUM_LINES, 3, snippets, this._snippetIntervals); + + // Convert to array and filter overlapping snippets + this._cache = Array.from(snippets); + console.log("Cache updated:", this._cache); + } + + public getCachedSnippets(): string[] { + return this._cache; + } + + // Basic snippet extraction. + private _getSnippetForRange(model: ITextModel, range: IRange, numLines: number): string { + const startLine = Math.max(range.startLineNumber - numLines, 1); + const endLine = Math.min(range.endLineNumber + numLines, model.getLineCount()); + + // Enforce maximum snippet size + const totalLines = endLine - startLine + 1; + const adjustedStartLine = totalLines > this._MAX_SNIPPET_LINES + ? endLine - this._MAX_SNIPPET_LINES + 1 + : startLine; + + const snippetRange = new Range(adjustedStartLine, 1, endLine, model.getLineMaxColumn(endLine)); + return this._cleanSnippet(model.getValueInRange(snippetRange)); + } + + private _cleanSnippet(snippet: string): string { + return snippet + .split('\n') + // Remove empty lines and lines with only comments + .filter(line => { + const trimmed = line.trim(); + return trimmed && !/^\/\/+$/.test(trimmed); + }) + // Rejoin with newlines + .join('\n') + // Remove excess whitespace + .trim(); + } + + private _normalizeSnippet(snippet: string): string { + return snippet + // Remove multiple newlines + .replace(/\n{2,}/g, '\n') + // Remove trailing whitespace + .trim(); + } + + private _addSnippetIfNotOverlapping( + model: ITextModel, + range: IRange, + snippets: Set, + visited: IVisitedInterval[] + ): void { + const startLine = range.startLineNumber; + const endLine = range.endLineNumber; + const uri = model.uri.toString(); + + if (!this._isRangeVisited(uri, startLine, endLine, visited)) { + visited.push({ uri, startLine, endLine }); + const snippet = this._normalizeSnippet(this._getSnippetForRange(model, range, this._NUM_LINES)); + if (snippet.length > 0) { + snippets.add(snippet); + } + } + } + + private async _gatherNearbySnippets( + model: ITextModel, + pos: Position, + numLines: number, + depth: number, + snippets: Set, + visited: IVisitedInterval[] + ): Promise { + if (depth <= 0) return; + + const startLine = Math.max(pos.lineNumber - numLines, 1); + const endLine = Math.min(pos.lineNumber + numLines, model.getLineCount()); + const range = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine)); + + this._addSnippetIfNotOverlapping(model, range, snippets, visited); + + const symbols = await this._getSymbolsNearPosition(model, pos, numLines); + for (const sym of symbols) { + const defs = await this._getDefinitionSymbols(model, sym); + for (const def of defs) { + const defModel = this._modelService.getModel(def.uri); + if (defModel) { + const defPos = new Position(def.range.startLineNumber, def.range.startColumn); + this._addSnippetIfNotOverlapping(defModel, def.range, snippets, visited); + await this._gatherNearbySnippets(defModel, defPos, numLines, depth - 1, snippets, visited); + } + } + } + } + + private async _gatherParentSnippets( + model: ITextModel, + pos: Position, + numLines: number, + depth: number, + snippets: Set, + visited: IVisitedInterval[] + ): Promise { + if (depth <= 0) return; + + const container = await this._findContainerFunction(model, pos); + if (!container) return; + + const containerRange = container.kind === SymbolKind.Method ? container.selectionRange : container.range; + this._addSnippetIfNotOverlapping(model, containerRange, snippets, visited); + + const symbols = await this._getSymbolsNearRange(model, containerRange, numLines); + for (const sym of symbols) { + const defs = await this._getDefinitionSymbols(model, sym); + for (const def of defs) { + const defModel = this._modelService.getModel(def.uri); + if (defModel) { + const defPos = new Position(def.range.startLineNumber, def.range.startColumn); + this._addSnippetIfNotOverlapping(defModel, def.range, snippets, visited); + await this._gatherNearbySnippets(defModel, defPos, numLines, depth - 1, snippets, visited); + } + } + } + + const containerPos = new Position(containerRange.startLineNumber, containerRange.startColumn); + await this._gatherParentSnippets(model, containerPos, numLines, depth - 1, snippets, visited); + } + + private _isRangeVisited(uri: string, startLine: number, endLine: number, visited: IVisitedInterval[]): boolean { + return visited.some(interval => + interval.uri === uri && + !(endLine < interval.startLine || startLine > interval.endLine) + ); + } + + private async _getSymbolsNearPosition(model: ITextModel, pos: Position, numLines: number): Promise { + const startLine = Math.max(pos.lineNumber - numLines, 1); + const endLine = Math.min(pos.lineNumber + numLines, model.getLineCount()); + const range = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine)); + return this._getSymbolsInRange(model, range); + } + + private async _getSymbolsNearRange(model: ITextModel, range: IRange, numLines: number): Promise { + const centerLine = Math.floor((range.startLineNumber + range.endLineNumber) / 2); + const startLine = Math.max(centerLine - numLines, 1); + const endLine = Math.min(centerLine + numLines, model.getLineCount()); + const searchRange = new Range(startLine, 1, endLine, model.getLineMaxColumn(endLine)); + return this._getSymbolsInRange(model, searchRange); + } + + private async _getSymbolsInRange(model: ITextModel, range: IRange): Promise { + const symbols: DocumentSymbol[] = []; + const providers = this._langFeaturesService.documentSymbolProvider.ordered(model); + for (const provider of providers) { + try { + const result = await provider.provideDocumentSymbols(model, CancellationToken.None); + if (result) { + const flat = this._flattenSymbols(result); + const intersecting = flat.filter(sym => this._rangesIntersect(sym.range, range)); + symbols.push(...intersecting); + } + } catch (e) { + console.warn("Symbol provider error:", e); + } + } + // Also check reference providers. + const refProviders = this._langFeaturesService.referenceProvider.ordered(model); + for (let line = range.startLineNumber; line <= range.endLineNumber; line++) { + const content = model.getLineContent(line); + const words = content.match(/[a-zA-Z_]\w*/g) || []; + for (const word of words) { + const startColumn = content.indexOf(word) + 1; + const pos = new Position(line, startColumn); + if (!this._positionInRange(pos, range)) continue; + for (const provider of refProviders) { + try { + const refs = await provider.provideReferences(model, pos, { includeDeclaration: true }, CancellationToken.None); + if (refs) { + const filtered = refs.filter(ref => this._rangesIntersect(ref.range, range)); + for (const ref of filtered) { + symbols.push({ + name: word, + detail: '', + kind: SymbolKind.Variable, + range: ref.range, + selectionRange: ref.range, + children: [], + tags: [] + }); + } + } + } catch (e) { + console.warn("Reference provider error:", e); + } + } + } + } + return symbols; + } + + private _flattenSymbols(symbols: DocumentSymbol[]): DocumentSymbol[] { + const flat: DocumentSymbol[] = []; + for (const sym of symbols) { + flat.push(sym); + if (sym.children && sym.children.length > 0) { + flat.push(...this._flattenSymbols(sym.children)); + } + } + return flat; + } + + private _rangesIntersect(a: IRange, b: IRange): boolean { + return !( + a.endLineNumber < b.startLineNumber || + a.startLineNumber > b.endLineNumber || + (a.endLineNumber === b.startLineNumber && a.endColumn < b.startColumn) || + (a.startLineNumber === b.endLineNumber && a.endColumn > b.endColumn) + ); + } + + private _positionInRange(pos: Position, range: IRange): boolean { + return pos.lineNumber >= range.startLineNumber && + pos.lineNumber <= range.endLineNumber && + (pos.lineNumber !== range.startLineNumber || pos.column >= range.startColumn) && + (pos.lineNumber !== range.endLineNumber || pos.column <= range.endColumn); + } + + // Get definition symbols for a given symbol. + private async _getDefinitionSymbols(model: ITextModel, symbol: DocumentSymbol): Promise<(DocumentSymbol & { uri: URI })[]> { + const pos = new Position(symbol.range.startLineNumber, symbol.range.startColumn); + const providers = this._langFeaturesService.definitionProvider.ordered(model); + const defs: (DocumentSymbol & { uri: URI })[] = []; + for (const provider of providers) { + try { + const res = await provider.provideDefinition(model, pos, CancellationToken.None); + if (res) { + const links = Array.isArray(res) ? res : [res]; + defs.push(...links.map(link => ({ + name: symbol.name, + detail: symbol.detail, + kind: symbol.kind, + range: link.range, + selectionRange: link.range, + children: [], + tags: symbol.tags || [], + uri: link.uri // Now keeping it as URI instead of converting to string + }))); + } + } catch (e) { + console.warn("Definition provider error:", e); + } + } + return defs; + } + + private async _findContainerFunction(model: ITextModel, pos: Position): Promise { + const searchRange = new Range( + Math.max(pos.lineNumber - 1, 1), 1, + Math.min(pos.lineNumber + 1, model.getLineCount()), + model.getLineMaxColumn(pos.lineNumber) + ); + const symbols = await this._getSymbolsInRange(model, searchRange); + const funcs = symbols.filter(s => + (s.kind === SymbolKind.Function || s.kind === SymbolKind.Method) && + this._positionInRange(pos, s.range) + ); + if (!funcs.length) return null; + return funcs.reduce((innermost, current) => { + if (!innermost) return current; + const moreInner = + (current.range.startLineNumber > innermost.range.startLineNumber || + (current.range.startLineNumber === innermost.range.startLineNumber && + current.range.startColumn > innermost.range.startColumn)) && + (current.range.endLineNumber < innermost.range.endLineNumber || + (current.range.endLineNumber === innermost.range.endLineNumber && + current.range.endColumn < innermost.range.endColumn)); + return moreInner ? current : innermost; + }, null as DocumentSymbol | null); + } +} + +registerSingleton(IContextGatheringService, ContextGatheringService, InstantiationType.Eager); diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts new file mode 100644 index 00000000..0f92d4ef --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -0,0 +1,2254 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ICodeEditor, IOverlayWidget, IViewZone, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js'; + +// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +// import { throttle } from '../../../../base/common/decorators.js'; +import { ComputedDiff, findDiffs } from './helpers/findDiffs.js'; +import { EndOfLinePreference, IModelDecorationOptions, ITextModel } from '../../../../editor/common/model.js'; +import { IRange } from '../../../../editor/common/core/range.js'; +import { registerColor } from '../../../../platform/theme/common/colorUtils.js'; +import { Color, RGBA } from '../../../../base/common/color.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType } from '../../../../platform/undoRedo/common/undoRedo.js'; +import { RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; +// import { IModelService } from '../../../../editor/common/services/model.js'; + +import * as dom from '../../../../base/browser/dom.js'; +import { Widget } from '../../../../base/browser/ui/widget.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js'; +import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from './prompt/prompts.js'; + +import { mountCtrlK } from './react/out/quick-edit-tsx/index.js' +import { QuickEditPropsType } from './quickEditActions.js'; +import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js'; +import { extractCodeFromFIM, extractCodeFromRegular, ExtractedSearchReplaceBlock, extractSearchReplaceBlocks } from './helpers/extractCodeFromResult.js'; +import { filenameToVscodeLanguage } from './helpers/detectLanguage.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +import { isMacintosh } from '../../../../base/common/platform.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { ILLMMessageService } from '../common/llmMessageService.js'; +import { LLMChatMessage, OnError, errorDetails } from '../common/llmMessageTypes.js'; +import { IMetricsService } from '../common/metricsService.js'; +import { IVoidFileService } from '../common/voidFileService.js'; + +const configOfBG = (color: Color) => { + return { dark: color, light: color, hcDark: color, hcLight: color, } +} +// gets converted to --vscode-void-greenBG, see void.css, asCssVariable +const greenBG = new Color(new RGBA(155, 185, 85, .3)); // default is RGBA(155, 185, 85, .2) +registerColor('void.greenBG', configOfBG(greenBG), '', true); + +const redBG = new Color(new RGBA(255, 0, 0, .3)); // default is RGBA(255, 0, 0, .2) +registerColor('void.redBG', configOfBG(redBG), '', true); + +const sweepBG = new Color(new RGBA(100, 100, 100, .2)); +registerColor('void.sweepBG', configOfBG(sweepBG), '', true); + +const highlightBG = new Color(new RGBA(100, 100, 100, .1)); +registerColor('void.highlightBG', configOfBG(highlightBG), '', true); + +const sweepIdxBG = new Color(new RGBA(100, 100, 100, .5)); +registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); + + + + + +const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number => { + + const model = editor.getModel(); + if (!model) { + return 0; + } + + // Get the line content, defaulting to empty string if line doesn't exist + const lineContent = model.getLineContent(startLine) || ''; + + // Find the first non-whitespace character + const firstNonWhitespaceIndex = lineContent.search(/\S/); + + // Extract leading whitespace, handling case where line is all whitespace + const leadingWhitespace = firstNonWhitespaceIndex === -1 + ? lineContent + : lineContent.slice(0, firstNonWhitespaceIndex); + + // Get font information from editor render options + const { tabSize: numSpacesInTab } = model.getFormattingOptions(); + const spaceWidth = editor.getOption(EditorOption.fontInfo).spaceWidth; + const tabWidth = numSpacesInTab * spaceWidth; + + let paddingLeft = 0; + for (const char of leadingWhitespace) { + if (char === '\t') { + paddingLeft += tabWidth + } else if (char === ' ') { + paddingLeft += spaceWidth; + } + } + + return paddingLeft; +}; + + + +// finds block.orig in fileContents and return its range in file +// startingAtLine is 1-indexed and inclusive +const findTextInCode = (text: string, fileContents: string, startingAtLine?: number) => { + const idx = fileContents.indexOf(text, + startingAtLine !== undefined ? + fileContents.split('\n').slice(0, startingAtLine).join('\n').length // num characters in all lines before startingAtLine + : 0 + ) + if (idx === -1) return 'Not found' as const + const lastIdx = fileContents.lastIndexOf(text) + if (lastIdx !== idx) return 'Not unique' as const + const startLine = fileContents.substring(0, idx).split('\n').length + const numLines = text.split('\n').length + const endLine = startLine + numLines - 1 + return [startLine, endLine] as const +} + + +export type URIStreamState = 'idle' | 'acceptRejectAll' | 'streaming' + + +export type StartApplyingOpts = { + from: 'QuickEdit'; + type: 'rewrite'; + diffareaid: number; // id of the CtrlK area (contains text selection) +} | { + from: 'ClickApply'; + type: 'searchReplace' | 'rewrite'; + applyStr: string; +} + + + +export type AddCtrlKOpts = { + startLine: number, + endLine: number, + editor: ICodeEditor, +} + +// // TODO diffArea should be removed if we just discovered it has no more diffs in it +// for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { +// const diffArea = this.diffAreaOfId[diffareaid] +// if (Object.keys(diffArea._diffOfId).length === 0 && !diffArea._sweepState.isStreaming) { +// const { onFinishEdit } = this._addToHistory(uri) +// this._deleteDiffArea(diffArea) +// onFinishEdit() +// } +// } + + +export type Diff = { + diffid: number; + diffareaid: number; // the diff area this diff belongs to, "computed" +} & ComputedDiff + + + +// _ means anything we don't include if we clone it +// DiffArea.originalStartLine is the line in originalCode (not the file) + +type CommonZoneProps = { + diffareaid: number; + startLine: number; + endLine: number; + + _URI: URI; // typically we get the URI from model + +} + +type CtrlKZone = { + type: 'CtrlKZone'; + originalCode?: undefined; + + editorId: string; // the editor the input lives on + + _mountInfo: null | { + textAreaRef: { current: HTMLTextAreaElement | null } + dispose: () => void; + refresh: () => void; + } + + _linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here + _removeStylesFns: Set // these don't remove diffs or this diffArea, only their styles + +} & CommonZoneProps + + +type DiffZone = { + type: 'DiffZone', + originalCode: string; + _diffOfId: Record; // diffid -> diff in this DiffArea + _streamState: { + isStreaming: true; + streamRequestIdRef: { current: string | null }; + line: number; + } | { + isStreaming: false; + streamRequestIdRef?: undefined; + line?: undefined; + }; + editorId?: undefined; + linkedStreamingDiffZone?: undefined; + _removeStylesFns: Set // these don't remove diffs or this diffArea, only their styles +} & CommonZoneProps + + + +type TrackingZone = { + type: 'TrackingZone'; + metadata: T; + originalCode?: undefined; + editorId?: undefined; + _removeStylesFns?: undefined; +} & CommonZoneProps + + +// called DiffArea for historical purposes, we can rename to something like TextRegion if we want +type DiffArea = CtrlKZone | DiffZone | TrackingZone + +const diffAreaSnapshotKeys = [ + 'type', + 'diffareaid', + 'originalCode', + 'startLine', + 'endLine', + 'editorId', + +] as const satisfies (keyof DiffArea)[] + +type DiffAreaSnapshot = Pick + + + +type HistorySnapshot = { + snapshottedDiffAreaOfId: Record; + entireFileCode: string; +} + + + +// line/col is the location, originalCodeStartLine is the start line of the original code being displayed +type StreamLocationMutable = { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number } + + +export interface IEditCodeService { + readonly _serviceBrand: undefined; + startApplying(opts: StartApplyingOpts): URI | null; + + addCtrlKZone(opts: AddCtrlKOpts): number | undefined; + removeCtrlKZone(opts: { diffareaid: number }): void; + removeDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept' }): void; + + // CtrlKZone streaming state + isCtrlKZoneStreaming(opts: { diffareaid: number }): boolean; + interruptCtrlKStreaming(opts: { diffareaid: number }): void; + onDidChangeCtrlKZoneStreaming: Event<{ uri: URI; diffareaid: number }>; + + // // DiffZone codeBoxId streaming state + getURIStreamState(opts: { uri: URI | null }): URIStreamState; + interruptURIStreaming(opts: { uri: URI }): void; + onDidChangeURIStreamState: Event<{ uri: URI; state: URIStreamState }>; + + // testDiffs(): void; +} + +export const IEditCodeService = createDecorator('editCodeService'); + +class EditCodeService extends Disposable implements IEditCodeService { + _serviceBrand: undefined; + + + // URI <--> model + diffAreasOfURI: Record> = {} + + diffAreaOfId: Record = {}; + diffOfId: Record = {}; // redundant with diffArea._diffs + + + // only applies to diffZones + // streamingDiffZones: Set = new Set() + private readonly _onDidChangeDiffZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); + private readonly _onDidAddOrDeleteDiffZones = new Emitter<{ uri: URI }>(); + + private readonly _onDidChangeCtrlKZoneStreaming = new Emitter<{ uri: URI; diffareaid: number }>(); + onDidChangeCtrlKZoneStreaming = this._onDidChangeCtrlKZoneStreaming.event + + private readonly _onDidChangeURIStreamState = new Emitter<{ uri: URI; state: URIStreamState }>(); + onDidChangeURIStreamState = this._onDidChangeURIStreamState.event + + + + + constructor( + // @IHistoryService private readonly _historyService: IHistoryService, // history service is the history of pressing alt left/right + @ICodeEditorService private readonly _editorService: ICodeEditorService, + @IModelService private readonly _modelService: IModelService, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, // undoRedo service is the history of pressing ctrl+z + @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, + @IConsistentItemService private readonly _consistentItemService: IConsistentItemService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConsistentEditorItemService private readonly _consistentEditorItemService: IConsistentEditorItemService, + @IMetricsService private readonly _metricsService: IMetricsService, + @INotificationService private readonly _notificationService: INotificationService, + @ICommandService private readonly _commandService: ICommandService, + @IVoidFileService private readonly _voidFileService: IVoidFileService, + ) { + super(); + + // this function initializes data structures and listens for changes + const initializeModel = (model: ITextModel) => { + if (!(model.uri.fsPath in this.diffAreasOfURI)) { + this.diffAreasOfURI[model.uri.fsPath] = new Set(); + } + else return // do not add listeners to the same model twice - important, or will see duplicates + + // when the user types, realign diff areas and re-render them + this._register( + model.onDidChangeContent(e => { + // it's as if we just called _write, now all we need to do is realign and refresh + if (this.weAreWriting) return + const uri = model.uri + this._onUserChangeContent(uri, e) + }) + ) + + // when a stream starts or ends, fire the event for onDidChangeURIStreamState + let prevStreamState = this.getURIStreamState({ uri: model.uri }) + const updateAcceptRejectAllUI = () => { + const state = this.getURIStreamState({ uri: model.uri }) + let prevStateActual = prevStreamState + prevStreamState = state + if (state === prevStateActual) return + this._onDidChangeURIStreamState.fire({ uri: model.uri, state }) + } + + + let _removeAcceptRejectAllUI: (() => void) | null = null + this._register(this._onDidChangeURIStreamState.event(({ uri, state }) => { + if (uri.fsPath !== model.uri.fsPath) return + if (state === 'acceptRejectAll') { + if (!_removeAcceptRejectAllUI) + _removeAcceptRejectAllUI = this._addAcceptRejectAllUI(model.uri) ?? null + } else { + _removeAcceptRejectAllUI?.() + _removeAcceptRejectAllUI = null + } + })) + this._register(this._onDidChangeDiffZoneStreaming.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() })) + this._register(this._onDidAddOrDeleteDiffZones.event(({ uri: uri_ }) => { if (uri_.fsPath === model.uri.fsPath) updateAcceptRejectAllUI() })) + + + } + // initialize all existing models + initialize when a new model mounts + for (let model of this._modelService.getModels()) { initializeModel(model) } + this._register(this._modelService.onModelAdded(model => initializeModel(model))); + + + // this function adds listeners to refresh styles when editor changes tab + let initializeEditor = (editor: ICodeEditor) => { + const uri = editor.getModel()?.uri ?? null + if (uri) this._refreshStylesAndDiffsInURI(uri) + } + // add listeners for all existing editors + listen for editor being added + for (let editor of this._editorService.listCodeEditors()) { initializeEditor(editor) } + this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) + + } + + + private _onUserChangeContent(uri: URI, e: IModelContentChangedEvent) { + for (const change of e.changes) { + this._realignAllDiffAreasLines(uri, change.text, change.range) + } + this._refreshStylesAndDiffsInURI(uri) + } + + private _onInternalChangeContent(uri: URI, { shouldRealign }: { shouldRealign: false | { newText: string, oldRange: IRange } }) { + if (shouldRealign) { + const { newText, oldRange } = shouldRealign + // console.log('realiging', newText, oldRange) + this._realignAllDiffAreasLines(uri, newText, oldRange) + } + this._refreshStylesAndDiffsInURI(uri) + + } + + + + + private _notifyError = (e: Parameters[0]) => { + const details = errorDetails(e.fullError) + this._notificationService.notify({ + severity: Severity.Warning, + message: `Void Error: ${e.message}`, + actions: { + secondary: [{ + id: 'void.onerror.opensettings', + enabled: true, + label: `Open Void's settings`, + tooltip: '', + class: undefined, + run: () => { this._commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) } + }] + }, + source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}\n\nIf this persists, feel free to [report](https://github.com/voideditor/void/issues/new) it.` : undefined + }) + } + + + + // highlight the region + private _addLineDecoration = (model: ITextModel | null, startLine: number, endLine: number, className: string, options?: Partial) => { + if (model === null) return + const id = model.changeDecorations(accessor => accessor.addDecoration( + { startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_SAFE_INTEGER }, + { + className: className, + description: className, + isWholeLine: true, + ...options + })) + const disposeHighlight = () => { + if (id && !model.isDisposed()) model.changeDecorations(accessor => accessor.removeDecoration(id)) + } + return disposeHighlight + } + + + private _addDiffAreaStylesToURI = (uri: URI) => { + const model = this._getModel(uri) + + for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + + if (diffArea.type === 'DiffZone') { + // add sweep styles to the diffZone + if (diffArea._streamState.isStreaming) { + // sweepLine ... sweepLine + const fn1 = this._addLineDecoration(model, diffArea._streamState.line, diffArea._streamState.line, 'void-sweepIdxBG') + // sweepLine+1 ... endLine + const fn2 = diffArea._streamState.line + 1 <= diffArea.endLine ? + this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'void-sweepBG') + : null + diffArea._removeStylesFns.add(() => { fn1?.(); fn2?.(); }) + + } + } + + else if (diffArea.type === 'CtrlKZone' && diffArea._linkedStreamingDiffZone === null) { + // highlight zone's text + const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'void-highlightBG') + diffArea._removeStylesFns.add(() => fn?.()); + } + } + } + + + private _computeDiffsAndAddStylesToURI = (uri: URI) => { + const fullFileText = this._readURI(uri) ?? '' + + for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type !== 'DiffZone') continue + + const newDiffAreaCode = fullFileText.split('\n').slice((diffArea.startLine - 1), (diffArea.endLine - 1) + 1).join('\n') + const computedDiffs = findDiffs(diffArea.originalCode, newDiffAreaCode) + for (let computedDiff of computedDiffs) { + if (computedDiff.type === 'deletion') { + computedDiff.startLine += diffArea.startLine - 1 + } + if (computedDiff.type === 'edit' || computedDiff.type === 'insertion') { + computedDiff.startLine += diffArea.startLine - 1 + computedDiff.endLine += diffArea.startLine - 1 + } + this._addDiff(computedDiff, diffArea) + } + + } + } + + private _addAcceptRejectAllUI(uri: URI) { + + // find all diffzones that aren't streaming + const diffZones: DiffZone[] = [] + for (let diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type !== 'DiffZone') continue + if (diffArea._streamState.isStreaming) continue + diffZones.push(diffArea) + } + if (diffZones.length === 0) return + + const consistentItemId = this._consistentItemService.addConsistentItemToURI({ + uri, + fn: (editor) => { + const buttonsWidget = new AcceptAllRejectAllWidget({ + editor, + onAcceptAll: () => { + this.removeDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false }) + this._metricsService.capture('Accept All', {}) + }, + onRejectAll: () => { + this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false }) + this._metricsService.capture('Reject All', {}) + }, + }) + return () => { buttonsWidget.dispose() } + } + }) + + + return () => { this._consistentItemService.removeConsistentItemFromURI(consistentItemId) } + } + + + mostRecentTextOfCtrlKZoneId: Record = {} + private _addCtrlKZoneInput = (ctrlKZone: CtrlKZone) => { + + const { editorId } = ctrlKZone + const editor = this._editorService.listCodeEditors().find(e => e.getId() === editorId) + if (!editor) { return null } + + let zoneId: string | null = null + let viewZone_: IViewZone | null = null + const textAreaRef: { current: HTMLTextAreaElement | null } = { current: null } + + + const paddingLeft = getLeadingWhitespacePx(editor, ctrlKZone.startLine) + + const itemId = this._consistentEditorItemService.addToEditor(editor, () => { + const domNode = document.createElement('div'); + domNode.style.zIndex = '1' + domNode.style.height = 'auto' + domNode.style.paddingLeft = `${paddingLeft}px` + const viewZone: IViewZone = { + afterLineNumber: ctrlKZone.startLine - 1, + domNode: domNode, + // heightInPx: 80, + suppressMouseDown: false, + showInHiddenAreas: true, + }; + viewZone_ = viewZone + + // mount zone + editor.changeViewZones(accessor => { + zoneId = accessor.addZone(viewZone) + }) + + // mount react + this._instantiationService.invokeFunction(accessor => { + mountCtrlK(domNode, accessor, { + + diffareaid: ctrlKZone.diffareaid, + + textAreaRef: (r) => { + textAreaRef.current = r + if (!textAreaRef.current) return + + if (!(ctrlKZone.diffareaid in this.mostRecentTextOfCtrlKZoneId)) { // detect first mount this way (a hack) + this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] = undefined + setTimeout(() => textAreaRef.current?.focus(), 100) + } + }, + onChangeHeight(height) { + if (height === 0) return // the viewZone sets this height to the container if it's out of view, ignore it + viewZone.heightInPx = height + // re-render with this new height + editor.changeViewZones(accessor => { + if (zoneId) accessor.layoutZone(zoneId) + }) + }, + onChangeText: (text) => { + this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] = text; + }, + initText: this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] ?? null, + } satisfies QuickEditPropsType) + + }) + + return () => editor.changeViewZones(accessor => { + if (zoneId) + accessor.removeZone(zoneId) + }) + }) + + return { + textAreaRef, + refresh: () => editor.changeViewZones(accessor => { + if (zoneId && viewZone_) { + viewZone_.afterLineNumber = ctrlKZone.startLine - 1 + accessor.layoutZone(zoneId) + } + }), + dispose: () => { + this._consistentEditorItemService.removeFromEditor(itemId) + }, + } satisfies CtrlKZone['_mountInfo'] + } + + + + private _refreshCtrlKInputs = async (uri: URI) => { + for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type !== 'CtrlKZone') continue + if (!diffArea._mountInfo) { + diffArea._mountInfo = this._addCtrlKZoneInput(diffArea) + // console.log('MOUNTED', diffArea.diffareaid) + } + else { + diffArea._mountInfo.refresh() + } + } + } + + + private _addDiffStylesToURI = (uri: URI, diff: Diff) => { + const { type, diffid } = diff + + const disposeInThisEditorFns: (() => void)[] = [] + + const model = this._modelService.getModel(uri) + + // green decoration and minimap decoration + if (type !== 'deletion') { + const fn = this._addLineDecoration(model, diff.startLine, diff.endLine, 'void-greenBG', { + minimap: { color: { id: 'minimapGutter.addedBackground' }, position: 2 }, + overviewRuler: { color: { id: 'editorOverviewRuler.addedForeground' }, position: 7 } + }) + disposeInThisEditorFns.push(() => { fn?.() }) + } + + + // red in a view zone + if (type !== 'insertion') { + const consistentZoneId = this._consistentItemService.addConsistentItemToURI({ + uri, + fn: (editor) => { + + const domNode = document.createElement('div'); + domNode.className = 'void-redBG' + + const renderOptions = RenderOptions.fromEditor(editor) + + const processedText = diff.originalCode.replace(/\t/g, ' '.repeat(renderOptions.tabSize)); + + const lines = processedText.split('\n'); + + const linesContainer = document.createElement('div'); + linesContainer.style.fontFamily = renderOptions.fontInfo.fontFamily + linesContainer.style.fontSize = `${renderOptions.fontInfo.fontSize}px` + linesContainer.style.lineHeight = `${renderOptions.fontInfo.lineHeight}px` + // linesContainer.style.tabSize = `${tabWidth}px` // \t + linesContainer.style.whiteSpace = 'pre' + linesContainer.style.position = 'relative' + linesContainer.style.width = '100%' + + lines.forEach(line => { + // div for current line + const lineDiv = document.createElement('div'); + lineDiv.className = 'view-line'; + lineDiv.style.whiteSpace = 'pre' + lineDiv.style.position = 'relative' + lineDiv.style.height = `${renderOptions.fontInfo.lineHeight}px` + + // span (this is just how vscode does it) + const span = document.createElement('span'); + span.textContent = line || '\u00a0'; + span.style.whiteSpace = 'pre' + span.style.display = 'inline-block' + + lineDiv.appendChild(span); + linesContainer.appendChild(lineDiv); + }); + + domNode.appendChild(linesContainer); + + // Calculate height based on number of lines and line height + const heightInLines = lines.length; + const minWidthInPx = Math.max(...lines.map(line => + Math.ceil(renderOptions.fontInfo.typicalFullwidthCharacterWidth * line.length) + )); + + const viewZone: IViewZone = { + afterLineNumber: diff.startLine - 1, + heightInLines, + minWidthInPx, + domNode, + marginDomNode: document.createElement('div'), + suppressMouseDown: false, + showInHiddenAreas: false, + }; + + let zoneId: string | null = null + editor.changeViewZones(accessor => { zoneId = accessor.addZone(viewZone) }) + return () => editor.changeViewZones(accessor => { if (zoneId) accessor.removeZone(zoneId) }) + }, + }) + + disposeInThisEditorFns.push(() => { this._consistentItemService.removeConsistentItemFromURI(consistentZoneId) }) + + } + + + + const diffZone = this.diffAreaOfId[diff.diffareaid] + if (diffZone.type === 'DiffZone' && !diffZone._streamState.isStreaming) { + // Accept | Reject widget + const consistentWidgetId = this._consistentItemService.addConsistentItemToURI({ + uri, + fn: (editor) => { + let startLine: number + let offsetLines: number + if (diff.type === 'insertion' || diff.type === 'edit') { + startLine = diff.startLine // green start + offsetLines = 0 + } + else if (diff.type === 'deletion') { + // if diff.startLine is out of bounds + if (diff.startLine === 1) { + const numRedLines = diff.originalEndLine - diff.originalStartLine + 1 + startLine = diff.startLine + offsetLines = -numRedLines + } + else { + startLine = diff.startLine - 1 + offsetLines = 1 + } + } + else { throw 1 } + + const buttonsWidget = new AcceptRejectWidget({ + editor, + onAccept: () => { + this.acceptDiff({ diffid }) + this._metricsService.capture('Accept Diff', {}) + }, + onReject: () => { + this.rejectDiff({ diffid }) + this._metricsService.capture('Reject Diff', {}) + }, + diffid: diffid.toString(), + startLine, + offsetLines + }) + return () => { buttonsWidget.dispose() } + } + }) + disposeInThisEditorFns.push(() => { this._consistentItemService.removeConsistentItemFromURI(consistentWidgetId) }) + } + + const disposeInEditor = () => { disposeInThisEditorFns.forEach(f => f()) } + return disposeInEditor; + + } + + + + private _getModel(uri: URI) { + const model = this._modelService.getModel(uri) + if (!model || model.isDisposed()) { + return null + } + return model + } + private _readURI(uri: URI, range?: IRange): string | null { + if (!range) return this._getModel(uri)?.getValue(EndOfLinePreference.LF) ?? null + else return this._getModel(uri)?.getValueInRange(range, EndOfLinePreference.LF) ?? null + } + private _getNumLines(uri: URI): number | null { + return this._getModel(uri)?.getLineCount() ?? null + } + private _getActiveEditorURI(): URI | null { + const editor = this._editorService.getActiveCodeEditor() + if (!editor) return null + const uri = editor.getModel()?.uri + if (!uri) return null + return uri + } + + weAreWriting = false + private _writeText(uri: URI, text: string, range: IRange, { shouldRealignDiffAreas }: { shouldRealignDiffAreas: boolean }) { + const model = this._getModel(uri) + if (!model) return + const uriStr = this._readURI(uri, range) + if (uriStr === null) return + + + // heuristic check if don't need to make edits + const dontNeedToWrite = uriStr === text + if (dontNeedToWrite) { + // at the end of a write, we still expect to refresh all styles + // e.g. sometimes we expect to restore all the decorations even if no edits were made when _writeText is used + this._refreshStylesAndDiffsInURI(uri) + return + } + + // minimal edits so not so flashy + // const edits = this.worker.$Void_computeMoreMinimalEdits(uri.toString(), [{ range, text }], false) + this.weAreWriting = true + model.applyEdits([{ range, text }]) + this.weAreWriting = false + + this._onInternalChangeContent(uri, { shouldRealign: shouldRealignDiffAreas && { newText: text, oldRange: range } }) + + } + + + + + private _addToHistory(uri: URI) { + + const getCurrentSnapshot = (): HistorySnapshot => { + const snapshottedDiffAreaOfId: Record = {} + + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + + if (diffArea._URI.fsPath !== uri.fsPath) continue + + snapshottedDiffAreaOfId[diffareaid] = structuredClone( // a structured clone must be on a JSON object + Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]])) + ) as DiffAreaSnapshot + } + return { + snapshottedDiffAreaOfId, + entireFileCode: this._readURI(uri) ?? '', // the whole file's code + } + } + + const restoreDiffAreas = (snapshot: HistorySnapshot) => { + + // for each diffarea in this uri, stop streaming if currently streaming + for (const diffareaid in this.diffAreaOfId) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type === 'DiffZone') + this._stopIfStreaming(diffArea) + } + + // delete all diffareas on this uri (clearing their styles) + this._deleteAllDiffAreas(uri) + this.diffAreasOfURI[uri.fsPath].clear() + + const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot + + // restore diffAreaOfId and diffAreasOfModelId + for (const diffareaid in snapshottedDiffAreaOfId) { + + const snapshottedDiffArea = snapshottedDiffAreaOfId[diffareaid] + + if (snapshottedDiffArea.type === 'DiffZone') { + this.diffAreaOfId[diffareaid] = { + ...snapshottedDiffArea as DiffAreaSnapshot, + type: 'DiffZone', + _diffOfId: {}, + _URI: uri, + _streamState: { isStreaming: false }, // when restoring, we will never be streaming + _removeStylesFns: new Set(), + } + } + else if (snapshottedDiffArea.type === 'CtrlKZone') { + this.diffAreaOfId[diffareaid] = { + ...snapshottedDiffArea as DiffAreaSnapshot, + _URI: uri, + _removeStylesFns: new Set(), + _mountInfo: null, + _linkedStreamingDiffZone: null, // when restoring, we will never be streaming + } + } + this.diffAreasOfURI[uri.fsPath].add(diffareaid) + } + this._onDidAddOrDeleteDiffZones.fire({ uri }) + + // restore file content + const numLines = this._getNumLines(uri) + if (numLines === null) return + + + this._writeText(uri, entireModelCode, + { startColumn: 1, startLineNumber: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }, + { shouldRealignDiffAreas: false } + ) + } + + const beforeSnapshot: HistorySnapshot = getCurrentSnapshot() + let afterSnapshot: HistorySnapshot | null = null + + const elt: IUndoRedoElement = { + type: UndoRedoElementType.Resource, + resource: uri, + label: 'Void Changes', + code: 'undoredo.editCode', + undo: () => { restoreDiffAreas(beforeSnapshot) }, + redo: () => { if (afterSnapshot) restoreDiffAreas(afterSnapshot) } + } + this._undoRedoService.pushElement(elt) + + const onFinishEdit = () => { afterSnapshot = getCurrentSnapshot() } + return { onFinishEdit } + } + + + // delete diffOfId and diffArea._diffOfId + private _deleteDiff(diff: Diff) { + const diffArea = this.diffAreaOfId[diff.diffareaid] + if (diffArea.type !== 'DiffZone') return + delete diffArea._diffOfId[diff.diffid] + delete this.diffOfId[diff.diffid] + } + + private _deleteDiffs(diffZone: DiffZone) { + for (const diffid in diffZone._diffOfId) { + const diff = diffZone._diffOfId[diffid] + this._deleteDiff(diff) + } + } + + private _clearAllDiffAreaEffects(diffArea: DiffArea) { + // clear diffZone effects (diffs) + if (diffArea.type === 'DiffZone') + this._deleteDiffs(diffArea) + + diffArea._removeStylesFns?.forEach(removeStyles => removeStyles()) + diffArea._removeStylesFns?.clear() + } + + + // clears all Diffs (and their styles) and all styles of DiffAreas, etc + private _clearAllEffects(uri: URI) { + for (let diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + this._clearAllDiffAreaEffects(diffArea) + } + } + + + // delete all diffs, update diffAreaOfId, update diffAreasOfModelId + private _deleteDiffZone(diffZone: DiffZone) { + this._clearAllDiffAreaEffects(diffZone) + delete this.diffAreaOfId[diffZone.diffareaid] + this.diffAreasOfURI[diffZone._URI.fsPath].delete(diffZone.diffareaid.toString()) + this._onDidAddOrDeleteDiffZones.fire({ uri: diffZone._URI }) + } + + private _deleteTrackingZone(trackingZone: TrackingZone) { + delete this.diffAreaOfId[trackingZone.diffareaid] + this.diffAreasOfURI[trackingZone._URI.fsPath].delete(trackingZone.diffareaid.toString()) + } + + private _deleteCtrlKZone(ctrlKZone: CtrlKZone) { + this._clearAllEffects(ctrlKZone._URI) + ctrlKZone._mountInfo?.dispose() + delete this.diffAreaOfId[ctrlKZone.diffareaid] + this.diffAreasOfURI[ctrlKZone._URI.fsPath].delete(ctrlKZone.diffareaid.toString()) + } + + + private _deleteAllDiffAreas(uri: URI) { + const diffAreas = this.diffAreasOfURI[uri.fsPath] + diffAreas.forEach(diffareaid => { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea.type === 'DiffZone') + this._deleteDiffZone(diffArea) + else if (diffArea.type === 'CtrlKZone') + this._deleteCtrlKZone(diffArea) + }) + } + + + + private _diffareaidPool = 0 // each diffarea has an id + private _addDiffArea(diffArea: Omit): T { + const diffareaid = this._diffareaidPool++ + const diffArea2 = { ...diffArea, diffareaid } as T + this.diffAreasOfURI[diffArea2._URI.fsPath].add(diffareaid.toString()) + this.diffAreaOfId[diffareaid] = diffArea2 + return diffArea2 + } + + private _diffidPool = 0 // each diff has an id + private _addDiff(computedDiff: ComputedDiff, diffZone: DiffZone): Diff { + const uri = diffZone._URI + const diffid = this._diffidPool++ + + // create a Diff of it + const newDiff: Diff = { + ...computedDiff, + diffid: diffid, + diffareaid: diffZone.diffareaid, + } + + const fn = this._addDiffStylesToURI(uri, newDiff) + diffZone._removeStylesFns.add(fn) + + this.diffOfId[diffid] = newDiff + diffZone._diffOfId[diffid] = newDiff + + return newDiff + } + + + + + // changes the start/line locations of all DiffAreas on the page (adjust their start/end based on the change) based on the change that was recently made + private _realignAllDiffAreasLines(uri: URI, text: string, recentChange: { startLineNumber: number; endLineNumber: number }) { + + // console.log('recent change', recentChange) + + const model = this._getModel(uri) + if (!model) return + + // compute net number of newlines lines that were added/removed + const startLine = recentChange.startLineNumber + const endLine = recentChange.endLineNumber + + const newTextHeight = (text.match(/\n/g) || []).length + 1 // number of newlines is number of \n's + 1, e.g. "ab\ncd" + + // compute overlap with each diffArea and shrink/elongate each diffArea accordingly + for (const diffareaid of this.diffAreasOfURI[model.uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + + // if the diffArea is entirely above the range, it is not affected + if (diffArea.endLine < startLine) { + // console.log('CHANGE FULLY BELOW DA (doing nothing)') + continue + } + // if a diffArea is entirely below the range, shift the diffArea up/down by the delta amount of newlines + else if (endLine < diffArea.startLine) { + // console.log('CHANGE FULLY ABOVE DA') + const changedRangeHeight = endLine - startLine + 1 + const deltaNewlines = newTextHeight - changedRangeHeight + diffArea.startLine += deltaNewlines + diffArea.endLine += deltaNewlines + } + // if the diffArea fully contains the change, elongate it by the delta amount of newlines + else if (startLine >= diffArea.startLine && endLine <= diffArea.endLine) { + // console.log('DA FULLY CONTAINS CHANGE') + const changedRangeHeight = endLine - startLine + 1 + const deltaNewlines = newTextHeight - changedRangeHeight + diffArea.endLine += deltaNewlines + } + // if the change fully contains the diffArea, make the diffArea have the same range as the change + else if (diffArea.startLine > startLine && diffArea.endLine < endLine) { + // console.log('CHANGE FULLY CONTAINS DA') + diffArea.startLine = startLine + diffArea.endLine = startLine + newTextHeight + } + // if the change contains only the diffArea's top + else if (startLine < diffArea.startLine && diffArea.startLine <= endLine) { + // console.log('CHANGE CONTAINS TOP OF DA ONLY') + const numOverlappingLines = endLine - diffArea.startLine + 1 + const numRemainingLinesInDA = diffArea.endLine - diffArea.startLine + 1 - numOverlappingLines + const newHeight = (numRemainingLinesInDA - 1) + (newTextHeight - 1) + 1 + diffArea.startLine = startLine + diffArea.endLine = startLine + newHeight + } + // if the change contains only the diffArea's bottom + else if (startLine <= diffArea.endLine && diffArea.endLine < endLine) { + // console.log('CHANGE CONTAINS BOTTOM OF DA ONLY') + const numOverlappingLines = diffArea.endLine - startLine + 1 + diffArea.endLine += newTextHeight - numOverlappingLines + } + } + + } + + + private _refreshStylesAndDiffsInURI(uri: URI) { + + // 1. clear DiffArea styles and Diffs + this._clearAllEffects(uri) + + // 2. style DiffAreas (sweep, etc) + this._addDiffAreaStylesToURI(uri) + + // 3. add Diffs + this._computeDiffsAndAddStylesToURI(uri) + + // 4. refresh ctrlK zones + this._refreshCtrlKInputs(uri) + } + + + + + // @throttle(100) + private _writeStreamedDiffZoneLLMText(uri: URI, originalCode: string, llmTextSoFar: string, deltaText: string, latestMutable: StreamLocationMutable) { + + let numNewLines = 0 + + // ----------- 1. Write the new code to the document ----------- + // figure out where to highlight based on where the AI is in the stream right now, use the last diff to figure that out + const computedDiffs = findDiffs(originalCode, llmTextSoFar) + + // if streaming, use diffs to figure out where to write new code + // these are two different coordinate systems - new and old line number + let endLineInLlmTextSoFar: number // get file[diffArea.startLine...newFileEndLine] with line=newFileEndLine highlighted + let startLineInOriginalCode: number // get original[oldStartingPoint...] (line in the original code, so starts at 1) + + const lastDiff = computedDiffs.pop() + + if (!lastDiff) { + // console.log('!lastDiff') + // if the writing is identical so far, display no changes + startLineInOriginalCode = 1 + endLineInLlmTextSoFar = 1 + } + else { + startLineInOriginalCode = lastDiff.originalStartLine + if (lastDiff.type === 'insertion' || lastDiff.type === 'edit') + endLineInLlmTextSoFar = lastDiff.endLine + else if (lastDiff.type === 'deletion') + endLineInLlmTextSoFar = lastDiff.startLine + else + throw new Error(`Void: diff.type not recognized on: ${lastDiff}`) + } + + // at the start, add a newline between the stream and originalCode to make reasoning easier + if (!latestMutable.addedSplitYet) { + this._writeText(uri, '\n', + { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line, endColumn: latestMutable.col, }, + { shouldRealignDiffAreas: true } + ) + latestMutable.addedSplitYet = true + numNewLines += 1 + } + + // insert deltaText at latest line and col + this._writeText(uri, deltaText, + { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line, endColumn: latestMutable.col }, + { shouldRealignDiffAreas: true } + ) + const deltaNumNewLines = deltaText.split('\n').length - 1 + latestMutable.line += deltaNumNewLines + const lastNewlineIdx = deltaText.lastIndexOf('\n') + latestMutable.col = lastNewlineIdx === -1 ? latestMutable.col + deltaText.length : deltaText.length - lastNewlineIdx + numNewLines += deltaNumNewLines + + // delete or insert to get original up to speed + if (latestMutable.originalCodeStartLine < startLineInOriginalCode) { + // moved up, delete + const numLinesDeleted = startLineInOriginalCode - latestMutable.originalCodeStartLine + this._writeText(uri, '', + { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line + numLinesDeleted, endColumn: Number.MAX_SAFE_INTEGER, }, + { shouldRealignDiffAreas: true } + ) + numNewLines -= numLinesDeleted + } + else if (latestMutable.originalCodeStartLine > startLineInOriginalCode) { + const newText = '\n' + originalCode.split('\n').slice((startLineInOriginalCode - 1), (latestMutable.originalCodeStartLine - 1) - 1 + 1).join('\n') + this._writeText(uri, newText, + { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line, endColumn: latestMutable.col }, + { shouldRealignDiffAreas: true } + ) + numNewLines += newText.split('\n').length - 1 + } + latestMutable.originalCodeStartLine = startLineInOriginalCode + + return { endLineInLlmTextSoFar, numNewLines } // numNewLines here might not be correct.... + } + + + + + // called first, then call startApplying + public addCtrlKZone({ startLine, endLine, editor }: AddCtrlKOpts) { + + const uri = editor.getModel()?.uri + if (!uri) return + + // check if there's overlap with any other ctrlKZone and if so, focus it + const overlappingCtrlKZone = this._findOverlappingDiffArea({ startLine, endLine, uri, filter: (diffArea) => diffArea.type === 'CtrlKZone' }) + if (overlappingCtrlKZone) { + editor.revealLine(overlappingCtrlKZone.startLine) // important + setTimeout(() => (overlappingCtrlKZone as CtrlKZone)._mountInfo?.textAreaRef.current?.focus(), 100) + return + } + + const overlappingDiffZone = this._findOverlappingDiffArea({ startLine, endLine, uri, filter: (diffArea) => diffArea.type === 'DiffZone' }) + if (overlappingDiffZone) + return + + editor.revealLine(startLine) + editor.setSelection({ startLineNumber: startLine, endLineNumber: startLine, startColumn: 1, endColumn: 1 }) + + const { onFinishEdit } = this._addToHistory(uri) + + const adding: Omit = { + type: 'CtrlKZone', + startLine: startLine, + endLine: endLine, + editorId: editor.getId(), + _URI: uri, + _removeStylesFns: new Set(), + _mountInfo: null, + _linkedStreamingDiffZone: null, + } + const ctrlKZone = this._addDiffArea(adding) + this._refreshStylesAndDiffsInURI(uri) + + onFinishEdit() + return ctrlKZone.diffareaid + } + + // _remove means delete and also add to history + public removeCtrlKZone({ diffareaid }: { diffareaid: number }) { + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (!ctrlKZone) return + if (ctrlKZone.type !== 'CtrlKZone') return + + const uri = ctrlKZone._URI + const { onFinishEdit } = this._addToHistory(uri) + this._deleteCtrlKZone(ctrlKZone) + this._refreshStylesAndDiffsInURI(uri) + onFinishEdit() + } + + + + + + + + public startApplying(opts: StartApplyingOpts) { + if (opts.type === 'rewrite') { + const addedDiffArea = this._initializeWriteoverStream(opts) + return addedDiffArea?._URI ?? null + } + else if (opts.type === 'searchReplace') { + const addedDiffArea = this._initializeSearchAndReplaceStream(opts) + return addedDiffArea?._URI ?? null + } + return null + } + + + + + private _findOverlappingDiffArea({ startLine, endLine, uri, filter }: { startLine: number, endLine: number, uri: URI, filter?: (diffArea: DiffArea) => boolean }): DiffArea | null { + // check if there's overlap with any other diffAreas and return early if there is + for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) continue + if (!filter?.(diffArea)) continue + const noOverlap = diffArea.startLine > endLine || diffArea.endLine < startLine + if (!noOverlap) { + return diffArea + } + } + return null + } + + + + + + + private _initializeWriteoverStream(opts: StartApplyingOpts): DiffZone | undefined { + + const { from } = opts + + let startLine: number + let endLine: number + let uri: URI + + if (from === 'ClickApply') { + + const uri_ = this._getActiveEditorURI() + if (!uri_) return + uri = uri_ + + // reject all diffZones on this URI, adding to history (there can't possibly be overlap after this) + this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: true }) + + // in ctrl+L the start and end lines are the full document + const numLines = this._getNumLines(uri) + if (numLines === null) return + startLine = 1 + endLine = numLines + + } + else if (from === 'QuickEdit') { + const { diffareaid } = opts + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (ctrlKZone.type !== 'CtrlKZone') return + + const { startLine: startLine_, endLine: endLine_, _URI } = ctrlKZone + uri = _URI + startLine = startLine_ + endLine = endLine_ + } + else { + throw new Error(`Void: diff.type not recognized on: ${from}`) + } + + const currentFileStr = this._readURI(uri) + if (currentFileStr === null) return + const originalCode = currentFileStr.split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n') + + + let streamRequestIdRef: { current: string | null } = { current: null } + + + // add to history + const { onFinishEdit } = this._addToHistory(uri) + + // __TODO__ let users customize modelFimTags + const quickEditFIMTags = defaultQuickEditFimTags + + const adding: Omit = { + type: 'DiffZone', + originalCode, + startLine, + endLine, + _URI: uri, + _streamState: { + isStreaming: true, + streamRequestIdRef, + line: startLine, + }, + _diffOfId: {}, // added later + _removeStylesFns: new Set(), + } + const diffZone = this._addDiffArea(adding) + this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + this._onDidAddOrDeleteDiffZones.fire({ uri }) + + if (from === 'QuickEdit') { + const { diffareaid } = opts + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (ctrlKZone.type !== 'CtrlKZone') return + + ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid + this._onDidChangeCtrlKZoneStreaming.fire({ uri, diffareaid: ctrlKZone.diffareaid }) + } + + // now handle messages + let messages: LLMChatMessage[] + + if (from === 'ClickApply') { + const userContent = rewriteCode_userMessage({ originalCode, applyStr: opts.applyStr, uri }) + messages = [ + { role: 'system', content: rewriteCode_systemMessage, }, + { role: 'user', content: userContent, } + ] + } + else if (from === 'QuickEdit') { + const { diffareaid } = opts + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (ctrlKZone.type !== 'CtrlKZone') return + const { _mountInfo } = ctrlKZone + const instructions = _mountInfo?.textAreaRef.current?.value ?? '' + + const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine }) + const language = filenameToVscodeLanguage(uri.fsPath) ?? '' + const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, isOllamaFIM: false, fimTags: quickEditFIMTags, language }) + // type: 'messages', + messages = [ + { role: 'system', content: ctrlKStream_systemMessage({ quickEditFIMTags: quickEditFIMTags }), }, + { role: 'user', content: userContent, } + ] + } + else { throw new Error(`featureName ${from} is invalid`) } + + + const onDone = () => { + diffZone._streamState = { isStreaming: false, } + this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + + if (from === 'QuickEdit') { + const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone + + ctrlKZone._linkedStreamingDiffZone = null + this._onDidChangeCtrlKZoneStreaming.fire({ uri, diffareaid: ctrlKZone.diffareaid }) + this._deleteCtrlKZone(ctrlKZone) + } + this._refreshStylesAndDiffsInURI(uri) + onFinishEdit() + } + + // refresh now in case onText takes a while to get 1st message + this._refreshStylesAndDiffsInURI(uri) + + + const extractText = (fullText: string, recentlyAddedTextLen: number) => { + if (from === 'QuickEdit') { + return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: quickEditFIMTags.midTag }) + } + else if (from === 'ClickApply') { + return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen }) + } + throw 1 + } + + const latestStreamInfoMutable: StreamLocationMutable = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 } + + // state used in onText: + let fullText = '' + let prevIgnoredSuffix = '' + + streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ + messagesType: 'chatMessages', + useProviderFor: opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K', + logging: { loggingName: `startApplying - ${from}` }, + messages, + onText: ({ newText: newText_ }) => { + + const newText = prevIgnoredSuffix + newText_ // add the previously ignored suffix because it's no longer the suffix! + fullText += prevIgnoredSuffix + newText // full text, including ```, etc + + const [croppedText, deltaCroppedText, croppedSuffix] = extractText(fullText, newText.length) + const { endLineInLlmTextSoFar } = this._writeStreamedDiffZoneLLMText(uri, originalCode, croppedText, deltaCroppedText, latestStreamInfoMutable) + diffZone._streamState.line = (diffZone.startLine - 1) + endLineInLlmTextSoFar // change coordinate systems from originalCode to full file + + this._refreshStylesAndDiffsInURI(uri) + + prevIgnoredSuffix = croppedSuffix + }, + onFinalMessage: ({ fullText }) => { + // console.log('DONE! FULL TEXT\n', extractText(fullText), diffZone.startLine, diffZone.endLine) + // at the end, re-write whole thing to make sure no sync errors + const [croppedText, _1, _2] = extractText(fullText, 0) + this._writeText(uri, croppedText, + { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + { shouldRealignDiffAreas: true } + ) + onDone() + }, + onError: (e) => { + this._notifyError(e) + onDone() + this._undoHistory(uri) + }, + + }) + + return diffZone + + } + + + + + private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }) { + const { applyStr } = opts + + const uri_ = this._getActiveEditorURI() + if (!uri_) return + const uri = uri_ + + // generate search/replace block text + const originalFileCode = this._voidFileService.readModel(uri) + if (originalFileCode === null) return + + const numLines = this._getNumLines(uri) + if (numLines === null) return + + // reject all diffZones on this URI, adding to history (there can't possibly be overlap after this) + this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: true }) + + const startLine = 1 + const endLine = numLines + + const userMessageContent = searchReplace_userMessage({ originalCode: originalFileCode, applyStr: applyStr }) + const messages: LLMChatMessage[] = [ + { role: 'system', content: searchReplace_systemMessage }, + { role: 'user', content: userMessageContent }, + ] + + // can use this as a proxy to set the diffArea's stream state requestId + let streamRequestIdRef: { current: string | null } = { current: null } + + let { onFinishEdit } = this._addToHistory(uri) + + // TODO replace these with whatever block we're on initially if already started + + type SearchReplaceDiffAreaMetadata = { + originalBounds: [number, number], // 1-indexed + originalCode: string, + } + + const addedTrackingZoneOfBlockNum: TrackingZone[] = [] + + const adding: Omit = { + type: 'DiffZone', + originalCode: originalFileCode, + startLine, + endLine, + _URI: uri, + _streamState: { + isStreaming: true, + streamRequestIdRef, + line: startLine, + }, + _diffOfId: {}, // added later + _removeStylesFns: new Set(), + } + const diffZone = this._addDiffArea(adding) + this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + this._onDidAddOrDeleteDiffZones.fire({ uri }) + + + const revertAndContinueHistory = () => { + this._undoHistory(uri) + const { onFinishEdit: onFinishEdit_ } = this._addToHistory(uri) + onFinishEdit = onFinishEdit_ + } + + + const convertOriginalRangeToFinalRange = (originalRange: readonly [number, number]): [number, number] => { + // adjust based on the changes by computing line offset + const [originalStart, originalEnd] = originalRange + let lineOffset = 0 + for (const blockDiffArea of addedTrackingZoneOfBlockNum) { + const { + startLine, endLine, + metadata: { originalBounds: [originalStart2, originalEnd2], }, + } = blockDiffArea + if (originalStart2 >= originalEnd) continue + const numNewLines = endLine - startLine + 1 + const numOldLines = originalEnd2 - originalStart2 + 1 + lineOffset += numNewLines - numOldLines + } + return [originalStart + lineOffset, originalEnd + lineOffset] + } + + + const errMsgOfInvalidStr = (str: string & ReturnType) => { + return str === 'Not found' ? + 'I interrupted you because the latest ORIGINAL code could not be found in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in ORIGINAL is identical to a code snippet in the file.' + : str === 'Not unique' ? + 'I interrupted you because the latest ORIGINAL code shows up multiple times in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in each ORIGINAL section is unique in the file.' + : '' + } + + + const onDone = () => { + diffZone._streamState = { isStreaming: false, } + this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + this._refreshStylesAndDiffsInURI(uri) + + // delete the tracking zones + for (const trackingZone of addedTrackingZoneOfBlockNum) + this._deleteTrackingZone(trackingZone) + + onFinishEdit() + } + + // refresh now in case onText takes a while to get 1st message + this._refreshStylesAndDiffsInURI(uri) + + // stream style related + let latestStreamLocationMutable: StreamLocationMutable | null = null + let shouldUpdateOrigStreamStyle = true + + let oldBlocks: ExtractedSearchReplaceBlock[] = [] + + // this generates >>>>>>> ORIGINAL <<<<<<< REPLACE blocks and and simultaneously applies it + let shouldSendAnotherMessage = true + let nMessagesSent = 0 + let currStreamingBlockNum = 0 + while (shouldSendAnotherMessage) { + shouldSendAnotherMessage = false + nMessagesSent += 1 + + streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ + messagesType: 'chatMessages', + useProviderFor: 'Apply', + logging: { loggingName: `generateSearchAndReplace` }, + messages, + onText: ({ fullText }) => { + // blocks are [done done done ... {writingFinal|writingOriginal}] + // ^ + // currStreamingBlockNum + + const blocks = extractSearchReplaceBlocks(fullText) + + for (let blockNum = currStreamingBlockNum; blockNum < blocks.length; blockNum += 1) { + const block = blocks[blockNum] + + if (block.state === 'writingOriginal') { + // update stream state to the first line of original if some portion of original has been written + if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) { + const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line + const originalRange = findTextInCode(block.orig, originalFileCode, startingAtLine) + if (typeof originalRange !== 'string') { + const [startLine, _] = convertOriginalRangeToFinalRange(originalRange) + diffZone._streamState.line = startLine + shouldUpdateOrigStreamStyle = false + } + } + // must be done writing original to move on to writing streamed content + continue + } + shouldUpdateOrigStreamStyle = true + + + // if this is the first time we're seeing this block, add it as a diffarea so we can start streaming + if (!(blockNum in addedTrackingZoneOfBlockNum)) { + const originalBounds = findTextInCode(block.orig, originalFileCode) + + // if error + if (typeof originalBounds === 'string') { + messages.push( + { role: 'assistant', content: fullText }, // latest output + { role: 'user', content: errMsgOfInvalidStr(originalBounds) } // user explanation of what's wrong + ) + if (streamRequestIdRef.current) this._llmMessageService.abort(streamRequestIdRef.current) + shouldSendAnotherMessage = true + revertAndContinueHistory() + continue + } + + const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds) + + // otherwise if no error, add the position as a diffarea + const adding: Omit, 'diffareaid'> = { + type: 'TrackingZone', + startLine: startLine, + endLine: endLine, + _URI: uri, + metadata: { + originalBounds: [...originalBounds], + originalCode: block.orig, + }, + } + const trackingZone = this._addDiffArea(adding) + addedTrackingZoneOfBlockNum.push(trackingZone) + latestStreamLocationMutable = { line: startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 } + } // <-- done adding diffarea + + + // should always be in streaming state here + if (!diffZone._streamState.isStreaming) { + console.error('DiffZone was not in streaming state in _initializeSearchAndReplaceStream') + continue + } + if (!latestStreamLocationMutable) continue + + // if a block is done, finish it by writing all + if (block.state === 'done') { + const { startLine: finalStartLine, endLine: finalEndLine } = addedTrackingZoneOfBlockNum[blockNum] + this._writeText(uri, block.final, + { startLineNumber: finalStartLine, startColumn: 1, endLineNumber: finalEndLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + { shouldRealignDiffAreas: true } + ) + diffZone._streamState.line = finalEndLine + 1 + currStreamingBlockNum = blockNum + 1 + continue + } + + // write the added text to the file + const deltaFinalText = block.final.substring((oldBlocks[blockNum]?.final ?? '').length, Infinity) + this._writeStreamedDiffZoneLLMText(uri, block.orig, block.final, deltaFinalText, latestStreamLocationMutable) + oldBlocks = blocks // oldblocks is only used if writingFinal + + // const { endLine: currentEndLine } = addedTrackingZoneOfBlockNum[blockNum] // would be bad to do this because a lot of the bottom lines might be the same. more accurate to go with latestStreamLocationMutable + // diffZone._streamState.line = currentEndLine + diffZone._streamState.line = latestStreamLocationMutable.line + + + + } // end for + + this._refreshStylesAndDiffsInURI(uri) + }, + onFinalMessage: async ({ fullText }) => { + console.log('final message!!', fullText) + + // 1. wait 500ms and fix lint errors - call lint error workflow + // (update react state to say "Fixing errors") + const blocks = extractSearchReplaceBlocks(fullText) + + if (blocks.length === 0) { + this._notificationService.info(`Void: We ran Apply, but the LLM didn't output any changes.`) + } + + // writeover the whole file + let newCode = originalFileCode + for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) { + const { originalBounds } = addedTrackingZoneOfBlockNum[blockNum].metadata + const finalCode = blocks[blockNum].final + + if (finalCode === null) continue + + const [originalStart, originalEnd] = originalBounds + const lines = newCode.split('\n') + newCode = [ + ...lines.slice(0, (originalStart - 1)), + ...finalCode.split('\n'), + ...lines.slice((originalEnd - 1) + 1, Infinity) + ].join('\n') + } + const numLines = this._getNumLines(uri) + if (numLines !== null) { + this._writeText(uri, newCode, + { startLineNumber: 1, startColumn: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER }, + { shouldRealignDiffAreas: true } + ) + } + + onDone() + }, + onError: (e) => { + this._notifyError(e) + onDone() + this._undoHistory(uri) + }, + + }) + } + + + return diffZone + } + + + + private _stopIfStreaming(diffZone: DiffZone) { + const uri = diffZone._URI + + const streamRequestId = diffZone._streamState.streamRequestIdRef?.current + if (!streamRequestId) return + + this._llmMessageService.abort(streamRequestId) + + diffZone._streamState = { isStreaming: false, } + this._onDidChangeDiffZoneStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) + } + + _undoHistory(uri: URI) { + this._undoRedoService.undo(uri) + } + + + + + + _interruptSingleDiffZoneStreaming({ diffareaid }: { diffareaid: number }) { + const diffZone = this.diffAreaOfId[diffareaid] + if (diffZone?.type !== 'DiffZone') return + if (!diffZone._streamState.isStreaming) return + + this._stopIfStreaming(diffZone) + this._undoHistory(diffZone._URI) + } + + + isCtrlKZoneStreaming({ diffareaid }: { diffareaid: number }) { + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (!ctrlKZone) return false + if (ctrlKZone.type !== 'CtrlKZone') return false + return !!ctrlKZone._linkedStreamingDiffZone + } + + + // diffareaid of the ctrlKZone (even though the stream state is dictated by the linked diffZone) + interruptCtrlKStreaming({ diffareaid }: { diffareaid: number }) { + const ctrlKZone = this.diffAreaOfId[diffareaid] + if (ctrlKZone?.type !== 'CtrlKZone') return + if (!ctrlKZone._linkedStreamingDiffZone) return + + const linkedStreamingDiffZone = this.diffAreaOfId[ctrlKZone._linkedStreamingDiffZone] + if (!linkedStreamingDiffZone) return + if (linkedStreamingDiffZone.type !== 'DiffZone') return + + this._interruptSingleDiffZoneStreaming({ diffareaid: linkedStreamingDiffZone.diffareaid }) + } + + + + + getURIStreamState = ({ uri }: { uri: URI | null }) => { + if (uri === null) return 'idle' + + const diffZones = [...this.diffAreasOfURI[uri.fsPath].values()] + .map(diffareaid => this.diffAreaOfId[diffareaid]) + .filter(diffArea => !!diffArea && diffArea.type === 'DiffZone') + const isStreaming = diffZones.find(diffZone => !!diffZone._streamState.isStreaming) + + const state: URIStreamState = isStreaming ? 'streaming' : (diffZones.length === 0 ? 'idle' : 'acceptRejectAll') + return state + } + + interruptURIStreaming({ uri }: { uri: URI }) { + // brute force for now is OK + for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) { + const diffArea = this.diffAreaOfId[diffareaid] + if (diffArea?.type !== 'DiffZone') continue + if (!diffArea._streamState.isStreaming) continue + this._stopIfStreaming(diffArea) + } + this._undoHistory(uri) + } + + + // public removeDiffZone(diffZone: DiffZone, behavior: 'reject' | 'accept') { + // const uri = diffZone._URI + // const { onFinishEdit } = this._addToHistory(uri) + + // if (behavior === 'reject') this._revertAndDeleteDiffZone(diffZone) + // else if (behavior === 'accept') this._deleteDiffZone(diffZone) + + // this._refreshStylesAndDiffsInURI(uri) + // onFinishEdit() + // } + + private _revertAndDeleteDiffZone(diffZone: DiffZone) { + const uri = diffZone._URI + + const writeText = diffZone.originalCode + const toRange: IRange = { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER } + this._writeText(uri, writeText, toRange, { shouldRealignDiffAreas: true }) + + this._deleteDiffZone(diffZone) + } + + + // remove a batch of diffareas all at once (and handle accept/reject of their diffs) + public removeDiffAreas({ uri, removeCtrlKs, behavior }: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept' }) { + + const diffareaids = this.diffAreasOfURI[uri.fsPath] + if (diffareaids.size === 0) return // do nothing + + const { onFinishEdit } = this._addToHistory(uri) + + for (const diffareaid of diffareaids) { + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) continue + + if (diffArea.type == 'DiffZone') { + if (behavior === 'reject') this._revertAndDeleteDiffZone(diffArea) + else if (behavior === 'accept') this._deleteDiffZone(diffArea) + } + else if (diffArea.type === 'CtrlKZone' && removeCtrlKs) { + this._deleteCtrlKZone(diffArea) + } + } + + this._refreshStylesAndDiffsInURI(uri) + onFinishEdit() + } + + + + // called on void.acceptDiff + public async acceptDiff({ diffid }: { diffid: number }) { + + const diff = this.diffOfId[diffid] + if (!diff) return + + const { diffareaid } = diff + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) return + + if (diffArea.type !== 'DiffZone') return + + const uri = diffArea._URI + + // add to history + const { onFinishEdit } = this._addToHistory(uri) + + const originalLines = diffArea.originalCode.split('\n') + let newOriginalCode: string + + if (diff.type === 'deletion') { + newOriginalCode = [ + ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine + // <-- deletion has nothing here + ...originalLines.slice((diff.originalEndLine - 1) + 1, Infinity) // everything after endLine + ].join('\n') + } + else if (diff.type === 'insertion') { + newOriginalCode = [ + ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine + diff.code, // code + ...originalLines.slice((diff.originalStartLine - 1), Infinity) // startLine (inclusive) and on (no +1) + ].join('\n') + } + else if (diff.type === 'edit') { + newOriginalCode = [ + ...originalLines.slice(0, (diff.originalStartLine - 1)), // everything before startLine + diff.code, // code + ...originalLines.slice((diff.originalEndLine - 1) + 1, Infinity) // everything after endLine + ].join('\n') + } + else { + throw new Error(`Void error: ${diff}.type not recognized`) + } + + // console.log('DIFF', diff) + // console.log('DIFFAREA', diffArea) + // console.log('ORIGINAL', diffArea.originalCode) + // console.log('new original Code', newOriginalCode) + + // update code now accepted as original + diffArea.originalCode = newOriginalCode + + // delete the diff + this._deleteDiff(diff) + + // diffArea should be removed if it has no more diffs in it + if (Object.keys(diffArea._diffOfId).length === 0) { + this._deleteDiffZone(diffArea) + } + + this._refreshStylesAndDiffsInURI(uri) + + onFinishEdit() + + } + + + + // called on void.rejectDiff + public async rejectDiff({ diffid }: { diffid: number }) { + + const diff = this.diffOfId[diffid] + if (!diff) return + + const { diffareaid } = diff + const diffArea = this.diffAreaOfId[diffareaid] + if (!diffArea) return + + if (diffArea.type !== 'DiffZone') return + + const uri = diffArea._URI + + // add to history + const { onFinishEdit } = this._addToHistory(uri) + + let writeText: string + let toRange: IRange + + // if it was a deletion, need to re-insert + // (this image applies to writeText and toRange, not newOriginalCode) + // A + // |B <-- deleted here, diff.startLine == diff.endLine + // C + if (diff.type === 'deletion') { + // if startLine is out of bounds (deleted lines past the diffarea), applyEdit will do a weird rounding thing, to account for that we apply the edit the line before + if (diff.startLine - 1 === diffArea.endLine) { + writeText = '\n' + diff.originalCode + toRange = { startLineNumber: diff.startLine - 1, startColumn: Number.MAX_SAFE_INTEGER, endLineNumber: diff.startLine - 1, endColumn: Number.MAX_SAFE_INTEGER } + } + else { + writeText = diff.originalCode + '\n' + toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.startLine, endColumn: 1 } + } + } + // if it was an insertion, need to delete all the lines + // (this image applies to writeText and toRange, not newOriginalCode) + // |A <-- startLine + // B| <-- endLine (we want to delete this whole line) + // C + else if (diff.type === 'insertion') { + // console.log('REJECTING:', diff) + // handle the case where the insertion was a newline at end of diffarea (applying to the next line doesnt work because it doesnt exist, vscode just doesnt delete the correct # of newlines) + if (diff.endLine === diffArea.endLine) { + // delete the line before instead of after + writeText = '' + toRange = { startLineNumber: diff.startLine - 1, startColumn: Number.MAX_SAFE_INTEGER, endLineNumber: diff.endLine, endColumn: 1 } // 1-indexed + } + else { + writeText = '' + toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine + 1, endColumn: 1 } // 1-indexed + } + + } + // if it was an edit, just edit the range + // (this image applies to writeText and toRange, not newOriginalCode) + // |A <-- startLine + // B| <-- endLine (just swap out these lines for the originalCode) + // C + else if (diff.type === 'edit') { + writeText = diff.originalCode + toRange = { startLineNumber: diff.startLine, startColumn: 1, endLineNumber: diff.endLine, endColumn: Number.MAX_SAFE_INTEGER } // 1-indexed + } + else { + throw new Error(`Void error: ${diff}.type not recognized`) + } + + // update the file + this._writeText(uri, writeText, toRange, { shouldRealignDiffAreas: true }) + + // originalCode does not change! + + // delete the diff + this._deleteDiff(diff) + + // diffArea should be removed if it has no more diffs in it + if (Object.keys(diffArea._diffOfId).length === 0) { + this._deleteDiffZone(diffArea) + } + + this._refreshStylesAndDiffsInURI(uri) + + onFinishEdit() + + } + + + + + // testDiffs(): DiffZone | undefined { + // const uri = this._getActiveEditorURI() + // if (!uri) return + + // const startLine = 1 + // const endLine = 4 + + // const currentFileStr = this._readURI(uri) + // if (currentFileStr === null) return + // const originalCode = currentFileStr.split('\n').slice((startLine - 1), (endLine - 1) + 1).join('\n') + + // const { onFinishEdit } = this._addToHistory(uri) + // const adding: Omit = { + // type: 'DiffZone', + // originalCode, + // startLine, + // endLine, + // _URI: uri, + // _streamState: { isStreaming: false, }, + // _diffOfId: {}, // added later + // _removeStylesFns: new Set(), + // } + // const diffZone = this._addDiffArea(adding) + // const endResult = `\ + // const x = 1; + // if (x > 0) { + // console.log('hi!') + // }` + // this._writeText(uri, endResult, + // { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed + // { shouldRealignDiffAreas: true } + // ) + // diffZone._streamState = { isStreaming: false, } + // this._refreshStylesAndDiffsInURI(uri) + // onFinishEdit() + + // return diffZone + // } + +} + +registerSingleton(IEditCodeService, EditCodeService, InstantiationType.Eager); + +const acceptBg = '#1a7431' +const acceptAllBg = '#1e8538' +const acceptBorder = '1px solid #145626' +const rejectBg = '#b42331' +const rejectAllBg = '#cf2838' +const rejectBorder = '1px solid #8e1c27' +const buttonFontSize = '11px' +const buttonTextColor = 'white' + +class AcceptRejectWidget extends Widget implements IOverlayWidget { + + public getId() { return this.ID } + public getDomNode() { return this._domNode; } + public getPosition() { return null } + + private readonly _domNode: HTMLElement; + private readonly editor + private readonly ID + private readonly startLine + + constructor({ editor, onAccept, onReject, diffid, startLine, offsetLines }: { editor: ICodeEditor; onAccept: () => void; onReject: () => void; diffid: string, startLine: number, offsetLines: number }) { + super() + + + this.ID = editor.getModel()?.uri.fsPath + diffid; + this.editor = editor; + this.startLine = startLine; + + const lineHeight = editor.getOption(EditorOption.lineHeight); + + // Create container div with buttons + const { acceptButton, rejectButton, buttons } = dom.h('div@buttons', [ + dom.h('button@acceptButton', []), + dom.h('button@rejectButton', []) + ]); + + // Style the container + buttons.style.display = 'flex'; + buttons.style.position = 'absolute'; + buttons.style.gap = '4px'; + buttons.style.paddingRight = '4px'; + buttons.style.zIndex = '1'; + buttons.style.transform = `translateY(${offsetLines * lineHeight}px)`; + + + // Style accept button + acceptButton.onclick = onAccept; + acceptButton.textContent = 'Accept'; + acceptButton.style.backgroundColor = acceptBg; + acceptButton.style.border = acceptBorder; + acceptButton.style.color = buttonTextColor; + acceptButton.style.fontSize = buttonFontSize; + acceptButton.style.borderTop = 'none'; + acceptButton.style.padding = '1px 4px'; + acceptButton.style.borderBottomLeftRadius = '6px'; + acceptButton.style.borderBottomRightRadius = '6px'; + acceptButton.style.borderTopLeftRadius = '0'; + acceptButton.style.borderTopRightRadius = '0'; + acceptButton.style.cursor = 'pointer'; + acceptButton.style.height = '100%'; + acceptButton.style.boxShadow = '0 2px 3px rgba(0,0,0,0.2)'; + + // Style reject button + rejectButton.onclick = onReject; + rejectButton.textContent = 'Reject'; + rejectButton.style.backgroundColor = rejectBg; + rejectButton.style.border = rejectBorder; + rejectButton.style.color = buttonTextColor; + rejectButton.style.fontSize = buttonFontSize; + rejectButton.style.borderTop = 'none'; + rejectButton.style.padding = '1px 4px'; + rejectButton.style.borderBottomLeftRadius = '6px'; + rejectButton.style.borderBottomRightRadius = '6px'; + rejectButton.style.borderTopLeftRadius = '0'; + rejectButton.style.borderTopRightRadius = '0'; + rejectButton.style.cursor = 'pointer'; + rejectButton.style.height = '100%'; + rejectButton.style.boxShadow = '0 2px 3px rgba(0,0,0,0.2)'; + + + + this._domNode = buttons; + + const updateTop = () => { + const topPx = editor.getTopForLineNumber(this.startLine) - editor.getScrollTop() + this._domNode.style.top = `${topPx}px` + } + const updateLeft = () => { + const layoutInfo = editor.getLayoutInfo(); + const minimapWidth = layoutInfo.minimap.minimapWidth; + const verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth; + const buttonWidth = this._domNode.offsetWidth; + + const leftPx = layoutInfo.width - minimapWidth - verticalScrollbarWidth - buttonWidth; + this._domNode.style.left = `${leftPx}px`; + } + + // Mount first, then update positions + editor.addOverlayWidget(this); + + + updateTop() + updateLeft() + + this._register(editor.onDidScrollChange(e => { updateTop() })) + this._register(editor.onDidChangeModelContent(e => { updateTop() })) + this._register(editor.onDidLayoutChange(e => { updateTop(); updateLeft() })) + + // mount this widget + + editor.addOverlayWidget(this); + // console.log('created elt', this._domNode) + } + + public override dispose(): void { + this.editor.removeOverlayWidget(this) + super.dispose() + } + +} + + + + + + +class AcceptAllRejectAllWidget extends Widget implements IOverlayWidget { + private readonly _domNode: HTMLElement; + private readonly editor: ICodeEditor; + private readonly ID: string; + + constructor({ editor, onAcceptAll, onRejectAll }: { editor: ICodeEditor, onAcceptAll: () => void, onRejectAll: () => void }) { + super(); + + this.ID = editor.getModel()?.uri.fsPath + ''; + this.editor = editor; + + // Create container div with buttons + const { acceptButton, rejectButton, buttons } = dom.h('div@buttons', [ + dom.h('button@acceptButton', []), + dom.h('button@rejectButton', []) + ]); + + // Style the container + buttons.style.zIndex = '2'; + buttons.style.padding = '4px'; + buttons.style.display = 'flex'; + buttons.style.gap = '4px'; + buttons.style.alignItems = 'center'; + + // Style accept button + acceptButton.addEventListener('click', onAcceptAll) + acceptButton.textContent = 'Accept All'; + acceptButton.style.backgroundColor = acceptAllBg; + acceptButton.style.border = acceptBorder; + acceptButton.style.color = buttonTextColor; + acceptButton.style.fontSize = buttonFontSize; + acceptButton.style.padding = '4px 8px'; + acceptButton.style.borderRadius = '6px'; + acceptButton.style.cursor = 'pointer'; + + // Style reject button + rejectButton.addEventListener('click', onRejectAll) + rejectButton.textContent = 'Reject All'; + rejectButton.style.backgroundColor = rejectAllBg; + rejectButton.style.border = rejectBorder; + rejectButton.style.color = buttonTextColor; + rejectButton.style.fontSize = buttonFontSize; + rejectButton.style.color = 'white'; + rejectButton.style.padding = '4px 8px'; + rejectButton.style.borderRadius = '6px'; + rejectButton.style.cursor = 'pointer'; + + this._domNode = buttons; + + // Mount the widget + editor.addOverlayWidget(this); + } + + + public getId(): string { + return this.ID; + } + + public getDomNode(): HTMLElement { + return this._domNode; + } + + public getPosition() { + return { + preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER, + } + } + + public override dispose(): void { + this.editor.removeOverlayWidget(this); + super.dispose(); + } +} + + + diff --git a/src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts b/src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts new file mode 100644 index 00000000..3165e57f --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helperServices/consistentItemService.ts @@ -0,0 +1,422 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; +import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; + + +// lets you add a "consistent" item to a Model (aka URI), instead of just to a single editor + +type AddItemInputs = { uri: URI; fn: (editor: ICodeEditor) => (() => void); } + +export interface IConsistentItemService { + readonly _serviceBrand: undefined; + getEditorsOnURI(uri: URI): ICodeEditor[]; + addConsistentItemToURI(inputs: AddItemInputs): string; + removeConsistentItemFromURI(consistentItemId: string): void; +} + +export const IConsistentItemService = createDecorator('ConsistentItemService'); + +export class ConsistentItemService extends Disposable { + + readonly _serviceBrand: undefined + + // the items that are attached to each URI, completely independent from current state of editors + private readonly consistentItemIdsOfURI: Record | undefined> = {} + private readonly infoOfConsistentItemId: Record = {} + + + // current state of items on each editor, and the fns to call to remove them + private readonly itemIdsOfEditorId: Record | undefined> = {} + private readonly consistentItemIdOfItemId: Record = {} + private readonly disposeFnOfItemId: Record void> = {} + + + constructor( + @ICodeEditorService private readonly _editorService: ICodeEditorService, + ) { + super() + + + const removeItemsFromEditor = (editor: ICodeEditor) => { + const editorId = editor.getId() + for (const itemId of this.itemIdsOfEditorId[editorId] ?? []) + this._removeItemFromEditor(editor, itemId) + } + + // put items on the editor, based on the consistent items for that URI + const putItemsOnEditor = (editor: ICodeEditor, uri: URI | null) => { + if (!uri) return + for (const consistentItemId of this.consistentItemIdsOfURI[uri.fsPath] ?? []) + this._putItemOnEditor(editor, consistentItemId) + } + + + // when editor switches tabs (models) + const addTabSwitchListeners = (editor: ICodeEditor) => { + this._register( + editor.onDidChangeModel(e => { + removeItemsFromEditor(editor) + putItemsOnEditor(editor, e.newModelUrl) + }) + ) + } + + // when editor is disposed + const addDisposeListener = (editor: ICodeEditor) => { + this._register(editor.onDidDispose(() => { + // anything on the editor has been disposed already + for (const itemId of this.itemIdsOfEditorId[editor.getId()] ?? []) + delete this.disposeFnOfItemId[itemId] + })) + } + + const initializeEditor = (editor: ICodeEditor) => { + // if (editor.getModel()?.uri.scheme !== 'file') return // THIS BREAKS THINGS + addTabSwitchListeners(editor) + addDisposeListener(editor) + putItemsOnEditor(editor, editor.getModel()?.uri ?? null) + } + + // initialize current editors + any new editors + for (let editor of this._editorService.listCodeEditors()) initializeEditor(editor) + this._register(this._editorService.onCodeEditorAdd(editor => { initializeEditor(editor) })) + + // when an editor is deleted, remove its items + this._register(this._editorService.onCodeEditorRemove(editor => { removeItemsFromEditor(editor) })) + } + + + + _putItemOnEditor(editor: ICodeEditor, consistentItemId: string) { + const { fn } = this.infoOfConsistentItemId[consistentItemId] + + // add item + const dispose = fn(editor) + + const itemId = generateUuid() + const editorId = editor.getId() + + if (!(editorId in this.itemIdsOfEditorId)) + this.itemIdsOfEditorId[editorId] = new Set() + this.itemIdsOfEditorId[editorId]!.add(itemId) + + + this.consistentItemIdOfItemId[itemId] = consistentItemId + + this.disposeFnOfItemId[itemId] = () => { + // console.log('calling remove for', itemId) + dispose?.() + } + + } + + + _removeItemFromEditor(editor: ICodeEditor, itemId: string) { + + const editorId = editor.getId() + this.itemIdsOfEditorId[editorId]?.delete(itemId) + + this.disposeFnOfItemId[itemId]?.() + delete this.disposeFnOfItemId[itemId] + + delete this.consistentItemIdOfItemId[itemId] + } + + getEditorsOnURI(uri: URI) { + const editors = this._editorService.listCodeEditors().filter(editor => editor.getModel()?.uri.fsPath === uri.fsPath) + return editors + } + + consistentItemIdPool = 0 + addConsistentItemToURI({ uri, fn }: AddItemInputs) { + const consistentItemId = (this.consistentItemIdPool++) + '' + + if (!(uri.fsPath in this.consistentItemIdsOfURI)) + this.consistentItemIdsOfURI[uri.fsPath] = new Set() + this.consistentItemIdsOfURI[uri.fsPath]!.add(consistentItemId) + + this.infoOfConsistentItemId[consistentItemId] = { fn, uri } + + const editors = this.getEditorsOnURI(uri) + for (const editor of editors) + this._putItemOnEditor(editor, consistentItemId) + + return consistentItemId + } + + + removeConsistentItemFromURI(consistentItemId: string) { + if (!(consistentItemId in this.infoOfConsistentItemId)) + return + + const { uri } = this.infoOfConsistentItemId[consistentItemId] + const editors = this.getEditorsOnURI(uri) + + for (const editor of editors) { + for (const itemId of this.itemIdsOfEditorId[editor.getId()] ?? []) { + if (this.consistentItemIdOfItemId[itemId] === consistentItemId) + this._removeItemFromEditor(editor, itemId) + } + } + + // clear + this.consistentItemIdsOfURI[uri.fsPath]?.delete(consistentItemId) + + delete this.infoOfConsistentItemId[consistentItemId] + + } + +} + +registerSingleton(IConsistentItemService, ConsistentItemService, InstantiationType.Eager); + + + + + + + + + + + + + + + + + +// mostly generated by o1 (almost the same as above, but just for 1 editor) +export interface IConsistentEditorItemService { + readonly _serviceBrand: undefined; + addToEditor(editor: ICodeEditor, fn: () => () => void): string; + removeFromEditor(itemId: string): void; +} +export const IConsistentEditorItemService = createDecorator('ConsistentEditorItemService'); + + +export class ConsistentEditorItemService extends Disposable { + readonly _serviceBrand: undefined; + + /** + * For each editorId, we track the set of itemIds that have been "added" to that editor. + * This does *not* necessarily mean they're currently mounted (the user may have switched models). + */ + private readonly itemIdsByEditorId: Record> = {}; + + /** + * For each itemId, we store relevant info (the fn to call on the editor, the editorId, the uri, and the current dispose function). + */ + private readonly itemInfoById: Record< + string, + { + editorId: string; + uriFsPath: string; + fn: (editor: ICodeEditor) => () => void; + disposeFn?: () => void; + } + > = {}; + + constructor( + @ICodeEditorService private readonly _editorService: ICodeEditorService, + ) { + super(); + + // + // Wire up listeners to watch for new editors, removed editors, etc. + // + + // Initialize any already-existing editors + for (const editor of this._editorService.listCodeEditors()) { + this._initializeEditor(editor); + } + + // When an editor is added, track it + this._register( + this._editorService.onCodeEditorAdd((editor) => { + this._initializeEditor(editor); + }) + ); + + // When an editor is removed, remove all items associated with that editor + this._register( + this._editorService.onCodeEditorRemove((editor) => { + this._removeAllItemsFromEditor(editor); + }) + ); + } + + /** + * Sets up listeners on the provided editor so that: + * - If the editor changes models, we remove items and re-mount only if the new model matches. + * - If the editor is disposed, we do the needed cleanup. + */ + private _initializeEditor(editor: ICodeEditor) { + const editorId = editor.getId(); + + // + // Listen for model changes + // + this._register( + editor.onDidChangeModel((e) => { + this._removeAllItemsFromEditor(editor); + if (!e.newModelUrl) { + return; + } + // Re-mount any items that belong to this editor and match the new URI + const itemsForEditor = this.itemIdsByEditorId[editorId]; + if (itemsForEditor) { + for (const itemId of itemsForEditor) { + const itemInfo = this.itemInfoById[itemId]; + if (itemInfo && itemInfo.uriFsPath === e.newModelUrl.fsPath) { + this._mountItemOnEditor(editor, itemId); + } + } + } + }) + ); + + // + // When the editor is disposed, remove all items from it + // + this._register( + editor.onDidDispose(() => { + this._removeAllItemsFromEditor(editor); + }) + ); + + // + // If the editor already has a model (e.g. on initial load), try mounting items + // + const uri = editor.getModel()?.uri; + if (!uri) { + return; + } + + const itemsForEditor = this.itemIdsByEditorId[editorId]; + if (itemsForEditor) { + for (const itemId of itemsForEditor) { + const itemInfo = this.itemInfoById[itemId]; + if (itemInfo && itemInfo.uriFsPath === uri.fsPath) { + this._mountItemOnEditor(editor, itemId); + } + } + } + } + + /** + * Actually calls the item-creation function `fn(editor)` and saves the resulting disposeFn + * so we can later clean it up. + */ + private _mountItemOnEditor(editor: ICodeEditor, itemId: string) { + const info = this.itemInfoById[itemId]; + if (!info) { + return; + } + const { fn } = info; + const disposeFn = fn(editor); + info.disposeFn = disposeFn; + } + + /** + * Removes a single item from an editor (calling its `disposeFn` if present). + */ + private _removeItemFromEditor(editor: ICodeEditor, itemId: string) { + const info = this.itemInfoById[itemId]; + if (info?.disposeFn) { + info.disposeFn(); + info.disposeFn = undefined; + } + } + + /** + * Removes *all* items from the given editor. Typically called when the editor changes model or is disposed. + */ + private _removeAllItemsFromEditor(editor: ICodeEditor) { + const editorId = editor.getId(); + const itemsForEditor = this.itemIdsByEditorId[editorId]; + if (!itemsForEditor) { + return; + } + + for (const itemId of itemsForEditor) { + this._removeItemFromEditor(editor, itemId); + } + } + + /** + * Public API: Adds an item to an *individual* editor (determined by editor ID), + * but only when that editor is showing the same model (uri.fsPath). + */ + addToEditor(editor: ICodeEditor, fn: () => () => void): string { + const uri = editor.getModel()?.uri + if (!uri) { + throw new Error('No URI on the provided editor or in AddItemInputs.'); + } + + const editorId = editor.getId(); + + // Create an ID for this item + const itemId = generateUuid(); + + // Record the info + this.itemInfoById[itemId] = { + editorId, + uriFsPath: uri.fsPath, + fn, + }; + + // Add to the editor's known items + if (!this.itemIdsByEditorId[editorId]) { + this.itemIdsByEditorId[editorId] = new Set(); + } + this.itemIdsByEditorId[editorId].add(itemId); + + // If the editor's current URI matches, mount it now + if (editor.getModel()?.uri.fsPath === uri.fsPath) { + this._mountItemOnEditor(editor, itemId); + } + + return itemId; + } + + /** + * Public API: Removes an item from the *specific* editor. We look up which editor + * had this item and remove it from that editor. + */ + removeFromEditor(itemId: string): void { + const info = this.itemInfoById[itemId]; + if (!info) { + // Nothing to remove + return; + } + + const { editorId } = info; + + // Find the editor in question + const editor = this._editorService.listCodeEditors().find( + (ed) => ed.getId() === editorId + ); + if (editor) { + // Dispose on that editor + this._removeItemFromEditor(editor, itemId); + } + + // Clean up references + this.itemIdsByEditorId[editorId]?.delete(itemId); + delete this.itemInfoById[itemId]; + } +} + +registerSingleton(IConsistentEditorItemService, ConsistentEditorItemService, InstantiationType.Eager); + + diff --git a/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts b/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts new file mode 100644 index 00000000..9bc3c9bd --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts @@ -0,0 +1,174 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +// eg "bash" -> "shell" +export const nameToVscodeLanguage: { [key: string]: string } = { + // Web Technologies + 'html': 'html', + 'css': 'css', + 'scss': 'scss', + 'sass': 'scss', + 'less': 'less', + 'javascript': 'typescript', + 'js': 'typescript', // use more general renderer + 'jsx': 'typescript', + 'typescript': 'typescript', + 'ts': 'typescript', + 'tsx': 'typescript', + 'json': 'json', + 'jsonc': 'json', + + // Programming Languages + 'python': 'python', + 'py': 'python', + 'java': 'java', + 'cpp': 'cpp', + 'c++': 'cpp', + 'c': 'c', + 'csharp': 'csharp', + 'cs': 'csharp', + 'c#': 'csharp', + 'go': 'go', + 'golang': 'go', + 'rust': 'rust', + 'rs': 'rust', + 'ruby': 'ruby', + 'rb': 'ruby', + 'php': 'php', + 'shell': 'shell', + 'bash': 'shell', + 'sh': 'shell', + 'zsh': 'shell', + + // Markup and Config + 'markdown': 'markdown', + 'md': 'markdown', + 'xml': 'xml', + 'svg': 'xml', + 'yaml': 'yaml', + 'yml': 'yaml', + 'ini': 'ini', + 'toml': 'ini', + + // Database and Query Languages + 'sql': 'sql', + 'mysql': 'sql', + 'postgresql': 'sql', + 'graphql': 'graphql', + 'gql': 'graphql', + + // Others + 'dockerfile': 'dockerfile', + 'docker': 'dockerfile', + 'makefile': 'makefile', + 'plaintext': 'plaintext', + 'text': 'plaintext' +}; + + + +// eg ".ts" -> "typescript" +const fileExtensionToVscodeLanguage: { [key: string]: string } = { + // Web + 'html': 'html', + 'htm': 'html', + 'css': 'css', + 'scss': 'scss', + 'less': 'less', + 'js': 'javascript', + 'jsx': 'javascript', + 'ts': 'typescript', + 'tsx': 'typescript', + 'json': 'json', + 'jsonc': 'json', + + // Programming Languages + 'py': 'python', + 'java': 'java', + 'cpp': 'cpp', + 'cc': 'cpp', + 'c': 'c', + 'h': 'cpp', + 'hpp': 'cpp', + 'cs': 'csharp', + 'go': 'go', + 'rs': 'rust', + 'rb': 'ruby', + 'php': 'php', + 'sh': 'shell', + 'bash': 'shell', + 'zsh': 'shell', + + // Markup/Config + 'md': 'markdown', + 'markdown': 'markdown', + 'xml': 'xml', + 'svg': 'xml', + 'yaml': 'yaml', + 'yml': 'yaml', + 'ini': 'ini', + 'toml': 'ini', + + // Other + 'sql': 'sql', + 'graphql': 'graphql', + 'gql': 'graphql', + 'dockerfile': 'dockerfile', + 'docker': 'dockerfile', + 'mk': 'makefile', + + // Config Files and Dot Files + 'npmrc': 'ini', + 'env': 'ini', + 'gitignore': 'ignore', + 'dockerignore': 'ignore', + 'eslintrc': 'json', + 'babelrc': 'json', + 'prettierrc': 'json', + 'stylelintrc': 'json', + 'editorconfig': 'ini', + 'htaccess': 'apacheconf', + 'conf': 'ini', + 'config': 'ini', + + // Package Files + 'package': 'json', + 'package-lock': 'json', + 'gemfile': 'ruby', + 'podfile': 'ruby', + 'rakefile': 'ruby', + + // Build Systems + 'cmake': 'cmake', + 'makefile': 'makefile', + 'gradle': 'groovy', + + // Shell Scripts + 'bashrc': 'shell', + 'zshrc': 'shell', + 'fish': 'shell', + + // Version Control + 'gitconfig': 'ini', + 'hgrc': 'ini', + 'svnconfig': 'ini', + + // Web Server + 'nginx': 'nginx', + + // Misc Config + 'properties': 'properties', + 'cfg': 'ini', + 'reg': 'ini' +}; + + +export function filenameToVscodeLanguage(filename: string): string | undefined { + + const ext = filename.toLowerCase().split('.').pop(); + if (!ext) return undefined; + + return fileExtensionToVscodeLanguage[ext]; +} diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts new file mode 100644 index 00000000..00eb2ef1 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts @@ -0,0 +1,336 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { OnText } from '../../common/llmMessageTypes.js' +import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js' + +class SurroundingsRemover { + readonly originalS: string + i: number + j: number + + // string is s[i...j] + + constructor(s: string) { + this.originalS = s + this.i = 0 + this.j = s.length - 1 + } + value() { + return this.originalS.substring(this.i, this.j + 1) + } + + // returns whether it removed the whole prefix + removePrefix = (prefix: string): boolean => { + let offset = 0 + // console.log('prefix', prefix, Math.min(this.j, prefix.length - 1)) + while (this.i <= this.j && offset <= prefix.length - 1) { + if (this.originalS.charAt(this.i) !== prefix.charAt(offset)) + break + offset += 1 + this.i += 1 + } + return offset === prefix.length + } + + // // removes suffix from right to left + removeSuffix = (suffix: string): boolean => { + // e.g. suffix =
, the string is 
hi

= 1; len -= 1) { + if (s.endsWith(suffix.substring(0, len))) { // the end of the string equals a prefix + this.j -= len + return len === suffix.length + } + } + return false + } + // removeSuffix = (suffix: string): boolean => { + // let offset = 0 + + // while (this.j >= Math.max(this.i, 0)) { + // if (this.originalS.charAt(this.j) !== suffix.charAt(suffix.length - 1 - offset)) + // break + // offset += 1 + // this.j -= 1 + // } + // return offset === suffix.length + // } + + removeFromStartUntilFullMatch = (until: string, alsoRemoveUntilStr: boolean) => { + const index = this.originalS.indexOf(until, this.i) + + if (index === -1) { + this.i = this.j + 1 + return null + } + // console.log('index', index, until.length) + + if (alsoRemoveUntilStr) + this.i = index + until.length + else + this.i = index + + return true + } + + + removeCodeBlock = () => { + // Match either: + // 1. ```language\n\n```\n? + // 2. ```\n```\n? + + const pm = this + const foundCodeBlock = pm.removePrefix('```') + if (!foundCodeBlock) return false + + pm.removeFromStartUntilFullMatch('\n', true) // language + + const j = pm.j + let foundCodeBlockEnd = pm.removeSuffix('```') + + if (pm.j === j) foundCodeBlockEnd = pm.removeSuffix('```\n') // if no change, try again with \n after ``` + + if (!foundCodeBlockEnd) return false + + pm.removeSuffix('\n') // remove the newline before ``` + return true + } + + + deltaInfo = (recentlyAddedTextLen: number) => { + // aaaaaatextaaaaaa{recentlyAdded} + // ^ i j len + // | + // recentyAddedIdx + const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen + const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1) + const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity) + return [actualDelta, ignoredSuffix] as const + } + + + +} + + + +export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string, string] => { + + const pm = new SurroundingsRemover(text) + + pm.removeCodeBlock() + + const s = pm.value() + const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen) + + return [s, delta, ignoredSuffix] +} + + + + + +// Ollama has its own FIM, we should not use this if we use that +export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string, string] => { + + /* ------------- summary of the regex ------------- + [optional ` | `` | ```] + (match optional_language_name) + [optional strings here] + [required tag] + (match the stuff between mid tags) + [optional tag] + [optional ` | `` | ```] + */ + + const pm = new SurroundingsRemover(text) + + pm.removeCodeBlock() + + const foundMid = pm.removePrefix(`<${midTag}>`) + + if (foundMid) { + pm.removeSuffix(``) + } + const s = pm.value() + const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen) + + return [s, delta, ignoredSuffix] +} + + + +export type ExtractedSearchReplaceBlock = { + state: 'writingOriginal' | 'writingFinal' | 'done', + orig: string, + final: string, +} + + +const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => { + // for each prefix + for (let i = anyPrefix.length; i >= 0; i--) { + const prefix = anyPrefix.slice(0, i) + if (str.endsWith(prefix)) return prefix + } + return null +} + +// guarantees if you keep adding text, array length will strictly grow and state will progress without going back +export const extractSearchReplaceBlocks = (str: string) => { + + const ORIGINAL_ = ORIGINAL + `\n` + const DIVIDER_ = '\n' + DIVIDER + `\n` + // logic for FINAL_ is slightly more complicated - should be '\n' + FINAL, but that ignores if the final output is empty + + + const blocks: ExtractedSearchReplaceBlock[] = [] + + let i = 0 // search i and beyond (this is done by plain index, not by line number. much simpler this way) + while (true) { + let origStart = str.indexOf(ORIGINAL_, i) + if (origStart === -1) { return blocks } + origStart += ORIGINAL_.length + i = origStart + // wrote <<<< ORIGINAL + + let dividerStart = str.indexOf(DIVIDER_, i) + if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now + const isWritingDIVIDER = endsWithAnyPrefixOf(str, DIVIDER_) + blocks.push({ + orig: str.substring(origStart, str.length - (isWritingDIVIDER?.length ?? 0)), + final: '', + state: 'writingOriginal' + }) + return blocks + } + const origStrDone = str.substring(origStart, dividerStart) + dividerStart += DIVIDER_.length + i = dividerStart + // wrote ===== + + + + const finalStartA = str.indexOf(FINAL, i) + const finalStartB = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive + const FINAL_ = finalStartB !== -1 ? '\n' + FINAL : FINAL + let finalStart = finalStartB !== -1 ? finalStartB : finalStartA + + if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL_ right now + const isWritingFINAL = endsWithAnyPrefixOf(str, FINAL_) + blocks.push({ + orig: origStrDone, + final: str.substring(dividerStart, str.length - (isWritingFINAL?.length ?? 0)), + state: 'writingFinal' + }) + return blocks + } + const finalStrDone = str.substring(dividerStart, finalStart) + finalStart += FINAL_.length + i = finalStart + // wrote >>>>> FINAL + + blocks.push({ + orig: origStrDone, + final: finalStrDone, + state: 'done' + }) + } +} + + + + + + + + + +// could simplify this - this assumes we can never add a tag without committing it to the user's screen, but that's not true +export const extractReasoningFromText = ( + onText_: OnText, + thinkTags: [string, string], +): OnText => { + + let latestAddIdx = 0 // exclusive + let foundTag1 = false + let foundTag2 = false + + let fullText = '' + let fullReasoning = '' + + const onText: OnText = ({ newText: newText_, fullText: fullText_ }) => { + // abcdefghi + // | + // until found the first think tag, keep adding to fullText + if (!foundTag1) { + const endsWithTag1 = endsWithAnyPrefixOf(fullText_, thinkTags[0]) + if (endsWithTag1) { + // wait until we get the full tag or know more + return + } + // if found the first tag + const tag1Index = fullText_.lastIndexOf(thinkTags[0]) + if (tag1Index !== -1) { + foundTag1 = true + const newText = fullText.substring(latestAddIdx, tag1Index) + const newReasoning = fullText.substring(tag1Index + thinkTags[0].length, Infinity) + + fullText += newText + fullReasoning += newReasoning + latestAddIdx += newText.length + newReasoning.length + onText_({ newText, fullText, newReasoning: newReasoning, fullReasoning }) + return + } + + // add the text to fullText + const newText = fullText.substring(latestAddIdx, Infinity) + fullText += newText + latestAddIdx += newText.length + onText_({ newText, fullText, newReasoning: '', fullReasoning }) + return + } + // at this point, we found + + // until found the second think tag, keep adding to fullReasoning + if (!foundTag2) { + const endsWithTag2 = endsWithAnyPrefixOf(fullText_, thinkTags[1]) + if (endsWithTag2) { + // wait until we get the full tag or know more + return + } + // if found the second tag + const tag2Index = fullText_.lastIndexOf(thinkTags[1]) + if (tag2Index !== -1) { + foundTag2 = true + const newReasoning = fullText.substring(latestAddIdx, tag2Index) + const newText = fullText.substring(tag2Index + thinkTags[1].length, Infinity) + + fullText += newText + fullReasoning += newReasoning + latestAddIdx += newText.length + newReasoning.length + onText_({ newText, fullText, newReasoning: newReasoning, fullReasoning }) + return + } + + // add the text to fullReasoning + const newReasoning = fullText.substring(latestAddIdx, Infinity) + fullReasoning += newReasoning + latestAddIdx += newReasoning.length + onText_({ newText: '', fullText, newReasoning, fullReasoning }) + return + } + // at this point, we found + + fullText += newText_ + const newText = fullText.substring(latestAddIdx, Infinity) + latestAddIdx += newText.length + onText_({ newText, fullText, newReasoning: '', fullReasoning }) + } + + return onText +} diff --git a/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts b/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts new file mode 100644 index 00000000..c9235c14 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helpers/findDiffs.ts @@ -0,0 +1,276 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { diffLines } from '../react/out/diff/index.js' + +export type ComputedDiff = { + type: 'edit'; + originalCode: string; + originalStartLine: number; + originalEndLine: number; + code: string; + startLine: number; // 1-indexed + endLine: number; +} | { + type: 'insertion'; + // originalCode: string; + originalStartLine: number; // insertion starts on column 0 of this + // originalEndLine: number; + code: string; + startLine: number; + endLine: number; +} | { + type: 'deletion'; + originalCode: string; + originalStartLine: number; + originalEndLine: number; + // code: string; + startLine: number; // deletion starts on column 0 of this + // endLine: number; +} + +export function findDiffs(oldStr: string, newStr: string) { + + // this makes it so the end of the file always ends with a \n (if you don't have this, then diffing E vs E\n gives an "edit". With it, you end up diffing E\n vs E\n\n which now properly gives an insertion) + newStr += '\n'; + oldStr += '\n'; + + // an ordered list of every original line, line added to the new file, and line removed from the old file (order is unambiguous, think about it) + const lineByLineChanges = diffLines(oldStr, newStr); + lineByLineChanges.push({ value: '', added: false, removed: false }) // add a dummy so we flush any streaks we haven't yet at the very end (!line.added && !line.removed) + + let oldFileLineNum: number = 1; + let newFileLineNum: number = 1; + + let streakStartInNewFile: number | undefined = undefined + let streakStartInOldFile: number | undefined = undefined + + const oldStrLines = ('\n' + oldStr).split('\n') // add newline so indexing starts at 1 + const newStrLines = ('\n' + newStr).split('\n') + + const replacements: ComputedDiff[] = [] + for (const line of lineByLineChanges) { + + // no change on this line + if (!line.added && !line.removed) { + + // do nothing + + // if we were on a streak of +s and -s, end it + if (streakStartInNewFile !== undefined) { + let type: 'edit' | 'insertion' | 'deletion' = 'edit' + + const startLine = streakStartInNewFile + const endLine = newFileLineNum - 1 // don't include current line, the edit was up to this line but not including it + + const originalStartLine = streakStartInOldFile! + const originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it + + const newContent = newStrLines.slice(startLine, endLine + 1).join('\n') + const originalContent = oldStrLines.slice(originalStartLine, originalEndLine + 1).join('\n') + + // if the range is empty, mark it as a deletion / insertion (both won't be true at once) + // DELETION + if (endLine === startLine - 1) { + type = 'deletion' + // endLine = startLine + } + + // INSERTION + else if (originalEndLine === originalStartLine - 1) { + type = 'insertion' + // originalEndLine = originalStartLine + } + + const replacement: ComputedDiff = { + type, + startLine, endLine, + // startCol, endCol, + originalStartLine, originalEndLine, + // code: newContent, + // originalRange: new Range(originalStartLine, originalStartCol, originalEndLine, originalEndCol), + originalCode: originalContent, + code: newContent, + } + + replacements.push(replacement) + + streakStartInNewFile = undefined + streakStartInOldFile = undefined + } + oldFileLineNum += line.count ?? 0; + newFileLineNum += line.count ?? 0; + } + + // line was removed from old file + else if (line.removed) { + // if we weren't on a streak, start one on this current line num + if (streakStartInNewFile === undefined) { + streakStartInNewFile = newFileLineNum + streakStartInOldFile = oldFileLineNum + } + oldFileLineNum += line.count ?? 0 // we processed the line so add 1 (or "count") + } + + // line was added to new file + else if (line.added) { + // if we weren't on a streak, start one on this current line num + if (streakStartInNewFile === undefined) { + streakStartInNewFile = newFileLineNum + streakStartInOldFile = oldFileLineNum + } + newFileLineNum += line.count ?? 0; // we processed the line so add 1 (or "count") + } + } // end for + + // console.log('DIFF', { oldStr, newStr, replacements }) + return replacements +} + + + + + + + + + + + + + + + + + + + + +// // uncomment this to test +// let name_ = '' +// let testsFailed = 0 +// const assertEqual = (a: { [s: string]: any }, b: { [s: string]: any }) => { +// let keys = new Set([...Object.keys(a), ...Object.keys(b)]) +// for (let k of keys) { +// if (a[k] !== b[k]) { +// console.error('Void Test Error:', name_, '\n', `${k}=`, `${JSON.stringify(a[k])}, ${JSON.stringify(b[k])}`) +// // console.error(JSON.stringify(a, null, 4)) +// // console.error(JSON.stringify(b, null, 4)) +// testsFailed += 1 +// } +// } +// } +// const test = (name: string, fn: () => void) => { +// name_ = name +// fn() +// } + +// const originalCode = `\ +// A +// B +// C +// D +// E` + +// const insertedCode = `\ +// A +// B +// C +// F +// D +// E` + +// const modifiedCode = `\ +// A +// B +// C +// F +// E` + +// const modifiedCode2 = `\ +// A +// B +// C +// D +// E +// ` + + +// test('Diffs Insertion', () => { +// const diffs = findDiffs(originalCode, insertedCode) + +// const expected: BaseDiff = { +// type: 'insertion', +// originalCode: '', +// originalStartLine: 4, // empty range where the insertion happened +// originalEndLine: 4, + +// startLine: 4, +// startCol: 1, +// endLine: 4, +// endCol: Number.MAX_SAFE_INTEGER, +// } +// assertEqual(diffs[0], expected) +// }) + +// test('Diffs Deletion', () => { +// const diffs = findDiffs(insertedCode, originalCode) +// assertEqual({ length: diffs.length }, { length: 1 }) +// const expected: BaseDiff = { +// type: 'deletion', +// originalCode: 'F', +// originalStartLine: 4, +// originalEndLine: 4, + +// startLine: 4, +// startCol: 1, // empty range where the deletion happened +// endLine: 4, +// endCol: 1, +// } +// assertEqual(diffs[0], expected) +// }) + +// test('Diffs Modification', () => { +// const diffs = findDiffs(originalCode, modifiedCode) +// assertEqual({ length: diffs.length }, { length: 1 }) +// const expected: BaseDiff = { +// type: 'edit', +// originalCode: 'D', +// originalStartLine: 4, +// originalEndLine: 4, + +// startLine: 4, +// startCol: 1, +// endLine: 4, +// endCol: Number.MAX_SAFE_INTEGER, +// } +// assertEqual(diffs[0], expected) +// }) + +// test('Diffs Modification 2', () => { +// const diffs = findDiffs(originalCode, modifiedCode2) +// assertEqual({ length: diffs.length }, { length: 1 }) +// const expected: BaseDiff = { +// type: 'insertion', +// originalCode: '', +// originalStartLine: 6, +// originalEndLine: 6, + +// startLine: 6, +// startCol: 1, +// endLine: 6, +// endCol: Number.MAX_SAFE_INTEGER, +// } +// assertEqual(diffs[0], expected) +// }) + + + +// if (testsFailed === 0) { +// console.log('✅ Void - All tests passed') +// } +// else { +// console.log('❌ Void - At least one test failed') +// } diff --git a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts new file mode 100644 index 00000000..f7afa5dd --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts @@ -0,0 +1,52 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../../base/common/uri.js' +import { EndOfLinePreference } from '../../../../../editor/common/model.js' +import { IModelService } from '../../../../../editor/common/services/model.js' +import { IFileService } from '../../../../../platform/files/common/files.js' + + +// attempts to read URI of currently opened model, then of raw file +export const VSReadFile = async (uri: URI, modelService: IModelService, fileService: IFileService) => { + + const modelResult = await _VSReadModel(modelService, uri) + if (modelResult) return modelResult + + const fileResult = await _VSReadFileRaw(fileService, uri) + if (fileResult) return fileResult + + return '' + +} + +// read files from VSCode. preferred (but appears to only work if the model of this URI already exists. If it doesn't use the other function.) +const _VSReadModel = async (modelService: IModelService, uri: URI): Promise => { + + // attempt to read saved model (doesn't work if application was reloaded...) + const model = modelService.getModel(uri) + if (model) { + return model.getValue(EndOfLinePreference.LF) + } + + // backup logic - look at all opened models and check if they have the same `fsPath` + const models = modelService.getModels() + for (const model of models) { + if (model.uri.fsPath === uri.fsPath) + return model.getValue(EndOfLinePreference.LF); + } + + return null +} + +const _VSReadFileRaw = async (fileService: IFileService, uri: URI) => { + try { + const res = await fileService.readFile(uri) + const str = res.value.toString() + return str + } catch (e) { + return null + } +} diff --git a/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts b/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts new file mode 100644 index 00000000..85b909b2 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts @@ -0,0 +1,14 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { isLinux, isMacintosh, isWindows } from '../../../../../base/common/platform.js'; + +// import { OS, OperatingSystem } from '../../../../../base/common/platform.js'; +// alternatively could use ^ and OS === OperatingSystem.Windows ? ... + + + +export const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null + diff --git a/src/vs/workbench/contrib/void/browser/media/void.css b/src/vs/workbench/contrib/void/browser/media/void.css new file mode 100644 index 00000000..e5e9793e --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/media/void.css @@ -0,0 +1,168 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + + .monaco-editor .void-sweepIdxBG { + background-color: var(--vscode-void-sweepIdxBG); +} + +.void-sweepBG { + background-color: var(--vscode-void-sweepBG); +} + +.void-highlightBG { + background-color: var(--vscode-void-highlightBG); +} + +.void-greenBG { + background-color: var(--vscode-void-greenBG); +} + +.void-redBG { + background-color: var(--vscode-void-redBG); +} + +.void-watermark-button { + margin: 8px 0; + padding: 8px 20px; + background-color: #3b82f6; + color: white; + border: none; + border-radius: 4px; + outline: none !important; + box-shadow: none !important; + cursor: pointer; + transition: background-color 0.2s ease; +} +.void-watermark-button:hover { + background-color: #2563eb; +} +.void-watermark-button:active { + background-color: #2563eb; +} + + + + +.void-settings-watermark-button { + margin: 8px 0; + padding: 8px 20px; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: none; + border-radius: 4px; + outline: none !important; + box-shadow: none !important; + cursor: pointer; + transition: all 0.2s ease; +} +.void-settings-watermark-button:hover { + filter: brightness(1.1); +} +.void-settings-watermark-button:active { + filter: brightness(1.1); +} + + + + +.void-link { + color: #3b82f6; + cursor: pointer; + transition: all 0.2s ease; +} +.void-link:hover { + opacity: 80%; +} + + + + + +.void-scrollable-element::-webkit-scrollbar, +.void-scrollable-element *::-webkit-scrollbar { + width: 14px !important; + height: 14px !important; +} + +.void-scrollable-element::-webkit-scrollbar-track, +.void-scrollable-element *::-webkit-scrollbar-track { + background: transparent !important; +} + +.void-scrollable-element::-webkit-scrollbar-thumb, +.void-scrollable-element *::-webkit-scrollbar-thumb { + background-color: transparent !important; + border-radius: 0px !important; +} + +.void-scrollable-element::-webkit-scrollbar-thumb:hover, +.void-scrollable-element *::-webkit-scrollbar-thumb:hover { + background-color: var(--vscode-scrollbarSlider-hoverBackground) !important; +} + +.void-scrollable-element::-webkit-scrollbar-thumb:active, +.void-scrollable-element *::-webkit-scrollbar-thumb:active { + background-color: var(--vscode-scrollbarSlider-activeBackground) !important; +} + +.void-scrollable-element::-webkit-scrollbar-corner, +.void-scrollable-element *::-webkit-scrollbar-corner { + background-color: transparent !important; +} + +.void-scrollable-element.show-scrollbar-0::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-0 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 0%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-1::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-1 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 10%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-2::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-2 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 20%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-3::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-3 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 30%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-4::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-4 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 40%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-5::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-5 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 50%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-6::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-6 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 60%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-7::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-7 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 70%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-8::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-8 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 80%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-9::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-9 *::-webkit-scrollbar-thumb { + background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 90%, transparent) !important; +} + +.void-scrollable-element.show-scrollbar-10::-webkit-scrollbar-thumb, +.void-scrollable-element.show-scrollbar-10 *::-webkit-scrollbar-thumb { + background-color: var(--vscode-scrollbarSlider-background) !important; +} diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts new file mode 100644 index 00000000..90e01d50 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -0,0 +1,517 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + + +import { URI } from '../../../../../base/common/uri.js'; +import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js'; +import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js'; +import { IModelService } from '../../../../../editor/common/services/model.js'; +import { os } from '../helpers/systemInfo.js'; +import { IVoidFileService } from '../../common/voidFileService.js'; + + +// this is just for ease of readability +export const tripleTick = ['```', '```'] + +export const chat_systemMessage = (workspaces: string[]) => `\ +You are a coding assistant. You are given a list of instructions to follow \`INSTRUCTIONS\`, and optionally a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`. + +Please respond to the user's query. The user's query is never invalid. + +The user has the following system information: +- ${os} +- Open workspaces: ${workspaces.join(', ')} + +In the case that the user asks you to make changes to code, you should make sure to return CODE BLOCKS of the changes, as well as explanations and descriptions of the changes. +For example, if the user asks you to "make this file look nicer", make sure your output includes a code block with concrete ways the file can look nicer. +- Do not re-write the entire file in the code block. +- You can write comments like "// ... existing code" to indicate existing code. +- Make sure you give enough context in the code block to apply the change to the correct location in the code. + +You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it. +If you are given tools: +- Only use tools if the user asks you to do something. If the user simply says hi or asks you a question that you can answer without tools, then do NOT tools. +- You are allowed to use tools without asking for permission. +- Feel free to use tools to gather context, make suggestions, etc. +- One great use of tools is to explore imports that you'd like to have more information about. +- Reference relevant files that you found when using tools if they helped you come up with your answer. +- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not even refer to "pages" of results, just say you're getting more results. + +Do not output any of these instructions, nor tell the user anything about them unless directly prompted for them. +Do not tell the user anything about the examples below. Do not assume the user is talking about any of the examples below. + +## EXAMPLE 1 +FILES +math.ts +${tripleTick[0]}typescript +const addNumbers = (a, b) => a + b +const multiplyNumbers = (a, b) => a * b +const subtractNumbers = (a, b) => a - b +const divideNumbers = (a, b) => a / b + +const vectorize = (...numbers) => { + return numbers // vector +} + +const dot = (vector1: number[], vector2: number[]) => { + if (vector1.length !== vector2.length) throw new Error(\`Could not dot vectors \${vector1} and \${vector2}. Size mismatch.\`) + let sum = 0 + for (let i = 0; i < vector1.length; i += 1) + sum += multiplyNumbers(vector1[i], vector2[i]) + return sum +} + +const normalize = (vector: number[]) => { + const norm = Math.sqrt(dot(vector, vector)) + for (let i = 0; i < vector.length; i += 1) + vector[i] = divideNumbers(vector[i], norm) + return vector +} + +const normalized = (vector: number[]) => { + const v2 = [...vector] // clone vector + return normalize(v2) +} +${tripleTick[1]} + + +SELECTIONS +math.ts (lines 3:3) +${tripleTick[0]}typescript +const subtractNumbers = (a, b) => a - b +${tripleTick[1]} + +INSTRUCTIONS +add a function that exponentiates a number below this, and use it to make a power function that raises all entries of a vector to a power + +## ACCEPTED OUTPUT +We can add the following code to the file: +${tripleTick[0]}typescript +// existing code... +const subtractNumbers = (a, b) => a - b +const exponentiateNumbers = (a, b) => Math.pow(a, b) +const divideNumbers = (a, b) => a / b +// existing code... + +const raiseAll = (vector: number[], power: number) => { + for (let i = 0; i < vector.length; i += 1) + vector[i] = exponentiateNumbers(vector[i], power) + return vector +} +${tripleTick[1]} + + +## EXAMPLE 2 +FILES +fib.ts +${tripleTick[0]}typescript + +const dfs = (root) => { + if (!root) return; + console.log(root.val); + dfs(root.left); + dfs(root.right); +} +const fib = (n) => { + if (n < 1) return 1 + return fib(n - 1) + fib(n - 2) +} +${tripleTick[1]} + +SELECTIONS +fib.ts (lines 10:10) +${tripleTick[0]}typescript + return fib(n - 1) + fib(n - 2) +${tripleTick[1]} + +INSTRUCTIONS +memoize results + +## ACCEPTED OUTPUT +To implement memoization in your Fibonacci function, you can use a JavaScript object to store previously computed results. This will help avoid redundant calculations and improve performance. Here's how you can modify your function: +${tripleTick[0]}typescript +// existing code... +const fib = (n, memo = {}) => { + if (n < 1) return 1; + if (memo[n]) return memo[n]; // Check if result is already computed + memo[n] = fib(n - 1, memo) + fib(n - 2, memo); // Store result in memo + return memo[n]; +} +${tripleTick[1]} +Explanation: +Memoization Object: A memo object is used to store the results of Fibonacci calculations for each n. +Check Memo: Before computing fib(n), the function checks if the result is already in memo. If it is, it returns the stored result. +Store Result: After computing fib(n), the result is stored in memo for future reference. + +## END EXAMPLES\ +` + + +type FileSelnLocal = { fileURI: URI, content: string } +const stringifyFileSelection = ({ fileURI, content }: FileSelnLocal) => { + return `\ +${fileURI.fsPath} +${tripleTick[0]}${filenameToVscodeLanguage(fileURI.fsPath) ?? ''} +${content} +${tripleTick[1]} +` +} +const stringifyCodeSelection = ({ fileURI, selectionStr, range }: CodeSelection) => { + return `\ +${fileURI.fsPath} (lines ${range.startLineNumber}:${range.endLineNumber}) +${tripleTick[0]}${filenameToVscodeLanguage(fileURI.fsPath) ?? ''} +${selectionStr} +${tripleTick[1]} +` +} + +const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.' +const stringifyFileSelections = async (fileSelections: FileSelection[], voidFileService: IVoidFileService) => { + if (fileSelections.length === 0) return null + const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => { + const content = await voidFileService.readFile(sel.fileURI) ?? failToReadStr + return { ...sel, content } + })) + return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n') +} +const stringifyCodeSelections = (codeSelections: CodeSelection[]) => { + return codeSelections.map(sel => stringifyCodeSelection(sel)).join('\n') || null +} +const stringifySelectionNames = (currSelns: StagingSelectionItem[] | null): string => { + if (!currSelns) return '' + return currSelns.map(s => `${s.fileURI.fsPath}${s.range ? ` (lines ${s.range.startLineNumber}:${s.range.endLineNumber})` : ''}`).join('\n') +} + +export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null) => { + + const selnsStr = stringifySelectionNames(currSelns) + + let str = '' + if (selnsStr) { str += `SELECTIONS\n${selnsStr}\n` } + str += `\nINSTRUCTIONS\n${instructions}` + return str; +}; + +export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, voidFileService: IVoidFileService) => { + + // ADD IN FILES AT TOP + const allSelections = [...currSelns || [], ...prevSelns || []] + + if (allSelections.length === 0) return null + + const codeSelections: CodeSelection[] = [] + const fileSelections: FileSelection[] = [] + const filesURIs = new Set() + + for (const selection of allSelections) { + if (selection.type === 'Selection') { + codeSelections.push(selection) + } + else if (selection.type === 'File') { + const fileSelection = selection + const path = fileSelection.fileURI.fsPath + if (!filesURIs.has(path)) { + filesURIs.add(path) + fileSelections.push(fileSelection) + } + } + } + + const filesStr = await stringifyFileSelections(fileSelections, voidFileService) + const selnsStr = stringifyCodeSelections(codeSelections) + + + if (filesStr || selnsStr) return `\ +ALL FILE CONTENTS +${filesStr} +${selnsStr}` + + return null +} + +export const chat_userMessageContentWithAllFilesToo = (userMessage: string, selectionsString: string | null) => { + if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}` + else return userMessage +} + + +export const rewriteCode_systemMessage = `\ +You are a coding assistant that re-writes an entire file to make a change. You are given the original file \`ORIGINAL_FILE\` and a change \`CHANGE\`. + +Directions: +1. Please rewrite the original file \`ORIGINAL_FILE\`, making the change \`CHANGE\`. You must completely re-write the whole file. +2. Keep all of the original comments, spaces, newlines, and other details whenever possible. +3. ONLY output the full new file. Do not add any other explanations or text. +` + + + + +export const rewriteCode_userMessage = ({ originalCode, applyStr, uri }: { originalCode: string, applyStr: string, uri: URI }) => { + + const language = filenameToVscodeLanguage(uri.fsPath) ?? '' + + return `\ +ORIGINAL_FILE +${tripleTick[0]}${language} +${originalCode} +${tripleTick[1]} + +CHANGE +${tripleTick[0]} +${applyStr} +${tripleTick[1]} + +INSTRUCTIONS +Please finish writing the new file by applying the change to the original file. Return ONLY the completion of the file, without any explanation. +` +} + + + + + + +export const aiRegex_computeReplacementsForFile_systemMessage = `\ +You are a "search and replace" coding assistant. + +You are given a FILE that the user is editing, and your job is to search for all occurences of a SEARCH_CLAUSE, and change them according to a REPLACE_CLAUSE. + +The SEARCH_CLAUSE may be a string, regex, or high-level description of what the user is searching for. + +The REPLACE_CLAUSE will always be a high-level description of what the user wants to replace. + +The user's request may be "fuzzy" or not well-specified, and it is your job to interpret all of the changes they want to make for them. For example, the user may ask you to search and replace all instances of a variable, but this may involve changing parameters, function names, types, and so on to agree with the change they want to make. Feel free to make all of the changes you *think* that the user wants to make, but also make sure not to make unnessecary or unrelated changes. + +## Instructions + +1. If you do not want to make any changes, you should respond with the word "no". + +2. If you want to make changes, you should return a single CODE BLOCK of the changes that you want to make. +For example, if the user is asking you to "make this variable a better name", make sure your output includes all the changes that are needed to improve the variable name. +- Do not re-write the entire file in the code block +- You can write comments like "// ... existing code" to indicate existing code +- Make sure you give enough context in the code block to apply the changes to the correct location in the code` + + +export const aiRegex_computeReplacementsForFile_userMessage = async ({ searchClause, replaceClause, fileURI, voidFileService }: { searchClause: string, replaceClause: string, fileURI: URI, modelService: IModelService, voidFileService: IVoidFileService }) => { + + // we may want to do this in batches + const fileSelection: FileSelection = { type: 'File', fileURI, selectionStr: null, range: null } + + const file = await stringifyFileSelections([fileSelection], voidFileService) + + return `\ +## FILE +${file} + +## SEARCH_CLAUSE +Here is what the user is searching for: +${searchClause} + +## REPLACE_CLAUSE +Here is what the user wants to replace it with: +${replaceClause} + +## INSTRUCTIONS +Please return the changes you want to make to the file in a codeblock, or return "no" if you do not want to make changes.` +} + + + + +// don't have to tell it it will be given the history; just give it to it +export const aiRegex_search_systemMessage = `\ +You are a coding assistant that executes the SEARCH part of a user's search and replace query. + +You will be given the user's search query, SEARCH, which is the user's query for what files to search for in the codebase. You may also be given the user's REPLACE query for additional context. + +Output +- Regex query +- Files to Include (optional) +- Files to Exclude? (optional) + +` + + + +export const ORIGINAL = `<<<<<<< ORIGINAL` +export const DIVIDER = `=======` +export const FINAL = `>>>>>>> UPDATED` + +export const searchReplace_systemMessage = `\ +You are a coding assistant that generates SEARCH/REPLACE code blocks that will be used to edit a file. + +A SEARCH/REPLACE block describes the code before and after a change. Here is the format: +${tripleTick[0]} +${ORIGINAL} +// ... original code goes here +${DIVIDER} +// ... final code goes here +${FINAL} +${tripleTick[1]} + +You will be given the original file \`ORIGINAL_FILE\` and a description of a change \`CHANGE\` to make. +Output SEARCH/REPLACE blocks to edit the file according to the desired change. You may output multiple SEARCH/REPLACE blocks. + +Directions: +1. Your OUTPUT should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this. +2. The original code in each SEARCH/REPLACE block must EXACTLY match lines of code in the original file. +3. The original code in each SEARCH/REPLACE block must include enough text to uniquely identify the change in the file. +4. The original code in each SEARCH/REPLACE block must be disjoint from all other blocks. + +The SEARCH/REPLACE blocks you generate will be applied immediately, and so they **MUST** produce a file that the user can run IMMEDIATELY. +- Make sure you add all necessary imports. +- Make sure the "final" code is complete and will not result in syntax/lint errors. + +Follow coding conventions of the user (spaces, semilcolons, comments, etc). If the user spaces or formats things a certain way, CONTINUE formatting it that way, even if you prefer otherwise. + +## EXAMPLE 1 +ORIGINAL_FILE +${tripleTick[0]} +let w = 5 +let x = 6 +let y = 7 +let z = 8 +${tripleTick[1]} + +CHANGE +Make x equal to 6.5, not 6. +${tripleTick[0]} +// ... existing code +let x = 6.5 +// ... existing code +${tripleTick[1]} + + +## ACCEPTED OUTPUT +${tripleTick[0]} +${ORIGINAL} +let x = 6 +${DIVIDER} +let x = 6.5 +${FINAL} +${tripleTick[1]} +` + +export const searchReplace_userMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\ +ORIGINAL_FILE +${originalCode} + +CHANGE +${applyStr} + +INSTRUCTIONS +Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggested SEARCH/REPLACE blocks, without any explanation. +` + + + + + +export const voidPrefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullFileStr: string, startLine: number, endLine: number }) => { + + const fullFileLines = fullFileStr.split('\n') + + // we can optimize this later + const MAX_PREFIX_SUFFIX_CHARS = 20_000 + /* + + a + a + a <-- final i (prefix = a\na\n) + a + |b <-- startLine-1 (middle = b\nc\nd\n) <-- initial i (moves up) + c + d| <-- endLine-1 <-- initial j (moves down) + e + e <-- final j (suffix = e\ne\n) + e + e + */ + + let prefix = '' + let i = startLine - 1 // 0-indexed exclusive + // we'll include fullFileLines[i...(startLine-1)-1].join('\n') in the prefix. + while (i !== 0) { + const newLine = fullFileLines[i - 1] + if (newLine.length + 1 + prefix.length <= MAX_PREFIX_SUFFIX_CHARS) { // +1 to include the \n + prefix = `${newLine}\n${prefix}` + i -= 1 + } + else break + } + + let suffix = '' + let j = endLine - 1 + while (j !== fullFileLines.length - 1) { + const newLine = fullFileLines[j + 1] + if (newLine.length + 1 + suffix.length <= MAX_PREFIX_SUFFIX_CHARS) { // +1 to include the \n + suffix = `${suffix}\n${newLine}` + j += 1 + } + else break + } + + return { prefix, suffix } + +} + + +export type QuickEditFimTagsType = { + preTag: string, + sufTag: string, + midTag: string +} +export const defaultQuickEditFimTags: QuickEditFimTagsType = { + preTag: 'ABOVE', + sufTag: 'BELOW', + midTag: 'SELECTION', +} + +// this should probably be longer +export const ctrlKStream_systemMessage = ({ quickEditFIMTags: { preTag, midTag, sufTag } }: { quickEditFIMTags: QuickEditFimTagsType }) => { + return `\ +You are a FIM (fill-in-the-middle) coding assistant. Your task is to fill in the middle SELECTION marked by <${midTag}> tags. + +The user will give you INSTRUCTIONS, as well as code that comes BEFORE the SELECTION, indicated with <${preTag}>...before, and code that comes AFTER the SELECTION, indicated with <${sufTag}>...after. +The user will also give you the existing original SELECTION that will be be replaced by the SELECTION that you output, for additional context. + +Instructions: +1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_code. Do NOT output any text or explanations before or after this. +2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>... or <${sufTag}>... tags. +3. Make sure all brackets in the new selection are balanced the same as in the original selection. +4. Be careful not to duplicate or remove variables, comments, or other syntax by mistake. +` +} + +export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instructions, fimTags, isOllamaFIM, language }: { + selection: string, prefix: string, suffix: string, instructions: string, fimTags: QuickEditFimTagsType, language: string, + isOllamaFIM: false, // we require this be false for clarity +}) => { + const { preTag, sufTag, midTag } = fimTags + + // prompt the model artifically on how to do FIM + // const preTag = 'BEFORE' + // const sufTag = 'AFTER' + // const midTag = 'SELECTION' + return `\ + +CURRENT SELECTION +${tripleTick[0]}${language} +<${midTag}>${selection} +${tripleTick[1]} + +INSTRUCTIONS +${instructions} + +<${preTag}>${prefix} +<${sufTag}>${suffix} + +Return only the completion block of code (of the form ${tripleTick[0]}${language} +<${midTag}>...new code +${tripleTick[1]}).` +}; + diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts new file mode 100644 index 00000000..da8f5c55 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts @@ -0,0 +1,68 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { IEditCodeService } from './editCodeService.js'; +import { roundRangeToLines } from './sidebarActions.js'; +import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js'; +import { localize2 } from '../../../../nls.js'; +import { IMetricsService } from '../common/metricsService.js'; + + +export type QuickEditPropsType = { + diffareaid: number, + textAreaRef: (ref: HTMLTextAreaElement | null) => void; + onChangeHeight: (height: number) => void; + onChangeText: (text: string) => void; + initText: string | null; +} + +export type QuickEdit = { + startLine: number, // 0-indexed + beforeCode: string, + afterCode?: string, + instructions?: string, + responseText?: string, // model can produce a text response too +} + + +registerAction2(class extends Action2 { + constructor( + ) { + super({ + id: VOID_CTRL_K_ACTION_ID, + f1: true, + title: localize2('voidQuickEditAction', 'Void: Quick Edit'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyK, + weight: KeybindingWeight.VoidExtension, + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + + const editorService = accessor.get(ICodeEditorService) + const metricsService = accessor.get(IMetricsService) + metricsService.capture('Ctrl+K', {}) + + const editor = editorService.getActiveCodeEditor() + if (!editor) return; + const model = editor.getModel() + if (!model) return; + const selection = roundRangeToLines(editor.getSelection(), { emptySelectionBehavior: 'line' }) + if (!selection) return; + + + const { startLineNumber: startLine, endLineNumber: endLine } = selection + + const editCodeService = accessor.get(IEditCodeService) + editCodeService.addCtrlKZone({ startLine, endLine, editor }) + } +}); diff --git a/src/vs/workbench/contrib/void/browser/quickEditStateService.ts b/src/vs/workbench/contrib/void/browser/quickEditStateService.ts new file mode 100644 index 00000000..62f3823b --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/quickEditStateService.ts @@ -0,0 +1,77 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { QuickEdit } from './quickEditActions.js'; + + + +// service that manages state +export type VoidQuickEditState = { + quickEditsOfDocument: { [uri: string]: QuickEdit } +} + +export interface IQuickEditStateService { + readonly _serviceBrand: undefined; + + readonly state: VoidQuickEditState; // readonly to the user + setState(newState: Partial): void; + onDidChangeState: Event; + + onDidFocusChat: Event; + onDidBlurChat: Event; + fireFocusChat(): void; + fireBlurChat(): void; + +} + +export const IQuickEditStateService = createDecorator('voidQuickEditStateService'); +class VoidQuickEditStateService extends Disposable implements IQuickEditStateService { + _serviceBrand: undefined; + + static readonly ID = 'voidQuickEditStateService'; + + private readonly _onDidChangeState = new Emitter(); + readonly onDidChangeState: Event = this._onDidChangeState.event; + + private readonly _onFocusChat = new Emitter(); + readonly onDidFocusChat: Event = this._onFocusChat.event; + + private readonly _onBlurChat = new Emitter(); + readonly onDidBlurChat: Event = this._onBlurChat.event; + + + // state + state: VoidQuickEditState + + constructor( + ) { + super() + + // initial state + this.state = { quickEditsOfDocument: {} } + } + + + setState(newState: Partial) { + + this.state = { ...this.state, ...newState } + this._onDidChangeState.fire() + } + + fireFocusChat() { + this._onFocusChat.fire() + } + + fireBlurChat() { + this._onBlurChat.fire() + } + +} + +registerSingleton(IQuickEditStateService, VoidQuickEditStateService, InstantiationType.Eager); diff --git a/src/vs/workbench/contrib/void/browser/react/.gitignore b/src/vs/workbench/contrib/void/browser/react/.gitignore new file mode 100644 index 00000000..da187abd --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/.gitignore @@ -0,0 +1,2 @@ +out/ +src2/ diff --git a/src/vs/workbench/contrib/void/browser/react/README.md b/src/vs/workbench/contrib/void/browser/react/README.md new file mode 100644 index 00000000..9f998f8d --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/README.md @@ -0,0 +1,10 @@ + +Run `node build.js` to compile the React into `out/`. + +A couple things to remember: + +- Make sure to add .js at the end of any external imports used in here, e.g. ../../../../../my_file.js. If you don't do this, you will get untraceable errors. + +- src/ needs to be shallow (1 folder deep) so the detection of externals works properly (see tsup.config.js). + + diff --git a/src/vs/workbench/contrib/void/browser/react/build.js b/src/vs/workbench/contrib/void/browser/react/build.js new file mode 100755 index 00000000..ab4ea921 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/build.js @@ -0,0 +1,154 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { execSync } from 'child_process'; +import { spawn } from 'cross-spawn' +// Added lines below +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function doesPathExist(filePath) { + try { + const stats = fs.statSync(filePath); + + return stats.isFile(); + } catch (err) { + if (err.code === 'ENOENT') { + return false; + } + throw err; + } +} + +/* + +This function finds `globalDesiredPath` given `localDesiredPath` and `currentPath` + +Diagram: + +...basePath/ +└── void/ + ├── ...currentPath/ (defined globally) + └── ...localDesiredPath/ (defined locally) + +*/ +function findDesiredPathFromLocalPath(localDesiredPath, currentPath) { + + // walk upwards until currentPath + localDesiredPath exists + while (!doesPathExist(path.join(currentPath, localDesiredPath))) { + const parentDir = path.dirname(currentPath); + + if (parentDir === currentPath) { + return undefined; + } + + currentPath = parentDir; + } + + // return the `globallyDesiredPath` + const globalDesiredPath = path.join(currentPath, localDesiredPath) + return globalDesiredPath; +} + +// hack to refresh styles automatically +function saveStylesFile() { + setTimeout(() => { + try { + const pathToCssFile = findDesiredPathFromLocalPath('./src/vs/workbench/contrib/void/browser/react/src2/styles.css', __dirname); + + if (pathToCssFile === undefined) { + console.error('[scope-tailwind] Error finding styles.css'); + return; + } + + // Or re-write with the same content: + const content = fs.readFileSync(pathToCssFile, 'utf8'); + fs.writeFileSync(pathToCssFile, content, 'utf8'); + console.log('[scope-tailwind] Force-saved styles.css'); + } catch (err) { + console.error('[scope-tailwind] Error saving styles.css:', err); + } + }, 3000); +} + +const args = process.argv.slice(2); +const isWatch = args.includes('--watch') || args.includes('-w'); + +if (isWatch) { + // this just builds it if it doesn't exist instead of waiting for the watcher to trigger + // Check if src2/ exists; if not, do an initial scope-tailwind build + if (!fs.existsSync('src2')) { + try { + console.log('🔨 Running initial scope-tailwind build to create src2 folder...'); + execSync( + 'npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "void-"', + { stdio: 'inherit' } + ); + console.log('✅ src2/ created successfully.'); + } catch (err) { + console.error('❌ Error running initial scope-tailwind build:', err); + process.exit(1); + } + } + + // Watch mode + const scopeTailwindWatcher = spawn('npx', [ + 'nodemon', + '--watch', 'src', + '--ext', 'ts,tsx,css', + '--exec', + 'npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "void-"' + ]); + + const tsupWatcher = spawn('npx', [ + 'tsup', + '--watch' + ]); + + scopeTailwindWatcher.stdout.on('data', (data) => { + console.log(`[scope-tailwind] ${data}`); + // If the output mentions "styles.css", trigger the save: + if (data.toString().includes('styles.css')) { + saveStylesFile(); + } + }); + + scopeTailwindWatcher.stderr.on('data', (data) => { + console.error(`[scope-tailwind] ${data}`); + }); + + // Handle tsup watcher output + tsupWatcher.stdout.on('data', (data) => { + console.log(`[tsup] ${data}`); + }); + + tsupWatcher.stderr.on('data', (data) => { + console.error(`[tsup] ${data}`); + }); + + // Handle process termination + process.on('SIGINT', () => { + scopeTailwindWatcher.kill(); + tsupWatcher.kill(); + process.exit(); + }); + + console.log('🔄 Watchers started! Press Ctrl+C to stop both watchers.'); +} else { + // Build mode + console.log('📦 Building...'); + + // Run scope-tailwind once + execSync('npx scope-tailwind ./src -o src2/ -s void-scope -c styles.css -p "void-"', { stdio: 'inherit' }); + + // Run tsup once + execSync('npx tsup', { stdio: 'inherit' }); + + console.log('✅ Build complete!'); +} diff --git a/src/vs/workbench/contrib/void/browser/react/out/diff/index.js b/src/vs/workbench/contrib/void/browser/react/out/diff/index.js new file mode 100644 index 00000000..88c8a4a1 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/out/diff/index.js @@ -0,0 +1,553 @@ +// ../../../../../../../node_modules/diff/lib/index.mjs +function Diff() { +} +Diff.prototype = { + diff: function diff(oldString, newString) { + var _options$timeout; + var options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; + var callback = options.callback; + if (typeof options === "function") { + callback = options; + options = {}; + } + var self = this; + function done(value) { + value = self.postProcess(value, options); + if (callback) { + setTimeout(function() { + callback(value); + }, 0); + return true; + } else { + return value; + } + } + oldString = this.castInput(oldString, options); + newString = this.castInput(newString, options); + oldString = this.removeEmpty(this.tokenize(oldString, options)); + newString = this.removeEmpty(this.tokenize(newString, options)); + var newLen = newString.length, oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + if (options.maxEditLength != null) { + maxEditLength = Math.min(maxEditLength, options.maxEditLength); + } + var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity; + var abortAfterTimestamp = Date.now() + maxExecutionTime; + var bestPath = [{ + oldPos: -1, + lastComponent: void 0 + }]; + var newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options); + if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken)); + } + var minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; + function execEditLength() { + for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) { + var basePath = void 0; + var removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1]; + if (removePath) { + bestPath[diagonalPath - 1] = void 0; + } + var canAdd = false; + if (addPath) { + var addPathNewPos = addPath.oldPos - diagonalPath; + canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; + } + var canRemove = removePath && removePath.oldPos + 1 < oldLen; + if (!canAdd && !canRemove) { + bestPath[diagonalPath] = void 0; + continue; + } + if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) { + basePath = self.addToPath(addPath, true, false, 0, options); + } else { + basePath = self.addToPath(removePath, false, true, 1, options); + } + newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options); + if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken)); + } else { + bestPath[diagonalPath] = basePath; + if (basePath.oldPos + 1 >= oldLen) { + maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); + } + if (newPos + 1 >= newLen) { + minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); + } + } + } + editLength++; + } + if (callback) { + (function exec() { + setTimeout(function() { + if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { + return callback(); + } + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + addToPath: function addToPath(path, added, removed, oldPosInc, options) { + var last = path.lastComponent; + if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: { + count: last.count + 1, + added, + removed, + previousComponent: last.previousComponent + } + }; + } else { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: { + count: 1, + added, + removed, + previousComponent: last + } + }; + } + }, + extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath, options) { + var newLen = newString.length, oldLen = oldString.length, oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) { + newPos++; + oldPos++; + commonCount++; + if (options.oneChangePerToken) { + basePath.lastComponent = { + count: 1, + previousComponent: basePath.lastComponent, + added: false, + removed: false + }; + } + } + if (commonCount && !options.oneChangePerToken) { + basePath.lastComponent = { + count: commonCount, + previousComponent: basePath.lastComponent, + added: false, + removed: false + }; + } + basePath.oldPos = oldPos; + return newPos; + }, + equals: function equals(left, right, options) { + if (options.comparator) { + return options.comparator(left, right); + } else { + return left === right || options.ignoreCase && left.toLowerCase() === right.toLowerCase(); + } + }, + removeEmpty: function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, + castInput: function castInput(value) { + return value; + }, + tokenize: function tokenize(value) { + return Array.from(value); + }, + join: function join(chars) { + return chars.join(""); + }, + postProcess: function postProcess(changeObjects) { + return changeObjects; + } +}; +function buildValues(diff2, lastComponent, newString, oldString, useLongestToken) { + var components = []; + var nextComponent; + while (lastComponent) { + components.push(lastComponent); + nextComponent = lastComponent.previousComponent; + delete lastComponent.previousComponent; + lastComponent = nextComponent; + } + components.reverse(); + var componentPos = 0, componentLen = components.length, newPos = 0, oldPos = 0; + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = value.map(function(value2, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value2.length ? oldValue : value2; + }); + component.value = diff2.join(value); + } else { + component.value = diff2.join(newString.slice(newPos, newPos + component.count)); + } + newPos += component.count; + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = diff2.join(oldString.slice(oldPos, oldPos + component.count)); + oldPos += component.count; + } + } + return components; +} +function longestCommonPrefix(str1, str2) { + var i; + for (i = 0; i < str1.length && i < str2.length; i++) { + if (str1[i] != str2[i]) { + return str1.slice(0, i); + } + } + return str1.slice(0, i); +} +function longestCommonSuffix(str1, str2) { + var i; + if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) { + return ""; + } + for (i = 0; i < str1.length && i < str2.length; i++) { + if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) { + return str1.slice(-i); + } + } + return str1.slice(-i); +} +function replacePrefix(string, oldPrefix, newPrefix) { + if (string.slice(0, oldPrefix.length) != oldPrefix) { + throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug")); + } + return newPrefix + string.slice(oldPrefix.length); +} +function replaceSuffix(string, oldSuffix, newSuffix) { + if (!oldSuffix) { + return string + newSuffix; + } + if (string.slice(-oldSuffix.length) != oldSuffix) { + throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug")); + } + return string.slice(0, -oldSuffix.length) + newSuffix; +} +function removePrefix(string, oldPrefix) { + return replacePrefix(string, oldPrefix, ""); +} +function removeSuffix(string, oldSuffix) { + return replaceSuffix(string, oldSuffix, ""); +} +function maximumOverlap(string1, string2) { + return string2.slice(0, overlapCount(string1, string2)); +} +function overlapCount(a, b) { + var startA = 0; + if (a.length > b.length) { + startA = a.length - b.length; + } + var endB = b.length; + if (a.length < b.length) { + endB = a.length; + } + var map = Array(endB); + var k = 0; + map[0] = 0; + for (var j = 1; j < endB; j++) { + if (b[j] == b[k]) { + map[j] = map[k]; + } else { + map[j] = k; + } + while (k > 0 && b[j] != b[k]) { + k = map[k]; + } + if (b[j] == b[k]) { + k++; + } + } + k = 0; + for (var i = startA; i < a.length; i++) { + while (k > 0 && a[i] != b[k]) { + k = map[k]; + } + if (a[i] == b[k]) { + k++; + } + } + return k; +} +var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}"; +var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), "ug"); +var wordDiff = new Diff(); +wordDiff.equals = function(left, right, options) { + if (options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + return left.trim() === right.trim(); +}; +wordDiff.tokenize = function(value) { + var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; + var parts; + if (options.intlSegmenter) { + if (options.intlSegmenter.resolvedOptions().granularity != "word") { + throw new Error('The segmenter passed must have a granularity of "word"'); + } + parts = Array.from(options.intlSegmenter.segment(value), function(segment) { + return segment.segment; + }); + } else { + parts = value.match(tokenizeIncludingWhitespace) || []; + } + var tokens = []; + var prevPart = null; + parts.forEach(function(part) { + if (/\s/.test(part)) { + if (prevPart == null) { + tokens.push(part); + } else { + tokens.push(tokens.pop() + part); + } + } else if (/\s/.test(prevPart)) { + if (tokens[tokens.length - 1] == prevPart) { + tokens.push(tokens.pop() + part); + } else { + tokens.push(prevPart + part); + } + } else { + tokens.push(part); + } + prevPart = part; + }); + return tokens; +}; +wordDiff.join = function(tokens) { + return tokens.map(function(token, i) { + if (i == 0) { + return token; + } else { + return token.replace(/^\s+/, ""); + } + }).join(""); +}; +wordDiff.postProcess = function(changes, options) { + if (!changes || options.oneChangePerToken) { + return changes; + } + var lastKeep = null; + var insertion = null; + var deletion = null; + changes.forEach(function(change) { + if (change.added) { + insertion = change; + } else if (change.removed) { + deletion = change; + } else { + if (insertion || deletion) { + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); + } + lastKeep = change; + insertion = null; + deletion = null; + } + }); + if (insertion || deletion) { + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); + } + return changes; +}; +function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) { + if (deletion && insertion) { + var oldWsPrefix = deletion.value.match(/^\s*/)[0]; + var oldWsSuffix = deletion.value.match(/\s*$/)[0]; + var newWsPrefix = insertion.value.match(/^\s*/)[0]; + var newWsSuffix = insertion.value.match(/\s*$/)[0]; + if (startKeep) { + var commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix); + startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix); + deletion.value = removePrefix(deletion.value, commonWsPrefix); + insertion.value = removePrefix(insertion.value, commonWsPrefix); + } + if (endKeep) { + var commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix); + endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix); + deletion.value = removeSuffix(deletion.value, commonWsSuffix); + insertion.value = removeSuffix(insertion.value, commonWsSuffix); + } + } else if (insertion) { + if (startKeep) { + insertion.value = insertion.value.replace(/^\s*/, ""); + } + if (endKeep) { + endKeep.value = endKeep.value.replace(/^\s*/, ""); + } + } else if (startKeep && endKeep) { + var newWsFull = endKeep.value.match(/^\s*/)[0], delWsStart = deletion.value.match(/^\s*/)[0], delWsEnd = deletion.value.match(/\s*$/)[0]; + var newWsStart = longestCommonPrefix(newWsFull, delWsStart); + deletion.value = removePrefix(deletion.value, newWsStart); + var newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd); + deletion.value = removeSuffix(deletion.value, newWsEnd); + endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd); + startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length)); + } else if (endKeep) { + var endKeepWsPrefix = endKeep.value.match(/^\s*/)[0]; + var deletionWsSuffix = deletion.value.match(/\s*$/)[0]; + var overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix); + deletion.value = removeSuffix(deletion.value, overlap); + } else if (startKeep) { + var startKeepWsSuffix = startKeep.value.match(/\s*$/)[0]; + var deletionWsPrefix = deletion.value.match(/^\s*/)[0]; + var _overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix); + deletion.value = removePrefix(deletion.value, _overlap); + } +} +var wordWithSpaceDiff = new Diff(); +wordWithSpaceDiff.tokenize = function(value) { + var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), "ug"); + return value.match(regex) || []; +}; +var lineDiff = new Diff(); +lineDiff.tokenize = function(value, options) { + if (options.stripTrailingCr) { + value = value.replace(/\r\n/g, "\n"); + } + var retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/); + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + if (i % 2 && !options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + retLines.push(line); + } + } + return retLines; +}; +lineDiff.equals = function(left, right, options) { + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes("\n")) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes("\n")) { + right = right.trim(); + } + } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { + if (left.endsWith("\n")) { + left = left.slice(0, -1); + } + if (right.endsWith("\n")) { + right = right.slice(0, -1); + } + } + return Diff.prototype.equals.call(this, left, right, options); +}; +function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); +} +var sentenceDiff = new Diff(); +sentenceDiff.tokenize = function(value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); +}; +var cssDiff = new Diff(); +cssDiff.tokenize = function(value) { + return value.split(/([{}:;,]|\s+)/); +}; +function _typeof(o) { + "@babel/helpers - typeof"; + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o2) { + return typeof o2; + } : function(o2) { + return o2 && "function" == typeof Symbol && o2.constructor === Symbol && o2 !== Symbol.prototype ? "symbol" : typeof o2; + }, _typeof(o); +} +var jsonDiff = new Diff(); +jsonDiff.useLongestToken = true; +jsonDiff.tokenize = lineDiff.tokenize; +jsonDiff.castInput = function(value, options) { + var undefinedReplacement = options.undefinedReplacement, _options$stringifyRep = options.stringifyReplacer, stringifyReplacer = _options$stringifyRep === void 0 ? function(k, v) { + return typeof v === "undefined" ? undefinedReplacement : v; + } : _options$stringifyRep; + return typeof value === "string" ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, " "); +}; +jsonDiff.equals = function(left, right, options) { + return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, "$1"), right.replace(/,([\r\n])/g, "$1"), options); +}; +function canonicalize(obj, stack, replacementStack, replacer, key) { + stack = stack || []; + replacementStack = replacementStack || []; + if (replacer) { + obj = replacer(key, obj); + } + var i; + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + var canonicalizedObj; + if ("[object Array]" === Object.prototype.toString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); + } + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + if (_typeof(obj) === "object" && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], _key; + for (_key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, _key)) { + sortedKeys.push(_key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + _key = sortedKeys[i]; + canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; +} +var arrayDiff = new Diff(); +arrayDiff.tokenize = function(value) { + return value.slice(); +}; +arrayDiff.join = arrayDiff.removeEmpty = function(value) { + return value; +}; + +export { diffLines }; diff --git a/src/vs/workbench/contrib/void/browser/react/out/quick-edit-tsx/index.js b/src/vs/workbench/contrib/void/browser/react/out/quick-edit-tsx/index.js new file mode 100644 index 00000000..576008f7 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/out/quick-edit-tsx/index.js @@ -0,0 +1,29978 @@ +import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'; +import { IModelService } from '../../../../../../../editor/common/services/model.js'; +import { IClipboardService } from '../../../../../../../platform/clipboard/common/clipboardService.js'; +import { IContextViewService, IContextMenuService } from '../../../../../../../platform/contextview/browser/contextView.js'; +import { IFileService } from '../../../../../../../platform/files/common/files.js'; +import { IHoverService } from '../../../../../../../platform/hover/browser/hover.js'; +import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js'; +import { ILLMMessageService } from '../../../../../../../workbench/contrib/void/common/llmMessageService.js'; +import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'; +import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'; +import { IInlineDiffsService } from '../../../inlineDiffsService.js'; +import { IVoidUriStateService } from '../../../voidUriStateService.js'; +import { IQuickEditStateService } from '../../../quickEditStateService.js'; +import { ISidebarStateService } from '../../../sidebarStateService.js'; +import { IChatThreadService } from '../../../chatThreadService.js'; +import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; +import { ICodeEditorService } from '../../../../../../../editor/browser/services/codeEditorService.js'; +import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'; +import { IContextKeyService } from '../../../../../../../platform/contextkey/common/contextkey.js'; +import { INotificationService } from '../../../../../../../platform/notification/common/notification.js'; +import { IAccessibilityService } from '../../../../../../../platform/accessibility/common/accessibility.js'; +import { ILanguageConfigurationService } from '../../../../../../../editor/common/languages/languageConfigurationRegistry.js'; +import { ILanguageFeaturesService } from '../../../../../../../editor/common/services/languageFeatures.js'; +import { ILanguageDetectionService } from '../../../../../../services/languageDetection/common/languageDetectionWorkerService.js'; +import { IKeybindingService } from '../../../../../../../platform/keybinding/common/keybinding.js'; +import { IEnvironmentService } from '../../../../../../../platform/environment/common/environment.js'; +import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { IPathService } from '../../../../../../../workbench/services/path/common/pathService.js'; +import { IMetricsService } from '../../../../../../../workbench/contrib/void/common/metricsService.js'; +import { errorDetails } from '../../../../../../../workbench/contrib/void/common/llmMessageTypes.js'; +import '../../../../../../../base/browser/ui/inputbox/inputBox.js'; +import '../../../../../../../platform/theme/browser/defaultStyles.js'; +import '../../../../../../../base/browser/ui/selectBox/selectBox.js'; +import '../../../../../../../base/browser/ui/toggle/toggle.js'; +import { CodeEditorWidget } from '../../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; +import { inputBackground, inputForeground } from '../../../../../../../platform/theme/common/colorRegistry.js'; +import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js'; +import { isFeatureNameDisabled, modelSelectionsEqual } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; +import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; +import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js'; + +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// ../../../../../../../node_modules/react/cjs/react.development.js +var require_react_development = __commonJS({ + "../../../../../../../node_modules/react/cjs/react.development.js"(exports, module) { + { + (function() { + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var ReactVersion = "18.3.1"; + var REACT_ELEMENT_TYPE = Symbol.for("react.element"); + var REACT_PORTAL_TYPE = Symbol.for("react.portal"); + var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"); + var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"); + var REACT_PROFILER_TYPE = Symbol.for("react.profiler"); + var REACT_PROVIDER_TYPE = Symbol.for("react.provider"); + var REACT_CONTEXT_TYPE = Symbol.for("react.context"); + var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"); + var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"); + var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"); + var REACT_MEMO_TYPE = Symbol.for("react.memo"); + var REACT_LAZY_TYPE = Symbol.for("react.lazy"); + var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen"); + var MAYBE_ITERATOR_SYMBOL = Symbol.iterator; + var FAUX_ITERATOR_SYMBOL = "@@iterator"; + function getIteratorFn(maybeIterable) { + if (maybeIterable === null || typeof maybeIterable !== "object") { + return null; + } + var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]; + if (typeof maybeIterator === "function") { + return maybeIterator; + } + return null; + } + var ReactCurrentDispatcher = { + /** + * @internal + * @type {ReactComponent} + */ + current: null + }; + var ReactCurrentBatchConfig = { + transition: null + }; + var ReactCurrentActQueue = { + current: null, + // Used to reproduce behavior of `batchedUpdates` in legacy mode. + isBatchingLegacy: false, + didScheduleLegacyUpdate: false + }; + var ReactCurrentOwner = { + /** + * @internal + * @type {ReactComponent} + */ + current: null + }; + var ReactDebugCurrentFrame = {}; + var currentExtraStackFrame = null; + function setExtraStackFrame(stack) { + { + currentExtraStackFrame = stack; + } + } + { + ReactDebugCurrentFrame.setExtraStackFrame = function(stack) { + { + currentExtraStackFrame = stack; + } + }; + ReactDebugCurrentFrame.getCurrentStack = null; + ReactDebugCurrentFrame.getStackAddendum = function() { + var stack = ""; + if (currentExtraStackFrame) { + stack += currentExtraStackFrame; + } + var impl = ReactDebugCurrentFrame.getCurrentStack; + if (impl) { + stack += impl() || ""; + } + return stack; + }; + } + var enableScopeAPI = false; + var enableCacheElement = false; + var enableTransitionTracing = false; + var enableLegacyHidden = false; + var enableDebugTracing = false; + var ReactSharedInternals = { + ReactCurrentDispatcher, + ReactCurrentBatchConfig, + ReactCurrentOwner + }; + { + ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame; + ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue; + } + function warn(format) { + { + { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + printWarning("warn", format, args); + } + } + } + function error2(format) { + { + { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + printWarning("error", format, args); + } + } + } + function printWarning(level, format, args) { + { + var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame; + var stack = ReactDebugCurrentFrame2.getStackAddendum(); + if (stack !== "") { + format += "%s"; + args = args.concat([stack]); + } + var argsWithFormat = args.map(function(item) { + return String(item); + }); + argsWithFormat.unshift("Warning: " + format); + Function.prototype.apply.call(console[level], console, argsWithFormat); + } + } + var didWarnStateUpdateForUnmountedComponent = {}; + function warnNoop(publicInstance, callerName) { + { + var _constructor = publicInstance.constructor; + var componentName = _constructor && (_constructor.displayName || _constructor.name) || "ReactClass"; + var warningKey = componentName + "." + callerName; + if (didWarnStateUpdateForUnmountedComponent[warningKey]) { + return; + } + error2("Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the %s component.", callerName, componentName); + didWarnStateUpdateForUnmountedComponent[warningKey] = true; + } + } + var ReactNoopUpdateQueue = { + /** + * Checks whether or not this composite component is mounted. + * @param {ReactClass} publicInstance The instance we want to test. + * @return {boolean} True if mounted, false otherwise. + * @protected + * @final + */ + isMounted: function(publicInstance) { + return false; + }, + /** + * Forces an update. This should only be invoked when it is known with + * certainty that we are **not** in a DOM transaction. + * + * You may want to call this when you know that some deeper aspect of the + * component's state has changed but `setState` was not called. + * + * This will not invoke `shouldComponentUpdate`, but it will invoke + * `componentWillUpdate` and `componentDidUpdate`. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {?function} callback Called after component is updated. + * @param {?string} callerName name of the calling function in the public API. + * @internal + */ + enqueueForceUpdate: function(publicInstance, callback, callerName) { + warnNoop(publicInstance, "forceUpdate"); + }, + /** + * Replaces all of the state. Always use this or `setState` to mutate state. + * You should treat `this.state` as immutable. + * + * There is no guarantee that `this.state` will be immediately updated, so + * accessing `this.state` after calling this method may return the old value. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} completeState Next state. + * @param {?function} callback Called after component is updated. + * @param {?string} callerName name of the calling function in the public API. + * @internal + */ + enqueueReplaceState: function(publicInstance, completeState, callback, callerName) { + warnNoop(publicInstance, "replaceState"); + }, + /** + * Sets a subset of the state. This only exists because _pendingState is + * internal. This provides a merging strategy that is not available to deep + * properties which is confusing. TODO: Expose pendingState or don't use it + * during the merge. + * + * @param {ReactClass} publicInstance The instance that should rerender. + * @param {object} partialState Next partial state to be merged with state. + * @param {?function} callback Called after component is updated. + * @param {?string} Name of the calling function in the public API. + * @internal + */ + enqueueSetState: function(publicInstance, partialState, callback, callerName) { + warnNoop(publicInstance, "setState"); + } + }; + var assign = Object.assign; + var emptyObject = {}; + { + Object.freeze(emptyObject); + } + function Component2(props, context, updater) { + this.props = props; + this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + } + Component2.prototype.isReactComponent = {}; + Component2.prototype.setState = function(partialState, callback) { + if (typeof partialState !== "object" && typeof partialState !== "function" && partialState != null) { + throw new Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables."); + } + this.updater.enqueueSetState(this, partialState, callback, "setState"); + }; + Component2.prototype.forceUpdate = function(callback) { + this.updater.enqueueForceUpdate(this, callback, "forceUpdate"); + }; + { + var deprecatedAPIs = { + isMounted: ["isMounted", "Instead, make sure to clean up subscriptions and pending requests in componentWillUnmount to prevent memory leaks."], + replaceState: ["replaceState", "Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236)."] + }; + var defineDeprecationWarning = function(methodName, info) { + Object.defineProperty(Component2.prototype, methodName, { + get: function() { + warn("%s(...) is deprecated in plain JavaScript React classes. %s", info[0], info[1]); + return void 0; + } + }); + }; + for (var fnName in deprecatedAPIs) { + if (deprecatedAPIs.hasOwnProperty(fnName)) { + defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); + } + } + } + function ComponentDummy() { + } + ComponentDummy.prototype = Component2.prototype; + function PureComponent(props, context, updater) { + this.props = props; + this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + } + var pureComponentPrototype = PureComponent.prototype = new ComponentDummy(); + pureComponentPrototype.constructor = PureComponent; + assign(pureComponentPrototype, Component2.prototype); + pureComponentPrototype.isPureReactComponent = true; + function createRef() { + var refObject = { + current: null + }; + { + Object.seal(refObject); + } + return refObject; + } + var isArrayImpl = Array.isArray; + function isArray(a) { + return isArrayImpl(a); + } + function typeName(value) { + { + var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag; + var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object"; + return type; + } + } + function willCoercionThrow(value) { + { + try { + testStringCoercion(value); + return false; + } catch (e) { + return true; + } + } + } + function testStringCoercion(value) { + return "" + value; + } + function checkKeyStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error2("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + function getWrappedName(outerType, innerType, wrapperName) { + var displayName = outerType.displayName; + if (displayName) { + return displayName; + } + var functionName = innerType.displayName || innerType.name || ""; + return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName; + } + function getContextName(type) { + return type.displayName || "Context"; + } + function getComponentNameFromType(type) { + if (type == null) { + return null; + } + { + if (typeof type.tag === "number") { + error2("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."); + } + } + if (typeof type === "function") { + return type.displayName || type.name || null; + } + if (typeof type === "string") { + return type; + } + switch (type) { + case REACT_FRAGMENT_TYPE: + return "Fragment"; + case REACT_PORTAL_TYPE: + return "Portal"; + case REACT_PROFILER_TYPE: + return "Profiler"; + case REACT_STRICT_MODE_TYPE: + return "StrictMode"; + case REACT_SUSPENSE_TYPE: + return "Suspense"; + case REACT_SUSPENSE_LIST_TYPE: + return "SuspenseList"; + } + if (typeof type === "object") { + switch (type.$$typeof) { + case REACT_CONTEXT_TYPE: + var context = type; + return getContextName(context) + ".Consumer"; + case REACT_PROVIDER_TYPE: + var provider = type; + return getContextName(provider._context) + ".Provider"; + case REACT_FORWARD_REF_TYPE: + return getWrappedName(type, type.render, "ForwardRef"); + case REACT_MEMO_TYPE: + var outerName = type.displayName || null; + if (outerName !== null) { + return outerName; + } + return getComponentNameFromType(type.type) || "Memo"; + case REACT_LAZY_TYPE: { + var lazyComponent = type; + var payload = lazyComponent._payload; + var init = lazyComponent._init; + try { + return getComponentNameFromType(init(payload)); + } catch (x) { + return null; + } + } + } + } + return null; + } + var hasOwnProperty = Object.prototype.hasOwnProperty; + var RESERVED_PROPS = { + key: true, + ref: true, + __self: true, + __source: true + }; + var specialPropKeyWarningShown, specialPropRefWarningShown, didWarnAboutStringRefs; + { + didWarnAboutStringRefs = {}; + } + function hasValidRef(config) { + { + if (hasOwnProperty.call(config, "ref")) { + var getter = Object.getOwnPropertyDescriptor(config, "ref").get; + if (getter && getter.isReactWarning) { + return false; + } + } + } + return config.ref !== void 0; + } + function hasValidKey(config) { + { + if (hasOwnProperty.call(config, "key")) { + var getter = Object.getOwnPropertyDescriptor(config, "key").get; + if (getter && getter.isReactWarning) { + return false; + } + } + } + return config.key !== void 0; + } + function defineKeyPropWarningGetter(props, displayName) { + var warnAboutAccessingKey = function() { + { + if (!specialPropKeyWarningShown) { + specialPropKeyWarningShown = true; + error2("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName); + } + } + }; + warnAboutAccessingKey.isReactWarning = true; + Object.defineProperty(props, "key", { + get: warnAboutAccessingKey, + configurable: true + }); + } + function defineRefPropWarningGetter(props, displayName) { + var warnAboutAccessingRef = function() { + { + if (!specialPropRefWarningShown) { + specialPropRefWarningShown = true; + error2("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName); + } + } + }; + warnAboutAccessingRef.isReactWarning = true; + Object.defineProperty(props, "ref", { + get: warnAboutAccessingRef, + configurable: true + }); + } + function warnIfStringRefCannotBeAutoConverted(config) { + { + if (typeof config.ref === "string" && ReactCurrentOwner.current && config.__self && ReactCurrentOwner.current.stateNode !== config.__self) { + var componentName = getComponentNameFromType(ReactCurrentOwner.current.type); + if (!didWarnAboutStringRefs[componentName]) { + error2('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', componentName, config.ref); + didWarnAboutStringRefs[componentName] = true; + } + } + } + } + var ReactElement = function(type, key, ref, self, source, owner, props) { + var element = { + // This tag allows us to uniquely identify this as a React Element + $$typeof: REACT_ELEMENT_TYPE, + // Built-in properties that belong on the element + type, + key, + ref, + props, + // Record the component responsible for creating this element. + _owner: owner + }; + { + element._store = {}; + Object.defineProperty(element._store, "validated", { + configurable: false, + enumerable: false, + writable: true, + value: false + }); + Object.defineProperty(element, "_self", { + configurable: false, + enumerable: false, + writable: false, + value: self + }); + Object.defineProperty(element, "_source", { + configurable: false, + enumerable: false, + writable: false, + value: source + }); + if (Object.freeze) { + Object.freeze(element.props); + Object.freeze(element); + } + } + return element; + }; + function createElement3(type, config, children) { + var propName; + var props = {}; + var key = null; + var ref = null; + var self = null; + var source = null; + if (config != null) { + if (hasValidRef(config)) { + ref = config.ref; + { + warnIfStringRefCannotBeAutoConverted(config); + } + } + if (hasValidKey(config)) { + { + checkKeyStringCoercion(config.key); + } + key = "" + config.key; + } + self = config.__self === void 0 ? null : config.__self; + source = config.__source === void 0 ? null : config.__source; + for (propName in config) { + if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { + props[propName] = config[propName]; + } + } + } + var childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + var childArray = Array(childrenLength); + for (var i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + { + if (Object.freeze) { + Object.freeze(childArray); + } + } + props.children = childArray; + } + if (type && type.defaultProps) { + var defaultProps = type.defaultProps; + for (propName in defaultProps) { + if (props[propName] === void 0) { + props[propName] = defaultProps[propName]; + } + } + } + { + if (key || ref) { + var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type; + if (key) { + defineKeyPropWarningGetter(props, displayName); + } + if (ref) { + defineRefPropWarningGetter(props, displayName); + } + } + } + return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); + } + function cloneAndReplaceKey(oldElement, newKey) { + var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props); + return newElement; + } + function cloneElement2(element, config, children) { + if (element === null || element === void 0) { + throw new Error("React.cloneElement(...): The argument must be a React element, but you passed " + element + "."); + } + var propName; + var props = assign({}, element.props); + var key = element.key; + var ref = element.ref; + var self = element._self; + var source = element._source; + var owner = element._owner; + if (config != null) { + if (hasValidRef(config)) { + ref = config.ref; + owner = ReactCurrentOwner.current; + } + if (hasValidKey(config)) { + { + checkKeyStringCoercion(config.key); + } + key = "" + config.key; + } + var defaultProps; + if (element.type && element.type.defaultProps) { + defaultProps = element.type.defaultProps; + } + for (propName in config) { + if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { + if (config[propName] === void 0 && defaultProps !== void 0) { + props[propName] = defaultProps[propName]; + } else { + props[propName] = config[propName]; + } + } + } + } + var childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + var childArray = Array(childrenLength); + for (var i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + props.children = childArray; + } + return ReactElement(element.type, key, ref, self, source, owner, props); + } + function isValidElement(object) { + return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE; + } + var SEPARATOR = "."; + var SUBSEPARATOR = ":"; + function escape2(key) { + var escapeRegex = /[=:]/g; + var escaperLookup = { + "=": "=0", + ":": "=2" + }; + var escapedString = key.replace(escapeRegex, function(match) { + return escaperLookup[match]; + }); + return "$" + escapedString; + } + var didWarnAboutMaps = false; + var userProvidedKeyEscapeRegex = /\/+/g; + function escapeUserProvidedKey(text) { + return text.replace(userProvidedKeyEscapeRegex, "$&/"); + } + function getElementKey(element, index3) { + if (typeof element === "object" && element !== null && element.key != null) { + { + checkKeyStringCoercion(element.key); + } + return escape2("" + element.key); + } + return index3.toString(36); + } + function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) { + var type = typeof children; + if (type === "undefined" || type === "boolean") { + children = null; + } + var invokeCallback = false; + if (children === null) { + invokeCallback = true; + } else { + switch (type) { + case "string": + case "number": + invokeCallback = true; + break; + case "object": + switch (children.$$typeof) { + case REACT_ELEMENT_TYPE: + case REACT_PORTAL_TYPE: + invokeCallback = true; + } + } + } + if (invokeCallback) { + var _child = children; + var mappedChild = callback(_child); + var childKey = nameSoFar === "" ? SEPARATOR + getElementKey(_child, 0) : nameSoFar; + if (isArray(mappedChild)) { + var escapedChildKey = ""; + if (childKey != null) { + escapedChildKey = escapeUserProvidedKey(childKey) + "/"; + } + mapIntoArray(mappedChild, array, escapedChildKey, "", function(c) { + return c; + }); + } else if (mappedChild != null) { + if (isValidElement(mappedChild)) { + { + if (mappedChild.key && (!_child || _child.key !== mappedChild.key)) { + checkKeyStringCoercion(mappedChild.key); + } + } + mappedChild = cloneAndReplaceKey( + mappedChild, + // Keep both the (mapped) and old keys if they differ, just as + // traverseAllChildren used to do for objects as children + escapedPrefix + // $FlowFixMe Flow incorrectly thinks React.Portal doesn't have a key + (mappedChild.key && (!_child || _child.key !== mappedChild.key) ? ( + // $FlowFixMe Flow incorrectly thinks existing element's key can be a number + // eslint-disable-next-line react-internal/safe-string-coercion + escapeUserProvidedKey("" + mappedChild.key) + "/" + ) : "") + childKey + ); + } + array.push(mappedChild); + } + return 1; + } + var child; + var nextName; + var subtreeCount = 0; + var nextNamePrefix = nameSoFar === "" ? SEPARATOR : nameSoFar + SUBSEPARATOR; + if (isArray(children)) { + for (var i = 0; i < children.length; i++) { + child = children[i]; + nextName = nextNamePrefix + getElementKey(child, i); + subtreeCount += mapIntoArray(child, array, escapedPrefix, nextName, callback); + } + } else { + var iteratorFn = getIteratorFn(children); + if (typeof iteratorFn === "function") { + var iterableChildren = children; + { + if (iteratorFn === iterableChildren.entries) { + if (!didWarnAboutMaps) { + warn("Using Maps as children is not supported. Use an array of keyed ReactElements instead."); + } + didWarnAboutMaps = true; + } + } + var iterator = iteratorFn.call(iterableChildren); + var step; + var ii = 0; + while (!(step = iterator.next()).done) { + child = step.value; + nextName = nextNamePrefix + getElementKey(child, ii++); + subtreeCount += mapIntoArray(child, array, escapedPrefix, nextName, callback); + } + } else if (type === "object") { + var childrenString = String(children); + throw new Error("Objects are not valid as a React child (found: " + (childrenString === "[object Object]" ? "object with keys {" + Object.keys(children).join(", ") + "}" : childrenString) + "). If you meant to render a collection of children, use an array instead."); + } + } + return subtreeCount; + } + function mapChildren(children, func, context) { + if (children == null) { + return children; + } + var result = []; + var count2 = 0; + mapIntoArray(children, result, "", "", function(child) { + return func.call(context, child, count2++); + }); + return result; + } + function countChildren(children) { + var n = 0; + mapChildren(children, function() { + n++; + }); + return n; + } + function forEachChildren(children, forEachFunc, forEachContext) { + mapChildren(children, function() { + forEachFunc.apply(this, arguments); + }, forEachContext); + } + function toArray(children) { + return mapChildren(children, function(child) { + return child; + }) || []; + } + function onlyChild(children) { + if (!isValidElement(children)) { + throw new Error("React.Children.only expected to receive a single React element child."); + } + return children; + } + function createContext2(defaultValue) { + var context = { + $$typeof: REACT_CONTEXT_TYPE, + // As a workaround to support multiple concurrent renderers, we categorize + // some renderers as primary and others as secondary. We only expect + // there to be two concurrent renderers at most: React Native (primary) and + // Fabric (secondary); React DOM (primary) and React ART (secondary). + // Secondary renderers store their context values on separate fields. + _currentValue: defaultValue, + _currentValue2: defaultValue, + // Used to track how many concurrent renderers this context currently + // supports within in a single renderer. Such as parallel server rendering. + _threadCount: 0, + // These are circular + Provider: null, + Consumer: null, + // Add these to use same hidden class in VM as ServerContext + _defaultValue: null, + _globalName: null + }; + context.Provider = { + $$typeof: REACT_PROVIDER_TYPE, + _context: context + }; + var hasWarnedAboutUsingNestedContextConsumers = false; + var hasWarnedAboutUsingConsumerProvider = false; + var hasWarnedAboutDisplayNameOnConsumer = false; + { + var Consumer = { + $$typeof: REACT_CONTEXT_TYPE, + _context: context + }; + Object.defineProperties(Consumer, { + Provider: { + get: function() { + if (!hasWarnedAboutUsingConsumerProvider) { + hasWarnedAboutUsingConsumerProvider = true; + error2("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?"); + } + return context.Provider; + }, + set: function(_Provider) { + context.Provider = _Provider; + } + }, + _currentValue: { + get: function() { + return context._currentValue; + }, + set: function(_currentValue) { + context._currentValue = _currentValue; + } + }, + _currentValue2: { + get: function() { + return context._currentValue2; + }, + set: function(_currentValue2) { + context._currentValue2 = _currentValue2; + } + }, + _threadCount: { + get: function() { + return context._threadCount; + }, + set: function(_threadCount) { + context._threadCount = _threadCount; + } + }, + Consumer: { + get: function() { + if (!hasWarnedAboutUsingNestedContextConsumers) { + hasWarnedAboutUsingNestedContextConsumers = true; + error2("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?"); + } + return context.Consumer; + } + }, + displayName: { + get: function() { + return context.displayName; + }, + set: function(displayName) { + if (!hasWarnedAboutDisplayNameOnConsumer) { + warn("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.", displayName); + hasWarnedAboutDisplayNameOnConsumer = true; + } + } + } + }); + context.Consumer = Consumer; + } + { + context._currentRenderer = null; + context._currentRenderer2 = null; + } + return context; + } + var Uninitialized = -1; + var Pending = 0; + var Resolved = 1; + var Rejected = 2; + function lazyInitializer(payload) { + if (payload._status === Uninitialized) { + var ctor = payload._result; + var thenable = ctor(); + thenable.then(function(moduleObject2) { + if (payload._status === Pending || payload._status === Uninitialized) { + var resolved = payload; + resolved._status = Resolved; + resolved._result = moduleObject2; + } + }, function(error3) { + if (payload._status === Pending || payload._status === Uninitialized) { + var rejected = payload; + rejected._status = Rejected; + rejected._result = error3; + } + }); + if (payload._status === Uninitialized) { + var pending = payload; + pending._status = Pending; + pending._result = thenable; + } + } + if (payload._status === Resolved) { + var moduleObject = payload._result; + { + if (moduleObject === void 0) { + error2("lazy: Expected the result of a dynamic import() call. Instead received: %s\n\nYour code should look like: \n const MyComponent = lazy(() => import('./MyComponent'))\n\nDid you accidentally put curly braces around the import?", moduleObject); + } + } + { + if (!("default" in moduleObject)) { + error2("lazy: Expected the result of a dynamic import() call. Instead received: %s\n\nYour code should look like: \n const MyComponent = lazy(() => import('./MyComponent'))", moduleObject); + } + } + return moduleObject.default; + } else { + throw payload._result; + } + } + function lazy(ctor) { + var payload = { + // We use these fields to store the result. + _status: Uninitialized, + _result: ctor + }; + var lazyType = { + $$typeof: REACT_LAZY_TYPE, + _payload: payload, + _init: lazyInitializer + }; + { + var defaultProps; + var propTypes; + Object.defineProperties(lazyType, { + defaultProps: { + configurable: true, + get: function() { + return defaultProps; + }, + set: function(newDefaultProps) { + error2("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."); + defaultProps = newDefaultProps; + Object.defineProperty(lazyType, "defaultProps", { + enumerable: true + }); + } + }, + propTypes: { + configurable: true, + get: function() { + return propTypes; + }, + set: function(newPropTypes) { + error2("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."); + propTypes = newPropTypes; + Object.defineProperty(lazyType, "propTypes", { + enumerable: true + }); + } + } + }); + } + return lazyType; + } + function forwardRef5(render) { + { + if (render != null && render.$$typeof === REACT_MEMO_TYPE) { + error2("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."); + } else if (typeof render !== "function") { + error2("forwardRef requires a render function but was given %s.", render === null ? "null" : typeof render); + } else { + if (render.length !== 0 && render.length !== 2) { + error2("forwardRef render functions accept exactly two parameters: props and ref. %s", render.length === 1 ? "Did you forget to use the ref parameter?" : "Any additional parameter will be undefined."); + } + } + if (render != null) { + if (render.defaultProps != null || render.propTypes != null) { + error2("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?"); + } + } + } + var elementType = { + $$typeof: REACT_FORWARD_REF_TYPE, + render + }; + { + var ownName; + Object.defineProperty(elementType, "displayName", { + enumerable: false, + configurable: true, + get: function() { + return ownName; + }, + set: function(name) { + ownName = name; + if (!render.name && !render.displayName) { + render.displayName = name; + } + } + }); + } + return elementType; + } + var REACT_MODULE_REFERENCE; + { + REACT_MODULE_REFERENCE = Symbol.for("react.module.reference"); + } + function isValidElementType(type) { + if (typeof type === "string" || typeof type === "function") { + return true; + } + if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) { + return true; + } + if (typeof type === "object" && type !== null) { + if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object + // types supported by any Flight configuration anywhere since + // we don't know which Flight build this will end up being used + // with. + type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) { + return true; + } + } + return false; + } + function memo(type, compare) { + { + if (!isValidElementType(type)) { + error2("memo: The first argument must be a component. Instead received: %s", type === null ? "null" : typeof type); + } + } + var elementType = { + $$typeof: REACT_MEMO_TYPE, + type, + compare: compare === void 0 ? null : compare + }; + { + var ownName; + Object.defineProperty(elementType, "displayName", { + enumerable: false, + configurable: true, + get: function() { + return ownName; + }, + set: function(name) { + ownName = name; + if (!type.name && !type.displayName) { + type.displayName = name; + } + } + }); + } + return elementType; + } + function resolveDispatcher() { + var dispatcher = ReactCurrentDispatcher.current; + { + if (dispatcher === null) { + error2("Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem."); + } + } + return dispatcher; + } + function useContext2(Context) { + var dispatcher = resolveDispatcher(); + { + if (Context._context !== void 0) { + var realContext = Context._context; + if (realContext.Consumer === Context) { + error2("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"); + } else if (realContext.Provider === Context) { + error2("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?"); + } + } + } + return dispatcher.useContext(Context); + } + function useState11(initialState) { + var dispatcher = resolveDispatcher(); + return dispatcher.useState(initialState); + } + function useReducer2(reducer, initialArg, init) { + var dispatcher = resolveDispatcher(); + return dispatcher.useReducer(reducer, initialArg, init); + } + function useRef9(initialValue) { + var dispatcher = resolveDispatcher(); + return dispatcher.useRef(initialValue); + } + function useEffect12(create, deps) { + var dispatcher = resolveDispatcher(); + return dispatcher.useEffect(create, deps); + } + function useInsertionEffect2(create, deps) { + var dispatcher = resolveDispatcher(); + return dispatcher.useInsertionEffect(create, deps); + } + function useLayoutEffect3(create, deps) { + var dispatcher = resolveDispatcher(); + return dispatcher.useLayoutEffect(create, deps); + } + function useCallback9(callback, deps) { + var dispatcher = resolveDispatcher(); + return dispatcher.useCallback(callback, deps); + } + function useMemo6(create, deps) { + var dispatcher = resolveDispatcher(); + return dispatcher.useMemo(create, deps); + } + function useImperativeHandle(ref, create, deps) { + var dispatcher = resolveDispatcher(); + return dispatcher.useImperativeHandle(ref, create, deps); + } + function useDebugValue(value, formatterFn) { + { + var dispatcher = resolveDispatcher(); + return dispatcher.useDebugValue(value, formatterFn); + } + } + function useTransition() { + var dispatcher = resolveDispatcher(); + return dispatcher.useTransition(); + } + function useDeferredValue(value) { + var dispatcher = resolveDispatcher(); + return dispatcher.useDeferredValue(value); + } + function useId3() { + var dispatcher = resolveDispatcher(); + return dispatcher.useId(); + } + function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) { + var dispatcher = resolveDispatcher(); + return dispatcher.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); + } + var disabledDepth = 0; + var prevLog; + var prevInfo; + var prevWarn; + var prevError; + var prevGroup; + var prevGroupCollapsed; + var prevGroupEnd; + function disabledLog() { + } + disabledLog.__reactDisabledLog = true; + function disableLogs() { + { + if (disabledDepth === 0) { + prevLog = console.log; + prevInfo = console.info; + prevWarn = console.warn; + prevError = console.error; + prevGroup = console.group; + prevGroupCollapsed = console.groupCollapsed; + prevGroupEnd = console.groupEnd; + var props = { + configurable: true, + enumerable: true, + value: disabledLog, + writable: true + }; + Object.defineProperties(console, { + info: props, + log: props, + warn: props, + error: props, + group: props, + groupCollapsed: props, + groupEnd: props + }); + } + disabledDepth++; + } + } + function reenableLogs() { + { + disabledDepth--; + if (disabledDepth === 0) { + var props = { + configurable: true, + enumerable: true, + writable: true + }; + Object.defineProperties(console, { + log: assign({}, props, { + value: prevLog + }), + info: assign({}, props, { + value: prevInfo + }), + warn: assign({}, props, { + value: prevWarn + }), + error: assign({}, props, { + value: prevError + }), + group: assign({}, props, { + value: prevGroup + }), + groupCollapsed: assign({}, props, { + value: prevGroupCollapsed + }), + groupEnd: assign({}, props, { + value: prevGroupEnd + }) + }); + } + if (disabledDepth < 0) { + error2("disabledDepth fell below zero. This is a bug in React. Please file an issue."); + } + } + } + var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher; + var prefix; + function describeBuiltInComponentFrame(name, source, ownerFn) { + { + if (prefix === void 0) { + try { + throw Error(); + } catch (x) { + var match = x.stack.trim().match(/\n( *(at )?)/); + prefix = match && match[1] || ""; + } + } + return "\n" + prefix + name; + } + } + var reentry = false; + var componentFrameCache; + { + var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map; + componentFrameCache = new PossiblyWeakMap(); + } + function describeNativeComponentFrame(fn, construct) { + if (!fn || reentry) { + return ""; + } + { + var frame = componentFrameCache.get(fn); + if (frame !== void 0) { + return frame; + } + } + var control; + reentry = true; + var previousPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = void 0; + var previousDispatcher; + { + previousDispatcher = ReactCurrentDispatcher$1.current; + ReactCurrentDispatcher$1.current = null; + disableLogs(); + } + try { + if (construct) { + var Fake = function() { + throw Error(); + }; + Object.defineProperty(Fake.prototype, "props", { + set: function() { + throw Error(); + } + }); + if (typeof Reflect === "object" && Reflect.construct) { + try { + Reflect.construct(Fake, []); + } catch (x) { + control = x; + } + Reflect.construct(fn, [], Fake); + } else { + try { + Fake.call(); + } catch (x) { + control = x; + } + fn.call(Fake.prototype); + } + } else { + try { + throw Error(); + } catch (x) { + control = x; + } + fn(); + } + } catch (sample) { + if (sample && control && typeof sample.stack === "string") { + var sampleLines = sample.stack.split("\n"); + var controlLines = control.stack.split("\n"); + var s = sampleLines.length - 1; + var c = controlLines.length - 1; + while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) { + c--; + } + for (; s >= 1 && c >= 0; s--, c--) { + if (sampleLines[s] !== controlLines[c]) { + if (s !== 1 || c !== 1) { + do { + s--; + c--; + if (c < 0 || sampleLines[s] !== controlLines[c]) { + var _frame = "\n" + sampleLines[s].replace(" at new ", " at "); + if (fn.displayName && _frame.includes("")) { + _frame = _frame.replace("", fn.displayName); + } + { + if (typeof fn === "function") { + componentFrameCache.set(fn, _frame); + } + } + return _frame; + } + } while (s >= 1 && c >= 0); + } + break; + } + } + } + } finally { + reentry = false; + { + ReactCurrentDispatcher$1.current = previousDispatcher; + reenableLogs(); + } + Error.prepareStackTrace = previousPrepareStackTrace; + } + var name = fn ? fn.displayName || fn.name : ""; + var syntheticFrame = name ? describeBuiltInComponentFrame(name) : ""; + { + if (typeof fn === "function") { + componentFrameCache.set(fn, syntheticFrame); + } + } + return syntheticFrame; + } + function describeFunctionComponentFrame(fn, source, ownerFn) { + { + return describeNativeComponentFrame(fn, false); + } + } + function shouldConstruct(Component3) { + var prototype = Component3.prototype; + return !!(prototype && prototype.isReactComponent); + } + function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) { + if (type == null) { + return ""; + } + if (typeof type === "function") { + { + return describeNativeComponentFrame(type, shouldConstruct(type)); + } + } + if (typeof type === "string") { + return describeBuiltInComponentFrame(type); + } + switch (type) { + case REACT_SUSPENSE_TYPE: + return describeBuiltInComponentFrame("Suspense"); + case REACT_SUSPENSE_LIST_TYPE: + return describeBuiltInComponentFrame("SuspenseList"); + } + if (typeof type === "object") { + switch (type.$$typeof) { + case REACT_FORWARD_REF_TYPE: + return describeFunctionComponentFrame(type.render); + case REACT_MEMO_TYPE: + return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn); + case REACT_LAZY_TYPE: { + var lazyComponent = type; + var payload = lazyComponent._payload; + var init = lazyComponent._init; + try { + return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn); + } catch (x) { + } + } + } + } + return ""; + } + var loggedTypeFailures = {}; + var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame; + function setCurrentlyValidatingElement(element) { + { + if (element) { + var owner = element._owner; + var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null); + ReactDebugCurrentFrame$1.setExtraStackFrame(stack); + } else { + ReactDebugCurrentFrame$1.setExtraStackFrame(null); + } + } + } + function checkPropTypes(typeSpecs, values, location, componentName, element) { + { + var has = Function.call.bind(hasOwnProperty); + for (var typeSpecName in typeSpecs) { + if (has(typeSpecs, typeSpecName)) { + var error$1 = void 0; + try { + if (typeof typeSpecs[typeSpecName] !== "function") { + var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`."); + err.name = "Invariant Violation"; + throw err; + } + error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"); + } catch (ex) { + error$1 = ex; + } + if (error$1 && !(error$1 instanceof Error)) { + setCurrentlyValidatingElement(element); + error2("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1); + setCurrentlyValidatingElement(null); + } + if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) { + loggedTypeFailures[error$1.message] = true; + setCurrentlyValidatingElement(element); + error2("Failed %s type: %s", location, error$1.message); + setCurrentlyValidatingElement(null); + } + } + } + } + } + function setCurrentlyValidatingElement$1(element) { + { + if (element) { + var owner = element._owner; + var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null); + setExtraStackFrame(stack); + } else { + setExtraStackFrame(null); + } + } + } + var propTypesMisspellWarningShown; + { + propTypesMisspellWarningShown = false; + } + function getDeclarationErrorAddendum() { + if (ReactCurrentOwner.current) { + var name = getComponentNameFromType(ReactCurrentOwner.current.type); + if (name) { + return "\n\nCheck the render method of `" + name + "`."; + } + } + return ""; + } + function getSourceInfoErrorAddendum(source) { + if (source !== void 0) { + var fileName = source.fileName.replace(/^.*[\\\/]/, ""); + var lineNumber = source.lineNumber; + return "\n\nCheck your code at " + fileName + ":" + lineNumber + "."; + } + return ""; + } + function getSourceInfoErrorAddendumForProps(elementProps) { + if (elementProps !== null && elementProps !== void 0) { + return getSourceInfoErrorAddendum(elementProps.__source); + } + return ""; + } + var ownerHasKeyUseWarning = {}; + function getCurrentComponentErrorInfo(parentType) { + var info = getDeclarationErrorAddendum(); + if (!info) { + var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name; + if (parentName) { + info = "\n\nCheck the top-level render call using <" + parentName + ">."; + } + } + return info; + } + function validateExplicitKey(element, parentType) { + if (!element._store || element._store.validated || element.key != null) { + return; + } + element._store.validated = true; + var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType); + if (ownerHasKeyUseWarning[currentComponentErrorInfo]) { + return; + } + ownerHasKeyUseWarning[currentComponentErrorInfo] = true; + var childOwner = ""; + if (element && element._owner && element._owner !== ReactCurrentOwner.current) { + childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + "."; + } + { + setCurrentlyValidatingElement$1(element); + error2('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner); + setCurrentlyValidatingElement$1(null); + } + } + function validateChildKeys(node, parentType) { + if (typeof node !== "object") { + return; + } + if (isArray(node)) { + for (var i = 0; i < node.length; i++) { + var child = node[i]; + if (isValidElement(child)) { + validateExplicitKey(child, parentType); + } + } + } else if (isValidElement(node)) { + if (node._store) { + node._store.validated = true; + } + } else if (node) { + var iteratorFn = getIteratorFn(node); + if (typeof iteratorFn === "function") { + if (iteratorFn !== node.entries) { + var iterator = iteratorFn.call(node); + var step; + while (!(step = iterator.next()).done) { + if (isValidElement(step.value)) { + validateExplicitKey(step.value, parentType); + } + } + } + } + } + } + function validatePropTypes(element) { + { + var type = element.type; + if (type === null || type === void 0 || typeof type === "string") { + return; + } + var propTypes; + if (typeof type === "function") { + propTypes = type.propTypes; + } else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here. + // Inner props are checked in the reconciler. + type.$$typeof === REACT_MEMO_TYPE)) { + propTypes = type.propTypes; + } else { + return; + } + if (propTypes) { + var name = getComponentNameFromType(type); + checkPropTypes(propTypes, element.props, "prop", name, element); + } else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) { + propTypesMisspellWarningShown = true; + var _name = getComponentNameFromType(type); + error2("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown"); + } + if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) { + error2("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead."); + } + } + } + function validateFragmentProps(fragment) { + { + var keys = Object.keys(fragment.props); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (key !== "children" && key !== "key") { + setCurrentlyValidatingElement$1(fragment); + error2("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key); + setCurrentlyValidatingElement$1(null); + break; + } + } + if (fragment.ref !== null) { + setCurrentlyValidatingElement$1(fragment); + error2("Invalid attribute `ref` supplied to `React.Fragment`."); + setCurrentlyValidatingElement$1(null); + } + } + } + function createElementWithValidation(type, props, children) { + var validType = isValidElementType(type); + if (!validType) { + var info = ""; + if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) { + info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports."; + } + var sourceInfo = getSourceInfoErrorAddendumForProps(props); + if (sourceInfo) { + info += sourceInfo; + } else { + info += getDeclarationErrorAddendum(); + } + var typeString; + if (type === null) { + typeString = "null"; + } else if (isArray(type)) { + typeString = "array"; + } else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) { + typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />"; + info = " Did you accidentally export a JSX literal instead of a component?"; + } else { + typeString = typeof type; + } + { + error2("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info); + } + } + var element = createElement3.apply(this, arguments); + if (element == null) { + return element; + } + if (validType) { + for (var i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], type); + } + } + if (type === REACT_FRAGMENT_TYPE) { + validateFragmentProps(element); + } else { + validatePropTypes(element); + } + return element; + } + var didWarnAboutDeprecatedCreateFactory = false; + function createFactoryWithValidation(type) { + var validatedFactory = createElementWithValidation.bind(null, type); + validatedFactory.type = type; + { + if (!didWarnAboutDeprecatedCreateFactory) { + didWarnAboutDeprecatedCreateFactory = true; + warn("React.createFactory() is deprecated and will be removed in a future major release. Consider using JSX or use React.createElement() directly instead."); + } + Object.defineProperty(validatedFactory, "type", { + enumerable: false, + get: function() { + warn("Factory.type is deprecated. Access the class directly before passing it to createFactory."); + Object.defineProperty(this, "type", { + value: type + }); + return type; + } + }); + } + return validatedFactory; + } + function cloneElementWithValidation(element, props, children) { + var newElement = cloneElement2.apply(this, arguments); + for (var i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], newElement.type); + } + validatePropTypes(newElement); + return newElement; + } + function startTransition(scope, options2) { + var prevTransition = ReactCurrentBatchConfig.transition; + ReactCurrentBatchConfig.transition = {}; + var currentTransition = ReactCurrentBatchConfig.transition; + { + ReactCurrentBatchConfig.transition._updatedFibers = /* @__PURE__ */ new Set(); + } + try { + scope(); + } finally { + ReactCurrentBatchConfig.transition = prevTransition; + { + if (prevTransition === null && currentTransition._updatedFibers) { + var updatedFibersCount = currentTransition._updatedFibers.size; + if (updatedFibersCount > 10) { + warn("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."); + } + currentTransition._updatedFibers.clear(); + } + } + } + } + var didWarnAboutMessageChannel = false; + var enqueueTaskImpl = null; + function enqueueTask(task) { + if (enqueueTaskImpl === null) { + try { + var requireString = ("require" + Math.random()).slice(0, 7); + var nodeRequire = module && module[requireString]; + enqueueTaskImpl = nodeRequire.call(module, "timers").setImmediate; + } catch (_err) { + enqueueTaskImpl = function(callback) { + { + if (didWarnAboutMessageChannel === false) { + didWarnAboutMessageChannel = true; + if (typeof MessageChannel === "undefined") { + error2("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."); + } + } + } + var channel = new MessageChannel(); + channel.port1.onmessage = callback; + channel.port2.postMessage(void 0); + }; + } + } + return enqueueTaskImpl(task); + } + var actScopeDepth = 0; + var didWarnNoAwaitAct = false; + function act(callback) { + { + var prevActScopeDepth = actScopeDepth; + actScopeDepth++; + if (ReactCurrentActQueue.current === null) { + ReactCurrentActQueue.current = []; + } + var prevIsBatchingLegacy = ReactCurrentActQueue.isBatchingLegacy; + var result; + try { + ReactCurrentActQueue.isBatchingLegacy = true; + result = callback(); + if (!prevIsBatchingLegacy && ReactCurrentActQueue.didScheduleLegacyUpdate) { + var queue = ReactCurrentActQueue.current; + if (queue !== null) { + ReactCurrentActQueue.didScheduleLegacyUpdate = false; + flushActQueue(queue); + } + } + } catch (error3) { + popActScope(prevActScopeDepth); + throw error3; + } finally { + ReactCurrentActQueue.isBatchingLegacy = prevIsBatchingLegacy; + } + if (result !== null && typeof result === "object" && typeof result.then === "function") { + var thenableResult = result; + var wasAwaited = false; + var thenable = { + then: function(resolve, reject) { + wasAwaited = true; + thenableResult.then(function(returnValue2) { + popActScope(prevActScopeDepth); + if (actScopeDepth === 0) { + recursivelyFlushAsyncActWork(returnValue2, resolve, reject); + } else { + resolve(returnValue2); + } + }, function(error3) { + popActScope(prevActScopeDepth); + reject(error3); + }); + } + }; + { + if (!didWarnNoAwaitAct && typeof Promise !== "undefined") { + Promise.resolve().then(function() { + }).then(function() { + if (!wasAwaited) { + didWarnNoAwaitAct = true; + error2("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"); + } + }); + } + } + return thenable; + } else { + var returnValue = result; + popActScope(prevActScopeDepth); + if (actScopeDepth === 0) { + var _queue = ReactCurrentActQueue.current; + if (_queue !== null) { + flushActQueue(_queue); + ReactCurrentActQueue.current = null; + } + var _thenable = { + then: function(resolve, reject) { + if (ReactCurrentActQueue.current === null) { + ReactCurrentActQueue.current = []; + recursivelyFlushAsyncActWork(returnValue, resolve, reject); + } else { + resolve(returnValue); + } + } + }; + return _thenable; + } else { + var _thenable2 = { + then: function(resolve, reject) { + resolve(returnValue); + } + }; + return _thenable2; + } + } + } + } + function popActScope(prevActScopeDepth) { + { + if (prevActScopeDepth !== actScopeDepth - 1) { + error2("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "); + } + actScopeDepth = prevActScopeDepth; + } + } + function recursivelyFlushAsyncActWork(returnValue, resolve, reject) { + { + var queue = ReactCurrentActQueue.current; + if (queue !== null) { + try { + flushActQueue(queue); + enqueueTask(function() { + if (queue.length === 0) { + ReactCurrentActQueue.current = null; + resolve(returnValue); + } else { + recursivelyFlushAsyncActWork(returnValue, resolve, reject); + } + }); + } catch (error3) { + reject(error3); + } + } else { + resolve(returnValue); + } + } + } + var isFlushing = false; + function flushActQueue(queue) { + { + if (!isFlushing) { + isFlushing = true; + var i = 0; + try { + for (; i < queue.length; i++) { + var callback = queue[i]; + do { + callback = callback(true); + } while (callback !== null); + } + queue.length = 0; + } catch (error3) { + queue = queue.slice(i + 1); + throw error3; + } finally { + isFlushing = false; + } + } + } + } + var createElement$1 = createElementWithValidation; + var cloneElement$1 = cloneElementWithValidation; + var createFactory = createFactoryWithValidation; + var Children = { + map: mapChildren, + forEach: forEachChildren, + count: countChildren, + toArray, + only: onlyChild + }; + exports.Children = Children; + exports.Component = Component2; + exports.Fragment = REACT_FRAGMENT_TYPE; + exports.Profiler = REACT_PROFILER_TYPE; + exports.PureComponent = PureComponent; + exports.StrictMode = REACT_STRICT_MODE_TYPE; + exports.Suspense = REACT_SUSPENSE_TYPE; + exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactSharedInternals; + exports.act = act; + exports.cloneElement = cloneElement$1; + exports.createContext = createContext2; + exports.createElement = createElement$1; + exports.createFactory = createFactory; + exports.createRef = createRef; + exports.forwardRef = forwardRef5; + exports.isValidElement = isValidElement; + exports.lazy = lazy; + exports.memo = memo; + exports.startTransition = startTransition; + exports.unstable_act = act; + exports.useCallback = useCallback9; + exports.useContext = useContext2; + exports.useDebugValue = useDebugValue; + exports.useDeferredValue = useDeferredValue; + exports.useEffect = useEffect12; + exports.useId = useId3; + exports.useImperativeHandle = useImperativeHandle; + exports.useInsertionEffect = useInsertionEffect2; + exports.useLayoutEffect = useLayoutEffect3; + exports.useMemo = useMemo6; + exports.useReducer = useReducer2; + exports.useRef = useRef9; + exports.useState = useState11; + exports.useSyncExternalStore = useSyncExternalStore; + exports.useTransition = useTransition; + exports.version = ReactVersion; + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); + } + })(); + } + } +}); + +// ../../../../../../../node_modules/react/index.js +var require_react = __commonJS({ + "../../../../../../../node_modules/react/index.js"(exports, module) { + { + module.exports = require_react_development(); + } + } +}); + +// ../../../../../../../node_modules/scheduler/cjs/scheduler.development.js +var require_scheduler_development = __commonJS({ + "../../../../../../../node_modules/scheduler/cjs/scheduler.development.js"(exports) { + { + (function() { + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var enableSchedulerDebugging = false; + var enableProfiling = false; + var frameYieldMs = 5; + function push(heap, node) { + var index3 = heap.length; + heap.push(node); + siftUp(heap, node, index3); + } + function peek(heap) { + return heap.length === 0 ? null : heap[0]; + } + function pop(heap) { + if (heap.length === 0) { + return null; + } + var first = heap[0]; + var last = heap.pop(); + if (last !== first) { + heap[0] = last; + siftDown(heap, last, 0); + } + return first; + } + function siftUp(heap, node, i) { + var index3 = i; + while (index3 > 0) { + var parentIndex = index3 - 1 >>> 1; + var parent = heap[parentIndex]; + if (compare(parent, node) > 0) { + heap[parentIndex] = node; + heap[index3] = parent; + index3 = parentIndex; + } else { + return; + } + } + } + function siftDown(heap, node, i) { + var index3 = i; + var length = heap.length; + var halfLength = length >>> 1; + while (index3 < halfLength) { + var leftIndex = (index3 + 1) * 2 - 1; + var left = heap[leftIndex]; + var rightIndex = leftIndex + 1; + var right = heap[rightIndex]; + if (compare(left, node) < 0) { + if (rightIndex < length && compare(right, left) < 0) { + heap[index3] = right; + heap[rightIndex] = node; + index3 = rightIndex; + } else { + heap[index3] = left; + heap[leftIndex] = node; + index3 = leftIndex; + } + } else if (rightIndex < length && compare(right, node) < 0) { + heap[index3] = right; + heap[rightIndex] = node; + index3 = rightIndex; + } else { + return; + } + } + } + function compare(a, b) { + var diff = a.sortIndex - b.sortIndex; + return diff !== 0 ? diff : a.id - b.id; + } + var ImmediatePriority = 1; + var UserBlockingPriority = 2; + var NormalPriority = 3; + var LowPriority = 4; + var IdlePriority = 5; + function markTaskErrored(task, ms) { + } + var hasPerformanceNow = typeof performance === "object" && typeof performance.now === "function"; + if (hasPerformanceNow) { + var localPerformance = performance; + exports.unstable_now = function() { + return localPerformance.now(); + }; + } else { + var localDate = Date; + var initialTime = localDate.now(); + exports.unstable_now = function() { + return localDate.now() - initialTime; + }; + } + var maxSigned31BitInt = 1073741823; + var IMMEDIATE_PRIORITY_TIMEOUT = -1; + var USER_BLOCKING_PRIORITY_TIMEOUT = 250; + var NORMAL_PRIORITY_TIMEOUT = 5e3; + var LOW_PRIORITY_TIMEOUT = 1e4; + var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; + var taskQueue = []; + var timerQueue = []; + var taskIdCounter = 1; + var currentTask = null; + var currentPriorityLevel = NormalPriority; + var isPerformingWork = false; + var isHostCallbackScheduled = false; + var isHostTimeoutScheduled = false; + var localSetTimeout = typeof setTimeout === "function" ? setTimeout : null; + var localClearTimeout = typeof clearTimeout === "function" ? clearTimeout : null; + var localSetImmediate = typeof setImmediate !== "undefined" ? setImmediate : null; + typeof navigator !== "undefined" && navigator.scheduling !== void 0 && navigator.scheduling.isInputPending !== void 0 ? navigator.scheduling.isInputPending.bind(navigator.scheduling) : null; + function advanceTimers(currentTime) { + var timer = peek(timerQueue); + while (timer !== null) { + if (timer.callback === null) { + pop(timerQueue); + } else if (timer.startTime <= currentTime) { + pop(timerQueue); + timer.sortIndex = timer.expirationTime; + push(taskQueue, timer); + } else { + return; + } + timer = peek(timerQueue); + } + } + function handleTimeout(currentTime) { + isHostTimeoutScheduled = false; + advanceTimers(currentTime); + if (!isHostCallbackScheduled) { + if (peek(taskQueue) !== null) { + isHostCallbackScheduled = true; + requestHostCallback(flushWork); + } else { + var firstTimer = peek(timerQueue); + if (firstTimer !== null) { + requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); + } + } + } + } + function flushWork(hasTimeRemaining, initialTime2) { + isHostCallbackScheduled = false; + if (isHostTimeoutScheduled) { + isHostTimeoutScheduled = false; + cancelHostTimeout(); + } + isPerformingWork = true; + var previousPriorityLevel = currentPriorityLevel; + try { + var currentTime; if (enableProfiling) ; else { + return workLoop(hasTimeRemaining, initialTime2); + } + } finally { + currentTask = null; + currentPriorityLevel = previousPriorityLevel; + isPerformingWork = false; + } + } + function workLoop(hasTimeRemaining, initialTime2) { + var currentTime = initialTime2; + advanceTimers(currentTime); + currentTask = peek(taskQueue); + while (currentTask !== null && !enableSchedulerDebugging) { + if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) { + break; + } + var callback = currentTask.callback; + if (typeof callback === "function") { + currentTask.callback = null; + currentPriorityLevel = currentTask.priorityLevel; + var didUserCallbackTimeout = currentTask.expirationTime <= currentTime; + var continuationCallback = callback(didUserCallbackTimeout); + currentTime = exports.unstable_now(); + if (typeof continuationCallback === "function") { + currentTask.callback = continuationCallback; + } else { + if (currentTask === peek(taskQueue)) { + pop(taskQueue); + } + } + advanceTimers(currentTime); + } else { + pop(taskQueue); + } + currentTask = peek(taskQueue); + } + if (currentTask !== null) { + return true; + } else { + var firstTimer = peek(timerQueue); + if (firstTimer !== null) { + requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); + } + return false; + } + } + function unstable_runWithPriority(priorityLevel, eventHandler) { + switch (priorityLevel) { + case ImmediatePriority: + case UserBlockingPriority: + case NormalPriority: + case LowPriority: + case IdlePriority: + break; + default: + priorityLevel = NormalPriority; + } + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + try { + return eventHandler(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + } + function unstable_next(eventHandler) { + var priorityLevel; + switch (currentPriorityLevel) { + case ImmediatePriority: + case UserBlockingPriority: + case NormalPriority: + priorityLevel = NormalPriority; + break; + default: + priorityLevel = currentPriorityLevel; + break; + } + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + try { + return eventHandler(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + } + function unstable_wrapCallback(callback) { + var parentPriorityLevel = currentPriorityLevel; + return function() { + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = parentPriorityLevel; + try { + return callback.apply(this, arguments); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + }; + } + function unstable_scheduleCallback(priorityLevel, callback, options2) { + var currentTime = exports.unstable_now(); + var startTime2; + if (typeof options2 === "object" && options2 !== null) { + var delay = options2.delay; + if (typeof delay === "number" && delay > 0) { + startTime2 = currentTime + delay; + } else { + startTime2 = currentTime; + } + } else { + startTime2 = currentTime; + } + var timeout; + switch (priorityLevel) { + case ImmediatePriority: + timeout = IMMEDIATE_PRIORITY_TIMEOUT; + break; + case UserBlockingPriority: + timeout = USER_BLOCKING_PRIORITY_TIMEOUT; + break; + case IdlePriority: + timeout = IDLE_PRIORITY_TIMEOUT; + break; + case LowPriority: + timeout = LOW_PRIORITY_TIMEOUT; + break; + case NormalPriority: + default: + timeout = NORMAL_PRIORITY_TIMEOUT; + break; + } + var expirationTime = startTime2 + timeout; + var newTask = { + id: taskIdCounter++, + callback, + priorityLevel, + startTime: startTime2, + expirationTime, + sortIndex: -1 + }; + if (startTime2 > currentTime) { + newTask.sortIndex = startTime2; + push(timerQueue, newTask); + if (peek(taskQueue) === null && newTask === peek(timerQueue)) { + if (isHostTimeoutScheduled) { + cancelHostTimeout(); + } else { + isHostTimeoutScheduled = true; + } + requestHostTimeout(handleTimeout, startTime2 - currentTime); + } + } else { + newTask.sortIndex = expirationTime; + push(taskQueue, newTask); + if (!isHostCallbackScheduled && !isPerformingWork) { + isHostCallbackScheduled = true; + requestHostCallback(flushWork); + } + } + return newTask; + } + function unstable_pauseExecution() { + } + function unstable_continueExecution() { + if (!isHostCallbackScheduled && !isPerformingWork) { + isHostCallbackScheduled = true; + requestHostCallback(flushWork); + } + } + function unstable_getFirstCallbackNode() { + return peek(taskQueue); + } + function unstable_cancelCallback(task) { + task.callback = null; + } + function unstable_getCurrentPriorityLevel() { + return currentPriorityLevel; + } + var isMessageLoopRunning = false; + var scheduledHostCallback = null; + var taskTimeoutID = -1; + var frameInterval = frameYieldMs; + var startTime = -1; + function shouldYieldToHost() { + var timeElapsed = exports.unstable_now() - startTime; + if (timeElapsed < frameInterval) { + return false; + } + return true; + } + function requestPaint() { + } + function forceFrameRate(fps) { + if (fps < 0 || fps > 125) { + console["error"]("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"); + return; + } + if (fps > 0) { + frameInterval = Math.floor(1e3 / fps); + } else { + frameInterval = frameYieldMs; + } + } + var performWorkUntilDeadline = function() { + if (scheduledHostCallback !== null) { + var currentTime = exports.unstable_now(); + startTime = currentTime; + var hasTimeRemaining = true; + var hasMoreWork = true; + try { + hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); + } finally { + if (hasMoreWork) { + schedulePerformWorkUntilDeadline(); + } else { + isMessageLoopRunning = false; + scheduledHostCallback = null; + } + } + } else { + isMessageLoopRunning = false; + } + }; + var schedulePerformWorkUntilDeadline; + if (typeof localSetImmediate === "function") { + schedulePerformWorkUntilDeadline = function() { + localSetImmediate(performWorkUntilDeadline); + }; + } else if (typeof MessageChannel !== "undefined") { + var channel = new MessageChannel(); + var port = channel.port2; + channel.port1.onmessage = performWorkUntilDeadline; + schedulePerformWorkUntilDeadline = function() { + port.postMessage(null); + }; + } else { + schedulePerformWorkUntilDeadline = function() { + localSetTimeout(performWorkUntilDeadline, 0); + }; + } + function requestHostCallback(callback) { + scheduledHostCallback = callback; + if (!isMessageLoopRunning) { + isMessageLoopRunning = true; + schedulePerformWorkUntilDeadline(); + } + } + function requestHostTimeout(callback, ms) { + taskTimeoutID = localSetTimeout(function() { + callback(exports.unstable_now()); + }, ms); + } + function cancelHostTimeout() { + localClearTimeout(taskTimeoutID); + taskTimeoutID = -1; + } + var unstable_requestPaint = requestPaint; + var unstable_Profiling = null; + exports.unstable_IdlePriority = IdlePriority; + exports.unstable_ImmediatePriority = ImmediatePriority; + exports.unstable_LowPriority = LowPriority; + exports.unstable_NormalPriority = NormalPriority; + exports.unstable_Profiling = unstable_Profiling; + exports.unstable_UserBlockingPriority = UserBlockingPriority; + exports.unstable_cancelCallback = unstable_cancelCallback; + exports.unstable_continueExecution = unstable_continueExecution; + exports.unstable_forceFrameRate = forceFrameRate; + exports.unstable_getCurrentPriorityLevel = unstable_getCurrentPriorityLevel; + exports.unstable_getFirstCallbackNode = unstable_getFirstCallbackNode; + exports.unstable_next = unstable_next; + exports.unstable_pauseExecution = unstable_pauseExecution; + exports.unstable_requestPaint = unstable_requestPaint; + exports.unstable_runWithPriority = unstable_runWithPriority; + exports.unstable_scheduleCallback = unstable_scheduleCallback; + exports.unstable_shouldYield = shouldYieldToHost; + exports.unstable_wrapCallback = unstable_wrapCallback; + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); + } + })(); + } + } +}); + +// ../../../../../../../node_modules/scheduler/index.js +var require_scheduler = __commonJS({ + "../../../../../../../node_modules/scheduler/index.js"(exports, module) { + { + module.exports = require_scheduler_development(); + } + } +}); + +// ../../../../../../../node_modules/react-dom/cjs/react-dom.development.js +var require_react_dom_development = __commonJS({ + "../../../../../../../node_modules/react-dom/cjs/react-dom.development.js"(exports) { + { + (function() { + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") { + __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); + } + var React10 = require_react(); + var Scheduler = require_scheduler(); + var ReactSharedInternals = React10.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + var suppressWarning = false; + function setSuppressWarning(newSuppressWarning) { + { + suppressWarning = newSuppressWarning; + } + } + function warn(format) { + { + if (!suppressWarning) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + printWarning("warn", format, args); + } + } + } + function error2(format) { + { + if (!suppressWarning) { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + printWarning("error", format, args); + } + } + } + function printWarning(level, format, args) { + { + var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame; + var stack = ReactDebugCurrentFrame2.getStackAddendum(); + if (stack !== "") { + format += "%s"; + args = args.concat([stack]); + } + var argsWithFormat = args.map(function(item) { + return String(item); + }); + argsWithFormat.unshift("Warning: " + format); + Function.prototype.apply.call(console[level], console, argsWithFormat); + } + } + var FunctionComponent = 0; + var ClassComponent = 1; + var IndeterminateComponent = 2; + var HostRoot = 3; + var HostPortal = 4; + var HostComponent = 5; + var HostText = 6; + var Fragment6 = 7; + var Mode = 8; + var ContextConsumer = 9; + var ContextProvider = 10; + var ForwardRef = 11; + var Profiler = 12; + var SuspenseComponent = 13; + var MemoComponent = 14; + var SimpleMemoComponent = 15; + var LazyComponent = 16; + var IncompleteClassComponent = 17; + var DehydratedFragment = 18; + var SuspenseListComponent = 19; + var ScopeComponent = 21; + var OffscreenComponent = 22; + var LegacyHiddenComponent = 23; + var CacheComponent = 24; + var TracingMarkerComponent = 25; + var enableClientRenderFallbackOnTextMismatch = true; + var enableNewReconciler = false; + var enableLazyContextPropagation = false; + var enableLegacyHidden = false; + var enableSuspenseAvoidThisFallback = false; + var disableCommentsAsDOMContainers = true; + var enableCustomElementPropertySupport = false; + var warnAboutStringRefs = true; + var enableSchedulingProfiler = true; + var enableProfilerTimer = true; + var enableProfilerCommitHooks = true; + var allNativeEvents = /* @__PURE__ */ new Set(); + var registrationNameDependencies = {}; + var possibleRegistrationNames = {}; + function registerTwoPhaseEvent(registrationName, dependencies) { + registerDirectEvent(registrationName, dependencies); + registerDirectEvent(registrationName + "Capture", dependencies); + } + function registerDirectEvent(registrationName, dependencies) { + { + if (registrationNameDependencies[registrationName]) { + error2("EventRegistry: More than one plugin attempted to publish the same registration name, `%s`.", registrationName); + } + } + registrationNameDependencies[registrationName] = dependencies; + { + var lowerCasedName = registrationName.toLowerCase(); + possibleRegistrationNames[lowerCasedName] = registrationName; + if (registrationName === "onDoubleClick") { + possibleRegistrationNames.ondblclick = registrationName; + } + } + for (var i = 0; i < dependencies.length; i++) { + allNativeEvents.add(dependencies[i]); + } + } + var canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined"); + var hasOwnProperty = Object.prototype.hasOwnProperty; + function typeName(value) { + { + var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag; + var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object"; + return type; + } + } + function willCoercionThrow(value) { + { + try { + testStringCoercion(value); + return false; + } catch (e) { + return true; + } + } + } + function testStringCoercion(value) { + return "" + value; + } + function checkAttributeStringCoercion(value, attributeName) { + { + if (willCoercionThrow(value)) { + error2("The provided `%s` attribute is an unsupported type %s. This value must be coerced to a string before before using it here.", attributeName, typeName(value)); + return testStringCoercion(value); + } + } + } + function checkKeyStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error2("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + function checkPropStringCoercion(value, propName) { + { + if (willCoercionThrow(value)) { + error2("The provided `%s` prop is an unsupported type %s. This value must be coerced to a string before before using it here.", propName, typeName(value)); + return testStringCoercion(value); + } + } + } + function checkCSSPropertyStringCoercion(value, propName) { + { + if (willCoercionThrow(value)) { + error2("The provided `%s` CSS property is an unsupported type %s. This value must be coerced to a string before before using it here.", propName, typeName(value)); + return testStringCoercion(value); + } + } + } + function checkHtmlStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error2("The provided HTML markup uses a value of unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + function checkFormFieldValueStringCoercion(value) { + { + if (willCoercionThrow(value)) { + error2("Form field values (value, checked, defaultValue, or defaultChecked props) must be strings, not %s. This value must be coerced to a string before before using it here.", typeName(value)); + return testStringCoercion(value); + } + } + } + var RESERVED = 0; + var STRING = 1; + var BOOLEANISH_STRING = 2; + var BOOLEAN = 3; + var OVERLOADED_BOOLEAN = 4; + var NUMERIC = 5; + var POSITIVE_NUMERIC = 6; + var ATTRIBUTE_NAME_START_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; + var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; + var VALID_ATTRIBUTE_NAME_REGEX = new RegExp("^[" + ATTRIBUTE_NAME_START_CHAR + "][" + ATTRIBUTE_NAME_CHAR + "]*$"); + var illegalAttributeNameCache = {}; + var validatedAttributeNameCache = {}; + function isAttributeNameSafe(attributeName) { + if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) { + return true; + } + if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) { + return false; + } + if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { + validatedAttributeNameCache[attributeName] = true; + return true; + } + illegalAttributeNameCache[attributeName] = true; + { + error2("Invalid attribute name: `%s`", attributeName); + } + return false; + } + function shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) { + if (propertyInfo !== null) { + return propertyInfo.type === RESERVED; + } + if (isCustomComponentTag) { + return false; + } + if (name.length > 2 && (name[0] === "o" || name[0] === "O") && (name[1] === "n" || name[1] === "N")) { + return true; + } + return false; + } + function shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) { + if (propertyInfo !== null && propertyInfo.type === RESERVED) { + return false; + } + switch (typeof value) { + case "function": + // $FlowIssue symbol is perfectly valid here + case "symbol": + return true; + case "boolean": { + if (isCustomComponentTag) { + return false; + } + if (propertyInfo !== null) { + return !propertyInfo.acceptsBooleans; + } else { + var prefix2 = name.toLowerCase().slice(0, 5); + return prefix2 !== "data-" && prefix2 !== "aria-"; + } + } + default: + return false; + } + } + function shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag) { + if (value === null || typeof value === "undefined") { + return true; + } + if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag)) { + return true; + } + if (isCustomComponentTag) { + return false; + } + if (propertyInfo !== null) { + switch (propertyInfo.type) { + case BOOLEAN: + return !value; + case OVERLOADED_BOOLEAN: + return value === false; + case NUMERIC: + return isNaN(value); + case POSITIVE_NUMERIC: + return isNaN(value) || value < 1; + } + } + return false; + } + function getPropertyInfo(name) { + return properties.hasOwnProperty(name) ? properties[name] : null; + } + function PropertyInfoRecord(name, type, mustUseProperty, attributeName, attributeNamespace, sanitizeURL2, removeEmptyString) { + this.acceptsBooleans = type === BOOLEANISH_STRING || type === BOOLEAN || type === OVERLOADED_BOOLEAN; + this.attributeName = attributeName; + this.attributeNamespace = attributeNamespace; + this.mustUseProperty = mustUseProperty; + this.propertyName = name; + this.type = type; + this.sanitizeURL = sanitizeURL2; + this.removeEmptyString = removeEmptyString; + } + var properties = {}; + var reservedProps = [ + "children", + "dangerouslySetInnerHTML", + // TODO: This prevents the assignment of defaultValue to regular + // elements (not just inputs). Now that ReactDOMInput assigns to the + // defaultValue property -- do we need this? + "defaultValue", + "defaultChecked", + "innerHTML", + "suppressContentEditableWarning", + "suppressHydrationWarning", + "style" + ]; + reservedProps.forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + RESERVED, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [["acceptCharset", "accept-charset"], ["className", "class"], ["htmlFor", "for"], ["httpEquiv", "http-equiv"]].forEach(function(_ref) { + var name = _ref[0], attributeName = _ref[1]; + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + ["contentEditable", "draggable", "spellCheck", "value"].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEANISH_STRING, + false, + // mustUseProperty + name.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + ["autoReverse", "externalResourcesRequired", "focusable", "preserveAlpha"].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEANISH_STRING, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "allowFullScreen", + "async", + // Note: there is a special case that prevents it from being written to the DOM + // on the client side because the browsers are inconsistent. Instead we call focus(). + "autoFocus", + "autoPlay", + "controls", + "default", + "defer", + "disabled", + "disablePictureInPicture", + "disableRemotePlayback", + "formNoValidate", + "hidden", + "loop", + "noModule", + "noValidate", + "open", + "playsInline", + "readOnly", + "required", + "reversed", + "scoped", + "seamless", + // Microdata + "itemScope" + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEAN, + false, + // mustUseProperty + name.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "checked", + // Note: `option.selected` is not updated if `select.multiple` is + // disabled with `removeAttribute`. We have special logic for handling this. + "multiple", + "muted", + "selected" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + BOOLEAN, + true, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "capture", + "download" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + OVERLOADED_BOOLEAN, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "cols", + "rows", + "size", + "span" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + POSITIVE_NUMERIC, + false, + // mustUseProperty + name, + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + ["rowSpan", "start"].forEach(function(name) { + properties[name] = new PropertyInfoRecord( + name, + NUMERIC, + false, + // mustUseProperty + name.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + var CAMELIZE = /[\-\:]([a-z])/g; + var capitalize = function(token) { + return token[1].toUpperCase(); + }; + [ + "accent-height", + "alignment-baseline", + "arabic-form", + "baseline-shift", + "cap-height", + "clip-path", + "clip-rule", + "color-interpolation", + "color-interpolation-filters", + "color-profile", + "color-rendering", + "dominant-baseline", + "enable-background", + "fill-opacity", + "fill-rule", + "flood-color", + "flood-opacity", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "glyph-name", + "glyph-orientation-horizontal", + "glyph-orientation-vertical", + "horiz-adv-x", + "horiz-origin-x", + "image-rendering", + "letter-spacing", + "lighting-color", + "marker-end", + "marker-mid", + "marker-start", + "overline-position", + "overline-thickness", + "paint-order", + "panose-1", + "pointer-events", + "rendering-intent", + "shape-rendering", + "stop-color", + "stop-opacity", + "strikethrough-position", + "strikethrough-thickness", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "text-anchor", + "text-decoration", + "text-rendering", + "underline-position", + "underline-thickness", + "unicode-bidi", + "unicode-range", + "units-per-em", + "v-alphabetic", + "v-hanging", + "v-ideographic", + "v-mathematical", + "vector-effect", + "vert-adv-y", + "vert-origin-x", + "vert-origin-y", + "word-spacing", + "writing-mode", + "xmlns:xlink", + "x-height" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(attributeName) { + var name = attributeName.replace(CAMELIZE, capitalize); + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + [ + "xlink:actuate", + "xlink:arcrole", + "xlink:role", + "xlink:show", + "xlink:title", + "xlink:type" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(attributeName) { + var name = attributeName.replace(CAMELIZE, capitalize); + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + "http://www.w3.org/1999/xlink", + false, + // sanitizeURL + false + ); + }); + [ + "xml:base", + "xml:lang", + "xml:space" + // NOTE: if you add a camelCased prop to this list, + // you'll need to set attributeName to name.toLowerCase() + // instead in the assignment below. + ].forEach(function(attributeName) { + var name = attributeName.replace(CAMELIZE, capitalize); + properties[name] = new PropertyInfoRecord( + name, + STRING, + false, + // mustUseProperty + attributeName, + "http://www.w3.org/XML/1998/namespace", + false, + // sanitizeURL + false + ); + }); + ["tabIndex", "crossOrigin"].forEach(function(attributeName) { + properties[attributeName] = new PropertyInfoRecord( + attributeName, + STRING, + false, + // mustUseProperty + attributeName.toLowerCase(), + // attributeName + null, + // attributeNamespace + false, + // sanitizeURL + false + ); + }); + var xlinkHref = "xlinkHref"; + properties[xlinkHref] = new PropertyInfoRecord( + "xlinkHref", + STRING, + false, + // mustUseProperty + "xlink:href", + "http://www.w3.org/1999/xlink", + true, + // sanitizeURL + false + ); + ["src", "href", "action", "formAction"].forEach(function(attributeName) { + properties[attributeName] = new PropertyInfoRecord( + attributeName, + STRING, + false, + // mustUseProperty + attributeName.toLowerCase(), + // attributeName + null, + // attributeNamespace + true, + // sanitizeURL + true + ); + }); + var isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i; + var didWarn = false; + function sanitizeURL(url) { + { + if (!didWarn && isJavaScriptProtocol.test(url)) { + didWarn = true; + error2("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.", JSON.stringify(url)); + } + } + } + function getValueForProperty(node, name, expected, propertyInfo) { + { + if (propertyInfo.mustUseProperty) { + var propertyName = propertyInfo.propertyName; + return node[propertyName]; + } else { + { + checkAttributeStringCoercion(expected, name); + } + if (propertyInfo.sanitizeURL) { + sanitizeURL("" + expected); + } + var attributeName = propertyInfo.attributeName; + var stringValue = null; + if (propertyInfo.type === OVERLOADED_BOOLEAN) { + if (node.hasAttribute(attributeName)) { + var value = node.getAttribute(attributeName); + if (value === "") { + return true; + } + if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { + return value; + } + if (value === "" + expected) { + return expected; + } + return value; + } + } else if (node.hasAttribute(attributeName)) { + if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { + return node.getAttribute(attributeName); + } + if (propertyInfo.type === BOOLEAN) { + return expected; + } + stringValue = node.getAttribute(attributeName); + } + if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { + return stringValue === null ? expected : stringValue; + } else if (stringValue === "" + expected) { + return expected; + } else { + return stringValue; + } + } + } + } + function getValueForAttribute(node, name, expected, isCustomComponentTag) { + { + if (!isAttributeNameSafe(name)) { + return; + } + if (!node.hasAttribute(name)) { + return expected === void 0 ? void 0 : null; + } + var value = node.getAttribute(name); + { + checkAttributeStringCoercion(expected, name); + } + if (value === "" + expected) { + return expected; + } + return value; + } + } + function setValueForProperty(node, name, value, isCustomComponentTag) { + var propertyInfo = getPropertyInfo(name); + if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) { + return; + } + if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) { + value = null; + } + if (isCustomComponentTag || propertyInfo === null) { + if (isAttributeNameSafe(name)) { + var _attributeName = name; + if (value === null) { + node.removeAttribute(_attributeName); + } else { + { + checkAttributeStringCoercion(value, name); + } + node.setAttribute(_attributeName, "" + value); + } + } + return; + } + var mustUseProperty = propertyInfo.mustUseProperty; + if (mustUseProperty) { + var propertyName = propertyInfo.propertyName; + if (value === null) { + var type = propertyInfo.type; + node[propertyName] = type === BOOLEAN ? false : ""; + } else { + node[propertyName] = value; + } + return; + } + var attributeName = propertyInfo.attributeName, attributeNamespace = propertyInfo.attributeNamespace; + if (value === null) { + node.removeAttribute(attributeName); + } else { + var _type = propertyInfo.type; + var attributeValue; + if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) { + attributeValue = ""; + } else { + { + { + checkAttributeStringCoercion(value, attributeName); + } + attributeValue = "" + value; + } + if (propertyInfo.sanitizeURL) { + sanitizeURL(attributeValue.toString()); + } + } + if (attributeNamespace) { + node.setAttributeNS(attributeNamespace, attributeName, attributeValue); + } else { + node.setAttribute(attributeName, attributeValue); + } + } + } + var REACT_ELEMENT_TYPE = Symbol.for("react.element"); + var REACT_PORTAL_TYPE = Symbol.for("react.portal"); + var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"); + var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"); + var REACT_PROFILER_TYPE = Symbol.for("react.profiler"); + var REACT_PROVIDER_TYPE = Symbol.for("react.provider"); + var REACT_CONTEXT_TYPE = Symbol.for("react.context"); + var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"); + var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"); + var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"); + var REACT_MEMO_TYPE = Symbol.for("react.memo"); + var REACT_LAZY_TYPE = Symbol.for("react.lazy"); + var REACT_SCOPE_TYPE = Symbol.for("react.scope"); + var REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for("react.debug_trace_mode"); + var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen"); + var REACT_LEGACY_HIDDEN_TYPE = Symbol.for("react.legacy_hidden"); + var REACT_CACHE_TYPE = Symbol.for("react.cache"); + var REACT_TRACING_MARKER_TYPE = Symbol.for("react.tracing_marker"); + var MAYBE_ITERATOR_SYMBOL = Symbol.iterator; + var FAUX_ITERATOR_SYMBOL = "@@iterator"; + function getIteratorFn(maybeIterable) { + if (maybeIterable === null || typeof maybeIterable !== "object") { + return null; + } + var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]; + if (typeof maybeIterator === "function") { + return maybeIterator; + } + return null; + } + var assign = Object.assign; + var disabledDepth = 0; + var prevLog; + var prevInfo; + var prevWarn; + var prevError; + var prevGroup; + var prevGroupCollapsed; + var prevGroupEnd; + function disabledLog() { + } + disabledLog.__reactDisabledLog = true; + function disableLogs() { + { + if (disabledDepth === 0) { + prevLog = console.log; + prevInfo = console.info; + prevWarn = console.warn; + prevError = console.error; + prevGroup = console.group; + prevGroupCollapsed = console.groupCollapsed; + prevGroupEnd = console.groupEnd; + var props = { + configurable: true, + enumerable: true, + value: disabledLog, + writable: true + }; + Object.defineProperties(console, { + info: props, + log: props, + warn: props, + error: props, + group: props, + groupCollapsed: props, + groupEnd: props + }); + } + disabledDepth++; + } + } + function reenableLogs() { + { + disabledDepth--; + if (disabledDepth === 0) { + var props = { + configurable: true, + enumerable: true, + writable: true + }; + Object.defineProperties(console, { + log: assign({}, props, { + value: prevLog + }), + info: assign({}, props, { + value: prevInfo + }), + warn: assign({}, props, { + value: prevWarn + }), + error: assign({}, props, { + value: prevError + }), + group: assign({}, props, { + value: prevGroup + }), + groupCollapsed: assign({}, props, { + value: prevGroupCollapsed + }), + groupEnd: assign({}, props, { + value: prevGroupEnd + }) + }); + } + if (disabledDepth < 0) { + error2("disabledDepth fell below zero. This is a bug in React. Please file an issue."); + } + } + } + var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; + var prefix; + function describeBuiltInComponentFrame(name, source, ownerFn) { + { + if (prefix === void 0) { + try { + throw Error(); + } catch (x) { + var match = x.stack.trim().match(/\n( *(at )?)/); + prefix = match && match[1] || ""; + } + } + return "\n" + prefix + name; + } + } + var reentry = false; + var componentFrameCache; + { + var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map; + componentFrameCache = new PossiblyWeakMap(); + } + function describeNativeComponentFrame(fn, construct) { + if (!fn || reentry) { + return ""; + } + { + var frame = componentFrameCache.get(fn); + if (frame !== void 0) { + return frame; + } + } + var control; + reentry = true; + var previousPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = void 0; + var previousDispatcher; + { + previousDispatcher = ReactCurrentDispatcher.current; + ReactCurrentDispatcher.current = null; + disableLogs(); + } + try { + if (construct) { + var Fake = function() { + throw Error(); + }; + Object.defineProperty(Fake.prototype, "props", { + set: function() { + throw Error(); + } + }); + if (typeof Reflect === "object" && Reflect.construct) { + try { + Reflect.construct(Fake, []); + } catch (x) { + control = x; + } + Reflect.construct(fn, [], Fake); + } else { + try { + Fake.call(); + } catch (x) { + control = x; + } + fn.call(Fake.prototype); + } + } else { + try { + throw Error(); + } catch (x) { + control = x; + } + fn(); + } + } catch (sample) { + if (sample && control && typeof sample.stack === "string") { + var sampleLines = sample.stack.split("\n"); + var controlLines = control.stack.split("\n"); + var s = sampleLines.length - 1; + var c = controlLines.length - 1; + while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) { + c--; + } + for (; s >= 1 && c >= 0; s--, c--) { + if (sampleLines[s] !== controlLines[c]) { + if (s !== 1 || c !== 1) { + do { + s--; + c--; + if (c < 0 || sampleLines[s] !== controlLines[c]) { + var _frame = "\n" + sampleLines[s].replace(" at new ", " at "); + if (fn.displayName && _frame.includes("")) { + _frame = _frame.replace("", fn.displayName); + } + { + if (typeof fn === "function") { + componentFrameCache.set(fn, _frame); + } + } + return _frame; + } + } while (s >= 1 && c >= 0); + } + break; + } + } + } + } finally { + reentry = false; + { + ReactCurrentDispatcher.current = previousDispatcher; + reenableLogs(); + } + Error.prepareStackTrace = previousPrepareStackTrace; + } + var name = fn ? fn.displayName || fn.name : ""; + var syntheticFrame = name ? describeBuiltInComponentFrame(name) : ""; + { + if (typeof fn === "function") { + componentFrameCache.set(fn, syntheticFrame); + } + } + return syntheticFrame; + } + function describeClassComponentFrame(ctor, source, ownerFn) { + { + return describeNativeComponentFrame(ctor, true); + } + } + function describeFunctionComponentFrame(fn, source, ownerFn) { + { + return describeNativeComponentFrame(fn, false); + } + } + function shouldConstruct(Component2) { + var prototype = Component2.prototype; + return !!(prototype && prototype.isReactComponent); + } + function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) { + if (type == null) { + return ""; + } + if (typeof type === "function") { + { + return describeNativeComponentFrame(type, shouldConstruct(type)); + } + } + if (typeof type === "string") { + return describeBuiltInComponentFrame(type); + } + switch (type) { + case REACT_SUSPENSE_TYPE: + return describeBuiltInComponentFrame("Suspense"); + case REACT_SUSPENSE_LIST_TYPE: + return describeBuiltInComponentFrame("SuspenseList"); + } + if (typeof type === "object") { + switch (type.$$typeof) { + case REACT_FORWARD_REF_TYPE: + return describeFunctionComponentFrame(type.render); + case REACT_MEMO_TYPE: + return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn); + case REACT_LAZY_TYPE: { + var lazyComponent = type; + var payload = lazyComponent._payload; + var init = lazyComponent._init; + try { + return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn); + } catch (x) { + } + } + } + } + return ""; + } + function describeFiber(fiber) { + fiber._debugOwner ? fiber._debugOwner.type : null; + fiber._debugSource; + switch (fiber.tag) { + case HostComponent: + return describeBuiltInComponentFrame(fiber.type); + case LazyComponent: + return describeBuiltInComponentFrame("Lazy"); + case SuspenseComponent: + return describeBuiltInComponentFrame("Suspense"); + case SuspenseListComponent: + return describeBuiltInComponentFrame("SuspenseList"); + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + return describeFunctionComponentFrame(fiber.type); + case ForwardRef: + return describeFunctionComponentFrame(fiber.type.render); + case ClassComponent: + return describeClassComponentFrame(fiber.type); + default: + return ""; + } + } + function getStackByFiberInDevAndProd(workInProgress2) { + try { + var info = ""; + var node = workInProgress2; + do { + info += describeFiber(node); + node = node.return; + } while (node); + return info; + } catch (x) { + return "\nError generating stack: " + x.message + "\n" + x.stack; + } + } + function getWrappedName(outerType, innerType, wrapperName) { + var displayName = outerType.displayName; + if (displayName) { + return displayName; + } + var functionName = innerType.displayName || innerType.name || ""; + return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName; + } + function getContextName(type) { + return type.displayName || "Context"; + } + function getComponentNameFromType(type) { + if (type == null) { + return null; + } + { + if (typeof type.tag === "number") { + error2("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."); + } + } + if (typeof type === "function") { + return type.displayName || type.name || null; + } + if (typeof type === "string") { + return type; + } + switch (type) { + case REACT_FRAGMENT_TYPE: + return "Fragment"; + case REACT_PORTAL_TYPE: + return "Portal"; + case REACT_PROFILER_TYPE: + return "Profiler"; + case REACT_STRICT_MODE_TYPE: + return "StrictMode"; + case REACT_SUSPENSE_TYPE: + return "Suspense"; + case REACT_SUSPENSE_LIST_TYPE: + return "SuspenseList"; + } + if (typeof type === "object") { + switch (type.$$typeof) { + case REACT_CONTEXT_TYPE: + var context = type; + return getContextName(context) + ".Consumer"; + case REACT_PROVIDER_TYPE: + var provider = type; + return getContextName(provider._context) + ".Provider"; + case REACT_FORWARD_REF_TYPE: + return getWrappedName(type, type.render, "ForwardRef"); + case REACT_MEMO_TYPE: + var outerName = type.displayName || null; + if (outerName !== null) { + return outerName; + } + return getComponentNameFromType(type.type) || "Memo"; + case REACT_LAZY_TYPE: { + var lazyComponent = type; + var payload = lazyComponent._payload; + var init = lazyComponent._init; + try { + return getComponentNameFromType(init(payload)); + } catch (x) { + return null; + } + } + } + } + return null; + } + function getWrappedName$1(outerType, innerType, wrapperName) { + var functionName = innerType.displayName || innerType.name || ""; + return outerType.displayName || (functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName); + } + function getContextName$1(type) { + return type.displayName || "Context"; + } + function getComponentNameFromFiber(fiber) { + var tag2 = fiber.tag, type = fiber.type; + switch (tag2) { + case CacheComponent: + return "Cache"; + case ContextConsumer: + var context = type; + return getContextName$1(context) + ".Consumer"; + case ContextProvider: + var provider = type; + return getContextName$1(provider._context) + ".Provider"; + case DehydratedFragment: + return "DehydratedFragment"; + case ForwardRef: + return getWrappedName$1(type, type.render, "ForwardRef"); + case Fragment6: + return "Fragment"; + case HostComponent: + return type; + case HostPortal: + return "Portal"; + case HostRoot: + return "Root"; + case HostText: + return "Text"; + case LazyComponent: + return getComponentNameFromType(type); + case Mode: + if (type === REACT_STRICT_MODE_TYPE) { + return "StrictMode"; + } + return "Mode"; + case OffscreenComponent: + return "Offscreen"; + case Profiler: + return "Profiler"; + case ScopeComponent: + return "Scope"; + case SuspenseComponent: + return "Suspense"; + case SuspenseListComponent: + return "SuspenseList"; + case TracingMarkerComponent: + return "TracingMarker"; + // The display name for this tags come from the user-provided type: + case ClassComponent: + case FunctionComponent: + case IncompleteClassComponent: + case IndeterminateComponent: + case MemoComponent: + case SimpleMemoComponent: + if (typeof type === "function") { + return type.displayName || type.name || null; + } + if (typeof type === "string") { + return type; + } + break; + } + return null; + } + var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; + var current = null; + var isRendering = false; + function getCurrentFiberOwnerNameInDevOrNull() { + { + if (current === null) { + return null; + } + var owner = current._debugOwner; + if (owner !== null && typeof owner !== "undefined") { + return getComponentNameFromFiber(owner); + } + } + return null; + } + function getCurrentFiberStackInDev() { + { + if (current === null) { + return ""; + } + return getStackByFiberInDevAndProd(current); + } + } + function resetCurrentFiber() { + { + ReactDebugCurrentFrame.getCurrentStack = null; + current = null; + isRendering = false; + } + } + function setCurrentFiber(fiber) { + { + ReactDebugCurrentFrame.getCurrentStack = fiber === null ? null : getCurrentFiberStackInDev; + current = fiber; + isRendering = false; + } + } + function getCurrentFiber() { + { + return current; + } + } + function setIsRendering(rendering) { + { + isRendering = rendering; + } + } + function toString(value) { + return "" + value; + } + function getToStringValue(value) { + switch (typeof value) { + case "boolean": + case "number": + case "string": + case "undefined": + return value; + case "object": + { + checkFormFieldValueStringCoercion(value); + } + return value; + default: + return ""; + } + } + var hasReadOnlyValue = { + button: true, + checkbox: true, + image: true, + hidden: true, + radio: true, + reset: true, + submit: true + }; + function checkControlledValueProps(tagName, props) { + { + if (!(hasReadOnlyValue[props.type] || props.onChange || props.onInput || props.readOnly || props.disabled || props.value == null)) { + error2("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."); + } + if (!(props.onChange || props.readOnly || props.disabled || props.checked == null)) { + error2("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`."); + } + } + } + function isCheckable(elem) { + var type = elem.type; + var nodeName = elem.nodeName; + return nodeName && nodeName.toLowerCase() === "input" && (type === "checkbox" || type === "radio"); + } + function getTracker(node) { + return node._valueTracker; + } + function detachTracker(node) { + node._valueTracker = null; + } + function getValueFromNode(node) { + var value = ""; + if (!node) { + return value; + } + if (isCheckable(node)) { + value = node.checked ? "true" : "false"; + } else { + value = node.value; + } + return value; + } + function trackValueOnNode(node) { + var valueField = isCheckable(node) ? "checked" : "value"; + var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField); + { + checkFormFieldValueStringCoercion(node[valueField]); + } + var currentValue = "" + node[valueField]; + if (node.hasOwnProperty(valueField) || typeof descriptor === "undefined" || typeof descriptor.get !== "function" || typeof descriptor.set !== "function") { + return; + } + var get2 = descriptor.get, set2 = descriptor.set; + Object.defineProperty(node, valueField, { + configurable: true, + get: function() { + return get2.call(this); + }, + set: function(value) { + { + checkFormFieldValueStringCoercion(value); + } + currentValue = "" + value; + set2.call(this, value); + } + }); + Object.defineProperty(node, valueField, { + enumerable: descriptor.enumerable + }); + var tracker = { + getValue: function() { + return currentValue; + }, + setValue: function(value) { + { + checkFormFieldValueStringCoercion(value); + } + currentValue = "" + value; + }, + stopTracking: function() { + detachTracker(node); + delete node[valueField]; + } + }; + return tracker; + } + function track(node) { + if (getTracker(node)) { + return; + } + node._valueTracker = trackValueOnNode(node); + } + function updateValueIfChanged(node) { + if (!node) { + return false; + } + var tracker = getTracker(node); + if (!tracker) { + return true; + } + var lastValue = tracker.getValue(); + var nextValue = getValueFromNode(node); + if (nextValue !== lastValue) { + tracker.setValue(nextValue); + return true; + } + return false; + } + function getActiveElement(doc) { + doc = doc || (typeof document !== "undefined" ? document : void 0); + if (typeof doc === "undefined") { + return null; + } + try { + return doc.activeElement || doc.body; + } catch (e) { + return doc.body; + } + } + var didWarnValueDefaultValue = false; + var didWarnCheckedDefaultChecked = false; + var didWarnControlledToUncontrolled = false; + var didWarnUncontrolledToControlled = false; + function isControlled(props) { + var usesChecked = props.type === "checkbox" || props.type === "radio"; + return usesChecked ? props.checked != null : props.value != null; + } + function getHostProps(element, props) { + var node = element; + var checked = props.checked; + var hostProps = assign({}, props, { + defaultChecked: void 0, + defaultValue: void 0, + value: void 0, + checked: checked != null ? checked : node._wrapperState.initialChecked + }); + return hostProps; + } + function initWrapperState(element, props) { + { + checkControlledValueProps("input", props); + if (props.checked !== void 0 && props.defaultChecked !== void 0 && !didWarnCheckedDefaultChecked) { + error2("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components", getCurrentFiberOwnerNameInDevOrNull() || "A component", props.type); + didWarnCheckedDefaultChecked = true; + } + if (props.value !== void 0 && props.defaultValue !== void 0 && !didWarnValueDefaultValue) { + error2("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components", getCurrentFiberOwnerNameInDevOrNull() || "A component", props.type); + didWarnValueDefaultValue = true; + } + } + var node = element; + var defaultValue = props.defaultValue == null ? "" : props.defaultValue; + node._wrapperState = { + initialChecked: props.checked != null ? props.checked : props.defaultChecked, + initialValue: getToStringValue(props.value != null ? props.value : defaultValue), + controlled: isControlled(props) + }; + } + function updateChecked(element, props) { + var node = element; + var checked = props.checked; + if (checked != null) { + setValueForProperty(node, "checked", checked, false); + } + } + function updateWrapper(element, props) { + var node = element; + { + var controlled = isControlled(props); + if (!node._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) { + error2("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"); + didWarnUncontrolledToControlled = true; + } + if (node._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) { + error2("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"); + didWarnControlledToUncontrolled = true; + } + } + updateChecked(element, props); + var value = getToStringValue(props.value); + var type = props.type; + if (value != null) { + if (type === "number") { + if (value === 0 && node.value === "" || // We explicitly want to coerce to number here if possible. + // eslint-disable-next-line + node.value != value) { + node.value = toString(value); + } + } else if (node.value !== toString(value)) { + node.value = toString(value); + } + } else if (type === "submit" || type === "reset") { + node.removeAttribute("value"); + return; + } + { + if (props.hasOwnProperty("value")) { + setDefaultValue(node, props.type, value); + } else if (props.hasOwnProperty("defaultValue")) { + setDefaultValue(node, props.type, getToStringValue(props.defaultValue)); + } + } + { + if (props.checked == null && props.defaultChecked != null) { + node.defaultChecked = !!props.defaultChecked; + } + } + } + function postMountWrapper(element, props, isHydrating2) { + var node = element; + if (props.hasOwnProperty("value") || props.hasOwnProperty("defaultValue")) { + var type = props.type; + var isButton = type === "submit" || type === "reset"; + if (isButton && (props.value === void 0 || props.value === null)) { + return; + } + var initialValue = toString(node._wrapperState.initialValue); + if (!isHydrating2) { + { + if (initialValue !== node.value) { + node.value = initialValue; + } + } + } + { + node.defaultValue = initialValue; + } + } + var name = node.name; + if (name !== "") { + node.name = ""; + } + { + node.defaultChecked = !node.defaultChecked; + node.defaultChecked = !!node._wrapperState.initialChecked; + } + if (name !== "") { + node.name = name; + } + } + function restoreControlledState(element, props) { + var node = element; + updateWrapper(node, props); + updateNamedCousins(node, props); + } + function updateNamedCousins(rootNode, props) { + var name = props.name; + if (props.type === "radio" && name != null) { + var queryRoot = rootNode; + while (queryRoot.parentNode) { + queryRoot = queryRoot.parentNode; + } + { + checkAttributeStringCoercion(name, "name"); + } + var group = queryRoot.querySelectorAll("input[name=" + JSON.stringify("" + name) + '][type="radio"]'); + for (var i = 0; i < group.length; i++) { + var otherNode = group[i]; + if (otherNode === rootNode || otherNode.form !== rootNode.form) { + continue; + } + var otherProps = getFiberCurrentPropsFromNode(otherNode); + if (!otherProps) { + throw new Error("ReactDOMInput: Mixing React and non-React radio inputs with the same `name` is not supported."); + } + updateValueIfChanged(otherNode); + updateWrapper(otherNode, otherProps); + } + } + } + function setDefaultValue(node, type, value) { + if ( + // Focused number inputs synchronize on blur. See ChangeEventPlugin.js + type !== "number" || getActiveElement(node.ownerDocument) !== node + ) { + if (value == null) { + node.defaultValue = toString(node._wrapperState.initialValue); + } else if (node.defaultValue !== toString(value)) { + node.defaultValue = toString(value); + } + } + } + var didWarnSelectedSetOnOption = false; + var didWarnInvalidChild = false; + var didWarnInvalidInnerHTML = false; + function validateProps(element, props) { + { + if (props.value == null) { + if (typeof props.children === "object" && props.children !== null) { + React10.Children.forEach(props.children, function(child) { + if (child == null) { + return; + } + if (typeof child === "string" || typeof child === "number") { + return; + } + if (!didWarnInvalidChild) { + didWarnInvalidChild = true; + error2("Cannot infer the option value of complex children. Pass a `value` prop or use a plain string as children to