├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation_request.yml │ └── feature_request.yml ├── composite-actions │ └── install │ │ └── action.yml ├── pull_request_template.md └── workflows │ ├── quality.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarn └── releases │ └── yarn-4.0.1.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── package.json ├── packages ├── language-server │ ├── CHANGELOG.md │ ├── Roboto-Medium.ttf │ ├── __tests__ │ │ ├── fixtures │ │ │ ├── create-config-result.ts │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── get-completion-for.test.ts │ │ └── get-token.test.ts │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── builder-resolver.ts │ │ ├── capabilities.ts │ │ ├── completion-provider.ts │ │ ├── deferred.ts │ │ ├── extractor.ts │ │ ├── features │ │ │ ├── color-hints.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostics.ts │ │ │ ├── hover.ts │ │ │ └── inlay-hints.ts │ │ ├── index.ts │ │ ├── panda-language-server.ts │ │ ├── project-helper.ts │ │ ├── token-finder.ts │ │ ├── tokens │ │ │ ├── color2k-to-vscode-color.ts │ │ │ ├── error.ts │ │ │ ├── expand-token-fn.ts │ │ │ ├── get-token.ts │ │ │ ├── is-color.ts │ │ │ ├── render-markdown.ts │ │ │ ├── render-token-color-preview.ts │ │ │ ├── sort-text.ts │ │ │ └── utils.ts │ │ └── uri-to-path.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── shared │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── flatten.ts │ │ ├── index.ts │ │ └── settings.ts │ └── tsconfig.json ├── ts-plugin │ ├── CHANGELOG.md │ ├── index.ts │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── vscode │ ├── .vscodeignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── assets │ │ └── logo.png │ ├── index.ts │ ├── package.json │ ├── scripts │ │ └── publish.ts │ ├── src │ │ ├── index.ts │ │ ├── panda-extension.ts │ │ ├── server.ts │ │ └── typescript-language-features.ts │ ├── tsconfig.json │ └── tsup.config.ts └── vsix-builder │ ├── CHANGELOG.md │ ├── bin.js │ ├── package.json │ ├── src │ ├── cli-default.ts │ ├── cli-main.ts │ ├── create-vsix.ts │ ├── index.ts │ └── vsce │ │ ├── manifest.ts │ │ ├── package.ts │ │ ├── parse-semver.d.ts │ │ ├── util.ts │ │ ├── version-bump.ts │ │ ├── xml.ts │ │ └── zip.ts │ └── tsconfig.json ├── renovate.json ├── sandbox └── vite │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── panda.config.ts │ ├── postcss.config.cjs │ ├── public │ └── vite.svg │ ├── src │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── button.recipe.ts │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── tsconfig.json ├── vitest.config.ts └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["sandbox-vite"] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | *.js 3 | *.cjs 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 4 | "env": { 5 | "node": true, 6 | "browser": true 7 | }, 8 | "rules": { 9 | "no-prototype-builtins": "off", 10 | "@typescript-eslint/no-explicit-any": "off", 11 | "@typescript-eslint/explicit-module-boundary-types": "off", 12 | "@typescript-eslint/no-non-null-assertion": "off", 13 | "@typescript-eslint/ban-ts-comment": "off", 14 | "@typescript-eslint/no-var-requires": "off", 15 | "@typescript-eslint/ban-types": "off", 16 | "@typescript-eslint/no-unused-vars": [ 17 | "error", 18 | { 19 | "varsIgnorePattern": "^_", 20 | "argsIgnorePattern": "^_", 21 | "ignoreRestSiblings": true 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug Report" 2 | description: "File a bug report" 3 | body: 4 | - type: "markdown" 5 | attributes: 6 | value: | 7 | Thanks for creating an issue 😄! 8 | 9 | Please search open/closed issues before submitting. Someone 10 | might have asked the same thing before 😉! 11 | 12 | We're all volunteers here, so help us help you by taking the time to 13 | accurately fill out this template. ❤️ 14 | - type: "textarea" 15 | id: "description" 16 | attributes: 17 | label: "Description" 18 | description: "A brief description of the issue." 19 | placeholder: | 20 | When I ____, I expected ____ to happen but ____ happened instead. 21 | validations: 22 | required: true 23 | - type: "input" 24 | id: "reproduction" 25 | attributes: 26 | label: "Link to Reproduction" 27 | description: | 28 | A link to a Stackblitz reproduction which demonstrates the bug 29 | placeholder: "https://stackblitz.com/edit/vitejs-vite-lfwyue?file=src%2FApp.tsx&terminal=dev" 30 | validations: 31 | required: true 32 | - type: "textarea" 33 | id: "steps" 34 | attributes: 35 | label: "Steps to reproduce" 36 | description: | 37 | Explain how to cause the issue in the provided reproduction. 38 | value: | 39 | 1. Go to '...' 40 | 2. Click on '...' 41 | 3. Scroll down to '...' 42 | 4. See error 43 | - type: "input" 44 | id: "framework" 45 | attributes: 46 | label: "JS Framework" 47 | description: "The JS framework used in your project. Specify JS/TS." 48 | placeholder: "React (TS)" 49 | - type: "input" 50 | id: "panda-version" 51 | attributes: 52 | label: "Panda CSS Version" 53 | description: "The version of Panda CSS you use." 54 | placeholder: "x.x.x" 55 | validations: 56 | required: true 57 | - type: "input" 58 | id: "browser" 59 | attributes: 60 | label: "Browser" 61 | description: "The browser(s) this issue occurred with." 62 | placeholder: "Google Chrome 93" 63 | - type: "checkboxes" 64 | id: "operating-system" 65 | attributes: 66 | label: "Operating System" 67 | description: "The operating system(s) this issue occurred with." 68 | options: 69 | - label: "macOS" 70 | - label: "Windows" 71 | - label: "Linux" 72 | - type: "textarea" 73 | id: "additional-information" 74 | attributes: 75 | label: "Additional Information" 76 | description: | 77 | Use this section to provide any additional information you might have 78 | like screenshots, notes, or links to ideas. 79 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/chakra-ui/panda/discussions 5 | about: Ask questions and discuss topics with other community members 6 | - name: Chat with other community members 7 | url: https://discord.gg/VQrkpsgSx7 8 | about: The official Panda CSS Discord community 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.yml: -------------------------------------------------------------------------------- 1 | name: "Documentation Request" 2 | description: "Request for documentation to be added/altered" 3 | labels: ["needs triage", "Topic: Documentation 📚"] 4 | body: 5 | - type: "markdown" 6 | attributes: 7 | value: | 8 | Thanks for filing a documentation request! 9 | 10 | If you have an idea for a new documentation topic, noticed that 11 | something is not properly documented, or feel that something is 12 | incorrect with the current documentation, you're in the right place! 13 | - type: "input" 14 | id: "subject" 15 | attributes: 16 | label: "Subject" 17 | description: "What is the subject (component, function, topic) of this request?" 18 | placeholder: "Presets" 19 | validations: 20 | required: true 21 | - type: "textarea" 22 | id: "description" 23 | attributes: 24 | label: "Description" 25 | description: "What about the subject's documentation should be added or changed?" 26 | placeholder: "Add a usage example of RadioGroup in action" 27 | validations: 28 | required: true 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: "Request a feature or enhancement" 3 | labels: ["needs triage"] 4 | body: 5 | - type: "markdown" 6 | attributes: 7 | value: | 8 | Thanks for filing an issue 😄! 9 | 10 | Please search open/closed issues before submitting. Someone 11 | might have asked the same thing before 😉! 12 | - type: "textarea" 13 | id: "description" 14 | attributes: 15 | label: "Description" 16 | description: "Please describe your request in one or two sentences." 17 | validations: 18 | required: true 19 | - type: "textarea" 20 | id: "justification" 21 | attributes: 22 | label: "Problem Statement/Justification" 23 | description: | 24 | Please provide valid reason(s) why this should be added to Chakra UI 25 | 26 | If this feature is related to a problem you've noticed, mention it as 27 | well. 28 | validations: 29 | required: true 30 | - type: "textarea" 31 | id: "proposed-solution" 32 | attributes: 33 | label: "Proposed Solution or API" 34 | description: | 35 | Please provide code snippets, gists, or links to the ideal 36 | design or API. 37 | validations: 38 | required: true 39 | - type: "textarea" 40 | id: "alternatives" 41 | attributes: 42 | label: "Alternatives" 43 | description: | 44 | What alternative solutions have you considered before making this 45 | request? 46 | - type: "textarea" 47 | id: "additional-information" 48 | attributes: 49 | label: "Additional Information" 50 | description: | 51 | What resources (links, screenshots, etc.) do you have to assist this 52 | effort? 53 | -------------------------------------------------------------------------------- /.github/composite-actions/install/action.yml: -------------------------------------------------------------------------------- 1 | name: "Install" 2 | description: "Sets up Node.js and runs install" 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@v3 9 | with: 10 | node-version: 20.x 11 | registry-url: "https://registry.npmjs.org" 12 | cache: "yarn" 13 | 14 | - name: Setup Git User 15 | shell: bash 16 | run: | 17 | git config --global user.email "joseshegs@gmail.com" 18 | git config --global user.name "Segun Adebayo" 19 | 20 | - name: Install dependencies 21 | shell: bash 22 | run: yarn install --frozen-lockfile 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | Closes # 11 | 12 | ## 📝 Description 13 | 14 | > Add a brief description 15 | 16 | ## ⛳️ Current behavior (updates) 17 | 18 | > Please describe the current behavior that you are modifying 19 | 20 | ## 🚀 New behavior 21 | 22 | > Please describe the behavior or changes this PR adds 23 | 24 | ## 💣 Is this a breaking change (Yes/No): 25 | 26 | 27 | 28 | ## 📝 Additional Information 29 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: Quality 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 14 | 15 | jobs: 16 | prettier: 17 | name: Prettier 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Install 24 | uses: ./.github/composite-actions/install 25 | 26 | - name: Run Prettier 27 | run: yarn fmt 28 | 29 | # tests: 30 | # name: Unit Tests 31 | # runs-on: ubuntu-latest 32 | # steps: 33 | # - name: Checkout 34 | # uses: actions/checkout@v3 35 | 36 | # - name: Install 37 | # uses: ./.github/composite-actions/install 38 | 39 | # - name: Run tests 40 | # run: yarn test 41 | 42 | eslint: 43 | name: ESLint 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v3 48 | 49 | - name: Install 50 | uses: ./.github/composite-actions/install 51 | 52 | - name: Run ESLint 53 | run: yarn lint 54 | env: 55 | NODE_OPTIONS: "--max-old-space-size=4096" 56 | 57 | types: 58 | name: TypeScript 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Checkout 62 | uses: actions/checkout@v3 63 | 64 | - name: Install 65 | uses: ./.github/composite-actions/install 66 | 67 | - name: Run TypeScript type check 68 | run: yarn typecheck 69 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | paths: 6 | - ".changeset/**" 7 | - "packages/**" 8 | - ".github/workflows/release.yml" 9 | branches: 10 | - main 11 | - ci/release 12 | 13 | jobs: 14 | release: 15 | name: Release 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Install 24 | uses: ./.github/composite-actions/install 25 | 26 | - name: Build packages 27 | run: yarn build 28 | 29 | - name: Publish packages 30 | id: changesets 31 | uses: changesets/action@v1 32 | with: 33 | publish: yarn release 34 | env: 35 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 36 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | 38 | - name: Release to dev tag 39 | if: steps.changesets.outputs.published != 'true' 40 | run: | 41 | git checkout main 42 | yarn changeset version --snapshot dev 43 | yarn changeset publish --tag dev 44 | env: 45 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 46 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 47 | 48 | create_timestamp: 49 | runs-on: ubuntu-latest 50 | 51 | outputs: 52 | timestamp: ${{ steps.set_timestamp.outputs.timestamp }} 53 | 54 | steps: 55 | - name: Set Timestamp 56 | id: set_timestamp 57 | run: | 58 | echo "::set-output name=timestamp::$(date +'%s' | cut -c1-8)" 59 | 60 | release-extension: 61 | name: VSCode Marketplace 62 | needs: create_timestamp 63 | runs-on: ${{ matrix.os }} 64 | strategy: 65 | fail-fast: false 66 | matrix: 67 | include: 68 | - os: windows-latest 69 | target: win32-x64 70 | npm_config_arch: x64 71 | - os: windows-latest 72 | target: win32-arm64 73 | npm_config_arch: arm 74 | - os: ubuntu-latest 75 | target: linux-x64 76 | npm_config_arch: x64 77 | - os: ubuntu-latest 78 | target: linux-arm64 79 | npm_config_arch: arm64 80 | - os: ubuntu-latest 81 | target: linux-armhf 82 | npm_config_arch: arm 83 | - os: ubuntu-latest 84 | target: alpine-x64 85 | npm_config_arch: x64 86 | - os: macos-latest 87 | target: darwin-x64 88 | npm_config_arch: x64 89 | - os: macos-latest 90 | target: darwin-arm64 91 | npm_config_arch: arm64 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v3 95 | with: 96 | fetch-depth: 0 97 | 98 | - name: Install 99 | uses: ./.github/composite-actions/install 100 | 101 | - name: Build packages 102 | run: yarn build 103 | 104 | - name: Set Environment Variable 105 | run: echo "VSCE_RELEASE_VERSION=$(date +'%s' | cut -c1-8)" >> $GITHUB_ENV 106 | 107 | - name: Publish RC 108 | if: steps.changesets.outputs.published != 'true' 109 | working-directory: ./packages/vscode 110 | run: | 111 | yarn release 112 | env: 113 | VSCE_TOKEN: ${{secrets.VSCE_TOKEN}} 114 | VSCE_RELEASE_VERSION: ${{ needs.create_timestamp.outputs.timestamp }} 115 | VSCE_RELEASE_TYPE: rc 116 | VSCE_TARGET: ${{ matrix.target }} 117 | 118 | - name: Publish extension 119 | if: steps.changesets.outputs.published == 'true' 120 | working-directory: ./packages/vscode 121 | run: | 122 | yarn release 123 | env: 124 | VSCE_TOKEN: ${{secrets.VSCE_TOKEN}} 125 | VSCE_RELEASE_VERSION: ${{ needs.create_timestamp.outputs.timestamp }} 126 | VSCE_RELEASE_TYPE: stable 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directory 7 | **/node_modules/** 8 | node_modules 9 | 10 | dist 11 | dist-ssr 12 | out 13 | build 14 | **/buil 15 | tsconfig.tsbuildinfo 16 | .DS_Store 17 | __generated__ 18 | *.env 19 | .idea 20 | *.vsix 21 | 22 | ## Panda 23 | styled-system 24 | styled-system-static 25 | panda 26 | panda-static 27 | outdir 28 | ts-import-map-outdir 29 | 30 | !packages/studio/styled-system 31 | packages/studio/src/lib/analysis.json 32 | 33 | .yarn/install-state.gz 34 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | .next 5 | build 6 | *.hbs 7 | *.d.ts 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "bracketSpacing": true, 5 | "jsxSingleQuote": false, 6 | "proseWrap": "always", 7 | "semi": false, 8 | "tabWidth": 2, 9 | "trailingComma": "all" 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--disable-updates", 14 | "--disable-workspace-trust", 15 | "--profile-temp", 16 | "--skip-release-notes", 17 | "--skip-welcome", 18 | "--extensionDevelopmentPath=${workspaceFolder}/packages/vscode", 19 | "--folder-uri=${workspaceRoot}/sandbox/vite" 20 | ], 21 | "outFiles": ["${workspaceFolder}/packages/vscode/dist/**/*.js"], 22 | "env": { 23 | "TSS_DEBUG": "9559" 24 | } 25 | }, 26 | // See: https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code 27 | { 28 | "name": "Attach to Server", 29 | "type": "node", 30 | "request": "attach", 31 | "port": 9559, 32 | "restart": true, 33 | "outFiles": ["${workspaceFolder}/packages/typescript-server-plugin/dist/**/*.js"], 34 | "skipFiles": ["/**"] 35 | }, 36 | { 37 | "command": "yarn test ${relativeFile}", 38 | "name": "Run Vitest", 39 | "request": "launch", 40 | "type": "node-terminal" 41 | } 42 | ], 43 | "compounds": [ 44 | { 45 | "name": "Launch Extension + TS Plugin", 46 | "configurations": ["Run Extension", "Attach to Server"] 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "eslint.workingDirectories": ["./packages/**", "./sandbox/**", "./sandbox/next-js"] 4 | } 5 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | yarnPath: .yarn/releases/yarn-4.0.1.cjs 3 | nmHoistingLimits: dependencies 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Chakra UI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # panda-vscode 2 | 3 | The official Panda CSS VSCode extension 4 | 5 | ## Contributing 6 | 7 | ### Setup 8 | 9 | ```bash 10 | yarn install 11 | ``` 12 | 13 | ### Development 14 | 15 | ```bash 16 | yarn dev 17 | ``` 18 | 19 | Then start the VSCode extension development host with [the `Run extension` launch config.](.vscode/launch.json) 20 | 21 | ### Build 22 | 23 | ```bash 24 | yarn build 25 | ``` 26 | 27 | ### Test 28 | 29 | ```bash 30 | yarn test 31 | ``` 32 | 33 | ```bash 34 | yarn typecheck 35 | ``` 36 | 37 | ### Publishing 38 | 39 | Done automatically when the release PR (generated with 40 | [Changesets](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md)) is merged. 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "panda-vscode", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "The repository of css panda", 6 | "scripts": { 7 | "prepare": "yarn build-fast", 8 | "dev": "yarn workspaces foreach -pA --exclude 'sandbox-vite' --exclude 'panda-vscode' run dev", 9 | "build-fast": "yarn workspaces foreach -pA --exclude 'sandbox-vite' --exclude 'panda-vscode' run build-fast", 10 | "build": "yarn workspaces foreach -A --exclude 'sandbox-vite' --exclude 'panda-vscode' run build", 11 | "test": "vitest", 12 | "lint": "eslint packages --ext .ts", 13 | "fmt": "prettier --write packages", 14 | "typecheck": "tsc --noEmit", 15 | "release": "changeset publish", 16 | "release-dev": "changeset version --snapshot dev && changeset publish --tag dev" 17 | }, 18 | "keywords": [], 19 | "author": "Segun Adebayo ", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@changesets/changelog-github": "0.5.0", 23 | "@changesets/cli": "2.27.1", 24 | "@types/node": "20.11.30", 25 | "@typescript-eslint/eslint-plugin": "7.3.1", 26 | "@typescript-eslint/parser": "7.3.1", 27 | "prettier": "^3.2.5", 28 | "tsup": "8.0.2", 29 | "typescript": "5.4.2", 30 | "vitest": "1.4.0" 31 | }, 32 | "resolutions": { 33 | "panda-css-vscode/esbuild": "npm:esbuild-wasm@0.17.18", 34 | "panda-css-vscode/lightningcss": "npm:lightningcss-wasm@1.23.0" 35 | }, 36 | "packageManager": "yarn@4.0.1", 37 | "workspaces": [ 38 | "packages/*", 39 | "sandbox/**" 40 | ], 41 | "devDependencies": { 42 | "@types/eslint": "^8.56.6", 43 | "eslint": "^8.57.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/language-server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pandacss/language-server 2 | 3 | ## 0.19.0 4 | 5 | ### Minor Changes 6 | 7 | - 101ba76: improved token parsing for utilities 8 | - 01496c6: handle escape hatched colors 9 | - af8cf9c: auto complete utilities with complex tokens 10 | 11 | ## 0.18.0 12 | 13 | ### Minor Changes 14 | 15 | - bd5ca1b: Update Panda version to 0.37.2 (from 0.17.0), fixes a bunch of small issues 16 | 17 | ## 0.17.1 18 | 19 | ### Patch Changes 20 | 21 | - c457543: Fix inline token completions due to an issue with the settings retrieval 22 | - de1050f: Add support for color-hints / diagnostics / inlay-hints in `sva` definition and `pattern` usage 23 | 24 | ## 0.17.0 25 | 26 | ## 0.16.0 27 | 28 | ## 0.15.5 29 | 30 | ## 0.15.4 31 | 32 | ## 0.15.3 33 | 34 | ## 0.15.2 35 | 36 | ## 0.15.1 37 | 38 | ## 0.15.0 39 | 40 | ## 0.14.0 41 | 42 | ## 0.13.1 43 | 44 | ## 0.13.0 45 | 46 | ## 0.12.2 47 | 48 | ## 0.12.1 49 | 50 | ## 0.12.0 51 | 52 | ## 0.11.1 53 | 54 | ## 0.11.0 55 | 56 | ## 0.10.0 57 | 58 | ## 0.9.0 59 | 60 | ## 0.8.0 61 | 62 | ### Patch Changes 63 | 64 | - 78612d7f: Fix node evaluation in extractor process (can happen when using a BinaryExpression, simple CallExpression or 65 | conditions) 66 | 67 | ## 0.7.0 68 | 69 | ## 0.6.0 70 | 71 | ## 0.5.1 72 | 73 | ## 0.5.0 74 | 75 | ## 0.4.0 76 | 77 | ## 0.3.2 78 | 79 | ## 0.3.1 80 | 81 | ## 0.3.0 82 | 83 | ### Patch Changes 84 | 85 | - 02f4daf7: Publish extension packages 86 | -------------------------------------------------------------------------------- /packages/language-server/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chakra-ui/panda-vscode/f50ecaca5255e50e0913a76eb79b4cbdb21dd7f6/packages/language-server/Roboto-Medium.ttf -------------------------------------------------------------------------------- /packages/language-server/__tests__/fixtures/create-config-result.ts: -------------------------------------------------------------------------------- 1 | import { mergeConfigs } from '@pandacss/config' 2 | import { PandaContext } from '@pandacss/node' 3 | import presetBase from '@pandacss/preset-base' 4 | import presetPanda from '@pandacss/preset-panda' 5 | import { parseJson, stringifyJson } from '@pandacss/shared' 6 | import type { Config, LoadConfigResult, PresetCore, UserConfig } from '@pandacss/types' 7 | import { utils } from './utils' 8 | 9 | const buttonRecipe = { 10 | className: 'button', 11 | description: 'The styles for the Button component', 12 | base: { 13 | display: 'flex', 14 | cursor: 'pointer', 15 | fontWeight: 'bold', 16 | }, 17 | variants: { 18 | visual: { 19 | funky: { bg: 'red.200', color: 'slate.800' }, 20 | edgy: { border: '1px solid {colors.red.500}' }, 21 | }, 22 | size: { 23 | sm: { padding: '4', fontSize: '12px' }, 24 | lg: { padding: '8', fontSize: '40px' }, 25 | }, 26 | shape: { 27 | square: { borderRadius: '0' }, 28 | circle: { borderRadius: 'full' }, 29 | }, 30 | }, 31 | defaultVariants: { 32 | visual: 'funky', 33 | size: 'sm', 34 | shape: 'circle', 35 | }, 36 | } 37 | 38 | const fixturePreset: Omit = { 39 | ...presetBase, 40 | ...presetPanda, 41 | theme: { 42 | ...presetPanda.theme, 43 | recipes: { 44 | button: buttonRecipe, 45 | }, 46 | }, 47 | } 48 | 49 | const config: UserConfig = { 50 | ...fixturePreset, 51 | optimize: true, 52 | cwd: '', 53 | outdir: 'styled-system', 54 | include: [], 55 | // 56 | cssVarRoot: ':where(html)', 57 | jsxFramework: 'react', 58 | } 59 | 60 | const fixtureDefaults = { 61 | dependencies: [], 62 | config, 63 | path: '', 64 | hooks: {}, 65 | serialized: stringifyJson(config), 66 | deserialize: () => parseJson(stringifyJson(config)), 67 | } as LoadConfigResult 68 | 69 | export const createConfigResult = (userConfig?: Config) => { 70 | const resolvedConfig = ( 71 | userConfig ? mergeConfigs([userConfig, fixtureDefaults.config]) : fixtureDefaults.config 72 | ) as UserConfig 73 | 74 | return { ...fixtureDefaults, config: resolvedConfig } 75 | } 76 | 77 | export const createContext = (userConfig?: Config) => { 78 | let resolvedConfig = ( 79 | userConfig ? mergeConfigs([userConfig, fixtureDefaults.config]) : fixtureDefaults.config 80 | ) as UserConfig 81 | 82 | const hooks = userConfig?.hooks ?? {} 83 | 84 | // This allows editing the config before the context is created 85 | // since this function is only used in tests, we only look at the user hooks 86 | // not the presets hooks, so that we can keep this fn sync 87 | if (hooks['config:resolved']) { 88 | const result = hooks['config:resolved']({ 89 | config: resolvedConfig, 90 | path: fixtureDefaults.path, 91 | dependencies: fixtureDefaults.dependencies, 92 | utils: utils, 93 | }) 94 | if (result) { 95 | resolvedConfig = result as UserConfig 96 | } 97 | } 98 | 99 | return new PandaContext({ 100 | ...fixtureDefaults, 101 | hooks, 102 | config: resolvedConfig, 103 | tsconfig: { 104 | // @ts-expect-error 105 | useInMemoryFileSystem: true, 106 | }, 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /packages/language-server/__tests__/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-config-result' 2 | -------------------------------------------------------------------------------- /packages/language-server/__tests__/fixtures/utils.ts: -------------------------------------------------------------------------------- 1 | import { traverse } from '@pandacss/shared' 2 | 3 | const omit = (obj: T, paths: K[]): Omit => { 4 | const result = { ...obj } 5 | 6 | traverse(result, ({ path, parent, key }) => { 7 | if (paths.includes(path as K)) { 8 | delete (parent as any)[key] 9 | } 10 | }) 11 | 12 | return result as Omit 13 | } 14 | 15 | export const utils = { 16 | omit, 17 | traverse, 18 | } 19 | -------------------------------------------------------------------------------- /packages/language-server/__tests__/get-token.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | import { getTokenFromPropValue } from '../src/tokens/get-token' 3 | import { createContext } from './fixtures' 4 | 5 | test('display: block', () => { 6 | const ctx = createContext() 7 | expect(getTokenFromPropValue(ctx, 'display', 'block')).toMatchInlineSnapshot(`undefined`) 8 | }) 9 | 10 | test('zIndex: 1', () => { 11 | const ctx = createContext() 12 | expect(getTokenFromPropValue(ctx, 'zIndex', '1')).toMatchInlineSnapshot(`undefined`) 13 | }) 14 | 15 | test('margin: 2', () => { 16 | const ctx = createContext() 17 | expect(getTokenFromPropValue(ctx, 'margin', '2')).toMatchInlineSnapshot(` 18 | _Token { 19 | "description": undefined, 20 | "extensions": { 21 | "category": "spacing", 22 | "condition": "base", 23 | "pixelValue": "8px", 24 | "prop": "2", 25 | "var": "--spacing-2", 26 | "varRef": "var(--spacing-2)", 27 | }, 28 | "name": "spacing.2", 29 | "originalValue": "0.5rem", 30 | "path": [ 31 | "spacing", 32 | "2", 33 | ], 34 | "type": "dimension", 35 | "value": "0.5rem", 36 | } 37 | `) 38 | }) 39 | 40 | test('CSS var', () => { 41 | const ctx = createContext() 42 | expect(getTokenFromPropValue(ctx, '--btn-color', 'colors.gray.300')).toMatchInlineSnapshot(`undefined`) 43 | }) 44 | 45 | test('token reference with curly braces', () => { 46 | const ctx = createContext() 47 | expect(getTokenFromPropValue(ctx, 'border', '{colors.gray.300}')).toMatchInlineSnapshot(` 48 | _Token { 49 | "description": undefined, 50 | "extensions": { 51 | "category": "colors", 52 | "colorPalette": "gray", 53 | "colorPaletteRoots": [ 54 | [ 55 | "gray", 56 | ], 57 | ], 58 | "colorPaletteTokenKeys": [ 59 | [ 60 | "300", 61 | ], 62 | ], 63 | "condition": "base", 64 | "kind": "semantic-color", 65 | "prop": "gray.300", 66 | "var": "--colors-gray-300", 67 | "varRef": "var(--colors-gray-300)", 68 | "vscodeColor": { 69 | "alpha": 1, 70 | "blue": 0.8588235294117647, 71 | "green": 0.8352941176470589, 72 | "red": 0.8196078431372549, 73 | }, 74 | }, 75 | "name": "colors.gray.300", 76 | "originalValue": "#d1d5db", 77 | "path": [ 78 | "colors", 79 | "gray", 80 | "300", 81 | ], 82 | "type": "color", 83 | "value": "#d1d5db", 84 | } 85 | `) 86 | }) 87 | 88 | test('composite value with token reference with token()', () => { 89 | const ctx = createContext() 90 | expect(getTokenFromPropValue(ctx, 'border', '1px solid {colors.gray.300}')).toMatchInlineSnapshot(` 91 | _Token { 92 | "description": undefined, 93 | "extensions": { 94 | "category": "colors", 95 | "colorPalette": "gray", 96 | "colorPaletteRoots": [ 97 | [ 98 | "gray", 99 | ], 100 | ], 101 | "colorPaletteTokenKeys": [ 102 | [ 103 | "300", 104 | ], 105 | ], 106 | "condition": "base", 107 | "kind": "semantic-color", 108 | "prop": "gray.300", 109 | "var": "--colors-gray-300", 110 | "varRef": "var(--colors-gray-300)", 111 | "vscodeColor": { 112 | "alpha": 1, 113 | "blue": 0.8588235294117647, 114 | "green": 0.8352941176470589, 115 | "red": 0.8196078431372549, 116 | }, 117 | }, 118 | "name": "colors.gray.300", 119 | "originalValue": "#d1d5db", 120 | "path": [ 121 | "colors", 122 | "gray", 123 | "300", 124 | ], 125 | "type": "color", 126 | "value": "#d1d5db", 127 | } 128 | `) 129 | }) 130 | 131 | test('token reference with token()', () => { 132 | const ctx = createContext() 133 | expect(getTokenFromPropValue(ctx, 'border', '1px solid token(colors.gray.300)')).toMatchInlineSnapshot(` 134 | _Token { 135 | "description": undefined, 136 | "extensions": { 137 | "category": "colors", 138 | "colorPalette": "gray", 139 | "colorPaletteRoots": [ 140 | [ 141 | "gray", 142 | ], 143 | ], 144 | "colorPaletteTokenKeys": [ 145 | [ 146 | "300", 147 | ], 148 | ], 149 | "condition": "base", 150 | "kind": "semantic-color", 151 | "prop": "gray.300", 152 | "var": "--colors-gray-300", 153 | "varRef": "var(--colors-gray-300)", 154 | "vscodeColor": { 155 | "alpha": 1, 156 | "blue": 0.8588235294117647, 157 | "green": 0.8352941176470589, 158 | "red": 0.8196078431372549, 159 | }, 160 | }, 161 | "name": "colors.gray.300", 162 | "originalValue": "#d1d5db", 163 | "path": [ 164 | "colors", 165 | "gray", 166 | "300", 167 | ], 168 | "type": "color", 169 | "value": "#d1d5db", 170 | } 171 | `) 172 | }) 173 | 174 | test('fontSize: xl', () => { 175 | const ctx = createContext() 176 | expect(getTokenFromPropValue(ctx, 'fontSize', 'xl')).toMatchInlineSnapshot(` 177 | _Token { 178 | "description": undefined, 179 | "extensions": { 180 | "category": "fontSizes", 181 | "condition": "base", 182 | "pixelValue": "20px", 183 | "prop": "xl", 184 | "var": "--font-sizes-xl", 185 | "varRef": "var(--font-sizes-xl)", 186 | }, 187 | "name": "fontSizes.xl", 188 | "originalValue": "1.25rem", 189 | "path": [ 190 | "fontSizes", 191 | "xl", 192 | ], 193 | "type": "fontSize", 194 | "value": "1.25rem", 195 | } 196 | `) 197 | }) 198 | 199 | test('color: #fff', () => { 200 | const ctx = createContext() 201 | expect(getTokenFromPropValue(ctx, 'color', '#fff')).toMatchInlineSnapshot(` 202 | { 203 | "extensions": { 204 | "kind": "native-color", 205 | "vscodeColor": { 206 | "alpha": 1, 207 | "blue": 1, 208 | "green": 1, 209 | "red": 1, 210 | }, 211 | }, 212 | "name": "#fff", 213 | "path": "colors.#fff", 214 | "type": "color", 215 | "value": "#fff", 216 | } 217 | `) 218 | }) 219 | 220 | test('color: [#fff]', () => { 221 | const ctx = createContext() 222 | expect(getTokenFromPropValue(ctx, 'color', '[#fff]')).toMatchInlineSnapshot(` 223 | { 224 | "extensions": { 225 | "kind": "native-color", 226 | "vscodeColor": { 227 | "alpha": 1, 228 | "blue": 1, 229 | "green": 1, 230 | "red": 1, 231 | }, 232 | }, 233 | "name": "#fff", 234 | "path": "colors.#fff", 235 | "type": "color", 236 | "value": "#fff", 237 | } 238 | `) 239 | }) 240 | 241 | test('width: xs', () => { 242 | const ctx = createContext() 243 | expect(getTokenFromPropValue(ctx, 'width', 'xs')).toMatchInlineSnapshot(` 244 | _Token { 245 | "description": undefined, 246 | "extensions": { 247 | "category": "sizes", 248 | "condition": "base", 249 | "pixelValue": "320px", 250 | "prop": "xs", 251 | "var": "--sizes-xs", 252 | "varRef": "var(--sizes-xs)", 253 | }, 254 | "name": "sizes.xs", 255 | "originalValue": "20rem", 256 | "path": [ 257 | "sizes", 258 | "xs", 259 | ], 260 | "type": undefined, 261 | "value": "20rem", 262 | } 263 | `) 264 | }) 265 | 266 | test('color: green', () => { 267 | const ctx = createContext() 268 | expect(getTokenFromPropValue(ctx, 'color', 'green')).toMatchInlineSnapshot(` 269 | { 270 | "extensions": { 271 | "kind": "native-color", 272 | "vscodeColor": { 273 | "alpha": 1, 274 | "blue": 0, 275 | "green": 0.5019607843137255, 276 | "red": 0, 277 | }, 278 | }, 279 | "name": "green", 280 | "path": "colors.green", 281 | "type": "color", 282 | "value": "green", 283 | } 284 | `) 285 | }) 286 | 287 | test('color: blue.300', () => { 288 | const ctx = createContext() 289 | expect(getTokenFromPropValue(ctx, 'color', 'blue.300')).toMatchInlineSnapshot(` 290 | _Token { 291 | "description": undefined, 292 | "extensions": { 293 | "category": "colors", 294 | "colorPalette": "blue", 295 | "colorPaletteRoots": [ 296 | [ 297 | "blue", 298 | ], 299 | ], 300 | "colorPaletteTokenKeys": [ 301 | [ 302 | "300", 303 | ], 304 | ], 305 | "condition": "base", 306 | "kind": "color", 307 | "prop": "blue.300", 308 | "var": "--colors-blue-300", 309 | "varRef": "var(--colors-blue-300)", 310 | "vscodeColor": { 311 | "alpha": 1, 312 | "blue": 0.9921568627450981, 313 | "green": 0.7725490196078432, 314 | "red": 0.5764705882352941, 315 | }, 316 | }, 317 | "name": "colors.blue.300", 318 | "originalValue": "#93c5fd", 319 | "path": [ 320 | "colors", 321 | "blue", 322 | "300", 323 | ], 324 | "type": "color", 325 | "value": "#93c5fd", 326 | } 327 | `) 328 | }) 329 | -------------------------------------------------------------------------------- /packages/language-server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /packages/language-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pandacss/language-server", 3 | "displayName": "Panda CSS", 4 | "description": "🐼 The official Panda language server", 5 | "version": "0.19.0", 6 | "main": "dist/index.js", 7 | "repository": { 8 | "url": "https://github.com/chakra-ui/panda-vscode", 9 | "directory": "packages/language-server" 10 | }, 11 | "scripts": { 12 | "build": "tsup", 13 | "dev": "tsup --watch", 14 | "clean": "rimraf dist", 15 | "typecheck": "tsc --noEmit", 16 | "test": "vitest" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "files": [ 22 | "dist" 23 | ], 24 | "devDependencies": { 25 | "@pandacss/extension-shared": "workspace:*", 26 | "@types/base-64": "^1.0.2", 27 | "@types/node": "20.11.30", 28 | "@types/prettier": "3.0.0", 29 | "@types/utf8": "^3.0.3", 30 | "base-64": "^1.0.0", 31 | "color2k": "^2.0.3", 32 | "lil-fp": "1.4.5", 33 | "postcss": "^8.4.36", 34 | "prettier": "^3.2.5", 35 | "satori": "^0.10.13", 36 | "ts-morph": "21.0.1", 37 | "ts-pattern": "5.0.8", 38 | "tsup": "8.0.2", 39 | "typescript": "^5.4.2", 40 | "utf8": "^3.0.0", 41 | "vitest": "^1.4.0", 42 | "vscode-languageserver": "^9.0.1", 43 | "vscode-languageserver-textdocument": "^1.0.11", 44 | "vscode-uri": "^3.0.8" 45 | }, 46 | "dependencies": { 47 | "@pandacss/core": "^0.37.2", 48 | "@pandacss/extractor": "^0.37.2", 49 | "@pandacss/node": "^0.37.2", 50 | "@pandacss/parser": "^0.37.2", 51 | "@pandacss/shared": "^0.37.2", 52 | "@pandacss/token-dictionary": "^0.37.2", 53 | "@pandacss/types": "^0.37.2", 54 | "fast-glob": "^3.3.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/language-server/src/builder-resolver.ts: -------------------------------------------------------------------------------- 1 | import { Builder, PandaContext } from '@pandacss/node' 2 | import path from 'path' 3 | 4 | /** 5 | * - Calls builder.setup() for the closest config file to the given filepath 6 | * - Keep track of all the config files and their builders 7 | * - Share the setup promise so it can be awaited anywhere 8 | */ 9 | export class BuilderResolver { 10 | private builderByConfigDirpath = new Map() 11 | private synchronizingByConfigDirpath = new Map | false>() 12 | 13 | private configDirpathList = new Set() 14 | private configDirPathByFilepath = new Map() 15 | private configPathByDirpath = new Map() 16 | 17 | constructor(private onSetup: (args: { configPath: string; builder: Builder }) => void) {} 18 | 19 | findConfigDirpath(filepath: string, onFound: (configDirPath: string, configPath: string) => T) { 20 | const cachedDir = this.configDirPathByFilepath.get(filepath) 21 | if (cachedDir) { 22 | return onFound(cachedDir, this.configPathByDirpath.get(cachedDir)!) 23 | } 24 | 25 | const dirpath = path.dirname(filepath) 26 | 27 | for (const configDirpath of this.configDirpathList) { 28 | if (dirpath.startsWith(configDirpath)) { 29 | this.configDirPathByFilepath.set(filepath, configDirpath) 30 | return onFound(configDirpath, this.configPathByDirpath.get(configDirpath)!) 31 | } 32 | } 33 | } 34 | 35 | get(filepath: string) { 36 | return this.findConfigDirpath(filepath, (configDirpath) => this.builderByConfigDirpath.get(configDirpath)) 37 | } 38 | 39 | isContextSynchronizing(filepath: string) { 40 | return this.findConfigDirpath(filepath, (configDirpath) => this.synchronizingByConfigDirpath.get(configDirpath)) 41 | } 42 | 43 | create(configPath: string) { 44 | const builder = new Builder() 45 | 46 | const dirpath = path.dirname(configPath) 47 | this.configDirPathByFilepath.set(configPath, dirpath) 48 | this.builderByConfigDirpath.set(dirpath, builder) 49 | this.configDirpathList.add(dirpath) 50 | this.configPathByDirpath.set(dirpath, configPath) 51 | 52 | return this 53 | } 54 | 55 | async setup(filepath: string) { 56 | return this.findConfigDirpath(filepath, async (configDirpath, configPath) => { 57 | const builder = this.builderByConfigDirpath.get(configDirpath) 58 | if (!builder) { 59 | return 60 | } 61 | 62 | const current = this.synchronizingByConfigDirpath.get(configDirpath) 63 | if (current) { 64 | return current 65 | } 66 | 67 | const synchronizing = builder.setup({ configPath }) 68 | this.synchronizingByConfigDirpath.set(configDirpath, synchronizing) 69 | 70 | synchronizing.finally(() => { 71 | this.synchronizingByConfigDirpath.set(configDirpath, false) 72 | this.onSetup({ configPath, builder }) 73 | }) 74 | 75 | return synchronizing 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/language-server/src/capabilities.ts: -------------------------------------------------------------------------------- 1 | import { type ServerCapabilities, TextDocumentSyncKind } from 'vscode-languageserver' 2 | 3 | const TRIGGER_CHARACTERS = [ 4 | // class attributes 5 | '"', 6 | "'", 7 | '`', 8 | // token fn in strings 9 | '(', 10 | // token fn paths 11 | '.', 12 | // curly token reference in string 13 | '{', 14 | ] 15 | 16 | export const getDefaultCapabilities = (): ServerCapabilities => ({ 17 | textDocumentSync: TextDocumentSyncKind.Incremental, 18 | inlayHintProvider: { 19 | resolveProvider: false, 20 | }, 21 | workspace: { 22 | workspaceFolders: { 23 | supported: true, 24 | changeNotifications: true, 25 | }, 26 | }, 27 | 28 | completionProvider: { 29 | resolveProvider: true, 30 | // triggerCharacters: ['.', ':', '<', '"', "'", '/', '@', '*'], 31 | triggerCharacters: TRIGGER_CHARACTERS, 32 | 33 | completionItem: { 34 | labelDetailsSupport: true, 35 | }, 36 | }, 37 | definitionProvider: false, 38 | hoverProvider: true, 39 | colorProvider: true, 40 | inlineValueProvider: true, 41 | }) 42 | -------------------------------------------------------------------------------- /packages/language-server/src/completion-provider.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItem, CompletionItemKind, Position } from 'vscode-languageserver' 2 | import { TextDocument } from 'vscode-languageserver-textdocument' 3 | 4 | import { type PandaVSCodeSettings } from '@pandacss/extension-shared' 5 | import { BoxNodeLiteral, box } from '@pandacss/extractor' 6 | import { type PandaContext } from '@pandacss/node' 7 | import { traverse } from '@pandacss/shared' 8 | import { type Token } from '@pandacss/token-dictionary' 9 | import { getReferences, hasCurlyReference, hasTokenFnReference } from './tokens/expand-token-fn' 10 | import { makeColorTile, makeTable } from './tokens/render-markdown' 11 | import { getSortText } from './tokens/sort-text' 12 | import { getMarkdownCss, printTokenValue } from './tokens/utils' 13 | import type { GetContext } from './panda-language-server' 14 | import type { ProjectHelper } from './project-helper' 15 | import type { TokenFinder } from './token-finder' 16 | 17 | export class CompletionProvider { 18 | constructor( 19 | private getContext: GetContext, 20 | private getPandaSettings: () => Promise, 21 | private project: ProjectHelper, 22 | private tokenFinder: TokenFinder, 23 | ) {} 24 | 25 | async getClosestCompletionList(doc: TextDocument, position: Position) { 26 | const ctx = this.getContext() 27 | if (!ctx) return 28 | 29 | const match = this.project.getNodeAtPosition(doc, position) 30 | if (!match) return 31 | 32 | const settings = await this.getPandaSettings() 33 | const { node, stack } = match 34 | 35 | try { 36 | return this.tokenFinder.findClosestToken(node, stack, ({ propName, propNode, shorthand }) => { 37 | if (!box.isLiteral(propNode)) return undefined 38 | return getCompletionFor({ ctx, propName, propValue: propNode.value, settings, shorthand }) 39 | }) 40 | } catch (err) { 41 | console.error(err) 42 | console.trace() 43 | return 44 | } 45 | } 46 | 47 | async getCompletionDetails(item: CompletionItem) { 48 | const ctx = this.getContext() 49 | if (!ctx) return 50 | 51 | const settings = await this.getPandaSettings() 52 | const { propName, token, shorthand } = (item.data ?? {}) as { propName: string; token?: Token; shorthand: string } 53 | if (!token) return 54 | const markdownCss = await getMarkdownCss(ctx, { [propName]: token.value }, settings) 55 | 56 | const markdown = [markdownCss.withCss] 57 | if (shorthand !== propName) { 58 | markdown.push(`\`${shorthand}\` is shorthand for \`${propName}\``) 59 | } 60 | 61 | const conditions = token.extensions.conditions ?? { base: token.value } 62 | if (conditions) { 63 | const separator = '[___]' 64 | const table = [{ color: ' ', theme: 'Condition', value: 'Value' }] 65 | 66 | const tab = '    ' 67 | traverse( 68 | conditions, 69 | ({ key: cond, value, depth }) => { 70 | if (!ctx.conditions.get(cond) && cond !== 'base') return 71 | 72 | const indent = depth > 0 ? tab.repeat(depth) + '├ ' : '' 73 | 74 | if (typeof value === 'object') { 75 | table.push({ 76 | color: '', 77 | theme: `${indent}**${cond}**`, 78 | value: '─────', 79 | }) 80 | return 81 | } 82 | 83 | const [tokenRef] = ctx.tokens.getReferences(value) 84 | const color = tokenRef?.value ?? value 85 | if (!color) return 86 | 87 | table.push({ 88 | color: makeColorTile(color), 89 | theme: `${indent}**${cond}**`, 90 | value: `\`${color}\``, 91 | }) 92 | }, 93 | { separator }, 94 | ) 95 | 96 | markdown.push(makeTable(table)) 97 | markdown.push(`\n${tab}`) 98 | } 99 | 100 | item.documentation = { kind: 'markdown', value: markdown.join('\n') } 101 | } 102 | } 103 | 104 | export const getCompletionFor = ({ 105 | ctx, 106 | propName, 107 | shorthand, 108 | propValue, 109 | settings, 110 | }: { 111 | ctx: PandaContext 112 | propName: string 113 | shorthand?: string 114 | propValue: BoxNodeLiteral['value'] 115 | settings: PandaVSCodeSettings 116 | }) => { 117 | let str = String(propValue) 118 | let category: string | undefined 119 | 120 | // also provide completion in string such as: token('colors.blue.300') 121 | if (settings['completions.token-fn.enabled'] && (hasTokenFnReference(str) || hasCurlyReference(str))) { 122 | const matches = getReferences(str) 123 | const tokenPath = matches[0] ?? '' 124 | const split = tokenPath.split('.').filter(Boolean) 125 | 126 | // provide completion for token category when token() is empty or partial 127 | if (split.length < 1) { 128 | return Array.from(ctx.tokens.view.categoryMap.keys()).map((category) => { 129 | return { 130 | label: category, 131 | kind: CompletionItemKind.EnumMember, 132 | sortText: '-' + category, 133 | preselect: true, 134 | } as CompletionItem 135 | }) 136 | } 137 | 138 | str = tokenPath.split('.').slice(1).join('.') 139 | category = split[0] 140 | } 141 | 142 | // token(colors.red.300) -> category = "colors" 143 | // color="red.300" -> no category, need to find it 144 | let propValues: Record | undefined 145 | if (!category) { 146 | const utility = ctx.config.utilities?.[propName] 147 | if (!utility?.values) return 148 | 149 | // values: "spacing" 150 | if (typeof utility?.values === 'string') { 151 | category = utility.values 152 | } else if (typeof utility.values === 'function') { 153 | // values: (theme) => { ...theme("spacing") } 154 | const record = ctx.utility.getPropertyValues(utility, (key) => { 155 | return `types:Tokens["${key}"]` 156 | }) 157 | if (record) { 158 | if (record.type) { 159 | category = record.type as string 160 | } else { 161 | const newRecord: Record = {} 162 | // itterate through record keys and extract sub values if they are present 163 | Object.entries(record as Record).forEach(([name, value]) => { 164 | if (typeof value === 'string') { 165 | newRecord[name] = value 166 | return 167 | } 168 | // flatten token 169 | Object.entries(value as Record).forEach(([subName, subValue]) => { 170 | newRecord[subName] = subValue 171 | }) 172 | return 173 | }) 174 | propValues = newRecord 175 | } 176 | } 177 | } 178 | } 179 | 180 | // values: { "1": "1px", "2": "2px", ... } 181 | if (propValues) { 182 | const items = [] as CompletionItem[] 183 | Object.entries(propValues).map(([name, value]) => { 184 | // margin: "2" -> ['var(--spacing-2)', 'var(--spacing-12)', 'var(--spacing-20)', ...] 185 | if (str && !name.includes(str)) return 186 | 187 | const tokenPath = matchVar(value ?? '')?.replace('-', '.') 188 | const token = tokenPath && ctx.tokens.getByName(tokenPath) 189 | 190 | items.push({ 191 | data: { propName, token, shorthand }, 192 | label: name, 193 | kind: CompletionItemKind.EnumMember, 194 | sortText: '-' + getSortText(name), 195 | preselect: false, 196 | }) 197 | }) 198 | 199 | return items 200 | } 201 | 202 | if (!category) return [] 203 | 204 | const categoryValues = ctx.tokens.view.categoryMap.get(category as any) 205 | if (!categoryValues) return [] 206 | 207 | const items = [] as CompletionItem[] 208 | categoryValues.forEach((token, name) => { 209 | if (str && !name.includes(str)) return 210 | 211 | const isColor = token.extensions.category === 'colors' 212 | 213 | const completionItem = { 214 | data: { propName, token, shorthand }, 215 | label: name, 216 | kind: isColor ? CompletionItemKind.Color : CompletionItemKind.EnumMember, 217 | labelDetails: { description: printTokenValue(token, settings), detail: ` ${token.extensions.varRef}` }, 218 | sortText: '-' + getSortText(name), 219 | preselect: false, 220 | } as CompletionItem 221 | 222 | if (isColor) { 223 | completionItem.detail = token.value 224 | // TODO rgb conversion ? 225 | } 226 | 227 | items.push(completionItem) 228 | }) 229 | 230 | return items 231 | } 232 | const cssVarRegex = /var\(--([\w-.]+)\)/g 233 | const matchVar = (str: string) => { 234 | const match = cssVarRegex.exec(str) 235 | return match ? match[1] : null 236 | } 237 | -------------------------------------------------------------------------------- /packages/language-server/src/deferred.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deferred promise implementation\ 3 | * Allow resolving a promise later 4 | * 5 | * @example 6 | * ```ts 7 | * 8 | const deferred = new Deferred(); 9 | console.log("waiting 2 seconds..."); 10 | setTimeout(() => { 11 | deferred.resolve("whoa!"); 12 | }, 2000); 13 | 14 | async function someAsyncFunction() { 15 | const value = await deferred; 16 | console.log(value); 17 | } 18 | 19 | someAsyncFunction(); 20 | // "waiting 2 seconds..." 21 | // "whoa!" 22 | ``` 23 | */ 24 | export class Deferred { 25 | private _resolve!: (value: T | PromiseLike) => void 26 | private _reject!: (reason?: any) => void 27 | public promise: Promise 28 | 29 | public then: Promise['then'] 30 | public catch: Promise['catch'] 31 | public finally: Promise['finally'] 32 | 33 | constructor() { 34 | this.promise = new Promise((resolve, reject) => { 35 | this._resolve = resolve 36 | this._reject = reject 37 | }) 38 | 39 | this.then = this.promise.then.bind(this.promise) 40 | this.catch = this.promise.catch.bind(this.promise) 41 | this.finally = this.promise.finally.bind(this.promise) 42 | } 43 | 44 | resolve(value: T | PromiseLike): void { 45 | this._resolve(value) 46 | } 47 | 48 | reject(reason?: any): void { 49 | this._reject(reason) 50 | } 51 | 52 | get [Symbol.toStringTag](): string { 53 | return this.constructor.name 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/language-server/src/extractor.ts: -------------------------------------------------------------------------------- 1 | import { type SystemStyleObject } from '@pandacss/types' 2 | import { Identifier, Node } from 'ts-morph' 3 | 4 | import { 5 | BoxNodeArray, 6 | BoxNodeLiteral, 7 | BoxNodeMap, 8 | BoxNodeObject, 9 | box, 10 | findIdentifierValueDeclaration, 11 | type BoxContext, 12 | } from '@pandacss/extractor' 13 | import { Bool } from 'lil-fp' 14 | 15 | export type BoxNodeWithValue = BoxNodeObject | BoxNodeLiteral | BoxNodeMap | BoxNodeArray 16 | export type ExtractableFnName = (typeof extractableFns)[number] 17 | 18 | const extractableFns = ['css', 'cx'] as const 19 | const canEvalFn = (name: string): name is ExtractableFnName => extractableFns.includes(name as any) 20 | 21 | const mergeCx = (...args: any[]) => 22 | args.filter(Boolean).reduce((acc, curr) => { 23 | if (typeof curr === 'object') return Object.assign(acc, curr) 24 | 25 | return acc 26 | }, {}) 27 | 28 | const isFunctionMadeFromDefineParts = (expr: Identifier) => { 29 | const declaration = findIdentifierValueDeclaration(expr, [], boxCtx) 30 | if (!Node.isVariableDeclaration(declaration)) return 31 | 32 | const initializer = declaration.getInitializer() 33 | if (!Node.isCallExpression(initializer)) return 34 | 35 | const fromFunctionName = initializer.getExpression().getText() 36 | return fromFunctionName === 'defineParts' 37 | } 38 | 39 | const boxCtx: BoxContext = { 40 | flags: { skipTraverseFiles: true }, 41 | getEvaluateOptions: (node) => { 42 | if (!Node.isCallExpression(node)) return 43 | const expr = node.getExpression() 44 | 45 | if (!Node.isIdentifier(expr)) return 46 | const name = expr.getText() 47 | 48 | // TODO - check for import alias ? kinda overkill for now 49 | if (!canEvalFn(name as string) && !isFunctionMadeFromDefineParts(expr)) { 50 | return 51 | } 52 | 53 | return { 54 | environment: { 55 | extra: { 56 | cx: mergeCx, 57 | css: (styles: SystemStyleObject) => styles, 58 | }, 59 | }, 60 | } as any 61 | }, 62 | } 63 | 64 | const isNodeJsx = Bool.or(Node.isJsxSelfClosingElement, Node.isJsxOpeningElement) 65 | const getNestedBoxProp = (map: BoxNodeMap, path: string[]) => { 66 | return path.reduce((acc, curr) => { 67 | if (box.isMap(acc)) return acc.value.get(curr) 68 | if (box.isObject(acc)) return acc.value[curr] 69 | 70 | return acc 71 | }, map as any) 72 | } 73 | 74 | export const extractor = { 75 | canEvalFn, 76 | mergeCx, 77 | boxCtx, 78 | isNodeJsx, 79 | getNestedBoxProp, 80 | } 81 | -------------------------------------------------------------------------------- /packages/language-server/src/features/color-hints.ts: -------------------------------------------------------------------------------- 1 | import { ColorInformation } from 'vscode-languageserver' 2 | import { color2kToVsCodeColor } from '../tokens/color2k-to-vscode-color' 3 | import { tryCatch } from 'lil-fp/func' 4 | import { onError } from '../tokens/error' 5 | import type { PandaLanguageServer } from '../panda-language-server' 6 | 7 | export function registerColorHints(lsp: PandaLanguageServer) { 8 | lsp.log('🐼 Registering color hints') 9 | lsp.connection.onDocumentColor( 10 | tryCatch(async (params) => { 11 | const settings = await lsp.getPandaSettings() 12 | if (!settings['color-hints.enabled']) return 13 | 14 | await lsp.isReady('🐼 onDocumentColor') 15 | 16 | const doc = lsp.documents.get(params.textDocument.uri) 17 | if (!doc) { 18 | return [] 19 | } 20 | 21 | const ctx = lsp.getContext() 22 | if (!ctx) return [] 23 | 24 | const parserResult = lsp.project.parseSourceFile(doc) 25 | if (!parserResult) { 26 | return [] 27 | } 28 | 29 | const colors: ColorInformation[] = [] 30 | 31 | lsp.tokenFinder.getFileTokens(parserResult, (match) => { 32 | const isColor = match.kind === 'token' && match.token.extensions?.vscodeColor 33 | if (!isColor) return 34 | 35 | // Add 1 color hint for each root condition 36 | if (match.token.extensions.conditions) { 37 | if (settings['color-hints.semantic-tokens.enabled']) { 38 | Object.entries(match.token.extensions.conditions).forEach(([cond, value]) => { 39 | if (!ctx.conditions.get(cond) && cond !== 'base') return 40 | const [tokenRef] = ctx.tokens.getReferences(value) 41 | if (!tokenRef) return 42 | 43 | const color = color2kToVsCodeColor(tokenRef.value) 44 | if (!color) return 45 | 46 | colors.push({ color, range: match.range as any }) 47 | }) 48 | } 49 | 50 | return 51 | } 52 | 53 | colors.push({ 54 | color: match.token.extensions.vscodeColor, 55 | range: match.range as any, 56 | }) 57 | }) 58 | 59 | return colors 60 | }, onError), 61 | ) 62 | 63 | lsp.connection.onColorPresentation(() => { 64 | return [] 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /packages/language-server/src/features/completion.ts: -------------------------------------------------------------------------------- 1 | import { tryCatch } from 'lil-fp/func' 2 | import { onError } from '../tokens/error' 3 | import type { PandaLanguageServer } from '../panda-language-server' 4 | 5 | export function registerCompletion(lsp: PandaLanguageServer) { 6 | lsp.log('🐼 Registering completion') 7 | // This handler provides the initial list of the completion items. 8 | lsp.connection.onCompletion( 9 | tryCatch(async (params) => { 10 | const isEnabled = await lsp.getPandaSettings('completions.enabled') 11 | if (!isEnabled) return 12 | 13 | await lsp.isReady('✅ onCompletion') 14 | 15 | const doc = lsp.documents.get(params.textDocument.uri) 16 | if (!doc) { 17 | return 18 | } 19 | 20 | // TODO recipe 21 | const matches = await lsp.completions.getClosestCompletionList(doc, params.position) 22 | if (!matches?.length) { 23 | return 24 | } 25 | 26 | return matches 27 | }, onError), 28 | ) 29 | 30 | // This handler resolves additional information for the item selected in the completion list. 31 | lsp.connection.onCompletionResolve(async (item) => { 32 | await lsp.completions.getCompletionDetails(item) 33 | return item 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /packages/language-server/src/features/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver' 2 | import { TextDocument } from 'vscode-languageserver-textdocument' 3 | import { tryCatch } from 'lil-fp/func' 4 | import { onError } from '../tokens/error' 5 | import type { PandaLanguageServer } from '../panda-language-server' 6 | 7 | export function registerDiagnostics(lsp: PandaLanguageServer) { 8 | lsp.log('🐼 Registering diagnostics') 9 | const updateDocumentDiagnostics = tryCatch(async function (doc: TextDocument) { 10 | const settings = await lsp.getPandaSettings() 11 | 12 | if (!settings['diagnostics.enabled']) { 13 | // this allows us to clear diagnostics 14 | return lsp.connection.sendDiagnostics({ 15 | uri: doc.uri, 16 | version: doc.version, 17 | diagnostics: [], 18 | }) 19 | } 20 | 21 | lsp.log(`Update diagnostics for ${doc.uri}`) 22 | 23 | const diagnostics: Diagnostic[] = [] 24 | const parserResult = lsp.project.parseSourceFile(doc) 25 | 26 | if (!parserResult) { 27 | // this allows us to clear diagnostics 28 | return lsp.connection.sendDiagnostics({ 29 | uri: doc.uri, 30 | version: doc.version, 31 | diagnostics: [], 32 | }) 33 | } 34 | 35 | lsp.tokenFinder.getFileTokens(parserResult, (match) => { 36 | if ( 37 | match.kind === 'token' && 38 | match.token.extensions.kind === 'invalid-token-path' && 39 | settings['diagnostics.invalid-token-path'] 40 | ) { 41 | diagnostics.push({ 42 | message: `🐼 Invalid token path`, 43 | range: match.range, 44 | severity: DiagnosticSeverity.Error, 45 | code: match.token.name, 46 | }) 47 | } 48 | }) 49 | 50 | lsp.connection.sendDiagnostics({ 51 | uri: doc.uri, 52 | version: doc.version, 53 | diagnostics, 54 | }) 55 | }, onError) 56 | 57 | return updateDocumentDiagnostics 58 | } 59 | -------------------------------------------------------------------------------- /packages/language-server/src/features/hover.ts: -------------------------------------------------------------------------------- 1 | import { getMarkdownCss, nodeRangeToVsCodeRange, printTokenValue } from '../tokens/utils' 2 | import { renderTokenColorPreview } from '../tokens/render-token-color-preview' 3 | import { stringify } from '@pandacss/core' 4 | import { tryCatch } from 'lil-fp/func' 5 | import { onError } from '../tokens/error' 6 | import type { PandaLanguageServer } from '../panda-language-server' 7 | 8 | export function registerHover(lsp: PandaLanguageServer) { 9 | lsp.log('🐼 Registering hover') 10 | lsp.connection.onHover( 11 | tryCatch(async (params) => { 12 | const settings = await lsp.getPandaSettings() 13 | if (!settings['hovers.enabled']) return 14 | 15 | await lsp.isReady('🐼 onHover') 16 | 17 | const ctx = lsp.getContext() 18 | if (!ctx) return 19 | 20 | const doc = lsp.documents.get(params.textDocument.uri) 21 | if (!doc) { 22 | return 23 | } 24 | 25 | if (settings['hovers.tokens.enabled']) { 26 | // TODO recipe 27 | const tokenMatch = lsp.tokenFinder.getClosestToken(doc, params.position) 28 | if (tokenMatch) { 29 | if (tokenMatch.kind === 'token') { 30 | const { token } = tokenMatch 31 | 32 | const contents = [printTokenValue(token, settings)] as any[] 33 | if (settings['hovers.tokens.css-preview.enabled']) { 34 | const css = (await getMarkdownCss(ctx, { [tokenMatch.propName]: token.value }, settings)).raw 35 | contents.push({ language: 'css', value: css }) 36 | } 37 | 38 | const category = token.extensions.category 39 | 40 | if (category === 'colors' && settings['hovers.semantic-colors.enabled']) { 41 | const preview = await renderTokenColorPreview(ctx, token) 42 | if (preview) { 43 | contents.push(preview) 44 | } 45 | } 46 | 47 | if (category === 'animations' && token.extensions.prop) { 48 | const sheet = ctx.createSheet() 49 | ctx.appendCssOfType('keyframes', sheet) 50 | const keyframes = ctx.config.theme?.keyframes?.[token.extensions.prop] 51 | if (keyframes) { 52 | const keyframeCss = stringify(sheet.serialize({ ['@keyframes ' + token.extensions.prop]: keyframes })) 53 | contents.push({ language: 'css', value: keyframeCss }) 54 | } 55 | } 56 | 57 | return { contents, range: tokenMatch.range } 58 | } 59 | 60 | if (tokenMatch.kind === 'condition' && settings['hovers.conditions.enabled']) { 61 | const { condition, propValue, propName } = tokenMatch 62 | const css = (await getMarkdownCss(ctx, { [propName]: propValue }, settings)).raw 63 | 64 | return { 65 | contents: [`🐼 \`${condition.value}\``, { language: 'css', value: css }], 66 | range: tokenMatch.range, 67 | } 68 | } 69 | } 70 | } 71 | 72 | if (settings['hovers.instances.enabled']) { 73 | const instanceMatch = lsp.tokenFinder.getClosestInstance(doc, params.position) 74 | if (instanceMatch && instanceMatch.kind === 'styles') { 75 | const range = nodeRangeToVsCodeRange(instanceMatch.props.getRange()) 76 | return { contents: (await getMarkdownCss(ctx, instanceMatch.styles, settings)).withCss, range } 77 | } 78 | } 79 | }, onError), 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /packages/language-server/src/features/inlay-hints.ts: -------------------------------------------------------------------------------- 1 | import { InlayHint, InlayHintKind, type InlayHintParams } from 'vscode-languageserver' 2 | import { printTokenValue } from '../tokens/utils' 3 | import { tryCatch } from 'lil-fp/func' 4 | import { onError } from '../tokens/error' 5 | import type { PandaLanguageServer } from '../panda-language-server' 6 | 7 | export function registerInlayHints(lsp: PandaLanguageServer) { 8 | lsp.log('🐼 Registering inlay hints') 9 | lsp.connection.languages.inlayHint.on( 10 | tryCatch(async (params: InlayHintParams) => { 11 | const settings = await lsp.getPandaSettings() 12 | if (!settings['inlay-hints.enabled']) return 13 | 14 | await lsp.isReady('🐼 inlay hints') 15 | 16 | // await when the server starts, then just get the context 17 | if (!lsp.getContext()) { 18 | await lsp.loadPandaContext(params.textDocument.uri) 19 | } 20 | 21 | const doc = lsp.documents.get(params.textDocument.uri) 22 | if (!doc) { 23 | return [] 24 | } 25 | 26 | const parserResult = lsp.project.parseSourceFile(doc) 27 | if (!parserResult) return 28 | 29 | const inlayHints = [] as InlayHint[] 30 | 31 | lsp.tokenFinder.getFileTokens(parserResult, (match) => { 32 | if ( 33 | match.kind === 'token' && 34 | match.token.extensions.kind !== 'color' && 35 | match.token.extensions.kind !== 'semantic-color' && 36 | match.token.extensions.kind !== 'native-color' && 37 | match.token.extensions.kind !== 'invalid-token-path' 38 | ) { 39 | inlayHints.push({ 40 | position: match.range.end, 41 | label: printTokenValue(match.token, settings), 42 | kind: InlayHintKind.Type, 43 | paddingLeft: true, 44 | }) 45 | } 46 | }) 47 | 48 | return inlayHints 49 | }, onError), 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /packages/language-server/src/index.ts: -------------------------------------------------------------------------------- 1 | import { PandaLanguageServer } from './panda-language-server' 2 | 3 | const debug = false 4 | new PandaLanguageServer(debug) 5 | -------------------------------------------------------------------------------- /packages/language-server/src/panda-language-server.ts: -------------------------------------------------------------------------------- 1 | import { defaultSettings, getFlattenedSettings, type PandaVSCodeSettings } from '@pandacss/extension-shared' 2 | import type { Builder, PandaContext } from '@pandacss/node' 3 | import { glob } from 'fast-glob' 4 | import { TextDocument } from 'vscode-languageserver-textdocument' 5 | import { 6 | DidChangeConfigurationNotification, 7 | ProposedFeatures, 8 | TextDocuments, 9 | createConnection, 10 | type Connection, 11 | type TextDocumentChangeEvent, 12 | } from 'vscode-languageserver/node' 13 | import { BuilderResolver } from './builder-resolver' 14 | import { getDefaultCapabilities } from './capabilities' 15 | import { uriToPath } from './uri-to-path' 16 | import { ProjectHelper } from './project-helper' 17 | import { CompletionProvider } from './completion-provider' 18 | import { TokenFinder } from './token-finder' 19 | import { registerColorHints } from './features/color-hints' 20 | import { registerCompletion } from './features/completion' 21 | import { registerDiagnostics } from './features/diagnostics' 22 | import { registerHover } from './features/hover' 23 | import { registerInlayHints } from './features/inlay-hints' 24 | import { Deferred } from './deferred' 25 | 26 | export type GetContext = () => Builder['context'] | undefined 27 | interface InitializationOptions { 28 | activeDocumentFilepath: string | undefined 29 | settings: PandaVSCodeSettings 30 | } 31 | 32 | export class PandaLanguageServer { 33 | connection: Connection 34 | documents: TextDocuments 35 | /** 36 | * wait for the extension to have resolved all the workspaces configs before doing anything 37 | */ 38 | deferred: Deferred 39 | 40 | builderResolver: BuilderResolver 41 | project: ProjectHelper 42 | tokenFinder: TokenFinder 43 | completions: CompletionProvider 44 | /** 45 | * current builder's context, used by most features as we can only be in one context at a time 46 | * depending on the active document 47 | */ 48 | context: Builder['context'] 49 | synchronizing = false as Promise | false 50 | // 51 | settings: PandaVSCodeSettings | undefined 52 | activeDocumentFilepath = '' 53 | hasConfigurationCapability = false 54 | hasWorkspaceFolderCapability = false 55 | // 56 | events = new Map>() 57 | 58 | constructor(private debug: boolean = false) { 59 | // Create a connection for the server, using Node's IPC as a transport. 60 | // Also include all preview / proposed LSP features. 61 | this.connection = createConnection(ProposedFeatures.all) 62 | this.documents = new TextDocuments(TextDocument) 63 | this.deferred = new Deferred() 64 | const getContext = () => this.getContext() 65 | 66 | this.project = new ProjectHelper(getContext) 67 | this.tokenFinder = new TokenFinder(getContext, this.project) 68 | this.completions = new CompletionProvider( 69 | getContext, 70 | this.getPandaSettings.bind(this), 71 | this.project, 72 | this.tokenFinder, 73 | ) 74 | 75 | this.builderResolver = new BuilderResolver(({ configPath, builder }) => { 76 | const ctx = builder.context 77 | if (!ctx) return 78 | 79 | console.log('🐼 Context reloaded for:', configPath) 80 | this.context = ctx 81 | 82 | // sync ts plugin possible completion items 83 | const tokenNames = Array.from(new Set(ctx.tokens.allTokens.map((token) => token.path.slice(1).join('.')))) 84 | this.connection.sendNotification('$/panda-token-names', { configPath, tokenNames }) 85 | }) 86 | 87 | this.registerHandlers() 88 | 89 | this.documents.listen(this.connection) 90 | this.connection.listen() 91 | } 92 | 93 | log(...args: any[]) { 94 | this.debug && this.connection.console.log(args.join(' ')) 95 | } 96 | 97 | /** 98 | * 1 - wait for the extension to have resolved all the workspaces configs before doing anything 99 | * 2 - Check await until tokens are synchronized if a synchronization process is happening. 100 | * 101 | * should be used AFTER checking that the feature is enabled through settings 102 | * and BEFORE getting the current context 103 | */ 104 | async isReady(stepName: string) { 105 | // 1 106 | await this.deferred 107 | 108 | try { 109 | // 2 110 | const isSynchronizing = this.isSynchronizing() 111 | this.log('🤷 isReady', stepName, isSynchronizing) 112 | if (isSynchronizing) { 113 | await this.isSynchronizing() 114 | } 115 | 116 | this.log(stepName) 117 | } catch (error) { 118 | this.connection.console.error('error on step ' + stepName) 119 | this.connection.console.error(error as any) 120 | } 121 | } 122 | 123 | getContext = () => { 124 | return this.context 125 | } 126 | 127 | isSynchronizing() { 128 | return this.synchronizing 129 | } 130 | 131 | async getFreshPandaSettings() { 132 | return getFlattenedSettings((await this.connection.workspace.getConfiguration('panda')) ?? defaultSettings) 133 | } 134 | 135 | /** 136 | * Resolve current extension settings 137 | */ 138 | async getPandaSettings(): Promise 139 | async getPandaSettings(key: Key): Promise 140 | async getPandaSettings(key?: Key) { 141 | const getter = (settings: PandaVSCodeSettings) => { 142 | return key ? settings[key] : settings 143 | } 144 | 145 | if (!this.hasConfigurationCapability) { 146 | return getter(defaultSettings) 147 | } 148 | 149 | if (!this.settings) { 150 | this.settings = await this.getFreshPandaSettings() 151 | } 152 | 153 | return getter(this.settings ?? defaultSettings) 154 | } 155 | 156 | async loadPandaContext(uriOrFilepath: string) { 157 | const filepath = uriToPath(uriOrFilepath) ?? uriOrFilepath 158 | 159 | try { 160 | console.log('🚧 Loading context for:', filepath) 161 | this.synchronizing = this.builderResolver.setup(filepath) 162 | await this.synchronizing 163 | console.log('✅ Loading context done:', filepath) 164 | } catch (err) { 165 | // Ignore 166 | this.synchronizing = false 167 | console.log('❌ Loading context failed!', err) 168 | return 169 | } 170 | 171 | this.synchronizing = false 172 | 173 | const builder = this.builderResolver.get(filepath) 174 | if (!builder || !builder.context) return 175 | 176 | console.log('🐼 Context loaded for:', filepath) 177 | this.context = builder.context 178 | 179 | return this.context 180 | } 181 | 182 | private async setupWorkspaceBuilders(rootPath: string) { 183 | console.log('🐼 Setup workspace builders...') 184 | const configPathList = await glob(`${rootPath}/**/panda.config.{ts,cts,mts,js,cjs,mjs}`, { 185 | cwd: rootPath, 186 | onlyFiles: true, 187 | absolute: true, 188 | ignore: ['**/node_modules/**'], 189 | }) 190 | 191 | await Promise.all( 192 | configPathList.map(async (configPath) => { 193 | try { 194 | console.log('💼 Config setup at:', configPath) 195 | await this.builderResolver.create(configPath).setup(configPath) 196 | console.log('✅ Config setup done:', configPath) 197 | } catch (err) { 198 | // Ignore 199 | console.log('❌ Config setup failed!', configPath, err) 200 | } 201 | }), 202 | ) 203 | console.log('🐼 Workspaces builders ready !') 204 | } 205 | 206 | private getClosestPandaContext(uri: string) { 207 | const filepath = uriToPath(uri) ?? uri 208 | 209 | const builder = this.builderResolver.get(filepath) 210 | if (!builder || !builder.context) return 211 | 212 | console.log('🐼 Found panda context! ✅ for', filepath) 213 | this.context = builder.context 214 | 215 | return this.context 216 | } 217 | 218 | private registerHandlers() { 219 | this.onInitializingConnection() 220 | this.onActiveDocumentChanged() 221 | this.onActivePandaConfigChanged() 222 | this.onDidChangedSettings() 223 | this.onDidChangePandaConfigs() 224 | } 225 | 226 | private onInitializingConnection() { 227 | this.connection.onInitialize((params) => { 228 | this.connection.console.log('🤖 Starting PandaCss LSP...') 229 | 230 | const capabilities = params.capabilities 231 | 232 | const { activeDocumentFilepath, settings } = params.initializationOptions as InitializationOptions 233 | if (activeDocumentFilepath) { 234 | this.activeDocumentFilepath = activeDocumentFilepath 235 | console.log('📄 Init Active document:', activeDocumentFilepath) 236 | } 237 | 238 | if (settings) { 239 | this.settings = settings 240 | } 241 | 242 | const serverCapabilitites = getDefaultCapabilities() 243 | if (!settings['color-hints.enabled']) { 244 | serverCapabilitites.colorProvider = false 245 | } 246 | 247 | if (!settings['inlay-hints.enabled']) { 248 | serverCapabilitites.inlayHintProvider = false 249 | } 250 | 251 | if (!settings['completions.enabled'] && serverCapabilitites.completionProvider) { 252 | serverCapabilitites.completionProvider.resolveProvider = false 253 | } 254 | 255 | if (!settings['hovers.enabled']) { 256 | serverCapabilitites.hoverProvider = false 257 | } 258 | 259 | // Check context support 260 | this.hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration) 261 | this.hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders) 262 | 263 | this.onInitializedConnection() 264 | this.registerFeatures() 265 | 266 | return { capabilities: serverCapabilitites } 267 | }) 268 | } 269 | 270 | private onInitializedConnection() { 271 | this.connection.onInitialized(async () => { 272 | this.connection.console.log('⚡ Connection initialized!') 273 | 274 | if (this.hasConfigurationCapability) { 275 | // Register for all configuration changes. 276 | this.connection.client.register(DidChangeConfigurationNotification.type, undefined) 277 | 278 | this.settings = await this.getFreshPandaSettings() 279 | this.debug = this.settings['debug.enabled'] ?? false 280 | } 281 | 282 | if (this.hasWorkspaceFolderCapability) { 283 | this.connection.workspace.onDidChangeWorkspaceFolders((_event) => 284 | this.connection.console.log('Workspace folder change event received.'), 285 | ) 286 | } 287 | 288 | const workspaceFolders = await this.connection.workspace.getWorkspaceFolders() 289 | const validFolders = workspaceFolders?.map((folder) => uriToPath(folder.uri) || '').filter((path) => !!path) 290 | 291 | console.log('📁 Workspace folders:', validFolders) 292 | await Promise.all(validFolders?.map((folder) => this.setupWorkspaceBuilders(folder)) ?? []) 293 | this.onWorkspacesReady() 294 | }) 295 | } 296 | 297 | private onWorkspacesReady() { 298 | console.log('📁 Workspace folders setup ! ✅') 299 | 300 | this.synchronizing = false 301 | this.deferred.resolve() 302 | 303 | this.connection.sendNotification('$/panda-lsp-ready') 304 | 305 | if (this.activeDocumentFilepath) { 306 | const ctx = this.getClosestPandaContext(this.activeDocumentFilepath) 307 | 308 | if (ctx) { 309 | this.connection.console.log(`🐼 Found panda context! ✅ at ${ctx.conf.path}`) 310 | } 311 | } 312 | } 313 | 314 | private onActiveDocumentChanged() { 315 | this.connection.onNotification('$/panda-active-document-changed', (params) => { 316 | console.log('📄 Active document:', this.activeDocumentFilepath) 317 | this.activeDocumentFilepath = params.activeDocumentFilepath 318 | 319 | const configPath = this.builderResolver.findConfigDirpath( 320 | this.activeDocumentFilepath, 321 | (_, configPath) => configPath, 322 | ) 323 | if (!configPath) return 324 | 325 | this.connection.sendNotification('$/panda-doc-config-path', { 326 | activeDocumentFilepath: this.activeDocumentFilepath, 327 | configPath, 328 | }) 329 | }) 330 | } 331 | 332 | private onActivePandaConfigChanged() { 333 | this.connection.onRequest('$/get-config-path', ({ activeDocumentFilepath }: { activeDocumentFilepath: string }) => { 334 | activeDocumentFilepath ??= this.activeDocumentFilepath 335 | if (!activeDocumentFilepath) return 336 | 337 | return this.builderResolver.findConfigDirpath(activeDocumentFilepath, (_, configPath) => { 338 | console.log('config path', configPath) 339 | return configPath 340 | }) 341 | }) 342 | } 343 | 344 | private onDidChangedSettings() { 345 | this.connection.onDidChangeConfiguration(async (_change) => { 346 | this.connection.sendNotification('$/clear-colors') 347 | this.connection.console.log('⌛ onDidChangeConfiguration') 348 | 349 | if (this.hasConfigurationCapability) { 350 | this.settings = await this.getFreshPandaSettings() 351 | this.debug = this.settings['debug.enabled'] ?? false 352 | console.log('🐼 Settings changed!', this.settings) 353 | } 354 | }) 355 | } 356 | 357 | private onDidChangePandaConfigs() { 358 | this.connection.onDidChangeWatchedFiles(async ({ changes }) => { 359 | const events = this.events.get('onDidChangeWatchedFiles') 360 | events?.forEach((event) => event()) 361 | 362 | changes.forEach(async (fileEvent) => { 363 | const filepath = uriToPath(fileEvent.uri) ?? fileEvent.uri 364 | this.connection.console.log('🔃 Reloading panda context for:' + filepath) 365 | await this.builderResolver.setup(filepath) 366 | }) 367 | }) 368 | } 369 | 370 | onDidChangeContent(onChange: (params: TextDocumentChangeEvent) => void) { 371 | // Update diagnostics on document change 372 | this.documents.onDidChangeContent(async (params) => { 373 | await this.isReady('🐼 diagnostics - onDidChangeContent') 374 | 375 | // await when the server starts, then just get the context 376 | if (!this.getContext()) { 377 | await this.loadPandaContext(params.document.uri) 378 | } 379 | 380 | if (!this.getContext()) return 381 | 382 | onChange(params) 383 | }) 384 | } 385 | 386 | /** Update diagnostics when watched file changes */ 387 | registerDiagnosticsUpdateOnFilesChange(updateDocumentDiagnostics: ReturnType) { 388 | const set = getOrCreateSet(this.events, 'onDidChangeWatchedFiles') 389 | set.add(async () => { 390 | await this.isReady('🐼 diagnostics - onDidChangeWatchedFiles') 391 | 392 | const ctx = this.getContext() 393 | if (!ctx) return 394 | 395 | // Update all opened documents diagnostics 396 | const docs = this.documents.all() 397 | docs.forEach((doc) => updateDocumentDiagnostics(doc)) 398 | }) 399 | } 400 | 401 | private registerFeatures() { 402 | console.log('⚡ Registering features') 403 | try { 404 | const settings = this.settings 405 | if (settings) { 406 | if (settings['color-hints.enabled']) { 407 | registerColorHints(this) 408 | } 409 | 410 | if (settings['inlay-hints.enabled']) { 411 | registerInlayHints(this) 412 | } 413 | 414 | if (settings['completions.enabled']) { 415 | registerCompletion(this) 416 | } 417 | 418 | if (settings['hovers.enabled']) { 419 | registerHover(this) 420 | } 421 | 422 | if (settings['diagnostics.enabled']) { 423 | const updateDocumentDiagnostics = registerDiagnostics(this) 424 | this.onDidChangeContent((params) => updateDocumentDiagnostics(params.document)) 425 | this.registerDiagnosticsUpdateOnFilesChange(updateDocumentDiagnostics) 426 | } 427 | } 428 | } catch (err) { 429 | console.log('❌ Registering features failed!', err) 430 | } 431 | } 432 | } 433 | 434 | function getOrCreateSet(map: Map>, key: TKey) { 435 | let set = map.get(key) 436 | if (!set) { 437 | map.set(key, new Set()) 438 | set = map.get(key)! 439 | } 440 | return set as Set 441 | } 442 | -------------------------------------------------------------------------------- /packages/language-server/src/project-helper.ts: -------------------------------------------------------------------------------- 1 | import { Position } from 'vscode-languageserver' 2 | import { TextDocument } from 'vscode-languageserver-textdocument' 3 | 4 | import { type ParserResultInterface, type ResultItem } from '@pandacss/types' 5 | import { Node, SourceFile, ts } from 'ts-morph' 6 | 7 | import { box, type PrimitiveType } from '@pandacss/extractor' 8 | import { type PandaContext } from '@pandacss/node' 9 | import { type ParserResult } from '@pandacss/parser' 10 | import { walkObject } from '@pandacss/shared' 11 | import { extractor, type BoxNodeWithValue } from './extractor' 12 | 13 | interface RawToken { 14 | propName: string 15 | propValue: PrimitiveType 16 | propNode: BoxNodeWithValue 17 | shorthand: string 18 | } 19 | 20 | export class ProjectHelper { 21 | constructor(private getContext: () => PandaContext | undefined) {} 22 | 23 | getSourceFile(doc: TextDocument) { 24 | const ctx = this.getContext() 25 | if (!ctx) return 26 | 27 | return ctx.project.getSourceFile(doc.uri) as SourceFile | undefined 28 | } 29 | 30 | /** 31 | * Get the local component list of local tokens. 32 | */ 33 | parseSourceFile(doc: TextDocument) { 34 | const ctx = this.getContext() 35 | if (!ctx) return 36 | 37 | const project = ctx.project 38 | 39 | project.addSourceFile(doc.uri, doc.getText()) 40 | return project.parseSourceFile(doc.uri) as ParserResult 41 | } 42 | 43 | getNodeAtPosition = (doc: TextDocument, position: Position) => { 44 | const ctx = this.getContext() 45 | if (!ctx) return 46 | 47 | const sourceFile = this.getSourceFile(doc) 48 | if (!sourceFile) return 49 | 50 | const charIndex = ts.getPositionOfLineAndCharacter(sourceFile.compilerNode, position.line, position.character) 51 | return getDescendantAtPos(sourceFile, charIndex) 52 | } 53 | 54 | /** 55 | * Get all the tokens from the document and invoke a callback on it. 56 | */ 57 | getFileTokens(parserResult: ParserResultInterface, onRawToken: (token: RawToken) => void) { 58 | const ctx = this.getContext() 59 | if (!ctx) return 60 | 61 | const onResult = (result: ResultItem) => { 62 | const boxNode = result.box 63 | if (!boxNode) return 64 | if (box.isLiteral(boxNode)) return 65 | 66 | result.data.forEach((styles) => { 67 | const keys = Object.keys(styles) 68 | if (!keys.length) return 69 | 70 | walkObject(styles, (value, paths) => { 71 | // if value doesn't exist 72 | if (value == null) return 73 | 74 | const [prop, ..._allConditions] = ctx.conditions.shift(paths) 75 | const propNode = box.isArray(boxNode) 76 | ? boxNode.value.find((node) => box.isMap(node) && extractor.getNestedBoxProp(node, paths)) 77 | : extractor.getNestedBoxProp(boxNode, paths) 78 | if (!box.isLiteral(propNode) || !prop) return 79 | 80 | const propName = ctx.utility.resolveShorthand(prop) 81 | onRawToken({ propName, propValue: value, propNode, shorthand: prop }) 82 | }) 83 | }) 84 | } 85 | 86 | parserResult.css.forEach(onResult) 87 | parserResult.jsx.forEach(onResult) 88 | parserResult.cva.forEach((item) => { 89 | const map = item.box 90 | if (!box.isMap(map)) return 91 | return item.data.forEach(({ base }) => 92 | onResult(Object.assign({}, item, { box: map.value.get('base'), data: [base] })), 93 | ) 94 | }) 95 | parserResult.sva.forEach((item) => { 96 | const map = item.box 97 | if (!box.isMap(map)) return 98 | return item.data.forEach(({ base }) => 99 | onResult(Object.assign({}, item, { box: map.value.get('base'), data: [base] })), 100 | ) 101 | }) 102 | parserResult.pattern.forEach((set, name) => { 103 | set.forEach((item) => { 104 | const map = item.box 105 | if (!box.isMap(map)) return 106 | return item.data.forEach((obj) => { 107 | onResult({ box: map, data: [obj], name, type: 'pattern' }) 108 | }) 109 | }) 110 | }) 111 | } 112 | } 113 | 114 | const getDescendantAtPos = (from: Node, pos: number) => { 115 | let node: Node | undefined = from 116 | const stack: Node[] = [from] 117 | 118 | // eslint-disable-next-line no-constant-condition 119 | while (true) { 120 | const nextNode: Node | undefined = node.getChildAtPos(pos) 121 | if (nextNode == null) return { node, stack } 122 | else { 123 | node = nextNode 124 | stack.push(node) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /packages/language-server/src/token-finder.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from 'vscode-languageserver' 2 | import { TextDocument } from 'vscode-languageserver-textdocument' 3 | 4 | import { type Dict, type ParserResultInterface, type ConditionDetails } from '@pandacss/types' 5 | import { CallExpression, JsxOpeningElement, JsxSelfClosingElement, Node } from 'ts-morph' 6 | 7 | import { 8 | BoxNodeMap, 9 | BoxNodeObject, 10 | box, 11 | extractCallExpressionArguments, 12 | extractJsxAttribute, 13 | extractJsxElementProps, 14 | maybeBoxNode, 15 | unbox, 16 | type PrimitiveType, 17 | type Unboxed, 18 | } from '@pandacss/extractor' 19 | import { type Token } from '@pandacss/token-dictionary' 20 | import { match } from 'ts-pattern' 21 | import { type BoxNodeWithValue, type ExtractableFnName, extractor } from './extractor' 22 | import type { GetContext } from './panda-language-server' 23 | import type { ProjectHelper } from './project-helper' 24 | import { getTokenFromPropValue } from './tokens/get-token' 25 | import { isObjectLike, nodeRangeToVsCodeRange } from './tokens/utils' 26 | 27 | export class TokenFinder { 28 | constructor( 29 | private getContext: GetContext, 30 | private project: ProjectHelper, 31 | ) {} 32 | 33 | /** 34 | * Get all the tokens from the document and call a callback on it. 35 | */ 36 | getFileTokens(parserResult: ParserResultInterface, onToken: OnTokenCallback) { 37 | const ctx = this.getContext() 38 | if (!ctx) return 39 | 40 | this.project.getFileTokens(parserResult, ({ propName, propValue, shorthand, propNode }) => { 41 | const token = getTokenFromPropValue(ctx, propName, String(propValue)) 42 | if (!token) return 43 | 44 | const range = nodeRangeToVsCodeRange(propNode.getRange()) 45 | onToken?.({ kind: 'token', token, range, propName, propValue, propNode, shorthand }) 46 | }) 47 | } 48 | 49 | findClosestInstance(node: Node, stack: Node[], onFoundInstance: (args: ClosestInstance) => Return) { 50 | const ctx = this.getContext() 51 | if (!ctx) return 52 | 53 | let current: Node | undefined = node 54 | 55 | return match(node) 56 | .when( 57 | () => { 58 | const callExpression = getFirstAncestorMatching(stack, (node): node is CallExpression => { 59 | if (!Node.isCallExpression(node)) return false 60 | const expr = node.getExpression() 61 | 62 | // TODO - check for import alias ? kinda overkill for now 63 | if (Node.isIdentifier(expr) && !extractor.canEvalFn(expr.getText())) return false 64 | 65 | return true 66 | }) 67 | if (!callExpression) return 68 | 69 | current = callExpression 70 | return current 71 | }, 72 | () => { 73 | const callExpression = current as CallExpression 74 | const name = callExpression.getExpression().getText() as ExtractableFnName 75 | 76 | if (name === 'cx') { 77 | const list = extractCallExpressionArguments( 78 | callExpression, 79 | extractor.boxCtx, 80 | () => true, 81 | () => true, 82 | ) 83 | const styles = extractor.mergeCx( 84 | ...list.value 85 | .filter((node) => isObjectLike(node)) 86 | .map((item) => (item as BoxNodeMap | BoxNodeObject).value), 87 | ) 88 | 89 | return onFoundInstance({ 90 | kind: 'styles', 91 | name, 92 | props: box.object(styles, callExpression, stack), 93 | }) 94 | } 95 | 96 | const list = extractCallExpressionArguments( 97 | callExpression, 98 | extractor.boxCtx, 99 | () => true, 100 | (args) => args.index === 0, 101 | ) 102 | const config = list.value[0] 103 | if (!isObjectLike(config)) return 104 | 105 | if (name === 'css' || name === 'defineStyles') { 106 | return onFoundInstance({ kind: 'styles', name, props: config }) 107 | } 108 | }, 109 | ) 110 | .when( 111 | () => { 112 | current = getFirstAncestorMatching(stack, extractor.isNodeJsx) 113 | return current 114 | }, 115 | () => { 116 | const componentNode = current as JsxSelfClosingElement | JsxOpeningElement 117 | const { name, props } = extractJsxElementProps(componentNode, extractor.boxCtx) 118 | if (!props.size) return 119 | 120 | return onFoundInstance({ kind: 'styles', name, props: box.map(props, componentNode, stack) }) 121 | }, 122 | ) 123 | .otherwise(() => { 124 | // 125 | return undefined 126 | }) 127 | } 128 | 129 | findClosestToken( 130 | node: Node, 131 | stack: Node[], 132 | onFoundToken: (args: Pick) => Return, 133 | ) { 134 | const ctx = this.getContext() 135 | if (!ctx) return 136 | 137 | return match(node) 138 | .when( 139 | () => getFirstAncestorMatching(stack, Node.isPropertyAssignment), 140 | () => { 141 | const propAssignment = getFirstAncestorMatching(stack, Node.isPropertyAssignment)! 142 | const name = propAssignment.getName() 143 | 144 | const objectLiteral = getFirstAncestorMatching(stack, Node.isObjectLiteralExpression)! 145 | const maybeBox = maybeBoxNode(objectLiteral, [], extractor.boxCtx, (args) => args.propName === name) 146 | if (!box.isMap(maybeBox)) return 147 | 148 | const propNode = maybeBox.value.get(name) 149 | if (!box.hasValue(propNode)) return 150 | 151 | const propName = ctx.utility.resolveShorthand(name) 152 | 153 | return onFoundToken({ propName, propNode, shorthand: name }) 154 | }, 155 | ) 156 | .when( 157 | () => getFirstAncestorMatching(stack, Node.isJsxAttribute), 158 | () => { 159 | const attrNode = getFirstAncestorMatching(stack, Node.isJsxAttribute)! 160 | 161 | const nameNode = attrNode.getNameNode() 162 | const name = nameNode.getText() 163 | 164 | const attrBox = extractJsxAttribute(attrNode, extractor.boxCtx) 165 | if (!box.hasValue(attrBox)) return 166 | 167 | const propName = ctx.utility.resolveShorthand(name) 168 | 169 | return onFoundToken({ propName, propNode: attrBox, shorthand: name }) 170 | }, 171 | ) 172 | .otherwise(() => { 173 | // 174 | return undefined 175 | }) 176 | } 177 | 178 | getClosestToken(doc: TextDocument, position: Position): ClosestToken | undefined { 179 | const ctx = this.getContext() 180 | if (!ctx) return 181 | 182 | const match = this.project.getNodeAtPosition(doc, position) 183 | if (!match) return 184 | 185 | const { node, stack } = match 186 | 187 | return this.findClosestToken(node, stack, ({ propName, propNode }) => { 188 | if (box.isLiteral(propNode)) { 189 | const propValue = propNode.value 190 | const maybeToken = getTokenFromPropValue(ctx, propName, String(propValue)) 191 | if (!maybeToken) return 192 | 193 | const range = nodeRangeToVsCodeRange(propNode.getRange()) 194 | return { kind: 'token', token: maybeToken, range, propName, propValue, propNode } as ClosestTokenMatch 195 | } 196 | 197 | if (box.isMap(propNode) && ctx.conditions.isCondition(propName)) { 198 | const objectBox = maybeBoxNode(propNode.getNode(), [], extractor.boxCtx, () => true) 199 | if (!objectBox) return 200 | 201 | if (box.isMap(objectBox)) { 202 | const propValue = unbox(propNode).raw 203 | const condition = ctx.conditions.getRaw(propName) 204 | 205 | const range = nodeRangeToVsCodeRange(propNode.getRange()) 206 | return { kind: 'condition', condition, range, propName, propValue, propNode } as ClosestConditionMatch 207 | } 208 | } 209 | }) 210 | } 211 | 212 | getClosestInstance(doc: TextDocument, position: Position) { 213 | const ctx = this.getContext() 214 | if (!ctx) return 215 | 216 | const match = this.project.getNodeAtPosition(doc, position) 217 | if (!match) return 218 | 219 | const { node, stack } = match 220 | 221 | return this.findClosestInstance(node, stack, (instance) => { 222 | if (instance.kind === 'styles') { 223 | const { name, props } = instance 224 | 225 | const unboxed = unbox(props) 226 | const { className, css, ...rest } = unboxed.raw 227 | return { 228 | kind: 'styles', 229 | name, 230 | props, 231 | styles: Object.assign({}, className, css, rest), 232 | } as ClosestStylesInstance & { styles: Dict } 233 | } 234 | }) 235 | } 236 | } 237 | 238 | type ClosestMatch = { 239 | range: Range 240 | propName: string 241 | propNode: BoxNodeWithValue 242 | } 243 | type ClosestTokenMatch = ClosestMatch & { 244 | kind: 'token' 245 | token: Token 246 | propValue: PrimitiveType 247 | shorthand: string 248 | } 249 | 250 | type ClosestConditionMatch = ClosestMatch & { 251 | kind: 'condition' 252 | condition: ConditionDetails 253 | propValue: Unboxed['raw'] 254 | shorthand: never 255 | } 256 | type ClosestToken = ClosestTokenMatch | ClosestConditionMatch 257 | 258 | type ClosestInstanceMatch = { name: string } 259 | type ClosestStylesInstance = { kind: 'styles'; props: BoxNodeMap | BoxNodeObject } 260 | type ClosestInstance = ClosestInstanceMatch & ClosestStylesInstance 261 | 262 | type OnTokenCallback = (args: ClosestToken) => void 263 | 264 | // quick index based loop 265 | const getFirstAncestorMatching = ( 266 | stack: Node[], 267 | callback: (parent: Node, index: number) => parent is Ancestor, 268 | ) => { 269 | for (let i = stack.length - 1; i >= 0; i--) { 270 | const parent = stack[i] 271 | if (parent && callback(parent, i)) return parent 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/color2k-to-vscode-color.ts: -------------------------------------------------------------------------------- 1 | import { type ColorPresentationParams } from 'vscode-languageserver' 2 | import { parseToRgba } from 'color2k' 3 | 4 | export const color2kToVsCodeColor = (value: string) => { 5 | try { 6 | const [red, green, blue, alpha] = parseToRgba(value) 7 | 8 | const color = { 9 | red: red / 255, 10 | green: green / 255, 11 | blue: blue / 255, 12 | alpha, 13 | } 14 | return color as ColorPresentationParams['color'] 15 | } catch (e) { 16 | return 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/error.ts: -------------------------------------------------------------------------------- 1 | export const onError = (...args: any[]) => { 2 | console.error(args) 3 | } 4 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/expand-token-fn.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/chakra-ui/panda/blob/ab32d1d798c353ce4793f01b1e5cae3407bc209e/packages/token-dictionary/src/utils.ts 2 | 3 | import { logger } from '@pandacss/logger' 4 | import { esc } from '@pandacss/shared' 5 | import type { Token } from '@pandacss/token-dictionary' 6 | 7 | /* ----------------------------------------------------------------------------- 8 | * Token references 9 | * -----------------------------------------------------------------------------*/ 10 | 11 | /** 12 | * Regex for matching a tokenized reference. 13 | */ 14 | const REFERENCE_REGEX = /({([^}]*)})/ 15 | const curlyBracketRegex = /[{}]/ 16 | 17 | /** 18 | * Returns all references in a string 19 | * 20 | * @example 21 | * 22 | * `{colors.red.300} {sizes.sm}` => ['colors.red.300', 'sizes.sm'] 23 | */ 24 | export function getReferences(value: string) { 25 | if (typeof value !== 'string') return [] 26 | const matches = value.match(REFERENCE_REGEX) 27 | if (!matches) return [] 28 | return matches.map((match) => match.replace(curlyBracketRegex, '')).map((value) => value.trim()) 29 | } 30 | 31 | export const hasCurlyReference = (value: string) => REFERENCE_REGEX.test(value) 32 | export const hasTokenFnReference = (str: string) => str.includes('token(') 33 | 34 | export const hasTokenRef = (v: string) => hasCurlyReference(v) || hasTokenFnReference(v) 35 | const tokenFunctionRegex = /token\(([^)]+)\)/g 36 | const closingParenthesisRegex = /\)$/g 37 | 38 | const tokenReplacer = (a: string, b?: string) => 39 | b ? (a.endsWith(')') ? a.replace(closingParenthesisRegex, `, ${b})`) : `var(${a}, ${b})`) : a 40 | 41 | const notFoundMessage = (key: string, value: string) => `Reference not found: \`${key}\` in "${value}"` 42 | 43 | export function getTokensInString(value: string, fn: (key: string) => Token | undefined) { 44 | if (!hasTokenRef(value)) return [] 45 | 46 | const references = getReferences(value) 47 | 48 | const expanded = references.reduce((valueStr, key) => { 49 | const resolved = fn(key) 50 | if (!resolved) { 51 | logger.warn('token', notFoundMessage(key, value)) 52 | } 53 | const expandedValue = resolved?.value ?? esc(key) 54 | 55 | return valueStr.replace(`{${key}}`, expandedValue) 56 | }, value) 57 | 58 | if (!hasTokenFnReference(expanded)) return references.map((key) => fn(key)).filter(Boolean) 59 | 60 | const _rawValue = expanded.replace(tokenFunctionRegex, (_, token) => { 61 | const [tokenValue, tokenFallback] = token.split(',').map((s: string) => s.trim()) 62 | 63 | const result = [tokenValue, tokenFallback].filter(Boolean).map((key) => { 64 | const resolved = fn(key) 65 | 66 | if (!resolved && hasTokenRef(key)) { 67 | logger.warn('token', notFoundMessage(key, value)) 68 | } 69 | 70 | if (resolved) { 71 | references.push(key) 72 | } 73 | 74 | return resolved?.value ?? esc(key) 75 | }) 76 | 77 | if (result.length > 1) { 78 | const [a, b] = result 79 | return tokenReplacer(a!, b) 80 | } 81 | 82 | return tokenReplacer(result[0]!) 83 | }) 84 | 85 | return references.map((key) => fn(key)).filter(Boolean) 86 | } 87 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/get-token.ts: -------------------------------------------------------------------------------- 1 | import { type PandaContext } from '@pandacss/node' 2 | import { type Token } from '@pandacss/token-dictionary' 3 | import { color2kToVsCodeColor } from './color2k-to-vscode-color' 4 | import { isColor } from './is-color' 5 | import { getTokensInString, hasTokenRef } from './expand-token-fn' 6 | 7 | const ESCAPE_HATCHED = /^(\[)(.*)(\])$/ 8 | 9 | const removeEscapeHatch = (value: string) => { 10 | if (ESCAPE_HATCHED.test(value)) { 11 | return value.match(ESCAPE_HATCHED)?.[2] as string 12 | } 13 | return value 14 | } 15 | 16 | const getColorExtensions = (value: string, kind: string) => { 17 | const vscodeColor = color2kToVsCodeColor(value) 18 | if (!vscodeColor) return 19 | 20 | return { vscodeColor, kind } 21 | } 22 | 23 | export const getTokenFromPropValue = (ctx: PandaContext, prop: string, value: string): Token | undefined => { 24 | const utility = ctx.config.utilities?.[prop] 25 | 26 | const potentialCategories: string[] = [] 27 | 28 | if (typeof utility?.values === 'string' && utility?.values) { 29 | potentialCategories.push(utility?.values) 30 | } 31 | 32 | if (typeof utility?.values === 'function' && ctx.config.theme) { 33 | // Invoke the utility value function and capture categories potentially used by consumer 34 | utility.values((token: string) => { 35 | potentialCategories.push(token) 36 | }) 37 | } 38 | 39 | if (!potentialCategories.length) return 40 | 41 | // Attempt to locate a token 42 | const matchedToken = potentialCategories 43 | .map((category) => { 44 | return [category, value].join('.') 45 | }) 46 | .map((tokenPath) => { 47 | return { 48 | token: ctx.tokens.getByName(tokenPath), 49 | tokenPath, 50 | } 51 | }) 52 | .find((t) => t.token !== undefined) 53 | 54 | const token = matchedToken?.token 55 | 56 | // arbitrary value like 57 | // display: "block", zIndex: 1, ... 58 | if (!token) { 59 | // remove escape hatch if found 60 | value = removeEscapeHatch(value) 61 | // Use the first category for the token path 62 | const tokenPath = [potentialCategories[0], value].join('.') 63 | 64 | // any color 65 | // color: "blue", color: "#000", color: "rgb(0, 0, 0)", ... 66 | if (isColor(value)) { 67 | const extensions = getColorExtensions(value, 'native-color') 68 | if (!extensions) return 69 | 70 | return { value, name: value, path: tokenPath, type: 'color', extensions } as unknown as Token 71 | } 72 | 73 | // border="1px solid token(colors.gray.300)" 74 | if (typeof value === 'string' && hasTokenRef(value)) { 75 | const matches = getTokensInString(value, (key) => ctx.tokens.getByName(key)) 76 | 77 | // wrong token path 78 | if (!matches.length) { 79 | return { 80 | value, 81 | name: value, 82 | path: tokenPath, 83 | type: 'color', 84 | extensions: { kind: 'invalid-token-path' }, 85 | } as unknown as Token 86 | } 87 | 88 | // TODO: handle multiple tokens like : "token(colors.gray.300), token(colors.gray.500)" 89 | const first = matches?.[0] 90 | if (!first) return 91 | 92 | if (isColor(first.value)) { 93 | const extensions = getColorExtensions(first.value, 'semantic-color') 94 | if (!extensions) return 95 | 96 | return first.setExtensions(extensions) 97 | } 98 | 99 | return first 100 | } 101 | 102 | return 103 | } 104 | 105 | // known theme token 106 | // px: "2", fontSize: "xl", ... 107 | // color: "blue.300" 108 | 109 | let color = token.value 110 | // could be a semantic token, so the token.value wouldn't be a color directly, it's actually a CSS variable 111 | if (!isColor(color) && typeof token.value === 'string' && token.value.startsWith('var(--')) { 112 | const [tokenRef] = ctx.tokens.getReferences(token.originalValue) 113 | if (tokenRef?.value) { 114 | color = tokenRef.value 115 | } 116 | } 117 | 118 | // now it could be a color 119 | if (isColor(color)) { 120 | const extensions = getColorExtensions(color, 'color') 121 | if (!extensions) return 122 | 123 | return token.setExtensions(extensions) 124 | } 125 | 126 | return token 127 | } 128 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/is-color.ts: -------------------------------------------------------------------------------- 1 | // borrow from https://github.com/princejwesley/is-css-color/blob/master/index.js 2 | 'use strict' 3 | 4 | // every string I match against are lowercase 5 | const HEX_PATTERN = /^#(?:[a-f0-9]{3})?(?:[a-f0-9]{3})$/ 6 | 7 | // css color names + initial + inherit + currentColor + transparent 8 | const CSS_COLOR_NAMES = [ 9 | 'aliceblue', 10 | 'antiquewhite', 11 | 'aqua', 12 | 'aquamarine', 13 | 'azure', 14 | 'beige', 15 | 'bisque', 16 | 'black', 17 | 'blanchedalmond', 18 | 'blue', 19 | 'blueviolet', 20 | 'brown', 21 | 'burlywood', 22 | 'cadetblue', 23 | 'chartreuse', 24 | 'chocolate', 25 | 'coral', 26 | 'cornflowerblue', 27 | 'cornsilk', 28 | 'crimson', 29 | 'currentColor', 30 | 'cyan', 31 | 'darkblue', 32 | 'darkcyan', 33 | 'darkgoldenrod', 34 | 'darkgray', 35 | 'darkgreen', 36 | 'darkgrey', 37 | 'darkkhaki', 38 | 'darkmagenta', 39 | 'darkolivegreen', 40 | 'darkorange', 41 | 'darkorchid', 42 | 'darkred', 43 | 'darksalmon', 44 | 'darkseagreen', 45 | 'darkslateblue', 46 | 'darkslategray', 47 | 'darkslategrey', 48 | 'darkturquoise', 49 | 'darkviolet', 50 | 'deeppink', 51 | 'deepskyblue', 52 | 'dimgray', 53 | 'dimgrey', 54 | 'dodgerblue', 55 | 'firebrick', 56 | 'floralwhite', 57 | 'forestgreen', 58 | 'fuchsia', 59 | 'gainsboro', 60 | 'ghostwhite', 61 | 'gold', 62 | 'goldenrod', 63 | 'gray', 64 | 'green', 65 | 'greenyellow', 66 | 'grey', 67 | 'honeydew', 68 | 'hotpink', 69 | 'indianred', 70 | 'indigo', 71 | 'inherit', 72 | 'initial', 73 | 'ivory', 74 | 'khaki', 75 | 'lavender', 76 | 'lavenderblush', 77 | 'lawngreen', 78 | 'lemonchiffon', 79 | 'lightblue', 80 | 'lightcoral', 81 | 'lightcyan', 82 | 'lightgoldenrodyellow', 83 | 'lightgray', 84 | 'lightgreen', 85 | 'lightgrey', 86 | 'lightpink', 87 | 'lightsalmon', 88 | 'lightseagreen', 89 | 'lightskyblue', 90 | 'lightslategray', 91 | 'lightslategrey', 92 | 'lightsteelblue', 93 | 'lightyellow', 94 | 'lime', 95 | 'limegreen', 96 | 'linen', 97 | 'magenta', 98 | 'maroon', 99 | 'mediumaquamarine', 100 | 'mediumblue', 101 | 'mediumorchid', 102 | 'mediumpurple', 103 | 'mediumseagreen', 104 | 'mediumslateblue', 105 | 'mediumspringgreen', 106 | 'mediumturquoise', 107 | 'mediumvioletred', 108 | 'midnightblue', 109 | 'mintcream', 110 | 'mistyrose', 111 | 'moccasin', 112 | 'navajowhite', 113 | 'navy', 114 | 'oldlace', 115 | 'olive', 116 | 'olivedrab', 117 | 'orange', 118 | 'orangered', 119 | 'orchid', 120 | 'palegoldenrod', 121 | 'palegreen', 122 | 'paleturquoise', 123 | 'palevioletred', 124 | 'papayawhip', 125 | 'peachpuff', 126 | 'peru', 127 | 'pink', 128 | 'plum', 129 | 'powderblue', 130 | 'purple', 131 | 'rebeccapurple', 132 | 'red', 133 | 'rosybrown', 134 | 'royalblue', 135 | 'saddlebrown', 136 | 'salmon', 137 | 'sandybrown', 138 | 'seagreen', 139 | 'seashell', 140 | 'sienna', 141 | 'silver', 142 | 'skyblue', 143 | 'slateblue', 144 | 'slategray', 145 | 'slategrey', 146 | 'snow', 147 | 'springgreen', 148 | 'steelblue', 149 | 'tan', 150 | 'teal', 151 | 'thistle', 152 | 'tomato', 153 | 'transparent', 154 | 'turquoise', 155 | 'violet', 156 | 'wheat', 157 | 'white', 158 | 'whitesmoke', 159 | 'yellow', 160 | 'yellowgreen', 161 | ] 162 | 163 | const PREFIX = '^(rgb|hsl)(a?)\\s*\\(' 164 | const VALUE = '\\s*([-+]?\\d+%?)\\s*' 165 | const ALPHA = '(?:,\\s*([-+]?(?:(?:\\d+(?:.\\d+)?)|(?:.\\d+))\\s*))?' 166 | const SUFFIX = '\\)$' 167 | const RGB_HSL_PATTERN = new RegExp(PREFIX + VALUE + ',' + VALUE + ',' + VALUE + ALPHA + SUFFIX) 168 | 169 | const NUM_TYPE = 1 170 | const PERCENTAGE_TYPE = 2 171 | const ERROR_TYPE = NUM_TYPE & PERCENTAGE_TYPE 172 | 173 | export const isColor = (str: string) => { 174 | function getColorType(token: string) { 175 | return token.includes('%') ? PERCENTAGE_TYPE : NUM_TYPE 176 | } 177 | 178 | if (!str || typeof str !== 'string') { 179 | return false 180 | } 181 | 182 | const color = str.replace(/^\s+|\s+$/g, '').toLocaleLowerCase() 183 | 184 | // named colors or hex code 185 | if (CSS_COLOR_NAMES.includes(color) || HEX_PATTERN.test(color)) { 186 | return true 187 | } 188 | 189 | const result = color.match(RGB_HSL_PATTERN) 190 | if (result) { 191 | const flavor = result[1] 192 | const alpha = result[2] 193 | const rh = result[3] ?? '' 194 | const gs = result[4] ?? '' 195 | const bl = result[5] ?? '' 196 | const a = result[6] ?? '' 197 | 198 | // alpha test 199 | if ((alpha === 'a' && !a) || (a && alpha === '')) { 200 | return false 201 | } 202 | 203 | // hsl 204 | if (flavor === 'hsl') { 205 | if (getColorType(rh) !== NUM_TYPE) { 206 | return false 207 | } 208 | return (getColorType(gs) & getColorType(bl)) === PERCENTAGE_TYPE 209 | } 210 | 211 | // rgb 212 | return (getColorType(rh) & getColorType(gs) & getColorType(bl)) !== ERROR_TYPE 213 | } 214 | 215 | return false 216 | } 217 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/render-markdown.ts: -------------------------------------------------------------------------------- 1 | import { parseToRgba } from 'color2k' 2 | // import type { PandaVSCodeSettings } from '@pandacss/extension-shared' 3 | 4 | // taken from https://github.com/nderscore/tamagui-typescript-plugin/blob/eb4dbd4ea9a60cbfff2ffd9ae8992ec2e54c0b02/src/metadata.ts 5 | 6 | const squirclePath = `M 0,12 C 0,0 0,0 12,0 24,0 24,0 24,12 24,24 24,24 12,24 0, 24 0,24 0,12` 7 | 8 | const svgCheckerboard = ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ` 17 | 18 | export const makeColorTile = (value: string, size: number = 18) => { 19 | try { 20 | const [_r, _g, _b, alpha] = parseToRgba(value) 21 | const hasAlphaTransparency = alpha !== 1 22 | const svg = `${ 23 | hasAlphaTransparency ? svgCheckerboard : '' 24 | }` 25 | const image = `![Image](data:image/svg+xml;base64,${btoa(svg)})` 26 | return image 27 | } catch { 28 | return '' 29 | } 30 | } 31 | 32 | export const makeTable = (rows: Record[]) => { 33 | const header = rows[0]! 34 | const keys = Object.keys(header) 35 | const renderRow = (row: Record) => { 36 | return `| ${keys.map((key) => row[key]).join(' | ')} |` 37 | } 38 | const renderSplitter = () => { 39 | return `| ${keys.map(() => '---').join(' | ')} |` 40 | } 41 | 42 | return `${renderRow(header)}\n${renderSplitter()}\n${rows.slice(1).map(renderRow).join('\n')}` 43 | } 44 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/render-token-color-preview.ts: -------------------------------------------------------------------------------- 1 | import { type Dict } from '@pandacss/types' 2 | import { type PandaContext } from '@pandacss/node' 3 | import satori from 'satori' 4 | import { type Token } from '@pandacss/token-dictionary' 5 | import { svgToMarkdownLink } from './utils' 6 | 7 | // https://og-playground.vercel.app/?share=5ZTNTsMwDMdfJbLELdI2xHYIgwMfb4DEJZe2cdtAmlRJSqmqvjtNw8ZEGUzihMglsRP_9bMtp4fMCAQGWyFfuCbE-U7hVd-HMyElyqL0jHBYLZdnHGh0t1L4cuYV0tUq6YI_V_i69wfjTlrMvDQ63GZGNZXmEK6HgevrcNgBfEY4rhuVGVm920GKkEnsUG4uGANvEifdR3RYldSPu9TWB5l9U4o5Rlhpkj0X1jRa3BplbIgqLOKY8_5RpCVk8RvgL6BOy-Yk5FQ1eJR4uxiB_0XnOlTKtH-rdRbFj52L-yXXQMHUYTgdsB6m4QZ2vt5QiJDANhcUBKZNASxPlEMKWJkn-dDV4e_w7WSNMrnR_r5KUQDztsGBgk_S8UU5ldBYJWB4Aw 8 | // TODO use raw svg ? or precompile and just replace the color 9 | export const renderTokenColorPreview = async (ctx: PandaContext, token: Token) => { 10 | const colors = [] as [tokenName: string, value: string][] 11 | 12 | // Only bother displaying a preview for multiple colors 13 | if (token.extensions.conditions) { 14 | Object.entries(token.extensions.conditions).forEach(([conditionName, value]) => { 15 | if (!ctx.conditions.get(conditionName) && conditionName !== 'base') return 16 | const [tokenRef] = ctx.tokens.getReferences(value) 17 | if (!tokenRef) return 18 | 19 | colors.push([conditionName, tokenRef.value]) 20 | }) 21 | } 22 | if (!colors.length) return 23 | 24 | const svg = await satori( 25 | createDiv( 26 | { 27 | display: 'flex', 28 | flexDirection: 'column', 29 | height: '100%', 30 | width: '100%', 31 | }, 32 | colors.map(([conditionName, backgroundColor]) => 33 | createDiv( 34 | { height: '100%', flex: 1, padding: '4px', display: 'flex', justifyContent: 'center', backgroundColor }, 35 | createDiv({ height: '100%', padding: '4px', backgroundColor: 'white', color: 'black' }, conditionName), 36 | ), 37 | ), 38 | ), 39 | { 40 | width: 256, 41 | height: 28 * colors.length, 42 | fonts: [ 43 | { 44 | name: 'Roboto', 45 | data: Buffer.from(''), 46 | weight: 400, 47 | style: 'normal', 48 | }, 49 | ], 50 | }, 51 | ) 52 | 53 | return svgToMarkdownLink(svg) 54 | } 55 | 56 | const createDiv = (style: Dict, children?: DivNode) => ({ type: 'div', key: null, props: { style, children } }) 57 | type DivLike = { 58 | type: string 59 | key: null 60 | props: { 61 | style: Dict 62 | children: any 63 | } 64 | } 65 | type DivNode = string | DivLike | DivLike[] | null 66 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/sort-text.ts: -------------------------------------------------------------------------------- 1 | // Detect specific scale tokens 2 | const specificTokenPattern = /^\$(color|radius|size|space|zIndex)\.(.+)$/ 3 | 4 | /** 5 | * Reformats a token string to be used for sorting 6 | * 7 | * - Specific tokens are sorted last 8 | * - Numeric portions of tokens are sorted numerically 9 | */ 10 | export const getSortText = (sortText: string) => { 11 | let text = sortText 12 | // add prefix to specific tokens to sort them last: 13 | text = text.replace(specificTokenPattern, '$zzzzzzzzzz$1.$2') 14 | // add prefix to numeric portions of tokens to sort them numerically: 15 | text = text.replace(/(-?)(\d+)/g, (_, sign, num) => { 16 | return `${sign ? '1' : '0'}${num.padStart(12, '0')}` 17 | }) 18 | // special case for negative text tokens like $-true 19 | text = text.replace(/(^\$|\.)-(\w.+)$/, '$1z$2') 20 | return text 21 | } 22 | -------------------------------------------------------------------------------- /packages/language-server/src/tokens/utils.ts: -------------------------------------------------------------------------------- 1 | import { box, type NodeRange } from '@pandacss/extractor' 2 | import { Bool } from 'lil-fp' 3 | import { Range } from 'vscode-languageserver' 4 | 5 | import { type SystemStyleObject } from '@pandacss/types' 6 | 7 | import { RuleProcessor } from '@pandacss/core' 8 | import { type PandaContext } from '@pandacss/node' 9 | import { toPx } from '@pandacss/shared' 10 | import * as base64 from 'base-64' 11 | 12 | import prettierPluginBabel from 'prettier/plugins/babel' 13 | import prettierPluginHtml from 'prettier/plugins/html' 14 | import prettierPluginPostcss from 'prettier/plugins/postcss' 15 | import prettier from 'prettier' 16 | import { match } from 'ts-pattern' 17 | import * as utf8 from 'utf8' 18 | 19 | import { type PandaVSCodeSettings } from '@pandacss/extension-shared' 20 | import { type Token } from '@pandacss/token-dictionary' 21 | 22 | export const isObjectLike = Bool.or(box.isObject, box.isMap) 23 | 24 | export const nodeRangeToVsCodeRange = (range: NodeRange) => 25 | Range.create( 26 | { line: range.startLineNumber - 1, character: range.startColumn - 1 }, 27 | { line: range.endLineNumber - 1, character: range.endColumn - 1 }, 28 | ) 29 | 30 | function getPrettiedCSS(css: string) { 31 | return prettier.format(css, { 32 | parser: 'css', 33 | plugins: [prettierPluginHtml, prettierPluginBabel, prettierPluginPostcss], 34 | }) 35 | } 36 | 37 | export type DisplayOptions = { 38 | mode?: PandaVSCodeSettings['hovers.display.mode'] 39 | forceHash?: PandaVSCodeSettings['hovers.display.force-hash'] 40 | } 41 | 42 | export const getMarkdownCss = async (ctx: PandaContext, styles: SystemStyleObject, settings: PandaVSCodeSettings) => { 43 | const mode = settings['hovers.display.mode'] 44 | const forceHash = settings['hovers.display.force-hash'] 45 | 46 | const hash = ctx.config.hash 47 | if (forceHash) { 48 | ctx.config.hash = true 49 | } 50 | 51 | const processor = new RuleProcessor(ctx) 52 | processor.clone() 53 | processor.css({ styles }) 54 | 55 | const css = match(mode ?? 'optimized') 56 | .with('nested', () => processor.toCss({ optimize: false })) 57 | .with('optimized', () => processor.toCss({ optimize: true })) 58 | .with('minified', () => processor.toCss({ optimize: true, minify: true })) 59 | .run() 60 | 61 | const raw = await getPrettiedCSS(css) 62 | const withCss = '```css' + '\n' + raw + '\n' + '```' 63 | 64 | // restore hash 65 | ctx.config.hash = hash 66 | 67 | return { raw, withCss } 68 | } 69 | 70 | export const printTokenValue = (token: Token, settings: PandaVSCodeSettings) => 71 | `${token.value}${settings['rem-to-px.enabled'] && token.value.endsWith('rem') ? ` (${toPx(token.value)})` : ''}` 72 | 73 | export const svgToMarkdownLink = (svg: string) => { 74 | const dataUri = 'data:image/svg+xml;charset=UTF-8;base64,' + base64.encode(utf8.encode(svg)) 75 | return `![](${dataUri})` 76 | } 77 | -------------------------------------------------------------------------------- /packages/language-server/src/uri-to-path.ts: -------------------------------------------------------------------------------- 1 | import { URI } from 'vscode-uri' 2 | 3 | const RE_PATHSEP_WINDOWS = /\\/g 4 | 5 | /** 6 | * Returns an fs path the uri of a TextDocument. 7 | */ 8 | export function uriToPath(stringUri: string): string | undefined { 9 | const uri = URI.parse(stringUri) 10 | if (uri.scheme !== 'file') { 11 | return undefined 12 | } 13 | return normalizeFsPath(uri.fsPath) 14 | } 15 | 16 | export function fsPathToUri(fsPath: string): string { 17 | return URI.file(fsPath).toString() 18 | } 19 | 20 | /** 21 | * Normalizes the file system path. 22 | * 23 | * On systems other than Windows it should be an no-op. 24 | * 25 | * On Windows, an input path in a format like "C:/path/file.ts" 26 | * will be normalized to "c:/path/file.ts". 27 | */ 28 | export function normalizePath(filePath: string): string { 29 | const fsPath = URI.file(filePath).fsPath 30 | return normalizeFsPath(fsPath) 31 | } 32 | 33 | /** 34 | * Normalizes the path obtained through the "fsPath" property of the URI module. 35 | */ 36 | export function normalizeFsPath(fsPath: string): string { 37 | return fsPath.replace(RE_PATHSEP_WINDOWS, '/') 38 | } 39 | -------------------------------------------------------------------------------- /packages/language-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "index.ts"], 4 | "compilerOptions": { 5 | "declaration": false, 6 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/language-server/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { copyFile } from 'fs' 2 | import { resolve } from 'path' 3 | import { defineConfig } from 'tsup' 4 | 5 | export default defineConfig({ 6 | entry: ['src/index.ts'], 7 | format: ['cjs'], 8 | external: ['esbuild', 'lightningcss'], 9 | minify: true, 10 | outDir: 'dist', 11 | clean: true, 12 | shims: true, 13 | onSuccess() { 14 | console.log('✅ Build complete!') 15 | copyFile(resolve(__dirname, './dist/index.js'), resolve(__dirname, '../vscode/dist/server.js'), (err) => { 16 | if (err) return console.warn(err) 17 | console.log('✅ Server.js copied to vscode/dist') 18 | }) 19 | 20 | return Promise.resolve() 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /packages/shared/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pandacss/extension-shared 2 | 3 | ## 0.18.0 4 | 5 | ### Minor Changes 6 | 7 | - bd5ca1b: Update Panda version to 0.37.2 (from 0.17.0), fixes a bunch of small issues 8 | 9 | ## 0.17.0 10 | 11 | ### Patch Changes 12 | 13 | - Updated dependencies [12281ff8] 14 | - @pandacss/shared@0.17.0 15 | 16 | ## 0.16.0 17 | 18 | ### Patch Changes 19 | 20 | - @pandacss/shared@0.16.0 21 | 22 | ## 0.15.5 23 | 24 | ### Patch Changes 25 | 26 | - @pandacss/shared@0.15.5 27 | 28 | ## 0.15.4 29 | 30 | ### Patch Changes 31 | 32 | - @pandacss/shared@0.15.4 33 | 34 | ## 0.15.3 35 | 36 | ### Patch Changes 37 | 38 | - Updated dependencies [95b06bb1] 39 | - @pandacss/shared@0.15.3 40 | 41 | ## 0.15.2 42 | 43 | ### Patch Changes 44 | 45 | - @pandacss/shared@0.15.2 46 | 47 | ## 0.15.1 48 | 49 | ### Patch Changes 50 | 51 | - Updated dependencies [26f6982c] 52 | - @pandacss/shared@0.15.1 53 | 54 | ## 0.15.0 55 | 56 | ### Patch Changes 57 | 58 | - Updated dependencies [9f429d35] 59 | - Updated dependencies [f27146d6] 60 | - @pandacss/shared@0.15.0 61 | 62 | ## 0.14.0 63 | 64 | ### Patch Changes 65 | 66 | - @pandacss/shared@0.14.0 67 | 68 | ## 0.13.1 69 | 70 | ### Patch Changes 71 | 72 | - @pandacss/shared@0.13.1 73 | 74 | ## 0.13.0 75 | 76 | ### Patch Changes 77 | 78 | - @pandacss/shared@0.13.0 79 | 80 | ## 0.12.2 81 | 82 | ### Patch Changes 83 | 84 | - @pandacss/shared@0.12.2 85 | 86 | ## 0.12.1 87 | 88 | ### Patch Changes 89 | 90 | - @pandacss/shared@0.12.1 91 | 92 | ## 0.12.0 93 | 94 | ### Patch Changes 95 | 96 | - @pandacss/shared@0.12.0 97 | 98 | ## 0.11.1 99 | 100 | ### Patch Changes 101 | 102 | - Updated dependencies [c07e1beb] 103 | - @pandacss/shared@0.11.1 104 | 105 | ## 0.11.0 106 | 107 | ### Patch Changes 108 | 109 | - @pandacss/shared@0.11.0 110 | 111 | ## 0.10.0 112 | 113 | ### Patch Changes 114 | 115 | - Updated dependencies [24e783b3] 116 | - Updated dependencies [a669f4d5] 117 | - @pandacss/shared@0.10.0 118 | 119 | ## 0.9.0 120 | 121 | ### Patch Changes 122 | 123 | - @pandacss/shared@0.9.0 124 | 125 | ## 0.8.0 126 | 127 | ### Patch Changes 128 | 129 | - @pandacss/shared@0.8.0 130 | 131 | ## 0.7.0 132 | 133 | ### Patch Changes 134 | 135 | - Updated dependencies [f59154fb] 136 | - @pandacss/shared@0.7.0 137 | 138 | ## 0.6.0 139 | 140 | ### Patch Changes 141 | 142 | - @pandacss/shared@0.6.0 143 | 144 | ## 0.5.1 145 | 146 | ### Patch Changes 147 | 148 | - Updated dependencies [c0335cf4] 149 | - Updated dependencies [762fd0c9] 150 | - @pandacss/shared@0.5.1 151 | 152 | ## 0.5.0 153 | 154 | ### Patch Changes 155 | 156 | - Updated dependencies [60df9bd1] 157 | - Updated dependencies [ead9eaa3] 158 | - @pandacss/shared@0.5.0 159 | 160 | ## 0.4.0 161 | 162 | ### Patch Changes 163 | 164 | - @pandacss/shared@0.4.0 165 | 166 | ## 0.3.2 167 | 168 | ### Patch Changes 169 | 170 | - @pandacss/shared@0.3.2 171 | 172 | ## 0.3.1 173 | 174 | ### Patch Changes 175 | 176 | - Updated dependencies [efd79d83] 177 | - @pandacss/shared@0.3.1 178 | 179 | ## 0.3.0 180 | 181 | ### Patch Changes 182 | 183 | - 02f4daf7: Publish extension packages 184 | - @pandacss/shared@0.3.0 185 | -------------------------------------------------------------------------------- /packages/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "chakra-ui", 3 | "name": "@pandacss/extension-shared", 4 | "displayName": "🐼 Panda CSS", 5 | "description": "Shared utilities between extension client & server", 6 | "version": "0.18.0", 7 | "license": "MIT", 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "repository": { 11 | "url": "https://github.com/chakra-ui/panda-vscode", 12 | "directory": "packages/vscode" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsup src/index.ts --format=esm,cjs --dts", 22 | "build-fast": "tsup src/index.ts --format=esm,cjs --no-dts", 23 | "dev": "yarn build-fast --watch", 24 | "clean": "rimraf dist", 25 | "lint": "eslint ./src --ext .ts,.tsx --fix" 26 | }, 27 | "devDependencies": { 28 | "tsup": "8.0.2", 29 | "typescript": "^5.4.2" 30 | }, 31 | "dependencies": { 32 | "@pandacss/shared": "^0.37.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/shared/src/flatten.ts: -------------------------------------------------------------------------------- 1 | import { walkObject } from '@pandacss/shared' 2 | 3 | type Dict = Record 4 | 5 | export function flatten(values: Record) { 6 | const result: Dict = {} 7 | 8 | walkObject(values, (token, paths) => { 9 | result[paths.join('.')] = token 10 | }) 11 | 12 | return result 13 | } 14 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './settings' 2 | -------------------------------------------------------------------------------- /packages/shared/src/settings.ts: -------------------------------------------------------------------------------- 1 | import { flatten } from './flatten' 2 | 3 | export interface PandaVSCodeSettings { 4 | 'color-hints.enabled'?: boolean 5 | 'color-hints.color-preview.enabled'?: boolean 6 | 'color-hints.semantic-tokens.enabled'?: boolean 7 | 'rem-to-px.enabled'?: boolean 8 | 'completions.enabled'?: boolean 9 | 'completions.token-fn.enabled'?: boolean 10 | 'diagnostics.enabled'?: boolean 11 | 'diagnostics.invalid-token-path'?: 'disable' | 'hint' | 'information' | 'warning' | 'error' 12 | 'hovers.enabled'?: boolean 13 | 'hovers.instances.enabled'?: boolean 14 | 'hovers.tokens.enabled'?: boolean 15 | 'hovers.tokens.css-preview.enabled'?: boolean 16 | 'hovers.conditions.enabled'?: boolean 17 | 'hovers.semantic-colors.enabled'?: boolean 18 | 'hovers.display.mode'?: 'nested' | 'optimized' | 'minified' 19 | 'hovers.display.force-hash'?: boolean 20 | 'inlay-hints.enabled'?: boolean 21 | 'debug.enabled'?: boolean 22 | } 23 | 24 | export const defaultSettings: PandaVSCodeSettings = { 25 | 'debug.enabled': false, 26 | 'diagnostics.invalid-token-path': 'warning', 27 | } 28 | 29 | export const getFlattenedSettings = (settings: Record>) => 30 | flatten(settings) as PandaVSCodeSettings 31 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "index.ts"], 4 | "compilerOptions": { 5 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/ts-plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pandacss/ts-plugin 2 | 3 | ## 0.18.0 4 | 5 | ### Minor Changes 6 | 7 | - bd5ca1b: Update Panda version to 0.37.2 (from 0.17.0), fixes a bunch of small issues 8 | 9 | ## 0.17.0 10 | 11 | ## 0.16.0 12 | 13 | ## 0.15.5 14 | 15 | ## 0.15.4 16 | 17 | ## 0.15.3 18 | 19 | ## 0.15.2 20 | 21 | ## 0.15.1 22 | 23 | ## 0.15.0 24 | 25 | ## 0.14.0 26 | 27 | ## 0.13.1 28 | 29 | ## 0.13.0 30 | 31 | ## 0.12.2 32 | 33 | ## 0.12.1 34 | 35 | ## 0.12.0 36 | 37 | ## 0.11.1 38 | 39 | ## 0.11.0 40 | 41 | ## 0.10.0 42 | 43 | ## 0.9.0 44 | 45 | ## 0.8.0 46 | 47 | ## 0.7.0 48 | 49 | ## 0.6.0 50 | 51 | ## 0.5.1 52 | 53 | ### Patch Changes 54 | 55 | - 91b2dbf0: Fix issue where extension build fails due to the use of `pathe` 56 | 57 | ## 0.5.0 58 | 59 | ### Patch Changes 60 | 61 | - @pandacss/extension-shared@0.5.0 62 | 63 | ## 0.4.0 64 | 65 | ### Patch Changes 66 | 67 | - @pandacss/extension-shared@0.4.0 68 | 69 | ## 0.3.2 70 | 71 | ### Patch Changes 72 | 73 | - @pandacss/extension-shared@0.3.2 74 | 75 | ## 0.3.1 76 | 77 | ### Patch Changes 78 | 79 | - @pandacss/extension-shared@0.3.1 80 | 81 | ## 0.3.0 82 | 83 | ### Patch Changes 84 | 85 | - 02f4daf7: Publish extension packages 86 | - Updated dependencies [02f4daf7] 87 | - @pandacss/extension-shared@0.3.0 88 | -------------------------------------------------------------------------------- /packages/ts-plugin/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error 2 | export * from './src' 3 | -------------------------------------------------------------------------------- /packages/ts-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pandacss/ts-plugin", 3 | "displayName": "🐼 Panda CSS", 4 | "description": "The official Panda plugin for VS Code", 5 | "version": "0.18.0", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "type": "commonjs", 9 | "repository": { 10 | "url": "https://github.com/chakra-ui/panda-vscode", 11 | "directory": "packages/ts-plugin" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "build": "tsup src/index.ts --format=cjs --dts", 21 | "build-fast": "tsup src/index.ts --format=cjs --no-dts", 22 | "dev": "yarn build-fast --watch" 23 | }, 24 | "devDependencies": { 25 | "@pandacss/extension-shared": "workspace:^", 26 | "typescript": "^5.4.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/ts-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import type ts from 'typescript/lib/tsserverlibrary' 2 | import { defaultSettings, type PandaVSCodeSettings } from '@pandacss/extension-shared' 3 | 4 | /** 5 | * @see https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin#decorator-creation 6 | */ 7 | function init(_modules: { typescript: typeof import('typescript/lib/tsserverlibrary') }) { 8 | // const ts = modules.typescript 9 | 10 | // Get a list of things to remove from the completion list from the config object. 11 | 12 | function create(info: ts.server.PluginCreateInfo) { 13 | // Diagnostic logging 14 | info.project.projectService.logger.info('[@pandacss/ts-plugin] init.create ok') 15 | 16 | // Set up decorator object 17 | const proxy: ts.LanguageService = Object.create(null) 18 | for (const k of Object.keys(info.languageService) as Array) { 19 | const x = info.languageService[k]! 20 | // @ts-expect-error - JS runtime trickery which is tricky to type tersely 21 | proxy[k] = (...args: Array<{}>) => x.apply(info.languageService, args) 22 | } 23 | 24 | // Remove specified entries from completion list 25 | proxy.getCompletionsAtPosition = (fileName, position, options) => { 26 | const prior = info.languageService.getCompletionsAtPosition(fileName, position, options) 27 | if (!prior) return 28 | 29 | const configPath = configPathByDocFilepath.get(fileName) 30 | if (!configPath) return prior 31 | 32 | const tokenNames = tokenNamesByConfigPath.get(configPath) 33 | if (!tokenNames) return prior 34 | 35 | if (!settings['completions.enabled']) return prior 36 | 37 | prior.entries = prior.entries.filter((e) => tokenNames.indexOf(e.name) < 0) 38 | 39 | const oldLength = prior.entries.length 40 | if (oldLength !== prior.entries.length) { 41 | const entriesRemoved = oldLength - prior.entries.length 42 | info.project.projectService.logger.info( 43 | `[@pandacss/ts-plugin] Removed ${entriesRemoved} entries from the completion list`, 44 | ) 45 | } 46 | 47 | return prior 48 | } 49 | 50 | return proxy 51 | } 52 | 53 | const tokenNamesByConfigPath = new Map() 54 | const configPathByDocFilepath = new Map() 55 | let settings = defaultSettings as PandaVSCodeSettings 56 | 57 | // https://code.visualstudio.com/api/references/contribution-points#contributes.typescriptServerPlugins 58 | function onConfigurationChanged(event: ConfigEvent) { 59 | if (event.type === 'setup') { 60 | const { configPath, tokenNames } = event.data 61 | tokenNamesByConfigPath.set(configPath, tokenNames) 62 | } 63 | 64 | if (event.type === 'active-doc') { 65 | const { activeDocumentFilepath, configPath } = event.data 66 | configPathByDocFilepath.set(activeDocumentFilepath, configPath) 67 | } 68 | 69 | if (event.type === 'update-settings') { 70 | settings = event.data 71 | } 72 | } 73 | 74 | return { create, onConfigurationChanged } 75 | } 76 | 77 | // https://code.visualstudio.com/api/references/vscode-api#extensions 78 | // @ts-ignore 79 | export = init 80 | 81 | type ConfigEvent = 82 | | { type: 'setup'; data: { configPath: string; tokenNames: string[] } } 83 | | { type: 'active-doc'; data: { activeDocumentFilepath: string; configPath: string } } 84 | | { type: 'update-settings'; data: PandaVSCodeSettings } 85 | -------------------------------------------------------------------------------- /packages/ts-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "index.ts"], 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo", 7 | "verbatimModuleSyntax": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | node_modules 12 | .env 13 | -------------------------------------------------------------------------------- /packages/vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # panda-css-vscode 2 | 3 | ## 0.3.1 4 | 5 | ### Patch Changes 6 | 7 | - 976f7a5: Fix the CI build output 8 | 9 | ## 0.3.0 10 | 11 | ### Minor Changes 12 | 13 | - bd5ca1b: Update Panda version to 0.37.2 (from 0.17.0), fixes a bunch of small issues 14 | 15 | ### Patch Changes 16 | 17 | - 4ff4a13: Support extendion activation on `.gjs` and `.gts` files for Next-gen Ember files 18 | - Updated dependencies [bd5ca1b] 19 | - @pandacss/ts-plugin@0.18.0 20 | 21 | ## 0.2.27 22 | 23 | ### Patch Changes 24 | 25 | - @pandacss/dev@0.17.0 26 | - @pandacss/preset-base@0.17.0 27 | - @pandacss/preset-panda@0.17.0 28 | - @pandacss/ts-plugin@0.17.0 29 | 30 | ## 0.2.26 31 | 32 | ### Patch Changes 33 | 34 | - Updated dependencies [36252b1d] 35 | - Updated dependencies [0f3bede5] 36 | - @pandacss/dev@0.16.0 37 | - @pandacss/preset-base@0.16.0 38 | - @pandacss/ts-plugin@0.16.0 39 | - @pandacss/preset-panda@0.16.0 40 | 41 | ## 0.2.25 42 | 43 | ### Patch Changes 44 | 45 | - @pandacss/dev@0.15.5 46 | - @pandacss/ts-plugin@0.15.5 47 | - @pandacss/preset-base@0.15.5 48 | - @pandacss/preset-panda@0.15.5 49 | 50 | ## 0.2.24 51 | 52 | ### Patch Changes 53 | 54 | - @pandacss/dev@0.15.4 55 | - @pandacss/ts-plugin@0.15.4 56 | - @pandacss/preset-base@0.15.4 57 | - @pandacss/preset-panda@0.15.4 58 | 59 | ## 0.2.23 60 | 61 | ### Patch Changes 62 | 63 | - @pandacss/dev@0.15.3 64 | - @pandacss/preset-base@0.15.3 65 | - @pandacss/preset-panda@0.15.3 66 | - @pandacss/ts-plugin@0.15.3 67 | 68 | ## 0.2.22 69 | 70 | ### Patch Changes 71 | 72 | - Updated dependencies [f3c30d60] 73 | - @pandacss/dev@0.15.2 74 | - @pandacss/preset-base@0.15.2 75 | - @pandacss/preset-panda@0.15.2 76 | - @pandacss/ts-plugin@0.15.2 77 | 78 | ## 0.2.21 79 | 80 | ### Patch Changes 81 | 82 | - @pandacss/dev@0.15.1 83 | - @pandacss/ts-plugin@0.15.1 84 | - @pandacss/preset-base@0.15.1 85 | - @pandacss/preset-panda@0.15.1 86 | 87 | ## 0.2.20 88 | 89 | ### Patch Changes 90 | 91 | - @pandacss/dev@0.15.0 92 | - @pandacss/preset-base@0.15.0 93 | - @pandacss/preset-panda@0.15.0 94 | - @pandacss/ts-plugin@0.15.0 95 | 96 | ## 0.2.19 97 | 98 | ### Patch Changes 99 | 100 | - Updated dependencies [6552d715] 101 | - @pandacss/dev@0.14.0 102 | - @pandacss/preset-base@0.14.0 103 | - @pandacss/preset-panda@0.14.0 104 | - @pandacss/ts-plugin@0.14.0 105 | 106 | ## 0.2.18 107 | 108 | ### Patch Changes 109 | 110 | - Updated dependencies [a5d7d514] 111 | - @pandacss/dev@0.13.1 112 | - @pandacss/ts-plugin@0.13.1 113 | - @pandacss/preset-base@0.13.1 114 | - @pandacss/preset-panda@0.13.1 115 | 116 | ## 0.2.17 117 | 118 | ### Patch Changes 119 | 120 | - Updated dependencies [04b5fd6c] 121 | - @pandacss/dev@0.13.0 122 | - @pandacss/ts-plugin@0.13.0 123 | - @pandacss/preset-base@0.13.0 124 | - @pandacss/preset-panda@0.13.0 125 | 126 | ## 0.2.16 127 | 128 | ### Patch Changes 129 | 130 | - @pandacss/dev@0.12.2 131 | - @pandacss/ts-plugin@0.12.2 132 | - @pandacss/preset-base@0.12.2 133 | - @pandacss/preset-panda@0.12.2 134 | 135 | ## 0.2.15 136 | 137 | ### Patch Changes 138 | 139 | - @pandacss/dev@0.12.1 140 | - @pandacss/ts-plugin@0.12.1 141 | - @pandacss/preset-base@0.12.1 142 | - @pandacss/preset-panda@0.12.1 143 | 144 | ## 0.2.14 145 | 146 | ### Patch Changes 147 | 148 | - Updated dependencies [75ba44de] 149 | - Updated dependencies [7a041b16] 150 | - Updated dependencies [4c8c1715] 151 | - Updated dependencies [bf2ff391] 152 | - @pandacss/dev@0.12.0 153 | - @pandacss/preset-base@0.12.0 154 | - @pandacss/ts-plugin@0.12.0 155 | - @pandacss/preset-panda@0.12.0 156 | 157 | ## 0.2.13 158 | 159 | ### Patch Changes 160 | 161 | - @pandacss/dev@0.11.1 162 | - @pandacss/preset-base@0.11.1 163 | - @pandacss/preset-panda@0.11.1 164 | - @pandacss/ts-plugin@0.11.1 165 | 166 | ## 0.2.12 167 | 168 | ### Patch Changes 169 | 170 | - Updated dependencies [cde9702e] 171 | - Updated dependencies [164fbf27] 172 | - Updated dependencies [811f4fb1] 173 | - @pandacss/dev@0.11.0 174 | - @pandacss/preset-base@0.11.0 175 | - @pandacss/preset-panda@0.11.0 176 | - @pandacss/ts-plugin@0.11.0 177 | 178 | ## 0.2.11 179 | 180 | ### Patch Changes 181 | 182 | - Updated dependencies [00d11a8b] 183 | - Updated dependencies [1972b4fa] 184 | - Updated dependencies [a669f4d5] 185 | - @pandacss/preset-base@0.10.0 186 | - @pandacss/dev@0.10.0 187 | - @pandacss/preset-panda@0.10.0 188 | - @pandacss/ts-plugin@0.10.0 189 | 190 | ## 0.2.10 191 | 192 | ### Patch Changes 193 | 194 | - Updated dependencies [c08de87f] 195 | - @pandacss/preset-base@0.9.0 196 | - @pandacss/dev@0.9.0 197 | - @pandacss/preset-panda@0.9.0 198 | - @pandacss/ts-plugin@0.9.0 199 | 200 | ## 0.2.9 201 | 202 | ### Patch Changes 203 | 204 | - 92a09703: Temporarily disable extension in svelte/vue files 205 | - Updated dependencies [f7da0aea] 206 | - Updated dependencies [be0ad578] 207 | - @pandacss/dev@0.8.0 208 | - @pandacss/preset-base@0.8.0 209 | - @pandacss/preset-panda@0.8.0 210 | - @pandacss/ts-plugin@0.8.0 211 | 212 | ## 0.2.8 213 | 214 | ### Patch Changes 215 | 216 | - Updated dependencies [60a77841] 217 | - Updated dependencies [d9eeba60] 218 | - @pandacss/preset-base@0.7.0 219 | - @pandacss/dev@0.7.0 220 | - @pandacss/preset-panda@0.7.0 221 | - @pandacss/ts-plugin@0.7.0 222 | 223 | ## 0.2.7 224 | 225 | ### Patch Changes 226 | 227 | - Updated dependencies [97fbe63f] 228 | - Updated dependencies [08d33e0f] 229 | - Updated dependencies [f7aff8eb] 230 | - Updated dependencies [21f1326b] 231 | - @pandacss/preset-base@0.6.0 232 | - @pandacss/dev@0.6.0 233 | - @pandacss/ts-plugin@0.6.0 234 | - @pandacss/preset-panda@0.6.0 235 | 236 | ## 0.2.6 237 | 238 | ### Patch Changes 239 | 240 | - e116cbb8: Enable extension in .astro/svelte/vue files 241 | - 91b2dbf0: Fix issue where extension build fails due to the use of `pathe` 242 | - Updated dependencies [5b09ab3b] 243 | - Updated dependencies [f9247e52] 244 | - Updated dependencies [91b2dbf0] 245 | - @pandacss/dev@0.5.1 246 | - @pandacss/ts-plugin@0.5.1 247 | - @pandacss/preset-base@0.5.1 248 | - @pandacss/preset-panda@0.5.1 249 | 250 | ## 0.2.5 251 | 252 | ### Patch Changes 253 | 254 | - Updated dependencies [3a87cff8] 255 | - @pandacss/preset-panda@0.5.0 256 | - @pandacss/dev@0.5.0 257 | - @pandacss/preset-base@0.5.0 258 | - @pandacss/ts-plugin@0.5.0 259 | 260 | ## 0.2.4 261 | 262 | ### Patch Changes 263 | 264 | - Updated dependencies [e8024347] 265 | - Updated dependencies [8991b1e4] 266 | - Updated dependencies [d00eb17c] 267 | - Updated dependencies [9156c1c6] 268 | - Updated dependencies [54a8913c] 269 | - Updated dependencies [0f36ebad] 270 | - Updated dependencies [a48e5b00] 271 | - @pandacss/preset-base@0.4.0 272 | - @pandacss/dev@0.4.0 273 | - @pandacss/preset-panda@0.4.0 274 | - @pandacss/ts-plugin@0.4.0 275 | 276 | ## 0.2.3 277 | 278 | ### Patch Changes 279 | 280 | - Updated dependencies [c8bee958] 281 | - @pandacss/dev@0.3.2 282 | - @pandacss/ts-plugin@0.3.2 283 | - @pandacss/preset-base@0.3.2 284 | - @pandacss/preset-panda@0.3.2 285 | 286 | ## 0.2.2 287 | 288 | ### Patch Changes 289 | 290 | - Updated dependencies [efd79d83] 291 | - @pandacss/dev@0.3.1 292 | - @pandacss/preset-base@0.3.1 293 | - @pandacss/preset-panda@0.3.1 294 | - @pandacss/ts-plugin@0.3.1 295 | 296 | ## 0.2.1 297 | 298 | ### Patch Changes 299 | 300 | - Updated dependencies [02f4daf7] 301 | - Updated dependencies [bd5c049b] 302 | - @pandacss/ts-plugin@0.3.0 303 | - @pandacss/preset-base@0.3.0 304 | - @pandacss/preset-panda@0.3.0 305 | - @pandacss/dev@0.3.0 306 | -------------------------------------------------------------------------------- /packages/vscode/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Chakra UI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vscode/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chakra-ui/panda-vscode/f50ecaca5255e50e0913a76eb79b4cbdb21dd7f6/packages/vscode/assets/logo.png -------------------------------------------------------------------------------- /packages/vscode/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /packages/vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "chakra-ui", 3 | "packageManager": "yarn@4.0.1", 4 | "name": "panda-css-vscode", 5 | "displayName": "🐼 Panda CSS", 6 | "description": "The official Panda CSS plugin for VS Code", 7 | "version": "0.3.1", 8 | "private": true, 9 | "engines": { 10 | "vscode": "^1.67.0" 11 | }, 12 | "categories": [ 13 | "Programming Languages", 14 | "Linters", 15 | "Other" 16 | ], 17 | "activationEvents": [ 18 | "onStartupFinished" 19 | ], 20 | "icon": "assets/logo.png", 21 | "license": "MIT", 22 | "keywords": [ 23 | "css", 24 | "pandacss", 25 | "design tokens", 26 | "documentation", 27 | "framework", 28 | "design system", 29 | "design", 30 | "tokens", 31 | "panda", 32 | "semantic", 33 | "w3c" 34 | ], 35 | "main": "dist/index.js", 36 | "files": [ 37 | "dist", 38 | "assets", 39 | "package.json", 40 | "README.md", 41 | "LICENSE" 42 | ], 43 | "preview": true, 44 | "contributes": { 45 | "commands": [ 46 | { 47 | "command": "panda-css-vscode.restart", 48 | "title": "Restart Panda Server", 49 | "category": "Panda" 50 | }, 51 | { 52 | "command": "panda-css-vscode.show-output", 53 | "title": "Show panda output", 54 | "category": "Panda" 55 | }, 56 | { 57 | "command": "panda-css-vscode.open-config", 58 | "title": "Open current panda config", 59 | "category": "Panda" 60 | } 61 | ], 62 | "configuration": { 63 | "title": "Panda", 64 | "properties": { 65 | "panda.color-hints.enabled": { 66 | "type": "boolean", 67 | "default": true, 68 | "title": "Color hints", 69 | "description": "Enable all color hints" 70 | }, 71 | "panda.color-hints.color-preview.enabled": { 72 | "type": "boolean", 73 | "default": false, 74 | "title": "Show color preview on hover", 75 | "description": "Enable color picker preview on hover, will still show a color hint if disabled" 76 | }, 77 | "panda.color-hints.semantic-tokens.enabled": { 78 | "type": "boolean", 79 | "default": true, 80 | "title": "Color hints", 81 | "description": "Enable semantic tokens multiple color hints" 82 | }, 83 | "panda.rem-to-px.enabled": { 84 | "type": "boolean", 85 | "default": false, 86 | "title": "Rem to pixels conversion", 87 | "description": "Enable rem to px conversion" 88 | }, 89 | "panda.completions.enabled": { 90 | "type": "boolean", 91 | "default": true, 92 | "title": "Completions", 93 | "description": "Enable enhanced completions" 94 | }, 95 | "panda.completions.token-fn.enabled": { 96 | "type": "boolean", 97 | "default": true, 98 | "title": "token fn completions", 99 | "description": "Enable token references completions with token(xxx.yyy.zzz) or {xxx.yyy.zzz}" 100 | }, 101 | "panda.diagnostics.enabled": { 102 | "type": "boolean", 103 | "default": false, 104 | "title": "Diagnostics", 105 | "description": "Enable all diagnostics" 106 | }, 107 | "panda.diagnostics.invalid-token-path": { 108 | "title": "Invalid token path", 109 | "type": "string", 110 | "enum": [ 111 | "disable", 112 | "hint", 113 | "information", 114 | "warning", 115 | "error" 116 | ], 117 | "default": "warning", 118 | "markdownDescription": "Changes the severity of the diagnostic for invalid token paths \n(ex: `css({ border: '1px solid token(xxx) })`)" 119 | }, 120 | "panda.hovers.enabled": { 121 | "type": "boolean", 122 | "default": true, 123 | "title": "Hover previews", 124 | "description": "Enable all hovers" 125 | }, 126 | "panda.hovers.instances.enabled": { 127 | "type": "boolean", 128 | "default": false, 129 | "title": "Instance hover preview", 130 | "description": "Enable instance css preview on hover" 131 | }, 132 | "panda.hovers.tokens.enabled": { 133 | "type": "boolean", 134 | "default": true, 135 | "title": "Show token value on hover", 136 | "description": "Enable tokens value preview on hover" 137 | }, 138 | "panda.hovers.tokens.css-preview.enabled": { 139 | "type": "boolean", 140 | "default": false, 141 | "title": "Show CSS preview on hover", 142 | "description": "Enable tokens css preview on hover" 143 | }, 144 | "panda.hovers.tokens.conditions.enabled": { 145 | "type": "boolean", 146 | "default": false, 147 | "title": "Conditions hover preview", 148 | "description": "Enable conditions css preview on hover" 149 | }, 150 | "panda.hovers.semantic-colors.enabled": { 151 | "type": "boolean", 152 | "default": false, 153 | "title": "Semantic Colors hover preview", 154 | "description": "Enable semantic colors preview on hover" 155 | }, 156 | "panda.hovers.display.mode": { 157 | "title": "Hover preview display mode", 158 | "type": "string", 159 | "enum": [ 160 | "optimized", 161 | "nested", 162 | "minified" 163 | ], 164 | "default": "optimized", 165 | "markdownDescription": "Changes the display mode of the css preview on hover" 166 | }, 167 | "panda.hovers.display.force-hash": { 168 | "type": "boolean", 169 | "default": false, 170 | "title": "Hover preview force className hashing", 171 | "description": "Force className hashing on hover preview, overriding the panda.config `hash` option" 172 | }, 173 | "panda.inlay-hints.enabled": { 174 | "type": "boolean", 175 | "default": true, 176 | "title": "Inlay hints", 177 | "description": "Enable inlay hints next to dimensions related tokens" 178 | }, 179 | "panda.debug.enabled": { 180 | "type": "boolean", 181 | "default": false, 182 | "title": "Debug", 183 | "description": "Enable debug logs" 184 | } 185 | } 186 | }, 187 | "typescriptServerPlugins": [ 188 | { 189 | "name": "@pandacss/ts-plugin", 190 | "enableForWorkspaceTypeScriptVersions": true 191 | } 192 | ] 193 | }, 194 | "repository": { 195 | "url": "https://github.com/chakra-ui/panda-vscode", 196 | "directory": "packages/vscode" 197 | }, 198 | "scripts": { 199 | "build:all": "yarn workspaces foreach -R run build", 200 | "build": "tsup", 201 | "dev": "tsup --watch src/index.ts --watch ../language-server/dist/index.js --watch ../shared/dist/index.js --watch ../ts-plugin/dist/index.js", 202 | "clean": "rimraf dist node_modules", 203 | "typecheck": "tsc --noEmit", 204 | "release": "yarn build --silent && tsx ./scripts/publish.ts", 205 | "pkg": "yarn vsix-builder package" 206 | }, 207 | "dependencies": { 208 | "@pandacss/dev": "^0.37.2", 209 | "@pandacss/preset-base": "^0.37.2", 210 | "@pandacss/preset-panda": "^0.37.2", 211 | "@pandacss/ts-plugin": "workspace:^", 212 | "esbuild": "^0.20.2", 213 | "pathe": "1.1.2" 214 | }, 215 | "bundledDependencies": [ 216 | "@pandacss/ts-plugin", 217 | "@pandacss/dev", 218 | "@pandacss/preset-base", 219 | "@pandacss/preset-panda", 220 | "pathe", 221 | "esbuild" 222 | ], 223 | "devDependencies": { 224 | "@pandacss/extension-shared": "workspace:^", 225 | "@pandacss/language-server": "workspace:^", 226 | "@pandacss/types": "^0.37.2", 227 | "@pandacss/vsix-builder": "workspace:^", 228 | "@pnpm/find-workspace-dir": "^6.0.3", 229 | "@pnpm/find-workspace-packages": "^6.0.9", 230 | "@pnpm/types": "^9.4.2", 231 | "@types/node": "20.11.30", 232 | "@types/semver": "^7.5.8", 233 | "@types/vscode": "^1.87.0", 234 | "@vscode/vsce": "^2.24.0", 235 | "@vue/compiler-sfc": "^3.4.21", 236 | "dotenv": "^16.4.5", 237 | "execa": "8.0.1", 238 | "fs-extra": "11.2.0", 239 | "lightningcss-wasm": "^1.24.1", 240 | "semver": "^7.6.0", 241 | "tsup": "8.0.2", 242 | "tsx": "^4.7.1", 243 | "typescript": "^5.4.2", 244 | "vscode": "^1.1.37", 245 | "vscode-languageclient": "^9.0.1" 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /packages/vscode/scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import childProcess from 'child_process' 2 | import * as semver from 'semver' 3 | import { config } from 'dotenv' 4 | 5 | const execaSync = childProcess.execSync 6 | 7 | config() 8 | 9 | const { version } = require('../package.json') 10 | 11 | const releaseType = process.env.VSCE_RELEASE_TYPE 12 | const target = process.env.VSCE_TARGET 13 | 14 | const tokens = { 15 | vscode: releaseType === 'dry-run' ? 'dry-run' : process.env.VSCE_TOKEN, 16 | openvsx: releaseType === 'dry-run' ? 'dry-run' : process.env.OVSX_TOKEN, 17 | } 18 | 19 | const hasTokens = tokens.vscode !== undefined 20 | if (!hasTokens) { 21 | throw new Error('Cannot publish extension without tokens.') 22 | } 23 | 24 | const today = process.env.VSCE_RELEASE_VERSION ?? new Date().getTime().toString().slice(0, 8) 25 | const currentVersion = semver.valid(version) 26 | 27 | if (!currentVersion) { 28 | throw new Error('Cannot get the current version number from package.json') 29 | } 30 | 31 | const rcVersion = semver.inc(currentVersion, 'minor')?.replace(/\.\d+$/, `.${today}`) 32 | if (!rcVersion) { 33 | throw new Error("Could not populate the current version number for rc's build.") 34 | } 35 | 36 | const commands = { 37 | vscode_package: `yarn vsix-builder package ${rcVersion} --target ${target} -o panda.vsix`, 38 | vscode_publish: `yarn vsce publish --packagePath panda.vsix --pat ${process.env.VSCE_TOKEN}`, 39 | // rc release: publish to VS Code Marketplace with today's date as patch number 40 | vscode_package_rc: `yarn vsix-builder package ${rcVersion} --pre-release --target ${target} -o panda.vsix`, 41 | vscode_rc: `yarn vsce publish --pre-release --packagePath panda.vsix --pat ${process.env.VSCE_TOKEN}`, 42 | // To publish to the open-vsx registry 43 | openvsx_publish: `npx ovsx publish panda.vsix --pat ${process.env.OVSX_TOKEN}`, 44 | } 45 | 46 | console.log('[vsce:package]', commands.vscode_package_rc, target) 47 | switch (releaseType) { 48 | case 'rc': 49 | execaSync(commands.vscode_package_rc, { stdio: 'inherit' }) 50 | break 51 | case 'stable': 52 | execaSync(commands.vscode_package, { stdio: 'inherit' }) 53 | break 54 | default: 55 | console.log('[vsce:package]', "Skipping 'vsce package' step.") 56 | } 57 | 58 | console.log('[vsce:publish] publishing', rcVersion, target) 59 | switch (releaseType) { 60 | case 'rc': 61 | if (!rcVersion || !semver.valid(rcVersion) || semver.valid(rcVersion) === currentVersion) { 62 | throw new Error('Cannot publish rc build with an invalid version number: ' + rcVersion) 63 | } 64 | execaSync(commands.vscode_rc, { stdio: 'inherit' }) 65 | break 66 | 67 | case 'stable': 68 | execaSync(commands.vscode_rc, { stdio: 'inherit' }) 69 | execaSync(commands.openvsx_publish, { stdio: 'inherit' }) 70 | break 71 | 72 | case 'dry-run': 73 | console.info('[vsce:publish]', `Current version: ${currentVersion}.`) 74 | console.info('[vsce:publish]', `Pre-release version for rc's build: ${rcVersion}.`) 75 | break 76 | 77 | default: 78 | throw new Error(`Invalid release type: ${releaseType}`) 79 | } 80 | -------------------------------------------------------------------------------- /packages/vscode/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { LanguageClient } from 'vscode-languageclient/node' 3 | import { PandaExtension } from './panda-extension' 4 | 5 | const debug = false 6 | let client: LanguageClient | undefined 7 | 8 | export async function activate(context: vscode.ExtensionContext) { 9 | debug && console.log('activate') 10 | 11 | const extension = new PandaExtension(context, debug) 12 | await extension.connectToTsPlugin() 13 | await extension.start() 14 | client = extension.client 15 | 16 | debug && console.log('activation successful !') 17 | } 18 | 19 | export function deactivate(): Thenable | undefined { 20 | debug && console.log('deactivate') 21 | 22 | if (!client) { 23 | return undefined 24 | } 25 | 26 | debug && console.log('stoppping...') 27 | return client.stop() 28 | } 29 | -------------------------------------------------------------------------------- /packages/vscode/src/panda-extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { 3 | LanguageClient, 4 | type LanguageClientOptions, 5 | type MessageSignature, 6 | type ServerOptions, 7 | TransportKind, 8 | type ColorProviderMiddleware, 9 | } from 'vscode-languageclient/node' 10 | import { join } from 'pathe' 11 | import { defaultSettings, getFlattenedSettings } from '@pandacss/extension-shared' 12 | import { getTsApi, type TsLanguageFeaturesApiV0 } from './typescript-language-features' 13 | 14 | // Client entrypoint 15 | const docSelector: vscode.DocumentSelector = [ 16 | 'typescript', 17 | 'typescriptreact', 18 | 'javascript', 19 | 'javascriptreact', 20 | 'astro', 21 | 'glimmer-js', 22 | 'glimmer-ts', 23 | // TODO re-enable whenever we figured out how to map transformed file AST nodes to their original positions 24 | // 'svelte', 25 | // 'vue', 26 | ] 27 | 28 | const getFreshPandaSettings = () => { 29 | return getFlattenedSettings((vscode.workspace.getConfiguration('panda') as any) ?? defaultSettings) 30 | } 31 | 32 | const getActiveDoc = () => vscode.window.activeTextEditor?.document 33 | const getActiveDocFilepath = () => getActiveDoc()?.uri.fsPath 34 | 35 | export class PandaExtension { 36 | client: LanguageClient 37 | statusBarItem: vscode.StatusBarItem 38 | tsApi: TsLanguageFeaturesApiV0 | undefined 39 | colorDecorationType: vscode.TextEditorDecorationType | undefined 40 | 41 | constructor(private context: vscode.ExtensionContext, private debug: boolean) { 42 | this.log('new PandaExtension') 43 | 44 | this.statusBarItem = this.createStatusBarItem() 45 | this.client = this.createLanguageClient() 46 | 47 | this.registerSubscriptions() 48 | this.registerCommands() 49 | } 50 | 51 | private log(...args: any[]) { 52 | this.debug && console.log(...args) 53 | } 54 | 55 | private createLanguageClient() { 56 | // The server is implemented in node 57 | const serverModule = this.context.asAbsolutePath(join('dist', 'server.js')) 58 | 59 | // The debug options for the server 60 | // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging 61 | const debugOptions = this.debug ? { execArgv: ['--nolazy', '--inspect=6099'] } : {} 62 | 63 | // If the extension is launched in debug mode then the debug server options are used 64 | // Otherwise the run options are used 65 | const serverOptions: ServerOptions = { 66 | run: { module: serverModule, transport: TransportKind.ipc }, 67 | debug: { 68 | module: serverModule, 69 | transport: TransportKind.ipc, 70 | options: debugOptions, 71 | }, 72 | } 73 | 74 | const activeDocument = vscode.window.activeTextEditor?.document 75 | 76 | // Options to control the language client 77 | const clientOptions: LanguageClientOptions = { 78 | documentSelector: docSelector as string[], 79 | diagnosticCollectionName: 'panda', 80 | synchronize: { 81 | fileEvents: [vscode.workspace.createFileSystemWatcher('**/*/panda.config.{ts,js,cjs,mjs}')], 82 | }, 83 | initializationOptions: () => { 84 | return { 85 | settings: getFreshPandaSettings(), 86 | activeDocumentFilepath: activeDocument?.uri.fsPath, 87 | } 88 | }, 89 | middleware: { 90 | provideDocumentColors: this.getColorMiddleware(), 91 | }, 92 | } 93 | 94 | // Create the language client and start the client. 95 | const client = new LanguageClient('panda', 'Panda IntelliSense', serverOptions, clientOptions) 96 | client.outputChannel.appendLine('Starting PandaCss client extension...') 97 | 98 | // global error handler 99 | client.handleFailedRequest = ( 100 | type: MessageSignature, 101 | token: vscode.CancellationToken | undefined, 102 | error: any, 103 | defaultValue: any, 104 | showNotification?: boolean, 105 | ) => { 106 | console.log('handleFailedRequest', { type, token, error, defaultValue, showNotification }) 107 | console.trace() 108 | return defaultValue 109 | } 110 | 111 | return client 112 | } 113 | 114 | async connectToTsPlugin() { 115 | try { 116 | this.log('connecting to TS plugin...') 117 | this.tsApi = await getTsApi() 118 | this.log('connected to TS plugin !') 119 | } catch (err) { 120 | this.log('error loading TS', err) 121 | } 122 | } 123 | 124 | async start() { 125 | try { 126 | // Start the client. This will also launch the server 127 | this.statusBarItem.text = '🐼 Starting...' 128 | this.log('starting...') 129 | 130 | await this.client.start() 131 | 132 | this.log('client started !') 133 | this.statusBarItem.text = '🐼' 134 | this.statusBarItem.tooltip = 'Open current panda config' 135 | } catch (err) { 136 | this.log('error starting client', err) 137 | } 138 | } 139 | 140 | private createStatusBarItem() { 141 | const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left) 142 | statusBarItem.text = '🐼 Loading...' 143 | statusBarItem.show() 144 | statusBarItem.command = 'panda-css-vscode.open-config' 145 | return statusBarItem 146 | } 147 | 148 | private registerSubscriptions() { 149 | // client 150 | this.onClearColors() 151 | 152 | // language-server 153 | this.syncActiveDocument() 154 | this.onLanguageServerReady() 155 | 156 | // ts-plugin 157 | this.syncExtensionSettings() 158 | this.syncTokenNames() 159 | this.onDidChangeActivePandaConfigPath() 160 | } 161 | 162 | /** 163 | * synchronize the active document with the extension LSP 164 | * so that we can retrieve the corresponding configPath (xxx/yyy/panda.config.ts) 165 | */ 166 | private syncActiveDocument() { 167 | this.context.subscriptions.push( 168 | vscode.window.onDidChangeActiveTextEditor((editor) => { 169 | if (!editor) return 170 | if (!this.client.isRunning()) return 171 | if (editor.document.uri.scheme !== 'file') return 172 | 173 | const activeDocumentFilepath = editor.document.uri.fsPath 174 | this.client.sendNotification('$/panda-active-document-changed', { activeDocumentFilepath }) 175 | }), 176 | ) 177 | } 178 | 179 | /** 180 | * synchronize the extension settings with the TS server plugin 181 | * so that we can disable removing built-ins from the completion list if the user has disabled completions in the settings 182 | */ 183 | private syncExtensionSettings() { 184 | this.context.subscriptions.push( 185 | vscode.workspace.onDidChangeConfiguration((update) => { 186 | this.log('onDidChangeConfiguration', update) 187 | if (!this.tsApi) return 188 | 189 | const settings = getFreshPandaSettings() 190 | this.tsApi.configurePlugin('@pandacss/ts-plugin', { type: 'update-settings', data: settings }) 191 | }), 192 | ) 193 | } 194 | 195 | /** 196 | * synchronize token names by configPath to the TS server plugin 197 | */ 198 | private syncTokenNames() { 199 | this.context.subscriptions.push( 200 | this.client.onNotification('$/panda-token-names', (notif: { configPath: string; tokenNames: string[] }) => { 201 | if (!this.tsApi) return 202 | 203 | this.tsApi.configurePlugin('@pandacss/ts-plugin', { type: 'setup', data: notif }) 204 | this.log({ type: 'setup', data: notif }) 205 | }), 206 | ) 207 | } 208 | 209 | /** 210 | * send initial config to the TS server plugin 211 | * so that it doesn't have to wait for the first file to be opened or settings to be changed 212 | */ 213 | private onLanguageServerReady() { 214 | this.context.subscriptions.push( 215 | this.client.onNotification('$/panda-lsp-ready', async () => { 216 | const activeDocumentFilepath = getActiveDocFilepath() 217 | if (!activeDocumentFilepath) return 218 | 219 | try { 220 | // no need to await this one 221 | this.client.sendNotification('$/panda-active-document-changed', { activeDocumentFilepath }) 222 | if (!this.tsApi) return 223 | 224 | const configPath = await this.client.sendRequest('$/get-config-path', { activeDocumentFilepath }) 225 | if (!configPath) return 226 | 227 | this.tsApi.configurePlugin('@pandacss/ts-plugin', { 228 | type: 'active-doc', 229 | data: { activeDocumentFilepath, configPath }, 230 | }) 231 | 232 | const settings = getFreshPandaSettings() 233 | this.tsApi.configurePlugin('@pandacss/ts-plugin', { 234 | type: 'update-settings', 235 | data: settings, 236 | }) 237 | } catch (err) { 238 | this.log('error sending doc notif', err) 239 | } 240 | }), 241 | ) 242 | } 243 | 244 | /** 245 | * synchronize the active document + its config path with the TS server plugin 246 | * so that it can remove the corresponding built-ins tokens names from the completion list 247 | */ 248 | private onDidChangeActivePandaConfigPath() { 249 | this.context.subscriptions.push( 250 | this.client.onNotification( 251 | '$/panda-doc-config-path', 252 | (notif: { activeDocumentFilepath: string; configPath: string }) => { 253 | if (!this.tsApi) return 254 | 255 | this.tsApi.configurePlugin('@pandacss/ts-plugin', { type: 'active-doc', data: notif }) 256 | this.log({ type: 'active-doc', data: notif }) 257 | }, 258 | ), 259 | ) 260 | } 261 | 262 | /** 263 | * Remove the color hints decorators when settings change 264 | */ 265 | private onClearColors() { 266 | let colorDecorationType: vscode.TextEditorDecorationType | undefined 267 | const clearColors = () => { 268 | if (colorDecorationType) { 269 | colorDecorationType.dispose() 270 | colorDecorationType = undefined 271 | } 272 | } 273 | this.context.subscriptions.push({ dispose: clearColors }) 274 | this.client.onNotification('$/clear-colors', clearColors) 275 | } 276 | 277 | /** 278 | * emulate color hints with decorators to prevent the built-in vscode ColorPicker from showing on hover 279 | */ 280 | private getColorMiddleware(): ColorProviderMiddleware['provideDocumentColors'] { 281 | const provideDocumentColors: ColorProviderMiddleware['provideDocumentColors'] = async (document, token, next) => { 282 | const settings = getFreshPandaSettings() 283 | if (!settings['color-hints.enabled']) return next(document, token) 284 | if (settings['color-hints.color-preview.enabled']) return next(document, token) 285 | 286 | if (!this.colorDecorationType) { 287 | this.colorDecorationType = vscode.window.createTextEditorDecorationType({ 288 | before: { 289 | width: '0.8em', 290 | height: '0.8em', 291 | contentText: ' ', 292 | border: '0.1em solid', 293 | margin: '0.1em 0.2em 0', 294 | }, 295 | dark: { 296 | before: { 297 | borderColor: '#eeeeee', 298 | }, 299 | }, 300 | light: { 301 | before: { 302 | borderColor: '#000000', 303 | }, 304 | }, 305 | }) 306 | } 307 | 308 | const colors = (await next(document, token)) ?? [] 309 | const editor = vscode.window.visibleTextEditors.find((editor) => editor.document === document) 310 | 311 | editor?.setDecorations( 312 | this.colorDecorationType, 313 | colors.map(({ range, color }) => { 314 | return { 315 | range, 316 | renderOptions: { 317 | before: { 318 | backgroundColor: `rgba(${color.red * 255}, ${color.green * 255}, ${color.blue * 255}, ${color.alpha})`, 319 | }, 320 | }, 321 | } 322 | }), 323 | ) 324 | return [] 325 | } 326 | 327 | return provideDocumentColors 328 | } 329 | 330 | private registerCommands() { 331 | this.registerShowOutputCommand() 332 | this.registerRestartCommand() 333 | this.registerOpenConfigCommand() 334 | } 335 | 336 | private registerRestartCommand() { 337 | const restartCmd = vscode.commands.registerCommand('panda-css-vscode.restart', async () => { 338 | this.statusBarItem.text = '🐼 Restarting...' 339 | this.statusBarItem.show() 340 | 341 | this.log('restarting...') 342 | await this.client.restart() 343 | 344 | this.client.outputChannel.show(true) 345 | this.statusBarItem.hide() 346 | this.log('restarted !') 347 | }) 348 | 349 | this.context.subscriptions.push(restartCmd) 350 | } 351 | 352 | private registerShowOutputCommand() { 353 | const showOutputCmd = vscode.commands.registerCommand('panda-css-vscode.show-output', async () => { 354 | // Show and focus the output channel 355 | this.client.outputChannel.show(true) 356 | }) 357 | 358 | this.context.subscriptions.push(showOutputCmd) 359 | } 360 | 361 | private registerOpenConfigCommand() { 362 | const openConfigCmd = vscode.commands.registerCommand('panda-css-vscode.open-config', async () => { 363 | const configPath = await this.client.sendRequest('$/get-config-path') 364 | if (!configPath) return 365 | 366 | const configUri = vscode.Uri.file(configPath) 367 | const configDoc = await vscode.workspace.openTextDocument(configUri) 368 | await vscode.window.showTextDocument(configDoc) 369 | }) 370 | 371 | this.context.subscriptions.push(openConfigCmd) 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /packages/vscode/src/server.ts: -------------------------------------------------------------------------------- 1 | import '@pandacss/language-server' 2 | -------------------------------------------------------------------------------- /packages/vscode/src/typescript-language-features.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | interface TsLanguageFeatures { 4 | getAPI(version: 0): TsLanguageFeaturesApiV0 | undefined 5 | } 6 | 7 | export interface TsLanguageFeaturesApiV0 { 8 | configurePlugin(pluginId: string, configuration: Record): void 9 | onCompletionAccepted( 10 | cb: (item: vscode.CompletionItem & { document: vscode.TextDocument; tsEntry: any }) => void | Promise, 11 | ): void 12 | } 13 | 14 | export async function getTsApi() { 15 | // Get the TS extension 16 | const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features') 17 | if (!tsExtension) { 18 | return 19 | } 20 | 21 | await tsExtension.activate() 22 | 23 | // Get the API from the TS extension 24 | if (!tsExtension.exports || !tsExtension.exports.getAPI) { 25 | return 26 | } 27 | 28 | return tsExtension.exports.getAPI(0) 29 | } 30 | -------------------------------------------------------------------------------- /packages/vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "index.ts"], 4 | "compilerOptions": { 5 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/vscode/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig } from 'tsup' 3 | 4 | const aliases = { 5 | '@vue/compiler-sfc': '@vue/compiler-sfc/dist/compiler-sfc.esm-browser.js', 6 | lightningcss: 'lightningcss-wasm/index.mjs', 7 | } 8 | 9 | const nodeModulesPath = path.resolve(__dirname, 'node_modules') 10 | 11 | export default defineConfig({ 12 | entry: ['src/index.ts', 'src/server.ts'], 13 | format: ['cjs'], 14 | external: ['vscode', 'esbuild', 'lightningcss'], 15 | minify: true, 16 | outDir: 'dist', 17 | clean: true, 18 | shims: true, 19 | esbuildPlugins: [ 20 | { 21 | name: 'resolve-alias-plugin', 22 | setup(build) { 23 | build.onResolve({ filter: /.*/ }, (args) => { 24 | for (const alias in aliases) { 25 | if (args.path.startsWith(alias)) { 26 | const updated = path.resolve(nodeModulesPath, aliases[alias as keyof typeof aliases]) 27 | 28 | return { path: updated } 29 | } 30 | } 31 | return null 32 | }) 33 | }, 34 | }, 35 | ], 36 | }) 37 | -------------------------------------------------------------------------------- /packages/vsix-builder/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pandacss/vsix-builder 2 | 3 | ## 0.18.0 4 | 5 | ### Minor Changes 6 | 7 | - bd5ca1b: Update Panda version to 0.37.2 (from 0.17.0), fixes a bunch of small issues 8 | 9 | ## 0.17.0 10 | 11 | ## 0.16.0 12 | 13 | ## 0.15.5 14 | 15 | ## 0.15.4 16 | 17 | ## 0.15.3 18 | 19 | ## 0.15.2 20 | 21 | ## 0.15.1 22 | 23 | ## 0.15.0 24 | 25 | ## 0.14.0 26 | 27 | ## 0.13.1 28 | 29 | ## 0.13.0 30 | 31 | ## 0.12.2 32 | 33 | ## 0.12.1 34 | 35 | ## 0.12.0 36 | 37 | ## 0.11.1 38 | 39 | ## 0.11.0 40 | 41 | ## 0.10.0 42 | 43 | ## 0.9.0 44 | 45 | ## 0.8.0 46 | 47 | ## 0.7.0 48 | 49 | ## 0.6.0 50 | 51 | ## 0.5.1 52 | 53 | ## 0.5.0 54 | 55 | ## 0.4.0 56 | 57 | ## 0.3.2 58 | 59 | ## 0.3.1 60 | 61 | ## 0.3.0 62 | -------------------------------------------------------------------------------- /packages/vsix-builder/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('./dist/cli-default.js') 4 | -------------------------------------------------------------------------------- /packages/vsix-builder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pandacss/vsix-builder", 3 | "description": "Forked version of @vscode/vsce to support symlinks", 4 | "version": "0.18.0", 5 | "license": "MIT", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "bin": "bin.js", 9 | "repository": { 10 | "url": "https://github.com/chakra-ui/panda-vscode", 11 | "directory": "packages/vsix-builder" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "tsup src/cli-*.ts src/index.ts --format=cjs --dts --shims --clean", 18 | "build-fast": "tsup src/cli-*.ts src/index.ts --format=cjs --no-dts --shims", 19 | "dev": "yarn build-fast --watch", 20 | "clean": "rimraf dist", 21 | "typecheck": "tsc --noEmit" 22 | }, 23 | "devDependencies": { 24 | "@types/mime": "^1", 25 | "@types/npm-packlist": "^3.0.0", 26 | "@types/read": "^0.0.29", 27 | "@types/semver": "^7.5.4", 28 | "@types/tar-stream": "^2.2.2", 29 | "@types/xml2js": "^0.4.11", 30 | "@types/yauzl": "^2.10.0", 31 | "chalk": "^5.3.0", 32 | "tsup": "7.1.0", 33 | "typescript": "^5.2.2" 34 | }, 35 | "dependencies": { 36 | "@npmcli/arborist": "^6.3.0", 37 | "@pnpm/cli-utils": "^2.0.14", 38 | "@pnpm/config": "^18.4.4", 39 | "@pnpm/error": "^5.0.2", 40 | "@pnpm/exportable-manifest": "^5.0.5", 41 | "@pnpm/logger": "^5.0.0", 42 | "@pnpm/package-bins": "^8.0.2", 43 | "@pnpm/types": "^9.2.0", 44 | "@types/hosted-git-info": "^3.0.2", 45 | "@types/markdown-it": "^12.2.3", 46 | "@types/yazl": "^2.4.2", 47 | "cac": "6.7.14", 48 | "cheerio": "1.0.0-rc.12", 49 | "fast-glob": "^3.3.1", 50 | "hosted-git-info": "^6.1.1", 51 | "markdown-it": "^13.0.1", 52 | "mime": "^1.3.4", 53 | "npm-packlist": "^5.1.3", 54 | "parse-semver": "^1.1.1", 55 | "pathe": "^1.1.1", 56 | "read": "^2.1.0", 57 | "semver": "^7.5.4", 58 | "tar-stream": "^3.1.6", 59 | "url-join": "^5.0.0", 60 | "xml2js": "^0.6.2", 61 | "yauzl": "^2.10.0", 62 | "yazl": "^2.5.1" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/cli-default.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { main } from './cli-main' 3 | 4 | main().catch((err) => { 5 | console.error(err) 6 | }) 7 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/cli-main.ts: -------------------------------------------------------------------------------- 1 | import { cac } from 'cac' 2 | import fs from 'fs' 3 | import { join } from 'pathe' 4 | import { createVsix } from './create-vsix' 5 | import { Targets } from './vsce/package' 6 | 7 | /** 8 | * @see https://github.com/microsoft/vscode-vsce/blob/8c1b1a095f4214666c5efdd57f5ca70d3e6a9fd7/src/main.ts#L60 9 | */ 10 | export async function main() { 11 | const cwd = process.cwd() 12 | 13 | const cli = cac('vsix-builder') 14 | const pkgJson = require('../package.json') 15 | 16 | const ValidTargets = [...Targets].join(', ') 17 | 18 | cli 19 | .command('package [version]') 20 | .option('--pre-release', 'Mark this package as a pre-release') 21 | .option('-o, --out ', 'Output .vsix extension file to location (defaults to panda.vsix)') 22 | .option('-t, --target ', `Target architecture. Valid targets: ${ValidTargets}`) 23 | .option('--dry', 'List the files that would have been included in the package') 24 | .action(async (version, flags) => { 25 | console.log(`Creating a VSIX...`) 26 | const outfile = flags?.out ?? join(cwd, 'panda.vsix') 27 | 28 | const start = performance.now() 29 | const vsix = await createVsix({ dir: cwd, outfile, dry: flags?.dry }, { ...flags, version }) 30 | const end = performance.now() 31 | 32 | if (!vsix) { 33 | throw new Error('There was an error while creating the VSIX package.') 34 | } 35 | 36 | if (vsix.dry) { 37 | console.log('Dry run, no package created.') 38 | console.log(`outfile: ${vsix.outfile}`) 39 | console.log(`<${vsix.manifest.name}> v${vsix.manifest.version}, with ${vsix.files.length} files`) 40 | if (vsix.files.length < 550) { 41 | vsix.files.forEach((file) => { 42 | console.log(` ${file.path}`) 43 | }) 44 | } 45 | 46 | return 47 | } 48 | 49 | const { files } = vsix 50 | 51 | const stats = await fs.promises.stat(vsix.outfile) 52 | 53 | let size = 0 54 | let unit = '' 55 | 56 | if (stats.size > 1048576) { 57 | size = Math.round(stats.size / 10485.76) / 100 58 | unit = 'MB' 59 | } else { 60 | size = Math.round(stats.size / 10.24) / 100 61 | unit = 'KB' 62 | } 63 | 64 | console.log( 65 | `Packaged: ${outfile} (${files.length} files in ${Math.round((end - start) / 10) / 100}s, ${size}${unit})`, 66 | ) 67 | }) 68 | 69 | cli.help() 70 | 71 | cli.version(pkgJson.version) 72 | 73 | cli.parse(process.argv, { run: false }) 74 | await cli.runMatchedCommand() 75 | } 76 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/create-vsix.ts: -------------------------------------------------------------------------------- 1 | import { readProjectManifest } from '@pnpm/cli-utils' 2 | import type { ProjectManifest } from '@pnpm/types' 3 | import packlist from 'npm-packlist' 4 | import { join } from 'pathe' 5 | import type { Manifest } from './vsce/manifest' 6 | import { createDefaultProcessors, processFiles, writeVsix, type IFile, type IPackageOptions } from './vsce/package' 7 | import { versionBump } from './vsce/version-bump' 8 | 9 | /** 10 | * @see https://github.com/microsoft/vscode-vsce/blob/c2e71d5bcee680b31d009cf17423d4fb64c1883c/src/package.ts#L1744 11 | */ 12 | export const createVsix = async ( 13 | target: { dir: string; outfile: string; dry?: boolean }, 14 | options: IPackageOptions = {}, 15 | ) => { 16 | const { manifest, dir } = await getManifest(target.dir) 17 | const fileNames = await packlist({ path: dir }) 18 | const files = fileNames.map((f) => ({ path: `extension/${f}`, localPath: join(target.dir, f) })) 19 | 20 | const { outfile } = target 21 | if (target.dry) { 22 | return { manifest, outfile, files, dry: true } 23 | } 24 | 25 | await versionBump(manifest as Manifest, options) 26 | 27 | const processors = createDefaultProcessors(manifest as Manifest, options) 28 | const processedFiles = await processFiles(processors, files) 29 | 30 | await writeVsix(processedFiles, outfile) 31 | 32 | return { manifest, outfile, files } as { manifest: ProjectManifest; outfile: string; files: IFile[]; dry?: boolean } 33 | } 34 | 35 | /** @see https://github.com/pnpm/pnpm/blob/29a2a698060a86459f579572ac6d13151bf294ba/releasing/plugin-commands-publishing/src/pack.ts#L70 */ 36 | const getManifest = async (packageDir: string) => { 37 | const { manifest: entryManifest, fileName: manifestFileName } = await readProjectManifest(packageDir, {}) 38 | 39 | const dir = entryManifest.publishConfig?.directory 40 | ? join(packageDir, entryManifest.publishConfig.directory) 41 | : packageDir 42 | const manifest = packageDir !== dir ? (await readProjectManifest(dir, {})).manifest : entryManifest 43 | 44 | if (!manifest.name) { 45 | throw new Error(`Package name is not defined in the ${manifestFileName}.`) 46 | } 47 | 48 | if (!manifest.version) { 49 | throw new Error(`Package version is not defined in the ${manifestFileName}.`) 50 | } 51 | 52 | return { manifest, dir, manifestFileName } 53 | } 54 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-vsix' 2 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/vsce/manifest.ts: -------------------------------------------------------------------------------- 1 | export interface Person { 2 | name: string 3 | url?: string 4 | email?: string 5 | } 6 | 7 | export interface Translation { 8 | id: string 9 | path: string 10 | } 11 | 12 | export interface Localization { 13 | languageId: string 14 | languageName?: string 15 | localizedLanguageName?: string 16 | translations: Translation[] 17 | } 18 | 19 | export interface Language { 20 | readonly id: string 21 | readonly aliases?: string[] 22 | readonly extensions?: string[] 23 | } 24 | 25 | export interface Grammar { 26 | readonly language: string 27 | readonly scopeName: string 28 | readonly path: string 29 | } 30 | 31 | export interface Command { 32 | readonly command: string 33 | readonly title: string 34 | } 35 | 36 | export interface Authentication { 37 | readonly id: string 38 | readonly label: string 39 | } 40 | 41 | export interface CustomEditor { 42 | readonly viewType: string 43 | readonly priority: string 44 | readonly selector: readonly { 45 | readonly filenamePattern?: string 46 | }[] 47 | } 48 | 49 | export interface View { 50 | readonly id: string 51 | readonly name: string 52 | } 53 | 54 | export interface Contributions { 55 | readonly localizations?: Localization[] 56 | readonly languages?: Language[] 57 | readonly grammars?: Grammar[] 58 | readonly commands?: Command[] 59 | readonly authentication?: Authentication[] 60 | readonly customEditors?: CustomEditor[] 61 | readonly views?: { [location: string]: View[] } 62 | readonly [contributionType: string]: any 63 | } 64 | 65 | export type ExtensionKind = 'ui' | 'workspace' | 'web' 66 | 67 | export interface Manifest { 68 | // mandatory (npm) 69 | name: string 70 | version: string 71 | engines: { [name: string]: string } 72 | 73 | // vscode 74 | publisher: string 75 | icon?: string 76 | contributes?: Contributions 77 | activationEvents?: string[] 78 | extensionDependencies?: string[] 79 | extensionPack?: string[] 80 | galleryBanner?: { color?: string; theme?: string } 81 | preview?: boolean 82 | badges?: { url: string; href: string; description: string }[] 83 | markdown?: 'github' | 'standard' 84 | _bundling?: { [name: string]: string }[] 85 | _testing?: string 86 | enableProposedApi?: boolean 87 | enabledApiProposals?: readonly string[] 88 | qna?: 'marketplace' | string | false 89 | extensionKind?: ExtensionKind | ExtensionKind[] 90 | sponsor?: { url: string } 91 | 92 | // optional (npm) 93 | author?: string | Person 94 | displayName?: string 95 | description?: string 96 | keywords?: string[] 97 | categories?: string[] 98 | homepage?: string 99 | bugs?: string | { url?: string; email?: string } 100 | license?: string 101 | contributors?: string | Person[] 102 | main?: string 103 | browser?: string 104 | repository?: string | { type?: string; url?: string } 105 | scripts?: { [name: string]: string } 106 | dependencies?: { [name: string]: string } 107 | devDependencies?: { [name: string]: string } 108 | private?: boolean 109 | pricing?: string 110 | 111 | // vsce 112 | vsce?: any 113 | 114 | // not supported (npm) 115 | // files?: string[]; 116 | // bin 117 | // man 118 | // directories 119 | // config 120 | // peerDependencies 121 | // bundledDependencies 122 | // optionalDependencies 123 | // os?: string[]; 124 | // cpu?: string[]; 125 | // preferGlobal 126 | // publishConfig 127 | } 128 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/vsce/parse-semver.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'parse-semver' { 2 | interface Result { 3 | readonly name: string; 4 | readonly version: string; 5 | } 6 | module parseSemver {} 7 | function parseSemver(input: string): Result; 8 | export = parseSemver; 9 | } 10 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/vsce/util.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { EOL } from 'os' 3 | import _read from 'read' 4 | import { promisify } from 'util' 5 | import { type Manifest } from './manifest' 6 | 7 | const __read = promisify<_read.Options, string>(_read) 8 | export function read(prompt: string, options: _read.Options = {}): Promise { 9 | if (process.env['VSCE_TESTS'] || !process.stdout.isTTY) { 10 | return Promise.resolve('y') 11 | } 12 | 13 | return __read({ prompt, ...options }) 14 | } 15 | 16 | const marketplaceUrl = process.env['VSCE_MARKETPLACE_URL'] || 'https://marketplace.visualstudio.com' 17 | 18 | export function getPublishedUrl(extension: string): string { 19 | return `${marketplaceUrl}/items?itemName=${extension}` 20 | } 21 | 22 | export function getHubUrl(publisher: string, name: string): string { 23 | return `${marketplaceUrl}/manage/publishers/${publisher}/extensions/${name}/hub` 24 | } 25 | 26 | export function normalize(path: string): string { 27 | return path.replace(/\\/g, '/') 28 | } 29 | 30 | function chain2(a: A, b: B[], fn: (a: A, b: B) => Promise, index = 0): Promise { 31 | if (index >= b.length) { 32 | return Promise.resolve(a) 33 | } 34 | 35 | return fn(a, b[index]).then((a) => chain2(a, b, fn, index + 1)) 36 | } 37 | 38 | export function chain(initial: T, processors: P[], process: (a: T, b: P) => Promise): Promise { 39 | return chain2(initial, processors, process) 40 | } 41 | 42 | export function flatten(arr: T[][]): T[] { 43 | return ([] as T[]).concat.apply([], arr) as T[] 44 | } 45 | 46 | export function nonnull(arg: T | null | undefined): arg is T { 47 | return !!arg 48 | } 49 | 50 | const CancelledError = 'Cancelled' 51 | 52 | export function isCancelledError(error: any) { 53 | return error === CancelledError 54 | } 55 | 56 | export class CancellationToken { 57 | private listeners: Function[] = [] 58 | private _cancelled = false 59 | get isCancelled(): boolean { 60 | return this._cancelled 61 | } 62 | 63 | subscribe(fn: Function): Function { 64 | this.listeners.push(fn) 65 | 66 | return () => { 67 | const index = this.listeners.indexOf(fn) 68 | 69 | if (index > -1) { 70 | this.listeners.splice(index, 1) 71 | } 72 | } 73 | } 74 | 75 | cancel(): void { 76 | const emit = !this._cancelled 77 | this._cancelled = true 78 | 79 | if (emit) { 80 | this.listeners.forEach((l) => l(CancelledError)) 81 | this.listeners = [] 82 | } 83 | } 84 | } 85 | 86 | export async function sequence(promiseFactories: { (): Promise }[]): Promise { 87 | for (const factory of promiseFactories) { 88 | await factory() 89 | } 90 | } 91 | 92 | enum LogMessageType { 93 | DONE, 94 | INFO, 95 | WARNING, 96 | ERROR, 97 | } 98 | 99 | const LogPrefix = { 100 | [LogMessageType.DONE]: chalk.bgGreen.black(' DONE '), 101 | [LogMessageType.INFO]: chalk.bgBlueBright.black(' INFO '), 102 | [LogMessageType.WARNING]: chalk.bgYellow.black(' WARNING '), 103 | [LogMessageType.ERROR]: chalk.bgRed.black(' ERROR '), 104 | } 105 | 106 | function _log(type: LogMessageType, msg: any, ...args: any[]): void { 107 | args = [LogPrefix[type], msg, ...args] 108 | 109 | if (type === LogMessageType.WARNING) { 110 | process.env['GITHUB_ACTIONS'] ? logToGitHubActions('warning', msg) : console.warn(...args) 111 | } else if (type === LogMessageType.ERROR) { 112 | process.env['GITHUB_ACTIONS'] ? logToGitHubActions('error', msg) : console.error(...args) 113 | } else { 114 | process.env['GITHUB_ACTIONS'] ? logToGitHubActions('info', msg) : console.log(...args) 115 | } 116 | } 117 | 118 | const EscapeCharacters = new Map([ 119 | ['%', '%25'], 120 | ['\r', '%0D'], 121 | ['\n', '%0A'], 122 | ]) 123 | 124 | const EscapeRegex = new RegExp(`[${[...EscapeCharacters.keys()].join('')}]`, 'g') 125 | 126 | function escapeGitHubActionsMessage(message: string): string { 127 | return message.replace(EscapeRegex, (c) => EscapeCharacters.get(c) ?? c) 128 | } 129 | 130 | function logToGitHubActions(type: string, message: string): void { 131 | const command = type === 'info' ? message : `::${type}::${escapeGitHubActionsMessage(message)}` 132 | process.stdout.write(command + EOL) 133 | } 134 | 135 | export interface LogFn { 136 | (msg: any, ...args: any[]): void 137 | } 138 | 139 | export const log = { 140 | done: _log.bind(null, LogMessageType.DONE) as LogFn, 141 | info: _log.bind(null, LogMessageType.INFO) as LogFn, 142 | warn: _log.bind(null, LogMessageType.WARNING) as LogFn, 143 | error: _log.bind(null, LogMessageType.ERROR) as LogFn, 144 | } 145 | 146 | export function patchOptionsWithManifest(options: any, manifest: Manifest): void { 147 | if (!manifest.vsce) { 148 | return 149 | } 150 | 151 | for (const key of Object.keys(manifest.vsce)) { 152 | const optionsKey = key === 'yarn' ? 'useYarn' : key 153 | 154 | if (options[optionsKey] === undefined) { 155 | options[optionsKey] = manifest.vsce[key] 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/vsce/version-bump.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util' 2 | import * as cp from 'child_process' 3 | import * as semver from 'semver' 4 | import type { Manifest } from './manifest' 5 | 6 | export interface IVersionBumpOptions { 7 | readonly cwd?: string 8 | readonly version?: string 9 | readonly commitMessage?: string 10 | readonly gitTagVersion?: boolean 11 | readonly updatePackageJson?: boolean 12 | } 13 | 14 | /** 15 | * @see https://github.com/microsoft/vscode-vsce/blob/7d1cc98c297f764fe15f2a189392c22c7242fabb/src/package.ts#L350 16 | */ 17 | export async function versionBump(manifest: Manifest, options: IVersionBumpOptions): Promise { 18 | if (!options.version) { 19 | return 20 | } 21 | 22 | if (!(options.updatePackageJson ?? true)) { 23 | return 24 | } 25 | 26 | if (manifest.version === options.version) { 27 | return 28 | } 29 | 30 | // update the manifest object 31 | const manifestVersion = manifest.version 32 | const today = new Date().getTime().toString().slice(0, 8) 33 | const currentVersion = semver.valid(manifestVersion) 34 | 35 | if (!currentVersion) { 36 | throw new Error('Cannot get the current version number from package.json') 37 | } 38 | 39 | const rcVersion = options.version ?? semver.inc(currentVersion, 'minor')?.replace(/\.\d+$/, `.${today}`) 40 | if (!rcVersion) { 41 | throw new Error("Could not populate the current version number for rc's build.") 42 | } 43 | 44 | if (rcVersion) { 45 | manifest.version = rcVersion 46 | console.log(`Bumped version from ${manifestVersion} to ${rcVersion}`) 47 | } 48 | 49 | let command = `yarn version ${rcVersion}` 50 | 51 | if (options.commitMessage) { 52 | command = `${command} -m "${options.commitMessage}"` 53 | } 54 | 55 | if (!(options.gitTagVersion ?? true)) { 56 | command = `${command} --no-git-tag-version` 57 | } 58 | 59 | // call `npm version` to do our dirty work 60 | const cwd = options.cwd ?? process.cwd() 61 | const { stdout, stderr } = await promisify(cp.exec)(command, { cwd }) 62 | 63 | if (!process.env['VSCE_TESTS']) { 64 | process.stdout.write(stdout) 65 | process.stderr.write(stderr) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/vsce/xml.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util' 2 | import { parseString } from 'xml2js' 3 | 4 | function createXMLParser(): (raw: string) => Promise { 5 | return promisify(parseString) 6 | } 7 | 8 | export type XMLManifest = { 9 | PackageManifest: { 10 | $: { Version: string; xmlns: string; 'xmlns:d': string } 11 | Metadata: { 12 | Description: { _: string }[] 13 | DisplayName: string[] 14 | Identity: { $: { Id: string; Version: string; Publisher: string; TargetPlatform?: string } }[] 15 | Tags: string[] 16 | GalleryFlags: string[] 17 | License: string[] 18 | Icon: string[] 19 | Properties: { Property: { $: { Id: string; Value: string } }[] }[] 20 | Categories: string[] 21 | Badges: { Badge: { $: { Link: string; ImgUri: string; Description: string } }[] }[] 22 | }[] 23 | Installation: { InstallationTarget: { $: { Id: string } }[] }[] 24 | Dependencies: string[] 25 | Assets: { Asset: { $: { Type: string; Path: string } }[] }[] 26 | } 27 | } 28 | 29 | export type ContentTypes = { 30 | Types: { 31 | Default: { $: { Extension: string; ContentType: string } }[] 32 | } 33 | } 34 | 35 | export const parseXmlManifest = createXMLParser() 36 | export const parseContentTypes = createXMLParser() 37 | -------------------------------------------------------------------------------- /packages/vsix-builder/src/vsce/zip.ts: -------------------------------------------------------------------------------- 1 | import { Entry, open, ZipFile } from 'yauzl' 2 | import { type Manifest } from './manifest' 3 | import { parseXmlManifest, type XMLManifest } from './xml' 4 | import { Readable } from 'stream' 5 | 6 | async function bufferStream(stream: Readable): Promise { 7 | return await new Promise((c, e) => { 8 | const buffers: Buffer[] = [] 9 | stream.on('data', (buffer) => buffers.push(buffer)) 10 | stream.once('error', e) 11 | stream.once('end', () => c(Buffer.concat(buffers))) 12 | }) 13 | } 14 | 15 | export async function readZip(packagePath: string, filter: (name: string) => boolean): Promise> { 16 | const zipfile = await new Promise((c, e) => 17 | open(packagePath, { lazyEntries: true }, (err, zipfile) => (err ? e(err) : c(zipfile!))), 18 | ) 19 | 20 | return await new Promise((c, e) => { 21 | const result = new Map() 22 | 23 | zipfile.once('close', () => c(result)) 24 | 25 | zipfile.readEntry() 26 | zipfile.on('entry', (entry: Entry) => { 27 | const name = entry.fileName.toLowerCase() 28 | 29 | if (filter(name)) { 30 | zipfile.openReadStream(entry, (err, stream) => { 31 | if (err) { 32 | zipfile.close() 33 | return e(err) 34 | } 35 | 36 | bufferStream(stream!).then((buffer) => { 37 | result.set(name, buffer) 38 | zipfile.readEntry() 39 | }) 40 | }) 41 | } else { 42 | zipfile.readEntry() 43 | } 44 | }) 45 | }) 46 | } 47 | 48 | export async function readVSIXPackage(packagePath: string): Promise<{ manifest: Manifest; xmlManifest: XMLManifest }> { 49 | const map = await readZip(packagePath, (name) => /^extension\/package\.json$|^extension\.vsixmanifest$/i.test(name)) 50 | const rawManifest = map.get('extension/package.json') 51 | 52 | if (!rawManifest) { 53 | throw new Error('Manifest not found') 54 | } 55 | 56 | const rawXmlManifest = map.get('extension.vsixmanifest') 57 | 58 | if (!rawXmlManifest) { 59 | throw new Error('VSIX manifest not found') 60 | } 61 | 62 | return { 63 | manifest: JSON.parse(rawManifest.toString('utf8')), 64 | xmlManifest: await parseXmlManifest(rawXmlManifest.toString('utf8')), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/vsix-builder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules"], 5 | "compilerOptions": { 6 | "noUncheckedIndexedAccess": false, 7 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /sandbox/vite/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /sandbox/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /sandbox/vite/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | parserOptions: { 18 | ecmaVersion: 'latest', 19 | sourceType: 'module', 20 | project: ['./tsconfig.json', './tsconfig.node.json'], 21 | tsconfigRootDir: __dirname, 22 | }, 23 | ``` 24 | 25 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 26 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 27 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 28 | -------------------------------------------------------------------------------- /sandbox/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sandbox/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "prepare": "panda codegen --clean", 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@pandacss/dev": "^0.37.2", 19 | "@types/react": "^18.2.67", 20 | "@types/react-dom": "^18.2.22", 21 | "@typescript-eslint/eslint-plugin": "^7.3.1", 22 | "@typescript-eslint/parser": "^7.3.1", 23 | "@vitejs/plugin-react": "^4.2.1", 24 | "eslint": "^8.57.0", 25 | "eslint-plugin-react-hooks": "^4.6.0", 26 | "eslint-plugin-react-refresh": "^0.4.6", 27 | "typescript": "^5.4.2", 28 | "vite": "^5.1.6" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sandbox/vite/panda.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@pandacss/dev' 2 | import { button} from "./src/button.recipe" 3 | 4 | export default defineConfig({ 5 | // Whether to use css reset 6 | preflight: true, 7 | 8 | // Where to look for your css declarations 9 | include: ['./src/**/*.{js,jsx,ts,tsx}', './pages/**/*.{js,jsx,ts,tsx}'], 10 | 11 | // Files to exclude 12 | exclude: [], 13 | 14 | // Useful for theme customization 15 | theme: { 16 | extend: { 17 | recipes: { 18 | button, 19 | }, 20 | tokens: { 21 | lineHeights: { 22 | normal: { value: 1.4 } 23 | } 24 | }, 25 | semanticTokens: { 26 | colors: { 27 | danger: { 28 | value: { base: '{colors.red.300}', _dark: '{colors.orange.300}' }, 29 | }, 30 | success: { 31 | value: { base: '{colors.green.300}', _dark: '{colors.lime.400}' }, 32 | }, 33 | bg: { 34 | DEFAULT: { value: '{colors.yellow.100}' }, 35 | muted: { value: '{colors.gray.100}' }, 36 | text: { 37 | value: { 38 | base: '{colors.blue.100}', 39 | _light: '{colors.blue.200}', 40 | _dark: '{colors.blue.300}', 41 | md: { 42 | base: '{colors.blue.900}', 43 | _focus: '{colors.blue.400}', 44 | _active: '{colors.blue.500}', 45 | _hover: { 46 | base: '{colors.blue.600}', 47 | _light: '{colors.blue.700}', 48 | _dark: '{colors.blue.800}', 49 | } 50 | } 51 | }, 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | 59 | // The output directory for your css system 60 | outdir: 'styled-system', 61 | 62 | // The JSX framework to use 63 | jsxFramework: 'react', 64 | }) 65 | -------------------------------------------------------------------------------- /sandbox/vite/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@pandacss/dev/postcss': {}, 4 | }, 5 | } -------------------------------------------------------------------------------- /sandbox/vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sandbox/vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { css, sva } from '../styled-system/css' 2 | import { button } from '../styled-system/recipes' 3 | import { flex } from '../styled-system/patterns' 4 | import './App.css' 5 | 6 | const card = sva({ 7 | slots: ['label', 'icon'], 8 | base: { 9 | label: { 10 | color: 'red.200', 11 | }, 12 | icon: { 13 | fontSize: '3xl', 14 | }, 15 | }, 16 | }) 17 | 18 | card() 19 | button({ size: 'sm' }) 20 | flex({ direction: 'column', gap: 'initial', color: 'teal.300' }) 21 | 22 | function App() { 23 | return ( 24 | <> 25 |
39 | Hello from Panda ! 40 |
41 | 42 | ) 43 | } 44 | 45 | export default App 46 | -------------------------------------------------------------------------------- /sandbox/vite/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sandbox/vite/src/button.recipe.ts: -------------------------------------------------------------------------------- 1 | import { defineRecipe } from "@pandacss/dev"; 2 | 3 | export const button = defineRecipe({ 4 | className: "btn", 5 | base: { 6 | color: "amber.200", 7 | }, 8 | variants: { 9 | size: { 10 | sm: { 11 | fontSize: "2xl", 12 | } 13 | } 14 | } 15 | }) -------------------------------------------------------------------------------- /sandbox/vite/src/index.css: -------------------------------------------------------------------------------- 1 | @layer reset, base, tokens, recipes, utilities; 2 | 3 | :root { 4 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 5 | line-height: 1.5; 6 | font-weight: 400; 7 | 8 | color-scheme: light dark; 9 | color: rgba(255, 255, 255, 0.87); 10 | background-color: #242424; 11 | 12 | font-synthesis: none; 13 | text-rendering: optimizeLegibility; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | -webkit-text-size-adjust: 100%; 17 | } 18 | 19 | @media (prefers-color-scheme: light) { 20 | :root { 21 | color: #213547; 22 | background-color: #ffffff; 23 | } 24 | a:hover { 25 | color: #747bff; 26 | } 27 | button { 28 | background-color: #f9f9f9; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sandbox/vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /sandbox/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sandbox/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src", "styled-system"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /sandbox/vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /sandbox/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowSyntheticDefaultImports": true, 5 | "declaration": true, 6 | "downlevelIteration": true, 7 | "esModuleInterop": true, 8 | "jsx": "preserve", 9 | "incremental": true, 10 | "noEmit": true, 11 | "lib": ["dom", "dom.iterable", "esnext"], 12 | "target": "esnext", 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "noImplicitReturns": false, 16 | "resolveJsonModule": true, 17 | "noUncheckedIndexedAccess": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "verbatimModuleSyntax": true, 21 | // 22 | "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" 23 | }, 24 | "include": ["packages"], 25 | "types": ["vitest/globals"], 26 | "exclude": ["node_modules", "packages/vsix-builder"] 27 | } 28 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | hideSkippedTests: true, 6 | }, 7 | }) 8 | --------------------------------------------------------------------------------